diff -Nru ovn-20.09.0/AUTHORS.rst ovn-20.12.0/AUTHORS.rst --- ovn-20.09.0/AUTHORS.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/AUTHORS.rst 2020-12-17 17:53:32.000000000 +0000 @@ -202,6 +202,7 @@ JunhanYan juyan@redhat.com JunoZhu zhunatuzi@gmail.com Justin Pettit jpettit@ovn.org +Kai Li likailichee@gmail.com Kaige Fu fukaige@huawei.com Keith Amidon Ken Ajiro ajiro@mxw.nes.nec.co.jp @@ -305,6 +306,7 @@ Raymond Burkholder ray@oneunified.net Reid Price Remko Tronçon git@el-tramo.be +Renat Nurgaliyev impleman@gmail.com Rich Lane rlane@bigswitch.com Richard Oliver richard@richard-oliver.co.uk Rishi Bamba rishi.bamba@tcs.com @@ -312,6 +314,7 @@ Robert Åkerblom-Andersson Robert.nr1@gmail.com Robert Wojciechowicz robertx.wojciechowicz@intel.com Rob Hoes rob.hoes@citrix.com +Robin Brämer robin.braemer@web.de Rohith Basavaraja rohith.basavaraja@gmail.com Roi Dayan roid@mellanox.com Róbert Mulik robert.mulik@ericsson.com diff -Nru ovn-20.09.0/.ci/linux-build.sh ovn-20.12.0/.ci/linux-build.sh --- ovn-20.09.0/.ci/linux-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/.ci/linux-build.sh 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,57 @@ +#!/bin/bash + +set -o errexit +set -x + +CFLAGS="" +SPARSE_FLAGS="" +EXTRA_OPTS="--enable-Werror" + +function configure_ovs() +{ + git clone https://github.com/openvswitch/ovs.git ovs_src + pushd ovs_src + ./boot.sh && ./configure $* || { cat config.log; exit 1; } + make -j4 || { cat config.log; exit 1; } + popd +} + +function configure_ovn() +{ + configure_ovs $* + ./boot.sh && ./configure --with-ovs-source=$PWD/ovs_src $* || \ + { cat config.log; exit 1; } +} + +save_OPTS="${OPTS} $*" +OPTS="${EXTRA_OPTS} ${save_OPTS}" + +if [ "$CC" = "clang" ]; then + export OVS_CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" +elif [ "$M32" ]; then + # Not using sparse for 32bit builds on 64bit machine. + # Adding m32 flag directly to CC to avoid any posiible issues with API/ABI + # difference on 'configure' and 'make' stages. + export CC="$CC -m32" +else + OPTS="$OPTS --enable-sparse" + export OVS_CFLAGS="$CFLAGS $SPARSE_FLAGS" +fi + +if [ "$TESTSUITE" ]; then + # 'distcheck' will reconfigure with required options. + # Now we only need to prepare the Makefile without sparse-wrapped CC. + configure_ovn + + export DISTCHECK_CONFIGURE_FLAGS="$OPTS --with-ovs-source=$PWD/ovs_src" + if ! make distcheck -j4 TESTSUITEFLAGS="-j4" RECHECK=yes; then + # testsuite.log is necessary for debugging. + cat */_build/sub/tests/testsuite.log + exit 1 + fi +else + configure_ovn $OPTS + make -j4 || { cat config.log; exit 1; } +fi + +exit 0 diff -Nru ovn-20.09.0/.ci/linux-prepare.sh ovn-20.12.0/.ci/linux-prepare.sh --- ovn-20.09.0/.ci/linux-prepare.sh 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/.ci/linux-prepare.sh 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,16 @@ +#!/bin/bash + +set -ev + +# Build and install sparse. +# +# Explicitly disable sparse support for llvm because some travis +# environments claim to have LLVM (llvm-config exists and works) but +# linking against it fails. +# Disabling sqlite support because sindex build fails and we don't +# really need this utility being installed. +git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git +cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd .. + +pip install --disable-pip-version-check --user six flake8 hacking +pip install --user --upgrade docutils diff -Nru ovn-20.09.0/.ci/osx-build.sh ovn-20.12.0/.ci/osx-build.sh --- ovn-20.09.0/.ci/osx-build.sh 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/.ci/osx-build.sh 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,43 @@ +#!/bin/bash + +set -o errexit + +CFLAGS="-Werror $CFLAGS" +EXTRA_OPTS="" + +function configure_ovs() +{ + git clone https://github.com/openvswitch/ovs.git ovs_src + pushd ovs_src + ./boot.sh && ./configure $* + make -j4 || { cat config.log; exit 1; } + popd +} + +function configure_ovn() +{ + configure_ovs $* + ./boot.sh && ./configure $* --with-ovs-source=$PWD/ovs_src +} + +configure_ovn $EXTRA_OPTS $* + +if [ "$CC" = "clang" ]; then + set make CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" +else + set make CFLAGS="$CFLAGS $BUILD_ENV" +fi +if ! "$@"; then + cat config.log + exit 1 +fi +if [ "$TESTSUITE" ] && [ "$CC" != "clang" ]; then + export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS --with-ovs-source=$PWD/ovs_src" + if ! make distcheck RECHECK=yes; then + # testsuite.log is necessary for debugging. + cat */_build/sub/tests/testsuite.log + exit 1 + fi +fi + +exit 0 diff -Nru ovn-20.09.0/.ci/osx-prepare.sh ovn-20.12.0/.ci/osx-prepare.sh --- ovn-20.09.0/.ci/osx-prepare.sh 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/.ci/osx-prepare.sh 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,4 @@ +#!/bin/bash +set -ev +pip3 install --user six +pip3 install --user --upgrade docutils diff -Nru ovn-20.09.0/configure.ac ovn-20.12.0/configure.ac --- ovn-20.09.0/configure.ac 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/configure.ac 2020-12-17 17:53:32.000000000 +0000 @@ -13,7 +13,7 @@ # limitations under the License. AC_PREREQ(2.63) -AC_INIT(ovn, 20.09.0, bugs@openvswitch.org) +AC_INIT(ovn, 20.12.0, bugs@openvswitch.org) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) diff -Nru ovn-20.09.0/controller/binding.c ovn-20.12.0/controller/binding.c --- ovn-20.09.0/controller/binding.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/binding.c 2020-12-17 17:53:32.000000000 +0000 @@ -1523,21 +1523,41 @@ } static const struct sbrec_port_binding * -get_peer_lport(const struct sbrec_port_binding *pb, - struct binding_ctx_in *b_ctx_in) +get_peer_lport__(const struct sbrec_port_binding *pb, + struct binding_ctx_in *b_ctx_in) { const char *peer_name = smap_get(&pb->options, "peer"); - if (strcmp(pb->type, "patch") || !peer_name) { + + if (!peer_name) { return NULL; } const struct sbrec_port_binding *peer; peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name, peer_name); - return (peer && peer->datapath) ? peer : NULL; } +static const struct sbrec_port_binding * +get_l3gw_peer_lport(const struct sbrec_port_binding *pb, + struct binding_ctx_in *b_ctx_in) +{ + if (strcmp(pb->type, "l3gateway")) { + return NULL; + } + return get_peer_lport__(pb, b_ctx_in); +} + +static const struct sbrec_port_binding * +get_peer_lport(const struct sbrec_port_binding *pb, + struct binding_ctx_in *b_ctx_in) +{ + if (strcmp(pb->type, "patch")) { + return NULL; + } + return get_peer_lport__(pb, b_ctx_in); +} + /* This function adds the local datapath of the 'peer' of * lport 'pb' to the local datapaths if it is not yet added. */ @@ -1654,7 +1674,9 @@ pb->logical_port)) { ld->localnet_port = NULL; } - } else if (!strcmp(pb->type, "l3gateway")) { + } + + if (!strcmp(pb->type, "l3gateway")) { const char *chassis_id = smap_get(&pb->options, "l3gateway-chassis"); if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) { @@ -1956,12 +1978,27 @@ struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) { + /* If the binding is local, remove it. */ struct local_datapath *ld = get_local_datapath(b_ctx_out->local_datapaths, pb->datapath->tunnel_key); if (ld) { remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec, b_ctx_out, ld); + return; + } + + /* If the binding is not local, if 'pb' is a L3 gateway port, we should + * remove its peer, if that one is local. + */ + pb = get_l3gw_peer_lport(pb, b_ctx_in); + if (pb) { + ld = get_local_datapath(b_ctx_out->local_datapaths, + pb->datapath->tunnel_key); + if (ld) { + remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec, b_ctx_out, + ld); + } } } diff -Nru ovn-20.09.0/controller/chassis.c ovn-20.12.0/controller/chassis.c --- ovn-20.09.0/controller/chassis.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/chassis.c 2020-12-17 17:53:32.000000000 +0000 @@ -37,44 +37,6 @@ #endif /* HOST_NAME_MAX */ /* - * Structure to hold chassis specific state (currently just chassis-id) - * to avoid database lookups when changes happen while the controller is - * running. - */ -struct chassis_info { - /* Last ID we initialized the Chassis SB record with. */ - struct ds id; - - /* True if Chassis SB record is initialized, false otherwise. */ - uint32_t id_inited : 1; -}; - -static struct chassis_info chassis_state = { - .id = DS_EMPTY_INITIALIZER, - .id_inited = false, -}; - -static void -chassis_info_set_id(struct chassis_info *info, const char *id) -{ - ds_clear(&info->id); - ds_put_cstr(&info->id, id); - info->id_inited = true; -} - -static bool -chassis_info_id_inited(const struct chassis_info *info) -{ - return info->id_inited; -} - -static const char * -chassis_info_id(const struct chassis_info *info) -{ - return ds_cstr_ro(&info->id); -} - -/* * Structure for storing the chassis config parsed from the ovs table. */ struct ovs_chassis_cfg { @@ -420,6 +382,9 @@ bool changed = false; for (size_t i = 0; i < chassis_rec->n_encaps; i++) { + if (strcmp(chassis_rec->name, chassis_rec->encaps[i]->chassis_name)) { + return true; + } if (!sset_contains(encap_type_set, chassis_rec->encaps[i]->type)) { changed = true; @@ -500,54 +465,11 @@ return encaps; } -/* - * Updates encaps for a given chassis. This can happen when the chassis - * name has changed. Also, the only thing we support updating is the - * chassis_name. For other changes the encaps will be recreated. - */ -static void -chassis_update_encaps(const struct sbrec_chassis *chassis) -{ - for (size_t i = 0; i < chassis->n_encaps; i++) { - sbrec_encap_set_chassis_name(chassis->encaps[i], chassis->name); - } -} - -/* - * Returns a pointer to a chassis record from 'chassis_table' that - * matches at least one tunnel config. - */ -static const struct sbrec_chassis * -chassis_get_stale_record(const struct sbrec_chassis_table *chassis_table, - const struct ovs_chassis_cfg *ovs_cfg, - const char *chassis_id) -{ - const struct sbrec_chassis *chassis_rec; - - SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { - for (size_t i = 0; i < chassis_rec->n_encaps; i++) { - if (sset_contains(&ovs_cfg->encap_type_set, - chassis_rec->encaps[i]->type) && - sset_contains(&ovs_cfg->encap_ip_set, - chassis_rec->encaps[i]->ip)) { - return chassis_rec; - } - if (strcmp(chassis_rec->name, chassis_id) == 0) { - return chassis_rec; - } - } - } - - return NULL; -} - /* If this is a chassis config update after we initialized the record once * then we should always be able to find it with the ID we saved in * chassis_state. * Otherwise (i.e., first time we create the record or if the system-id - * changed) then we check if there's a stale record from a previous - * controller run that didn't end gracefully and reuse it. If not then we - * create a new record. + * changed) we create a new record. * * Sets '*chassis_rec' to point to the local chassis record. * Returns true if this record was already in the database, false if it was @@ -556,33 +478,16 @@ static bool chassis_get_record(struct ovsdb_idl_txn *ovnsb_idl_txn, struct ovsdb_idl_index *sbrec_chassis_by_name, - const struct sbrec_chassis_table *chassis_table, - const struct ovs_chassis_cfg *ovs_cfg, const char *chassis_id, const struct sbrec_chassis **chassis_rec) { - const struct sbrec_chassis *chassis = NULL; - - if (chassis_info_id_inited(&chassis_state)) { - chassis = chassis_lookup_by_name(sbrec_chassis_by_name, - chassis_info_id(&chassis_state)); - if (!chassis) { - VLOG_DBG("Could not find Chassis, will check if the id changed: " - "stored (%s) ovs (%s)", - chassis_info_id(&chassis_state), chassis_id); - } - } - - if (!chassis) { - chassis = chassis_get_stale_record(chassis_table, ovs_cfg, chassis_id); - } + const struct sbrec_chassis *chassis = + chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); - if (!chassis) { - /* Recreate the chassis record. */ + if (!chassis && ovnsb_idl_txn) { + /* Create the chassis record. */ VLOG_DBG("Could not find Chassis, will create it: %s", chassis_id); - if (ovnsb_idl_txn) { - *chassis_rec = sbrec_chassis_insert(ovnsb_idl_txn); - } + *chassis_rec = sbrec_chassis_insert(ovnsb_idl_txn); return false; } @@ -650,7 +555,6 @@ &ovs_cfg->encap_ip_set, ovs_cfg->encap_csum, chassis_rec); if (!tunnels_changed) { - chassis_update_encaps(chassis_rec); return updated; } @@ -666,33 +570,11 @@ return true; } -/* - * Returns a pointer to a chassis_private record from 'chassis_pvt_table' that - * matches the chassis record. - */ -static const struct sbrec_chassis_private * -chassis_private_get_stale_record( - const struct sbrec_chassis_private_table *chassis_pvt_table, - const struct sbrec_chassis *chassis) -{ - const struct sbrec_chassis_private *chassis_pvt_rec; - - SBREC_CHASSIS_PRIVATE_TABLE_FOR_EACH (chassis_pvt_rec, chassis_pvt_table) { - if (chassis_pvt_rec->chassis == chassis) { - return chassis_pvt_rec; - } - } - - return NULL; -} - /* If this is a chassis_private config update after we initialized the record * once then we should always be able to find it with the ID we saved in * chassis_state. * Otherwise (i.e., first time we created the chassis record or if the - * system-id changed) then we check if there's a stale record from a previous - * controller run that didn't end gracefully and reuse it. If not then we - * create a new record. + * system-id changed) we create a new record. * * Returns the local chassis record. */ @@ -700,21 +582,11 @@ chassis_private_get_record( struct ovsdb_idl_txn *ovnsb_idl_txn, struct ovsdb_idl_index *sbrec_chassis_pvt_by_name, - const struct sbrec_chassis_private_table *chassis_pvt_table, - const struct sbrec_chassis *chassis) + const char *chassis_id) { - const struct sbrec_chassis_private *chassis_p = NULL; - - if (chassis_info_id_inited(&chassis_state)) { - chassis_p = + const struct sbrec_chassis_private *chassis_p = chassis_private_lookup_by_name(sbrec_chassis_pvt_by_name, - chassis_info_id(&chassis_state)); - } - - if (!chassis_p) { - chassis_p = chassis_private_get_stale_record(chassis_pvt_table, - chassis); - } + chassis_id); if (!chassis_p && ovnsb_idl_txn) { return sbrec_chassis_private_insert(ovnsb_idl_txn); @@ -743,8 +615,6 @@ struct ovsdb_idl_index *sbrec_chassis_by_name, struct ovsdb_idl_index *sbrec_chassis_private_by_name, const struct ovsrec_open_vswitch_table *ovs_table, - const struct sbrec_chassis_table *chassis_table, - const struct sbrec_chassis_private_table *chassis_pvt_table, const char *chassis_id, const struct ovsrec_bridge *br_int, const struct sset *transport_zones, @@ -762,8 +632,7 @@ const struct sbrec_chassis *chassis_rec = NULL; bool existed = chassis_get_record(ovnsb_idl_txn, sbrec_chassis_by_name, - chassis_table, &ovs_cfg, chassis_id, - &chassis_rec); + chassis_id, &chassis_rec); /* If we found (or created) a record, update it with the correct config * and store the current chassis_id for fast lookup in case it gets @@ -783,13 +652,10 @@ *chassis_private = chassis_private_get_record(ovnsb_idl_txn, sbrec_chassis_private_by_name, - chassis_pvt_table, chassis_rec); - + chassis_id); if (*chassis_private) { chassis_private_update(*chassis_private, chassis_rec, chassis_id); } - - chassis_info_set_id(&chassis_state, chassis_id); } ovs_chassis_cfg_destroy(&ovs_cfg); @@ -865,16 +731,3 @@ } return false; } - -/* - * Returns the last initialized chassis-id. - */ -const char * -chassis_get_id(void) -{ - if (chassis_info_id_inited(&chassis_state)) { - return chassis_info_id(&chassis_state); - } - - return NULL; -} diff -Nru ovn-20.09.0/controller/chassis.h ovn-20.12.0/controller/chassis.h --- ovn-20.09.0/controller/chassis.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/chassis.h 2020-12-17 17:53:32.000000000 +0000 @@ -37,8 +37,6 @@ struct ovsdb_idl_index *sbrec_chassis_by_name, struct ovsdb_idl_index *sbrec_chassis_private_by_name, const struct ovsrec_open_vswitch_table *, - const struct sbrec_chassis_table *, - const struct sbrec_chassis_private_table *, const char *chassis_id, const struct ovsrec_bridge *br_int, const struct sset *transport_zones, const struct sbrec_chassis_private **chassis_private); @@ -48,7 +46,6 @@ bool chassis_get_mac(const struct sbrec_chassis *chassis, const char *bridge_mapping, struct eth_addr *chassis_mac); -const char *chassis_get_id(void); const char * get_chassis_mac_mappings(const struct smap *ext_ids); diff -Nru ovn-20.09.0/controller/lflow.c ovn-20.12.0/controller/lflow.c --- ovn-20.09.0/controller/lflow.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/lflow.c 2020-12-17 17:53:32.000000000 +0000 @@ -26,6 +26,7 @@ #include "ovn-controller.h" #include "ovn/actions.h" #include "ovn/expr.h" +#include "lib/lb.h" #include "lib/ovn-l7.h" #include "lib/ovn-sb-idl.h" #include "lib/extend-table.h" @@ -666,6 +667,7 @@ static void add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, + const struct sbrec_datapath_binding *dp, struct hmap *matches, size_t conj_id_ofs, uint8_t ptable, uint8_t output_ptable, struct ofpbuf *ovnacts, @@ -676,7 +678,7 @@ .sbrec_multicast_group_by_name_datapath = l_ctx_in->sbrec_multicast_group_by_name_datapath, .sbrec_port_binding_by_name = l_ctx_in->sbrec_port_binding_by_name, - .dp = lflow->logical_datapath + .dp = dp, }; /* Encode OVN logical actions into OpenFlow. */ @@ -686,7 +688,7 @@ .lookup_port = lookup_port_cb, .tunnel_ofport = tunnel_ofport_cb, .aux = &aux, - .is_switch = datapath_is_switch(lflow->logical_datapath), + .is_switch = datapath_is_switch(dp), .group_table = l_ctx_out->group_table, .meter_table = l_ctx_out->meter_table, .lflow_uuid = lflow->header_.uuid, @@ -697,22 +699,24 @@ .output_ptable = output_ptable, .mac_bind_ptable = OFTABLE_MAC_BINDING, .mac_lookup_ptable = OFTABLE_MAC_LOOKUP, + .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN, + .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY, + .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP, }; ovnacts_encode(ovnacts->data, ovnacts->size, &ep, &ofpacts); struct expr_match *m; HMAP_FOR_EACH (m, hmap_node, matches) { - match_set_metadata(&m->match, - htonll(lflow->logical_datapath->tunnel_key)); + match_set_metadata(&m->match, htonll(dp->tunnel_key)); if (m->match.wc.masks.conj_id) { m->match.flow.conj_id += conj_id_ofs; } - if (datapath_is_switch(lflow->logical_datapath)) { + if (datapath_is_switch(dp)) { unsigned int reg_index = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0; int64_t port_id = m->match.flow.regs[reg_index]; if (port_id) { - int64_t dp_id = lflow->logical_datapath->tunnel_key; + int64_t dp_id = dp->tunnel_key; char buf[16]; get_unique_lport_key(dp_id, port_id, buf, sizeof(buf)); lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_PORTBINDING, buf, @@ -755,41 +759,48 @@ ofpbuf_uninit(&ofpacts); } -/* Converts the match and returns the simplified expre tree. - * The caller should evaluate the conditions and normalize the - * expr tree. */ +/* Converts the match and returns the simplified expr tree. + * + * The caller should evaluate the conditions and normalize the expr tree. + */ static struct expr * convert_match_to_expr(const struct sbrec_logical_flow *lflow, + const struct sbrec_datapath_binding *dp, struct expr *prereqs, - struct lflow_ctx_in *l_ctx_in, - struct lflow_ctx_out *l_ctx_out, - bool *pg_addr_set_ref, char **errorp) + const struct shash *addr_sets, + const struct shash *port_groups, + struct lflow_resource_ref *lfrr, + bool *pg_addr_set_ref) { struct sset addr_sets_ref = SSET_INITIALIZER(&addr_sets_ref); struct sset port_groups_ref = SSET_INITIALIZER(&port_groups_ref); - char *error; + char *error = NULL; - struct expr *e = expr_parse_string(lflow->match, &symtab, - l_ctx_in->addr_sets, - l_ctx_in->port_groups, &addr_sets_ref, - &port_groups_ref, - lflow->logical_datapath->tunnel_key, + struct expr *e = expr_parse_string(lflow->match, &symtab, addr_sets, + port_groups, &addr_sets_ref, + &port_groups_ref, dp->tunnel_key, &error); const char *addr_set_name; SSET_FOR_EACH (addr_set_name, &addr_sets_ref) { - lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_ADDRSET, addr_set_name, + lflow_resource_add(lfrr, REF_TYPE_ADDRSET, addr_set_name, &lflow->header_.uuid); } const char *port_group_name; SSET_FOR_EACH (port_group_name, &port_groups_ref) { - lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_PORTGROUP, - port_group_name, &lflow->header_.uuid); + lflow_resource_add(lfrr, REF_TYPE_PORTGROUP, port_group_name, + &lflow->header_.uuid); } + if (pg_addr_set_ref) { + *pg_addr_set_ref = (!sset_is_empty(&port_groups_ref) || + !sset_is_empty(&addr_sets_ref)); + } + sset_destroy(&addr_sets_ref); + sset_destroy(&port_groups_ref); + if (!error) { if (prereqs) { e = expr_combine(EXPR_T_AND, e, prereqs); - prereqs = NULL; } e = expr_annotate(e, &symtab, &error); } @@ -797,44 +808,26 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s", lflow->match, error); - sset_destroy(&addr_sets_ref); - sset_destroy(&port_groups_ref); - *errorp = error; + free(error); return NULL; } - e = expr_simplify(e); - - if (pg_addr_set_ref) { - *pg_addr_set_ref = (!sset_is_empty(&port_groups_ref) || - !sset_is_empty(&addr_sets_ref)); - } - - sset_destroy(&addr_sets_ref); - sset_destroy(&port_groups_ref); - - *errorp = NULL; - return e; + return expr_simplify(e); } static bool -consider_logical_flow(const struct sbrec_logical_flow *lflow, - struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, - struct hmap *nd_ra_opts, - struct controller_event_options *controller_event_opts, - struct lflow_ctx_in *l_ctx_in, - struct lflow_ctx_out *l_ctx_out) +consider_logical_flow__(const struct sbrec_logical_flow *lflow, + const struct sbrec_datapath_binding *dp, + struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts, + struct controller_event_options *controller_event_opts, + struct lflow_ctx_in *l_ctx_in, + struct lflow_ctx_out *l_ctx_out) { /* Determine translation of logical table IDs to physical table IDs. */ bool ingress = !strcmp(lflow->pipeline, "ingress"); - const struct sbrec_datapath_binding *ldp = lflow->logical_datapath; - if (!ldp) { - VLOG_DBG("lflow "UUID_FMT" has no datapath binding, skip", - UUID_ARGS(&lflow->header_.uuid)); - return true; - } - if (!get_local_datapath(l_ctx_in->local_datapaths, ldp->tunnel_key)) { + if (!get_local_datapath(l_ctx_in->local_datapaths, dp->tunnel_key)) { VLOG_DBG("lflow "UUID_FMT" is not for local datapath, skip", UUID_ARGS(&lflow->header_.uuid)); return true; @@ -883,7 +876,7 @@ .sbrec_multicast_group_by_name_datapath = l_ctx_in->sbrec_multicast_group_by_name_datapath, .sbrec_port_binding_by_name = l_ctx_in->sbrec_port_binding_by_name, - .dp = lflow->logical_datapath + .dp = dp, }; struct condition_aux cond_aux = { .sbrec_port_binding_by_name = l_ctx_in->sbrec_port_binding_by_name, @@ -896,13 +889,13 @@ struct expr *expr = NULL; if (!l_ctx_out->lflow_cache_map) { /* Caching is disabled. */ - expr = convert_match_to_expr(lflow, prereqs, l_ctx_in, - l_ctx_out, NULL, &error); - if (error) { + expr = convert_match_to_expr(lflow, dp, prereqs, l_ctx_in->addr_sets, + l_ctx_in->port_groups, l_ctx_out->lfrr, + NULL); + if (!expr) { expr_destroy(prereqs); ovnacts_free(ovnacts.data, ovnacts.size); ofpbuf_uninit(&ovnacts); - free(error); return true; } @@ -922,7 +915,7 @@ return true; } - add_matches_to_flow_table(lflow, &matches, *l_ctx_out->conj_id_ofs, + add_matches_to_flow_table(lflow, dp, &matches, *l_ctx_out->conj_id_ofs, ptable, output_ptable, &ovnacts, ingress, l_ctx_in, l_ctx_out); @@ -939,7 +932,7 @@ if (lc && lc->type == LCACHE_T_MATCHES) { /* 'matches' is cached. No need to do expr parsing. * Add matches to flow table and return. */ - add_matches_to_flow_table(lflow, lc->expr_matches, lc->conj_id_ofs, + add_matches_to_flow_table(lflow, dp, lc->expr_matches, lc->conj_id_ofs, ptable, output_ptable, &ovnacts, ingress, l_ctx_in, l_ctx_out); ovnacts_free(ovnacts.data, ovnacts.size); @@ -959,13 +952,13 @@ bool pg_addr_set_ref = false; if (!expr) { - expr = convert_match_to_expr(lflow, prereqs, l_ctx_in, l_ctx_out, - &pg_addr_set_ref, &error); - if (error) { + expr = convert_match_to_expr(lflow, dp, prereqs, l_ctx_in->addr_sets, + l_ctx_in->port_groups, l_ctx_out->lfrr, + &pg_addr_set_ref); + if (!expr) { expr_destroy(prereqs); ovnacts_free(ovnacts.data, ovnacts.size); ofpbuf_uninit(&ovnacts); - free(error); return true; } } else { @@ -1017,7 +1010,7 @@ } /* Encode OVN logical actions into OpenFlow. */ - add_matches_to_flow_table(lflow, matches, lc->conj_id_ofs, + add_matches_to_flow_table(lflow, dp, matches, lc->conj_id_ofs, ptable, output_ptable, &ovnacts, ingress, l_ctx_in, l_ctx_out); ovnacts_free(ovnacts.data, ovnacts.size); @@ -1039,6 +1032,42 @@ return true; } +static bool +consider_logical_flow(const struct sbrec_logical_flow *lflow, + struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts, + struct controller_event_options *controller_event_opts, + struct lflow_ctx_in *l_ctx_in, + struct lflow_ctx_out *l_ctx_out) +{ + const struct sbrec_logical_dp_group *dp_group = lflow->logical_dp_group; + const struct sbrec_datapath_binding *dp = lflow->logical_datapath; + bool ret = true; + + if (!dp_group && !dp) { + VLOG_DBG("lflow "UUID_FMT" has no datapath binding, skip", + UUID_ARGS(&lflow->header_.uuid)); + return true; + } + ovs_assert(!dp_group || !dp); + + if (dp && !consider_logical_flow__(lflow, dp, + dhcp_opts, dhcpv6_opts, nd_ra_opts, + controller_event_opts, + l_ctx_in, l_ctx_out)) { + ret = false; + } + for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) { + if (!consider_logical_flow__(lflow, dp_group->datapaths[i], + dhcp_opts, dhcpv6_opts, nd_ra_opts, + controller_event_opts, + l_ctx_in, l_ctx_out)) { + ret = false; + } + } + return ret; +} + static void put_load(const uint8_t *data, size_t len, enum mf_field_id dst, int ofs, int n_bits, @@ -1144,6 +1173,203 @@ } } +static void +add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, + struct ovn_lb_vip *lb_vip, + struct ovn_lb_backend *lb_backend, + uint8_t lb_proto, + struct ovn_desired_flow_table *flow_table) +{ + uint64_t stub[1024 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); + + uint8_t value = 1; + put_load(&value, sizeof value, MFF_LOG_FLAGS, + MLF_LOOKUP_LB_HAIRPIN_BIT, 1, &ofpacts); + + struct match hairpin_match = MATCH_CATCHALL_INITIALIZER; + struct match hairpin_reply_match = MATCH_CATCHALL_INITIALIZER; + + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip); + + match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IP)); + match_set_nw_src(&hairpin_match, ip4); + match_set_nw_dst(&hairpin_match, ip4); + + match_set_dl_type(&hairpin_reply_match, + htons(ETH_TYPE_IP)); + match_set_nw_src(&hairpin_reply_match, ip4); + match_set_nw_dst(&hairpin_reply_match, + in6_addr_get_mapped_ipv4(&lb_vip->vip)); + } else { + match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IPV6)); + match_set_ipv6_src(&hairpin_match, &lb_backend->ip); + match_set_ipv6_dst(&hairpin_match, &lb_backend->ip); + + match_set_dl_type(&hairpin_reply_match, + htons(ETH_TYPE_IPV6)); + match_set_ipv6_src(&hairpin_reply_match, &lb_backend->ip); + match_set_ipv6_dst(&hairpin_reply_match, &lb_vip->vip); + } + + if (lb_backend->port) { + match_set_nw_proto(&hairpin_match, lb_proto); + match_set_tp_dst(&hairpin_match, htons(lb_backend->port)); + + match_set_nw_proto(&hairpin_reply_match, lb_proto); + match_set_tp_src(&hairpin_reply_match, htons(lb_backend->port)); + } + + /* In the original direction, only match on traffic that was already + * load balanced, i.e., "ct.natted == 1". Also, it's good enough + * to not include the datapath tunnel_key in the match when determining + * that a packet needs to be hairpinned because the rest of the match is + * restrictive enough: + * - traffic must have already been load balanced. + * - packets must have ip.src == ip.dst at this point. + * - the destination protocol and port must be of a valid backend that + * has the same IP as ip.dst. + */ + ovs_u128 lb_ct_label = { + .u64.lo = OVN_CT_NATTED, + }; + match_set_ct_label_masked(&hairpin_match, lb_ct_label, lb_ct_label); + + ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN, 100, + lb->slb->header_.uuid.parts[0], &hairpin_match, + &ofpacts, &lb->slb->header_.uuid); + + for (size_t i = 0; i < lb->slb->n_datapaths; i++) { + match_set_metadata(&hairpin_reply_match, + htonll(lb->slb->datapaths[i]->tunnel_key)); + + ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN_REPLY, 100, + lb->slb->header_.uuid.parts[0], + &hairpin_reply_match, + &ofpacts, &lb->slb->header_.uuid); + } + + ofpbuf_uninit(&ofpacts); +} + +static void +add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, + struct ovn_lb_vip *lb_vip, + struct ovn_desired_flow_table *flow_table) +{ + uint64_t stub[1024 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); + + struct ofpact_conntrack *ct = ofpact_put_CT(&ofpacts); + ct->recirc_table = NX_CT_RECIRC_NONE; + ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE); + ct->zone_src.ofs = 0; + ct->zone_src.n_bits = 16; + ct->flags = NX_CT_F_COMMIT; + ct->alg = 0; + + size_t nat_offset; + nat_offset = ofpacts.size; + ofpbuf_pull(&ofpacts, nat_offset); + + struct ofpact_nat *nat = ofpact_put_NAT(&ofpacts); + nat->flags = NX_NAT_F_SRC; + nat->range_af = AF_UNSPEC; + + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + nat->range_af = AF_INET; + nat->range.addr.ipv4.min = in6_addr_get_mapped_ipv4(&lb_vip->vip); + } else { + nat->range_af = AF_INET6; + nat->range.addr.ipv6.min = lb_vip->vip; + } + ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset); + ofpact_finish(&ofpacts, &ct->ofpact); + + struct match match = MATCH_CATCHALL_INITIALIZER; + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { + match_set_dl_type(&match, htons(ETH_TYPE_IP)); + match_set_ct_nw_dst(&match, nat->range.addr.ipv4.min); + } else { + match_set_dl_type(&match, htons(ETH_TYPE_IPV6)); + match_set_ct_ipv6_dst(&match, &lb_vip->vip); + } + + uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT; + match_set_ct_state_masked(&match, ct_state, ct_state); + + for (size_t i = 0; i < lb->slb->n_datapaths; i++) { + match_set_metadata(&match, + htonll(lb->slb->datapaths[i]->tunnel_key)); + + ofctrl_add_flow(flow_table, OFTABLE_CT_SNAT_FOR_VIP, 100, + lb->slb->header_.uuid.parts[0], + &match, &ofpacts, &lb->slb->header_.uuid); + } + + ofpbuf_uninit(&ofpacts); +} + +static void +consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, + const struct hmap *local_datapaths, + struct ovn_desired_flow_table *flow_table) +{ + /* Check if we need to add flows or not. If there is one datapath + * in the local_datapaths, it means all the datapaths of the lb + * will be in the local_datapaths. */ + size_t i; + for (i = 0; i < sbrec_lb->n_datapaths; i++) { + if (get_local_datapath(local_datapaths, + sbrec_lb->datapaths[i]->tunnel_key)) { + break; + } + } + + if (i == sbrec_lb->n_datapaths) { + return; + } + + struct ovn_controller_lb *lb = ovn_controller_lb_create(sbrec_lb); + uint8_t lb_proto = IPPROTO_TCP; + if (lb->slb->protocol && lb->slb->protocol[0]) { + if (!strcmp(lb->slb->protocol, "udp")) { + lb_proto = IPPROTO_UDP; + } else if (!strcmp(lb->slb->protocol, "sctp")) { + lb_proto = IPPROTO_SCTP; + } + } + + for (i = 0; i < lb->n_vips; i++) { + struct ovn_lb_vip *lb_vip = &lb->vips[i]; + + for (size_t j = 0; j < lb_vip->n_backends; j++) { + struct ovn_lb_backend *lb_backend = &lb_vip->backends[j]; + + add_lb_vip_hairpin_flows(lb, lb_vip, lb_backend, lb_proto, + flow_table); + } + + add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table); + } + + ovn_controller_lb_destroy(lb); +} + +/* Adds OpenFlow flows to flow tables for each Load balancer VIPs and + * backends to handle the load balanced hairpin traffic. */ +static void +add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table, + const struct hmap *local_datapaths, + struct ovn_desired_flow_table *flow_table) +{ + const struct sbrec_load_balancer *lb; + SBREC_LOAD_BALANCER_TABLE_FOR_EACH (lb, lb_table) { + consider_lb_hairpin_flows(lb, local_datapaths, flow_table); + } +} + /* Handles neighbor changes in mac_binding table. */ void lflow_handle_changed_neighbors( @@ -1203,6 +1429,8 @@ add_neighbor_flows(l_ctx_in->sbrec_port_binding_by_name, l_ctx_in->mac_binding_table, l_ctx_in->local_datapaths, l_ctx_out->flow_table); + add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, + l_ctx_out->flow_table); } void @@ -1248,20 +1476,61 @@ const struct sbrec_logical_flow *lflow; SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_datapath) { - if (!consider_logical_flow(lflow, &dhcp_opts, &dhcpv6_opts, - &nd_ra_opts, &controller_event_opts, - l_ctx_in, l_ctx_out)) { + if (!consider_logical_flow__(lflow, dp, &dhcp_opts, &dhcpv6_opts, + &nd_ra_opts, &controller_event_opts, + l_ctx_in, l_ctx_out)) { handled = false; l_ctx_out->conj_id_overflow = true; - break; + goto lflow_processing_end; } } sbrec_logical_flow_index_destroy_row(lf_row); + lf_row = sbrec_logical_flow_index_init_row( + l_ctx_in->sbrec_logical_flow_by_logical_dp_group); + /* There are far fewer datapath groups than logical flows. */ + const struct sbrec_logical_dp_group *ldpg; + SBREC_LOGICAL_DP_GROUP_TABLE_FOR_EACH (ldpg, + l_ctx_in->logical_dp_group_table) { + bool found = false; + for (size_t i = 0; i < ldpg->n_datapaths; i++) { + if (ldpg->datapaths[i] == dp) { + found = true; + break; + } + } + if (!found) { + continue; + } + + sbrec_logical_flow_index_set_logical_dp_group(lf_row, ldpg); + SBREC_LOGICAL_FLOW_FOR_EACH_EQUAL ( + lflow, lf_row, l_ctx_in->sbrec_logical_flow_by_logical_dp_group) { + if (!consider_logical_flow__(lflow, dp, &dhcp_opts, &dhcpv6_opts, + &nd_ra_opts, &controller_event_opts, + l_ctx_in, l_ctx_out)) { + handled = false; + l_ctx_out->conj_id_overflow = true; + goto lflow_processing_end; + } + } + } +lflow_processing_end: + sbrec_logical_flow_index_destroy_row(lf_row); + dhcp_opts_destroy(&dhcp_opts); dhcp_opts_destroy(&dhcpv6_opts); nd_ra_opts_destroy(&nd_ra_opts); controller_event_opts_destroy(&controller_event_opts); + + /* Add load balancer hairpin flows if the datapath has any load balancers + * associated. */ + for (size_t i = 0; i < dp->n_load_balancers; i++) { + consider_lb_hairpin_flows(dp->load_balancers[i], + l_ctx_in->local_datapaths, + l_ctx_out->flow_table); + } + return handled; } @@ -1279,3 +1548,37 @@ return lflow_handle_changed_ref(REF_TYPE_PORTBINDING, pb_ref_name, l_ctx_in, l_ctx_out, &changed); } + +bool +lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in, + struct lflow_ctx_out *l_ctx_out) +{ + const struct sbrec_load_balancer *lb; + + SBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (lb, l_ctx_in->lb_table) { + if (sbrec_load_balancer_is_deleted(lb)) { + VLOG_DBG("Remove hairpin flows for deleted load balancer "UUID_FMT, + UUID_ARGS(&lb->header_.uuid)); + ofctrl_remove_flows(l_ctx_out->flow_table, &lb->header_.uuid); + } + } + + SBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (lb, l_ctx_in->lb_table) { + if (sbrec_load_balancer_is_deleted(lb)) { + continue; + } + + if (!sbrec_load_balancer_is_new(lb)) { + VLOG_DBG("Remove hairpin flows for updated load balancer "UUID_FMT, + UUID_ARGS(&lb->header_.uuid)); + ofctrl_remove_flows(l_ctx_out->flow_table, &lb->header_.uuid); + } + + VLOG_DBG("Add load balancer hairpin flows for "UUID_FMT, + UUID_ARGS(&lb->header_.uuid)); + consider_lb_hairpin_flows(lb, l_ctx_in->local_datapaths, + l_ctx_out->flow_table); + } + + return true; +} diff -Nru ovn-20.09.0/controller/lflow.h ovn-20.12.0/controller/lflow.h --- ovn-20.09.0/controller/lflow.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/lflow.h 2020-12-17 17:53:32.000000000 +0000 @@ -68,6 +68,9 @@ #define OFTABLE_LOG_TO_PHY 65 #define OFTABLE_MAC_BINDING 66 #define OFTABLE_MAC_LOOKUP 67 +#define OFTABLE_CHK_LB_HAIRPIN 68 +#define OFTABLE_CHK_LB_HAIRPIN_REPLY 69 +#define OFTABLE_CT_SNAT_FOR_VIP 70 /* The number of tables for the ingress and egress pipelines. */ #define LOG_PIPELINE_LEN 24 @@ -124,14 +127,17 @@ struct lflow_ctx_in { struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath; struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath; + struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group; struct ovsdb_idl_index *sbrec_port_binding_by_name; const struct sbrec_dhcp_options_table *dhcp_options_table; const struct sbrec_dhcpv6_options_table *dhcpv6_options_table; const struct sbrec_datapath_binding_table *dp_binding_table; const struct sbrec_mac_binding_table *mac_binding_table; const struct sbrec_logical_flow_table *logical_flow_table; + const struct sbrec_logical_dp_group_table *logical_dp_group_table; const struct sbrec_multicast_group_table *mc_group_table; const struct sbrec_chassis *chassis; + const struct sbrec_load_balancer_table *lb_table; const struct hmap *local_datapaths; const struct shash *addr_sets; const struct shash *port_groups; @@ -160,7 +166,7 @@ const struct sbrec_mac_binding_table *, const struct hmap *local_datapaths, struct ovn_desired_flow_table *); - +bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *); void lflow_destroy(void); void lflow_cache_init(struct hmap *); diff -Nru ovn-20.09.0/controller/ofctrl.c ovn-20.12.0/controller/ofctrl.c --- ovn-20.09.0/controller/ofctrl.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/ofctrl.c 2020-12-17 17:53:32.000000000 +0000 @@ -188,6 +188,16 @@ * relationship is 1 to N. A link is added when a flow addition is processed. * A link is removed when a flow deletion is processed, the desired flow * table is cleared, or the installed flow table is cleared. + * + * To ensure predictable behavior, the list of desired flows is maintained + * partially sorted in the following way (from least restrictive to most + * restrictive wrt. match): + * - allow flows without action conjunction. + * - drop flows without action conjunction. + * - a single flow with action conjunction. + * + * The first desired_flow in the list is the active one, the one that is + * actually installed. */ struct installed_flow { struct ovn_flow flow; @@ -199,13 +209,11 @@ * installed flow, e.g. when there are conflict/duplicated ACLs that * generates same match conditions). */ struct ovs_list desired_refs; - - /* The corresponding flow in desired table. It must be one of the flows in - * desired_refs list. If there are more than one flows in references list, - * this is the one that is actually installed. */ - struct desired_flow *desired_flow; }; +typedef bool +(*desired_flow_match_cb)(const struct desired_flow *candidate, + const void *arg); static struct desired_flow *desired_flow_alloc( uint8_t table_id, uint16_t priority, @@ -214,14 +222,21 @@ const struct ofpbuf *actions); static struct desired_flow *desired_flow_lookup( struct ovn_desired_flow_table *, + const struct ovn_flow *target); +static struct desired_flow *desired_flow_lookup_check_uuid( + struct ovn_desired_flow_table *, const struct ovn_flow *target, - const struct uuid *sb_uuid); + const struct uuid *); +static struct desired_flow *desired_flow_lookup_conjunctive( + struct ovn_desired_flow_table *, + const struct ovn_flow *target); static void desired_flow_destroy(struct desired_flow *); static struct installed_flow *installed_flow_lookup( const struct ovn_flow *target); static void installed_flow_destroy(struct installed_flow *); static struct installed_flow *installed_flow_dup(struct desired_flow *); +static struct desired_flow *installed_flow_get_active(struct installed_flow *); static uint32_t ovn_flow_match_hash(const struct ovn_flow *); static char *ovn_flow_to_string(const struct ovn_flow *); @@ -787,35 +802,109 @@ log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored"); } } - -static void -link_installed_to_desired(struct installed_flow *i, struct desired_flow *d) + +static bool +flow_action_has_drop(const struct ovn_flow *f) { - if (i->desired_flow == d) { - return; + return f->ofpacts_len == 0; +} + +static bool +flow_action_has_conj(const struct ovn_flow *f) +{ + const struct ofpact *a = NULL; + + OFPACT_FOR_EACH (a, f->ofpacts, f->ofpacts_len) { + if (a->type == OFPACT_CONJUNCTION) { + return true; + } + } + return false; +} + +static bool +flow_action_has_allow(const struct ovn_flow *f) +{ + return !flow_action_has_drop(f) && !flow_action_has_conj(f); +} + +/* Returns true if flow 'a' is preferred over flow 'b'. */ +static bool +flow_is_preferred(const struct ovn_flow *a, const struct ovn_flow *b) +{ + if (flow_action_has_allow(b)) { + return false; + } + if (flow_action_has_allow(a)) { + return true; } + if (flow_action_has_drop(b)) { + return false; + } + if (flow_action_has_drop(a)) { + return true; + } + + /* Flows 'a' and 'b' should never both have action conjunction. */ + OVS_NOT_REACHED(); +} - if (ovs_list_is_empty(&i->desired_refs)) { - ovs_assert(!i->desired_flow); - i->desired_flow = d; +/* Adds the desired flow to the list of desired flows that have same match + * conditions as the installed flow. + * + * It is caller's responsibility to make sure the link between the pair didn't + * exist before. + * + * Returns true if the newly added desired flow is selected to be the active + * one. + */ +static bool +link_installed_to_desired(struct installed_flow *i, struct desired_flow *d) +{ + struct desired_flow *f; + + /* Find first 'f' such that 'd' is preferred over 'f'. If no such desired + * flow exists then 'f' will point after the last element of the list. + */ + LIST_FOR_EACH (f, installed_ref_list_node, &i->desired_refs) { + if (flow_is_preferred(&d->flow, &f->flow)) { + break; + } } - ovs_list_insert(&i->desired_refs, &d->installed_ref_list_node); + ovs_list_insert(&f->installed_ref_list_node, &d->installed_ref_list_node); d->installed_flow = i; + return installed_flow_get_active(i) == d; } +/* Replaces 'old_desired' with 'new_desired' in the list of desired flows + * that have same match conditions as the installed flow. + */ static void +replace_installed_to_desired(struct installed_flow *i, + struct desired_flow *old_desired, + struct desired_flow *new_desired) +{ + ovs_assert(old_desired->installed_flow == i); + ovs_list_replace(&new_desired->installed_ref_list_node, + &old_desired->installed_ref_list_node); + old_desired->installed_flow = NULL; + new_desired->installed_flow = i; +} + +/* Removes the desired flow from the list of desired flows that have the same + * match conditions as the installed flow. + * + * Returns true if the desired flow was the previously active flow. + */ +static bool unlink_installed_to_desired(struct installed_flow *i, struct desired_flow *d) { - ovs_assert(i && i->desired_flow && !ovs_list_is_empty(&i->desired_refs)); + struct desired_flow *old_active = installed_flow_get_active(i); + ovs_assert(d && d->installed_flow == i); ovs_list_remove(&d->installed_ref_list_node); d->installed_flow = NULL; - if (i->desired_flow == d) { - i->desired_flow = ovs_list_is_empty(&i->desired_refs) ? NULL : - CONTAINER_OF(ovs_list_front(&i->desired_refs), - struct desired_flow, - installed_ref_list_node); - } + return old_active == d; } static void @@ -939,7 +1028,7 @@ struct desired_flow *f = desired_flow_alloc(table_id, priority, cookie, match, actions); - if (desired_flow_lookup(flow_table, &f->flow, sb_uuid)) { + if (desired_flow_lookup_check_uuid(flow_table, &f->flow, sb_uuid)) { if (log_duplicate_flow) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); if (!VLOG_DROP_DBG(&rl)) { @@ -979,14 +1068,15 @@ const struct ofpbuf *actions, const struct uuid *sb_uuid) { - struct desired_flow *f = desired_flow_alloc(table_id, priority, cookie, - match, actions); - struct desired_flow *existing; - existing = desired_flow_lookup(desired_flows, &f->flow, NULL); + struct desired_flow *f; + + f = desired_flow_alloc(table_id, priority, cookie, match, actions); + existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow); if (existing) { - /* There's already a flow with this particular match. Append the - * action to that flow rather than adding a new flow + /* There's already a flow with this particular match and action + * 'conjunction'. Append the action to that flow rather than + * adding a new flow. */ uint64_t compound_stub[64 / 8]; struct ofpbuf compound; @@ -1214,7 +1304,6 @@ { struct installed_flow *dst = xmalloc(sizeof *dst); ovs_list_init(&dst->desired_refs); - dst->desired_flow = NULL; dst->flow.table_id = src->flow.table_id; dst->flow.priority = src->flow.priority; minimatch_clone(&dst->flow.match, &src->flow.match); @@ -1225,15 +1314,22 @@ return dst; } -/* Finds and returns a desired_flow in 'flow_table' whose key is identical to - * 'target''s key, or NULL if there is none. - * - * If sb_uuid is not NULL, the function will also check if the found flow is - * referenced by the sb_uuid. */ static struct desired_flow * -desired_flow_lookup(struct ovn_desired_flow_table *flow_table, - const struct ovn_flow *target, - const struct uuid *sb_uuid) +installed_flow_get_active(struct installed_flow *f) +{ + if (!ovs_list_is_empty(&f->desired_refs)) { + return CONTAINER_OF(ovs_list_front(&f->desired_refs), + struct desired_flow, + installed_ref_list_node); + } + return NULL; +} + +static struct desired_flow * +desired_flow_lookup__(struct ovn_desired_flow_table *flow_table, + const struct ovn_flow *target, + desired_flow_match_cb match_cb, + const void *arg) { struct desired_flow *d; HMAP_FOR_EACH_WITH_HASH (d, match_hmap_node, target->hash, @@ -1242,20 +1338,76 @@ if (f->table_id == target->table_id && f->priority == target->priority && minimatch_equal(&f->match, &target->match)) { - if (!sb_uuid) { + + if (!match_cb || match_cb(d, arg)) { return d; } - struct sb_flow_ref *sfr; - LIST_FOR_EACH (sfr, sb_list, &d->references) { - if (uuid_equals(sb_uuid, &sfr->sb_uuid)) { - return d; - } - } } } return NULL; } +/* Finds and returns a desired_flow in 'flow_table' whose key is identical to + * 'target''s key, or NULL if there is none. + */ +static struct desired_flow * +desired_flow_lookup(struct ovn_desired_flow_table *flow_table, + const struct ovn_flow *target) +{ + return desired_flow_lookup__(flow_table, target, NULL, NULL); +} + +static bool +flow_lookup_match_uuid_cb(const struct desired_flow *candidate, + const void *arg) +{ + const struct uuid *sb_uuid = arg; + struct sb_flow_ref *sfr; + + LIST_FOR_EACH (sfr, sb_list, &candidate->references) { + if (uuid_equals(sb_uuid, &sfr->sb_uuid)) { + return true; + } + } + return false; +} + +/* Finds and returns a desired_flow in 'flow_table' whose key is identical to + * 'target''s key, or NULL if there is none. + * + * The function will also check if the found flow is referenced by the + * 'sb_uuid'. + */ +static struct desired_flow * +desired_flow_lookup_check_uuid(struct ovn_desired_flow_table *flow_table, + const struct ovn_flow *target, + const struct uuid *sb_uuid) +{ + return desired_flow_lookup__(flow_table, target, flow_lookup_match_uuid_cb, + sb_uuid); +} + +static bool +flow_lookup_match_conj_cb(const struct desired_flow *candidate, + const void *arg OVS_UNUSED) +{ + return flow_action_has_conj(&candidate->flow); +} + +/* Finds and returns a desired_flow in 'flow_table' whose key is identical to + * 'target''s key, or NULL if there is none. + * + * The function will only return a matching flow if it contains action + * 'conjunction'. + */ +static struct desired_flow * +desired_flow_lookup_conjunctive(struct ovn_desired_flow_table *flow_table, + const struct ovn_flow *target) +{ + return desired_flow_lookup__(flow_table, target, flow_lookup_match_conj_cb, + NULL); +} + /* Finds and returns an installed_flow in installed_flows whose key is * identical to 'target''s key, or NULL if there is none. */ static struct installed_flow * @@ -1321,8 +1473,7 @@ installed_flow_destroy(struct installed_flow *f) { if (f) { - ovs_assert(ovs_list_is_empty(&f->desired_refs)); - ovs_assert(!f->desired_flow); + ovs_assert(!installed_flow_get_active(f)); ovn_flow_uninit(&f->flow); free(f); } @@ -1524,6 +1675,7 @@ &usable_protocols); if (!error) { add_meter_mod(&mm, msgs); + free(mm.meter.bands); } else { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_ERR_RL(&rl, "new meter %s %s", error, meter_string); @@ -1653,8 +1805,7 @@ struct installed_flow *i, *next; HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) { unlink_all_refs_for_installed_flow(i); - struct desired_flow *d = - desired_flow_lookup(flow_table, &i->flow, NULL); + struct desired_flow *d = desired_flow_lookup(flow_table, &i->flow); if (!d) { /* Installed flow is no longer desirable. Delete it from the * switch and from installed_flows. */ @@ -1687,8 +1838,18 @@ /* Copy 'd' from 'flow_table' to installed_flows. */ i = installed_flow_dup(d); hmap_insert(&installed_flows, &i->match_hmap_node, i->flow.hash); + link_installed_to_desired(i, d); + } else if (!d->installed_flow) { + /* This is a desired_flow that conflicts with one installed + * previously but not linked yet. However, if this flow becomes + * active, e.g., it is less restrictive than the previous active + * flow then modify the installed flow. + */ + if (link_installed_to_desired(i, d)) { + installed_flow_mod(&i->flow, &d->flow, msgs); + ovn_flow_log(&i->flow, "updating installed (conflict)"); + } } - link_installed_to_desired(i, d); } } @@ -1740,11 +1901,10 @@ /* del_f must have been installed, otherwise it should have been * removed during track_flow_add_or_modify. */ ovs_assert(del_f->installed_flow); + if (!f->installed_flow) { /* f is not installed yet. */ - struct installed_flow *i = del_f->installed_flow; - unlink_installed_to_desired(i, del_f); - link_installed_to_desired(i, f); + replace_installed_to_desired(del_f->installed_flow, del_f, f); } else { /* f has been installed before, and now was updated to exact * the same flow as del_f. */ @@ -1778,22 +1938,22 @@ /* The desired flow was deleted */ if (f->installed_flow) { struct installed_flow *i = f->installed_flow; - unlink_installed_to_desired(i, f); + bool was_active = unlink_installed_to_desired(i, f); + struct desired_flow *d = installed_flow_get_active(i); - if (!i->desired_flow) { + if (!d) { installed_flow_del(&i->flow, msgs); ovn_flow_log(&i->flow, "removing installed (tracked)"); hmap_remove(&installed_flows, &i->match_hmap_node); installed_flow_destroy(i); - } else { + } else if (was_active) { /* There are other desired flow(s) referencing this * installed flow, so update the OVS flow for the new * active flow (at least the cookie will be different, * even if the actions are the same). */ - struct desired_flow *d = i->desired_flow; - ovn_flow_log(&i->flow, "updating installed (tracked)"); installed_flow_mod(&i->flow, &d->flow, msgs); + ovn_flow_log(&i->flow, "updating installed (tracked)"); } } desired_flow_destroy(f); @@ -1810,15 +1970,22 @@ hmap_insert(&installed_flows, &new_node->match_hmap_node, new_node->flow.hash); link_installed_to_desired(new_node, f); - } else if (i->desired_flow == f) { + } else if (installed_flow_get_active(i) == f) { /* The installed flow is installed for f, but f has change * tracked, so it must have been modified. */ - ovn_flow_log(&i->flow, "updating installed (tracked)"); installed_flow_mod(&i->flow, &f->flow, msgs); + ovn_flow_log(&i->flow, "updating installed (tracked)"); } else { /* Adding a new flow that conflicts with an existing installed - * flow, so just add it to the link. */ - link_installed_to_desired(i, f); + * flow, so add it to the link. If this flow becomes active, + * e.g., it is less restrictive than the previous active flow + * then modify the installed flow. + */ + if (link_installed_to_desired(i, f)) { + installed_flow_mod(&i->flow, &f->flow, msgs); + ovn_flow_log(&i->flow, + "updating installed (tracked conflict)"); + } } /* The track_list_node emptyness is used to check if the node is * already added to track list, so initialize it again here. */ @@ -1865,19 +2032,23 @@ bool need_put = false; if (flow_changed || skipped_last_time || need_reinstall_flows) { need_put = true; + old_nb_cfg = nb_cfg; } else if (nb_cfg != old_nb_cfg) { /* nb_cfg changed since last ofctrl_put() call */ if (cur_cfg == old_nb_cfg) { - /* we were up-to-date already, so just update with the - * new nb_cfg */ - cur_cfg = nb_cfg; + /* If there are no updates pending, we were up-to-date already, + * update with the new nb_cfg. + */ + if (ovs_list_is_empty(&flow_updates)) { + cur_cfg = nb_cfg; + old_nb_cfg = nb_cfg; + } } else { need_put = true; + old_nb_cfg = nb_cfg; } } - old_nb_cfg = nb_cfg; - if (!need_put) { VLOG_DBG("ofctrl_put not needed"); return; diff -Nru ovn-20.09.0/controller/ovn-controller.8.xml ovn-20.12.0/controller/ovn-controller.8.xml --- ovn-20.09.0/controller/ovn-controller.8.xml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/ovn-controller.8.xml 2020-12-17 17:53:32.000000000 +0000 @@ -68,7 +68,12 @@

external_ids:system-id
-
The chassis name to use in the Chassis table.
+
The chassis name to use in the Chassis table. Changing the + system-id while ovn-controller is running is + not directly supported. Users have two options: either first + gracefully stop ovn-controller or manually delete the + stale Chassis and Chassis_Private records + after changing the system-id.
external_ids:hostname
The hostname to use in the Chassis table.
@@ -233,6 +238,17 @@ The boolean flag indicates if the chassis is used as an interconnection gateway. + +
external_ids:ovn-match-northd-version
+
+ The boolean flag indicates if ovn-controller needs to + check ovn-northd version. If this + flag is set to true and the ovn-northd's version (reported + in the Southbound database) doesn't match with the + ovn-controller's internal version, then it will stop + processing the southbound and local Open vSwitch database changes. + The default value is considered false if this option is not defined. +

@@ -378,6 +394,18 @@ logical patch port that it implements.

+ +
+ external-ids:ovn-nb-cfg in the Bridge table +
+ +
+

+ This key represents the last known + OVN_Southbound.SB_Global.nb_cfg value for which all + flows have been successfully installed in OVS. +

+

OVN Southbound Database Usage

diff -Nru ovn-20.09.0/controller/ovn-controller.c ovn-20.12.0/controller/ovn-controller.c --- ovn-20.09.0/controller/ovn-controller.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/ovn-controller.c 2020-12-17 17:53:32.000000000 +0000 @@ -64,6 +64,7 @@ #include "timer.h" #include "stopwatch.h" #include "lib/inc-proc-eng.h" +#include "hmapx.h" VLOG_DEFINE_THIS_MODULE(main); @@ -85,6 +86,8 @@ #define CONTROLLER_LOOP_STOPWATCH_NAME "ovn-controller-flow-generation" +#define OVS_NB_CFG_NAME "ovn-nb-cfg" + static char *parse_options(int argc, char *argv[]); OVS_NO_RETURN static void usage(void); @@ -130,7 +133,7 @@ return NULL; } -static void +static unsigned int update_sb_monitors(struct ovsdb_idl *ovnsb_idl, const struct sbrec_chassis *chassis, const struct sset *local_ifaces, @@ -154,6 +157,7 @@ * the connected logical routers and logical switches. */ struct ovsdb_idl_condition pb = OVSDB_IDL_CONDITION_INIT(&pb); struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf); + struct ovsdb_idl_condition ldpg = OVSDB_IDL_CONDITION_INIT(&ldpg); struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb); struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg); struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns); @@ -165,6 +169,7 @@ if (monitor_all) { ovsdb_idl_condition_add_clause_true(&pb); ovsdb_idl_condition_add_clause_true(&lf); + ovsdb_idl_condition_add_clause_true(&ldpg); ovsdb_idl_condition_add_clause_true(&mb); ovsdb_idl_condition_add_clause_true(&mg); ovsdb_idl_condition_add_clause_true(&dns); @@ -181,7 +186,7 @@ * chassis */ sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "chassisredirect"); sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external"); - if (chassis && !sbrec_chassis_is_new(chassis)) { + if (chassis) { /* This should be mostly redundant with the other clauses for port * bindings, but it allows us to catch any ports that are assigned to * us but should not be. That way, we can clear their chassis @@ -205,8 +210,8 @@ &chassis->header_.uuid); /* Monitors Chassis_Private record for current chassis only. */ - sbrec_chassis_private_add_clause_chassis(&chprv, OVSDB_F_EQ, - &chassis->header_.uuid); + sbrec_chassis_private_add_clause_name(&chprv, OVSDB_F_EQ, + chassis->name); } else { /* During initialization, we monitor all records in Chassis_Private so * that we don't try to recreate existing ones. */ @@ -228,26 +233,56 @@ sbrec_port_binding_add_clause_datapath(&pb, OVSDB_F_EQ, uuid); sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ, uuid); + sbrec_logical_dp_group_add_clause_datapaths( + &ldpg, OVSDB_F_INCLUDES, &uuid, 1); sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid); sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid); sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1); sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ, uuid); } + + /* Updating conditions to receive logical flows that references + * datapath groups containing local datapaths. */ + const struct sbrec_logical_dp_group *group; + SBREC_LOGICAL_DP_GROUP_FOR_EACH (group, ovnsb_idl) { + struct uuid *uuid = CONST_CAST(struct uuid *, + &group->header_.uuid); + size_t i; + + for (i = 0; i < group->n_datapaths; i++) { + if (get_local_datapath(local_datapaths, + group->datapaths[i]->tunnel_key)) { + sbrec_logical_flow_add_clause_logical_dp_group( + &lf, OVSDB_F_EQ, uuid); + break; + } + } + } + } + +out:; + unsigned int cond_seqnos[] = { + sbrec_port_binding_set_condition(ovnsb_idl, &pb), + sbrec_logical_flow_set_condition(ovnsb_idl, &lf), + sbrec_logical_dp_group_set_condition(ovnsb_idl, &ldpg), + sbrec_mac_binding_set_condition(ovnsb_idl, &mb), + sbrec_multicast_group_set_condition(ovnsb_idl, &mg), + sbrec_dns_set_condition(ovnsb_idl, &dns), + sbrec_controller_event_set_condition(ovnsb_idl, &ce), + sbrec_ip_multicast_set_condition(ovnsb_idl, &ip_mcast), + sbrec_igmp_group_set_condition(ovnsb_idl, &igmp), + sbrec_chassis_private_set_condition(ovnsb_idl, &chprv), + }; + + unsigned int expected_cond_seqno = 0; + for (size_t i = 0; i < ARRAY_SIZE(cond_seqnos); i++) { + expected_cond_seqno = MAX(expected_cond_seqno, cond_seqnos[i]); } -out: - sbrec_port_binding_set_condition(ovnsb_idl, &pb); - sbrec_logical_flow_set_condition(ovnsb_idl, &lf); - sbrec_mac_binding_set_condition(ovnsb_idl, &mb); - sbrec_multicast_group_set_condition(ovnsb_idl, &mg); - sbrec_dns_set_condition(ovnsb_idl, &dns); - sbrec_controller_event_set_condition(ovnsb_idl, &ce); - sbrec_ip_multicast_set_condition(ovnsb_idl, &ip_mcast); - sbrec_igmp_group_set_condition(ovnsb_idl, &igmp); - sbrec_chassis_private_set_condition(ovnsb_idl, &chprv); ovsdb_idl_condition_destroy(&pb); ovsdb_idl_condition_destroy(&lf); + ovsdb_idl_condition_destroy(&ldpg); ovsdb_idl_condition_destroy(&mb); ovsdb_idl_condition_destroy(&mg); ovsdb_idl_condition_destroy(&dns); @@ -255,6 +290,7 @@ ovsdb_idl_condition_destroy(&ip_mcast); ovsdb_idl_condition_destroy(&igmp); ovsdb_idl_condition_destroy(&chprv); + return expected_cond_seqno; } static const char * @@ -488,7 +524,7 @@ 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, - bool *enable_lflow_cache) + bool *enable_lflow_cache, unsigned int *sb_cond_seqno) { const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); if (!cfg) { @@ -513,7 +549,11 @@ * Otherwise, don't call it here, because there would be unnecessary * extra cost. Instead, it is called after the engine execution only * when it is necessary. */ - update_sb_monitors(ovnsb_idl, NULL, NULL, NULL, true); + unsigned int next_cond_seqno = + update_sb_monitors(ovnsb_idl, NULL, NULL, NULL, true); + if (sb_cond_seqno) { + *sb_cond_seqno = next_cond_seqno; + } } if (monitor_all_p) { *monitor_all_p = monitor_all; @@ -532,20 +572,38 @@ } static void +add_pending_ct_zone_entry(struct shash *pending_ct_zones, + enum ct_zone_pending_state state, + int zone, bool add, const char *name) +{ + VLOG_DBG("%s ct zone %"PRId32" for '%s'", + add ? "assigning" : "removing", zone, name); + + struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); + pending->state = state; /* Skip flushing zone. */ + pending->zone = zone; + pending->add = add; + shash_add(pending_ct_zones, name, pending); +} + +static void update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, struct simap *ct_zones, unsigned long *ct_zone_bitmap, - struct shash *pending_ct_zones) + struct shash *pending_ct_zones, struct hmapx *updated_dps) { struct simap_node *ct_zone, *ct_zone_next; int scan_start = 1; const char *user; struct sset all_users = SSET_INITIALIZER(&all_users); + struct simap req_snat_zones = SIMAP_INITIALIZER(&req_snat_zones); + unsigned long unreq_snat_zones[BITMAP_N_LONGS(MAX_CT_ZONES)]; SSET_FOR_EACH(user, lports) { sset_add(&all_users, user); } /* Local patched datapath (gateway routers) need zones assigned. */ + struct shash all_lds = SHASH_INITIALIZER(&all_lds); const struct local_datapath *ld; HMAP_FOR_EACH (ld, hmap_node, local_datapaths) { /* XXX Add method to limit zone assignment to logical router @@ -554,6 +612,13 @@ char *snat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "snat"); sset_add(&all_users, dnat); sset_add(&all_users, snat); + shash_add(&all_lds, dnat, ld); + shash_add(&all_lds, snat, ld); + + int req_snat_zone = datapath_snat_ct_zone(ld->datapath); + if (req_snat_zone >= 0) { + simap_put(&req_snat_zones, snat, req_snat_zone); + } free(dnat); free(snat); } @@ -564,14 +629,60 @@ VLOG_DBG("removing ct zone %"PRId32" for '%s'", ct_zone->data, ct_zone->name); - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_DB_QUEUED; /* Skip flushing zone. */ - pending->zone = ct_zone->data; - pending->add = false; - shash_add(pending_ct_zones, ct_zone->name, pending); + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_DB_QUEUED, + ct_zone->data, false, ct_zone->name); bitmap_set0(ct_zone_bitmap, ct_zone->data); simap_delete(ct_zones, ct_zone); + } else if (!simap_find(&req_snat_zones, ct_zone->name)) { + bitmap_set1(unreq_snat_zones, ct_zone->data); + } + } + + /* Prioritize requested CT zones */ + struct simap_node *snat_req_node; + SIMAP_FOR_EACH (snat_req_node, &req_snat_zones) { + struct simap_node *node = simap_find(ct_zones, snat_req_node->name); + if (node) { + if (node->data == snat_req_node->data) { + /* No change to this request, so no action needed */ + continue; + } else { + /* Zone request has changed for this node. delete old entry */ + bitmap_set0(ct_zone_bitmap, node->data); + simap_delete(ct_zones, node); + } + } + + /* Determine if someone already had this zone auto-assigned. + * If so, then they need to give up their assignment since + * that zone is being explicitly requested now. + */ + if (bitmap_is_set(unreq_snat_zones, snat_req_node->data)) { + struct simap_node *dup; + struct simap_node *next; + SIMAP_FOR_EACH_SAFE (dup, next, ct_zones) { + if (dup != snat_req_node && dup->data == snat_req_node->data) { + simap_delete(ct_zones, dup); + break; + } + } + /* Set this bit to 0 so that if multiple datapaths have requested + * this zone, we don't needlessly double-detect this condition. + */ + bitmap_set0(unreq_snat_zones, snat_req_node->data); + } + + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_OF_QUEUED, + snat_req_node->data, true, + snat_req_node->name); + + bitmap_set1(ct_zone_bitmap, snat_req_node->data); + simap_put(ct_zones, snat_req_node->name, snat_req_node->data); + struct shash_node *ld_node = shash_find(&all_lds, snat_req_node->name); + if (ld_node) { + struct local_datapath *dp = ld_node->data; + hmapx_add(updated_dps, (void *) dp->datapath); } } @@ -592,32 +703,32 @@ if (zone == MAX_CT_ZONES + 1) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); VLOG_WARN_RL(&rl, "exhausted all ct zones"); - return; + break; } scan_start = zone + 1; - VLOG_DBG("assigning ct zone %"PRId32" to '%s'", zone, user); - - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_OF_QUEUED; - pending->zone = zone; - pending->add = true; - shash_add(pending_ct_zones, user, pending); + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_OF_QUEUED, + zone, true, user); bitmap_set1(ct_zone_bitmap, zone); simap_put(ct_zones, user, zone); + + struct shash_node *ld_node = shash_find(&all_lds, user); + if (ld_node) { + struct local_datapath *dp = ld_node->data; + hmapx_add(updated_dps, (void *) dp->datapath); + } } + simap_destroy(&req_snat_zones); sset_destroy(&all_users); + shash_destroy(&all_lds); } static void commit_ct_zones(const struct ovsrec_bridge *br_int, struct shash *pending_ct_zones) { - struct smap ct_add_ids = SMAP_INITIALIZER(&ct_add_ids); - struct sset ct_del_ids = SSET_INITIALIZER(&ct_del_ids); - struct shash_node *iter; SHASH_FOR_EACH(iter, pending_ct_zones) { struct ct_zone_pending_entry *ctzpe = iter->data; @@ -636,57 +747,19 @@ struct smap_node *node = smap_get_node(&br_int->external_ids, user_str); if (!node || strcmp(node->value, zone_str)) { - smap_add_nocopy(&ct_add_ids, user_str, zone_str); - user_str = NULL; - zone_str = NULL; + ovsrec_bridge_update_external_ids_setkey(br_int, + user_str, zone_str); } free(zone_str); } else { if (smap_get(&br_int->external_ids, user_str)) { - sset_add(&ct_del_ids, user_str); + ovsrec_bridge_update_external_ids_delkey(br_int, user_str); } } free(user_str); ctzpe->state = CT_ZONE_DB_SENT; } - - /* Update the bridge external IDs only if really needed (i.e., we must - * add a value or delete one). Rebuilding the external IDs map at - * every run is a costly operation when having lots of ct_zones. - */ - if (!smap_is_empty(&ct_add_ids) || !sset_is_empty(&ct_del_ids)) { - struct smap new_ids = SMAP_INITIALIZER(&new_ids); - - struct smap_node *id; - SMAP_FOR_EACH (id, &br_int->external_ids) { - if (sset_find_and_delete(&ct_del_ids, id->key)) { - continue; - } - - if (smap_get(&ct_add_ids, id->key)) { - continue; - } - - smap_add(&new_ids, id->key, id->value); - } - - struct smap_node *next_id; - SMAP_FOR_EACH_SAFE (id, next_id, &ct_add_ids) { - smap_replace(&new_ids, id->key, id->value); - smap_remove_node(&ct_add_ids, id); - } - - ovsrec_bridge_verify_external_ids(br_int); - ovsrec_bridge_set_external_ids(br_int, &new_ids); - smap_destroy(&new_ids); - } - - ovs_assert(smap_is_empty(&ct_add_ids)); - ovs_assert(sset_is_empty(&ct_del_ids)); - - smap_destroy(&ct_add_ids); - sset_destroy(&ct_del_ids); } static void @@ -726,11 +799,57 @@ } static int64_t -get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table) +get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table, + unsigned int cond_seqno, unsigned int expected_cond_seqno) { + static int64_t nb_cfg = 0; + + /* Delay getting nb_cfg if there are monitor condition changes + * in flight. It might be that those changes would instruct the + * server to send updates that happened before SB_Global.nb_cfg. + */ + if (cond_seqno != expected_cond_seqno) { + return nb_cfg; + } + const struct sbrec_sb_global *sb = sbrec_sb_global_table_first(sb_global_table); - return sb ? sb->nb_cfg : 0; + nb_cfg = sb ? sb->nb_cfg : 0; + return nb_cfg; +} + +/* Propagates the local cfg seqno, 'cur_cfg', to the chassis_private record + * and to the local OVS DB. + */ +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, + unsigned int delay_nb_cfg_report, + int64_t cur_cfg) +{ + if (!cur_cfg) { + return; + } + + if (sb_txn && chassis && cur_cfg != chassis->nb_cfg) { + sbrec_chassis_private_set_nb_cfg(chassis, cur_cfg); + sbrec_chassis_private_set_nb_cfg_timestamp(chassis, time_wall_msec()); + + if (delay_nb_cfg_report) { + VLOG_INFO("Sleep for %u sec", delay_nb_cfg_report); + xsleep(delay_nb_cfg_report); + } + } + + if (ovs_txn && br_int && + cur_cfg != smap_get_ullong(&br_int->external_ids, + OVS_NB_CFG_NAME, 0)) { + char *cur_cfg_str = xasprintf("%"PRId64, cur_cfg); + ovsrec_bridge_update_external_ids_setkey(br_int, OVS_NB_CFG_NAME, + cur_cfg_str); + free(cur_cfg_str); + } } static const char * @@ -785,12 +904,14 @@ SB_NODE(port_group, "port_group") \ SB_NODE(multicast_group, "multicast_group") \ SB_NODE(datapath_binding, "datapath_binding") \ + SB_NODE(logical_dp_group, "logical_dp_group") \ SB_NODE(port_binding, "port_binding") \ SB_NODE(mac_binding, "mac_binding") \ SB_NODE(logical_flow, "logical_flow") \ SB_NODE(dhcp_options, "dhcp_options") \ SB_NODE(dhcpv6_options, "dhcpv6_options") \ - SB_NODE(dns, "dns") + SB_NODE(dns, "dns") \ + SB_NODE(load_balancer, "load_balancer") enum sb_engine_node { #define SB_NODE(NAME, NAME_STR) SB_##NAME, @@ -849,7 +970,7 @@ engine_set_node_state(node, EN_UPDATED); return; } - engine_set_node_state(node, EN_VALID); + engine_set_node_state(node, EN_UNCHANGED); } struct ed_type_addr_sets { @@ -925,7 +1046,7 @@ !sset_is_empty(&as->updated)) { engine_set_node_state(node, EN_UPDATED); } else { - engine_set_node_state(node, EN_VALID); + engine_set_node_state(node, EN_UNCHANGED); } as->change_tracked = true; @@ -1005,7 +1126,7 @@ !sset_is_empty(&pg->updated)) { engine_set_node_state(node, EN_UPDATED); } else { - engine_set_node_state(node, EN_VALID); + engine_set_node_state(node, EN_UNCHANGED); } pg->change_tracked = true; @@ -1039,6 +1160,9 @@ bool tracked; bool local_lports_changed; struct hmap tracked_dp_bindings; + + /* CT zone data. Contains datapaths that had updated CT zones */ + struct hmapx ct_updated_datapaths; }; /* struct ed_type_runtime_data has the below members for tracking the @@ -1130,6 +1254,8 @@ /* Init the tracked data. */ hmap_init(&data->tracked_dp_bindings); + hmapx_init(&data->ct_updated_datapaths); + return data; } @@ -1152,6 +1278,7 @@ } hmap_destroy(&rt_data->local_datapaths); local_bindings_destroy(&rt_data->local_bindings); + hmapx_destroy(&rt_data->ct_updated_datapaths); } static void @@ -1289,6 +1416,7 @@ sset_init(&rt_data->egress_ifaces); smap_init(&rt_data->local_iface_ids); local_bindings_init(&rt_data->local_bindings); + hmapx_clear(&rt_data->ct_updated_datapaths); } struct binding_ctx_in b_ctx_in; @@ -1417,7 +1545,7 @@ struct ed_type_ct_zones *ct_zones_data = data; simap_destroy(&ct_zones_data->current); - shash_destroy(&ct_zones_data->pending); + shash_destroy_free_data(&ct_zones_data->pending); } static void @@ -1427,9 +1555,11 @@ struct ed_type_runtime_data *rt_data = engine_get_input_data("runtime_data", node); + hmapx_clear(&rt_data->ct_updated_datapaths); update_ct_zones(&rt_data->local_lports, &rt_data->local_datapaths, &ct_zones_data->current, ct_zones_data->bitmap, - &ct_zones_data->pending); + &ct_zones_data->pending, &rt_data->ct_updated_datapaths); + engine_set_node_state(node, EN_UPDATED); } @@ -1468,7 +1598,7 @@ engine_set_node_state(node, EN_UPDATED); return; } - engine_set_node_state(node, EN_VALID); + engine_set_node_state(node, EN_UNCHANGED); } /* Engine node en_physical_flow_changes indicates whether @@ -1543,6 +1673,16 @@ engine_set_node_state(node, EN_UPDATED); } +/* ct_zone changes are not handled incrementally but a handler is required + * to avoid skipping the ovs_iface incremental change handler. + */ +static bool +physical_flow_changes_ct_zones_handler(struct engine_node *node OVS_UNUSED, + void *data OVS_UNUSED) +{ + return false; +} + /* There are OVS interface changes. Indicate to the flow_output engine * to handle these OVS interface changes for physical flow computations. */ static bool @@ -1606,7 +1746,7 @@ (struct ovsrec_bridge_table *)EN_OVSDB_GET( engine_get_input("OVS_bridge", node)); const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); + const char *chassis_id = get_ovs_chassis_id(ovs_table); const struct sbrec_chassis *chassis = NULL; struct ovsdb_idl_index *sbrec_chassis_by_name = engine_ovsdb_node_get_index( @@ -1639,6 +1779,7 @@ 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->ct_updated_datapaths = &rt_data->ct_updated_datapaths; } static void init_lflow_ctx(struct engine_node *node, @@ -1657,6 +1798,11 @@ engine_get_input("SB_logical_flow", node), "logical_datapath"); + struct ovsdb_idl_index *sbrec_logical_flow_by_dp_group = + engine_ovsdb_node_get_index( + engine_get_input("SB_logical_flow", node), + "logical_dp_group"); + struct ovsdb_idl_index *sbrec_mc_group_by_name_dp = engine_ovsdb_node_get_index( engine_get_input("SB_multicast_group", node), @@ -1678,11 +1824,23 @@ (struct sbrec_logical_flow_table *)EN_OVSDB_GET( engine_get_input("SB_logical_flow", node)); + struct sbrec_logical_dp_group_table *logical_dp_group_table = + (struct sbrec_logical_dp_group_table *)EN_OVSDB_GET( + engine_get_input("SB_logical_dp_group", node)); + struct sbrec_multicast_group_table *multicast_group_table = (struct sbrec_multicast_group_table *)EN_OVSDB_GET( engine_get_input("SB_multicast_group", node)); - const char *chassis_id = chassis_get_id(); + struct sbrec_load_balancer_table *lb_table = + (struct sbrec_load_balancer_table *)EN_OVSDB_GET( + engine_get_input("SB_load_balancer", node)); + + struct ovsrec_open_vswitch_table *ovs_table = + (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( + engine_get_input("OVS_open_vswitch", node)); + + const char *chassis_id = get_ovs_chassis_id(ovs_table); const struct sbrec_chassis *chassis = NULL; struct ovsdb_idl_index *sbrec_chassis_by_name = engine_ovsdb_node_get_index( @@ -1706,13 +1864,17 @@ sbrec_mc_group_by_name_dp; l_ctx_in->sbrec_logical_flow_by_logical_datapath = sbrec_logical_flow_by_dp; + l_ctx_in->sbrec_logical_flow_by_logical_dp_group = + sbrec_logical_flow_by_dp_group; l_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name; l_ctx_in->dhcp_options_table = dhcp_table; l_ctx_in->dhcpv6_options_table = dhcpv6_table; l_ctx_in->mac_binding_table = mac_binding_table; l_ctx_in->logical_flow_table = logical_flow_table; + l_ctx_in->logical_dp_group_table = logical_dp_group_table; l_ctx_in->mc_group_table = multicast_group_table; l_ctx_in->chassis = chassis; + l_ctx_in->lb_table = lb_table; l_ctx_in->local_datapaths = &rt_data->local_datapaths; l_ctx_in->addr_sets = addr_sets; l_ctx_in->port_groups = port_groups; @@ -1772,7 +1934,7 @@ (struct ovsrec_bridge_table *)EN_OVSDB_GET( engine_get_input("OVS_bridge", node)); const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); + const char *chassis_id = get_ovs_chassis_id(ovs_table); struct ovsdb_idl_index *sbrec_chassis_by_name = engine_ovsdb_node_get_index( @@ -1956,7 +2118,7 @@ (struct ovsrec_bridge_table *)EN_OVSDB_GET( engine_get_input("OVS_bridge", node)); const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); + const char *chassis_id = get_ovs_chassis_id(ovs_table); struct ovsdb_idl_index *sbrec_chassis_by_name = engine_ovsdb_node_get_index( @@ -2063,19 +2225,22 @@ struct ed_type_pfc_data *pfc_data = engine_get_input_data("physical_flow_changes", node); + /* If there are OVS interface changes. Try to handle them incrementally. */ + if (pfc_data->ovs_ifaces_changed) { + if (!physical_handle_ovs_iface_changes(&p_ctx, &fo->flow_table)) { + return false; + } + } + if (pfc_data->recompute_physical_flows) { /* 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, + &fo->flow_table); physical_run(&p_ctx, &fo->flow_table); return true; } - if (pfc_data->ovs_ifaces_changed) { - /* There are OVS interface changes. Try to handle them - * incrementally. */ - return physical_handle_ovs_iface_changes(&p_ctx, &fo->flow_table); - } - return true; } @@ -2131,11 +2296,71 @@ return true; } +static bool +flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) +{ + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + struct ed_type_flow_output *fo = data; + struct lflow_ctx_in l_ctx_in; + struct lflow_ctx_out l_ctx_out; + init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out); + + bool handled = lflow_handle_changed_lbs(&l_ctx_in, &l_ctx_out); + + engine_set_node_state(node, EN_UPDATED); + return handled; +} + struct ovn_controller_exit_args { bool *exiting; bool *restart; }; +/* Returns false if the northd internal version stored in SB_Global + * and ovn-controller internal version don't match. + */ +static bool +check_northd_version(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl, + const char *version) +{ + static bool version_mismatch; + + const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); + if (!cfg || !smap_get_bool(&cfg->external_ids, "ovn-match-northd-version", + false)) { + version_mismatch = false; + return true; + } + + const struct sbrec_sb_global *sb = sbrec_sb_global_first(ovnsb_idl); + if (!sb) { + version_mismatch = true; + return false; + } + + const char *northd_version = + smap_get_def(&sb->options, "northd_internal_version", ""); + + if (strcmp(northd_version, version)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "controller version - %s mismatch with northd " + "version - %s", version, northd_version); + version_mismatch = true; + return false; + } + + /* If there used to be a mismatch and ovn-northd got updated, force a + * full recompute. + */ + if (version_mismatch) { + engine_set_force_recompute(true); + } + version_mismatch = false; + return true; +} + int main(int argc, char *argv[]) { @@ -2191,6 +2416,9 @@ struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_logical_datapath); + struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group + = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, + &sbrec_logical_flow_col_logical_dp_group); struct ovsdb_idl_index *sbrec_port_binding_by_name = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, &sbrec_port_binding_col_logical_port); @@ -2291,11 +2519,14 @@ /* Engine node physical_flow_changes indicates whether * we can recompute only physical flows or we can * incrementally process the physical flows. + * + * Note: The order of inputs is important, all OVS interface changes must + * be handled before any ct_zone changes. */ - engine_add_input(&en_physical_flow_changes, &en_ct_zones, - NULL); engine_add_input(&en_physical_flow_changes, &en_ovs_interface, physical_flow_changes_ovs_iface_handler); + engine_add_input(&en_physical_flow_changes, &en_ct_zones, + physical_flow_changes_ct_zones_handler); engine_add_input(&en_flow_output, &en_addr_sets, flow_output_addr_sets_handler); @@ -2324,12 +2555,21 @@ flow_output_sb_mac_binding_handler); engine_add_input(&en_flow_output, &en_sb_logical_flow, flow_output_sb_logical_flow_handler); + /* Using a noop handler since we don't really need any data from datapath + * groups or a full recompute. Update of a datapath group will put + * logical flow into the tracked list, so the logical flow handler will + * process all changes. */ + engine_add_input(&en_flow_output, &en_sb_logical_dp_group, + engine_noop_handler); engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL); engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL); engine_add_input(&en_flow_output, &en_sb_dns, NULL); + engine_add_input(&en_flow_output, &en_sb_load_balancer, + flow_output_sb_load_balancer_handler); engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL); engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL); + engine_add_input(&en_ct_zones, &en_sb_datapath_binding, NULL); engine_add_input(&en_ct_zones, &en_runtime_data, NULL); engine_add_input(&en_runtime_data, &en_ofctrl_is_connected, NULL); @@ -2364,6 +2604,8 @@ sbrec_multicast_group_by_name_datapath); engine_ovsdb_node_add_index(&en_sb_logical_flow, "logical_datapath", sbrec_logical_flow_by_logical_datapath); + engine_ovsdb_node_add_index(&en_sb_logical_flow, "logical_dp_group", + sbrec_logical_flow_by_logical_dp_group); engine_ovsdb_node_add_index(&en_sb_port_binding, "name", sbrec_port_binding_by_name); engine_ovsdb_node_add_index(&en_sb_port_binding, "key", @@ -2423,11 +2665,15 @@ unsigned int ovs_cond_seqno = UINT_MAX; unsigned int ovnsb_cond_seqno = UINT_MAX; + unsigned int ovnsb_expected_cond_seqno = UINT_MAX; struct controller_engine_ctx ctrl_engine_ctx = { .enable_lflow_cache = true }; + char *ovn_version = ovn_get_internal_version(); + VLOG_INFO("OVN internal version is : [%s]", ovn_version); + /* Main loop. */ exiting = false; restart = false; @@ -2457,7 +2703,8 @@ update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl, &sb_monitor_all, &reset_ovnsb_idl_min_index, - &ctrl_engine_ctx.enable_lflow_cache); + &ctrl_engine_ctx.enable_lflow_cache, + &ovnsb_expected_cond_seqno); update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl)); ofctrl_set_probe_interval(get_ofctrl_probe_interval(ovs_idl_loop.idl)); @@ -2481,31 +2728,32 @@ engine_set_context(&eng_ctx); - if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl)) { + bool northd_version_match = + check_northd_version(ovs_idl_loop.idl, ovnsb_idl_loop.idl, + ovn_version); + + const struct ovsrec_bridge_table *bridge_table = + 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); + + if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl) && + northd_version_match) { /* Contains the transport zones that this Chassis belongs to */ struct sset transport_zones = SSET_INITIALIZER(&transport_zones); sset_from_delimited_string(&transport_zones, get_transport_zones(ovsrec_open_vswitch_table_get( ovs_idl_loop.idl)), ","); - const struct ovsrec_bridge_table *bridge_table = - 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 sbrec_chassis_table *chassis_table = - sbrec_chassis_table_get(ovnsb_idl_loop.idl); - const struct sbrec_chassis_private_table *chassis_pvt_table = - sbrec_chassis_private_table_get(ovnsb_idl_loop.idl); - const struct ovsrec_bridge *br_int = - process_br_int(ovs_idl_txn, bridge_table, ovs_table); const char *chassis_id = get_ovs_chassis_id(ovs_table); const struct sbrec_chassis *chassis = NULL; const struct sbrec_chassis_private *chassis_private = NULL; if (chassis_id) { chassis = chassis_run(ovnsb_idl_txn, sbrec_chassis_by_name, sbrec_chassis_private_by_name, - ovs_table, chassis_table, - chassis_pvt_table, chassis_id, + ovs_table, chassis_id, br_int, &transport_zones, &chassis_private); } @@ -2568,15 +2816,6 @@ sbrec_sb_global_table_get(ovnsb_idl_loop.idl)); } - flow_output_data = engine_get_data(&en_flow_output); - if (flow_output_data && ct_zones_data) { - ofctrl_put(&flow_output_data->flow_table, - &ct_zones_data->pending, - sbrec_meter_table_get(ovnsb_idl_loop.idl), - get_nb_cfg(sbrec_sb_global_table_get( - ovnsb_idl_loop.idl)), - engine_node_changed(&en_flow_output)); - } runtime_data = engine_get_data(&en_runtime_data); if (runtime_data) { patch_run(ovs_idl_txn, @@ -2601,13 +2840,29 @@ br_int, chassis, &runtime_data->local_datapaths, &runtime_data->active_tunnels); - if (engine_node_changed(&en_runtime_data)) { - update_sb_monitors(ovnsb_idl_loop.idl, chassis, - &runtime_data->local_lports, - &runtime_data->local_datapaths, - sb_monitor_all); + /* Updating monitor conditions if runtime data or + * logical datapath goups changed. */ + if (engine_node_changed(&en_runtime_data) + || engine_node_changed(&en_sb_logical_dp_group)) { + ovnsb_expected_cond_seqno = + update_sb_monitors( + ovnsb_idl_loop.idl, chassis, + &runtime_data->local_lports, + &runtime_data->local_datapaths, + sb_monitor_all); } } + flow_output_data = engine_get_data(&en_flow_output); + if (flow_output_data && ct_zones_data) { + ofctrl_put(&flow_output_data->flow_table, + &ct_zones_data->pending, + sbrec_meter_table_get(ovnsb_idl_loop.idl), + get_nb_cfg(sbrec_sb_global_table_get( + ovnsb_idl_loop.idl), + ovnsb_cond_seqno, + ovnsb_expected_cond_seqno), + engine_node_changed(&en_flow_output)); + } } } @@ -2632,19 +2887,8 @@ engine_set_force_recompute(false); } - if (ovnsb_idl_txn && chassis_private) { - int64_t cur_cfg = ofctrl_get_cur_cfg(); - if (cur_cfg && cur_cfg != chassis_private->nb_cfg) { - sbrec_chassis_private_set_nb_cfg(chassis_private, cur_cfg); - sbrec_chassis_private_set_nb_cfg_timestamp( - chassis_private, time_wall_msec()); - if (delay_nb_cfg_report) { - VLOG_INFO("Sleep for %u sec", delay_nb_cfg_report); - xsleep(delay_nb_cfg_report); - } - } - } - + store_nb_cfg(ovnsb_idl_txn, ovs_idl_txn, chassis_private, + br_int, delay_nb_cfg_report, ofctrl_get_cur_cfg()); if (pending_pkt.conn) { struct ed_type_addr_sets *as_data = @@ -2678,6 +2922,13 @@ } } + if (!northd_version_match && br_int) { + /* Set the integration bridge name to pinctrl so that the pinctrl + * thread can handle any packet-ins when we are not processing + * any DB updates due to version mismatch. */ + pinctrl_set_br_int_name(br_int->name); + } + unixctl_server_run(unixctl); unixctl_server_wait(unixctl); @@ -2723,7 +2974,7 @@ bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl); while (!done) { update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl, - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl)); struct ovsdb_idl_txn *ovs_idl_txn @@ -2741,7 +2992,7 @@ const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - const char *chassis_id = chassis_get_id(); + const char *chassis_id = get_ovs_chassis_id(ovs_table); const struct sbrec_chassis *chassis = (chassis_id ? chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id) @@ -2770,6 +3021,7 @@ } } + free(ovn_version); unixctl_server_destroy(unixctl); lflow_destroy(); ofctrl_destroy(); @@ -2822,6 +3074,7 @@ case 'V': ovs_print_version(OFP15_VERSION, OFP15_VERSION); + printf("SB DB Schema %s\n", sbrec_get_db_version()); exit(EXIT_SUCCESS); VLOG_OPTION_HANDLERS diff -Nru ovn-20.09.0/controller/physical.c ovn-20.12.0/controller/physical.c --- ovn-20.09.0/controller/physical.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/physical.c 2020-12-17 17:53:32.000000000 +0000 @@ -44,6 +44,7 @@ #include "sset.h" #include "util.h" #include "vswitch-idl.h" +#include "hmapx.h" VLOG_DEFINE_THIS_MODULE(physical); @@ -384,15 +385,15 @@ if (!tun) { continue; } - if (bundle->n_slaves >= BUNDLE_MAX_SLAVES) { + if (bundle->n_members >= BUNDLE_MAX_MEMBERS) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); VLOG_WARN_RL(&rl, "Remote endpoints for port beyond " - "BUNDLE_MAX_SLAVES"); + "BUNDLE_MAX_MEMBERS"); break; } ofpbuf_put(ofpacts_p, &tun->ofport, sizeof tun->ofport); bundle = ofpacts_p->header; - bundle->n_slaves++; + bundle->n_members++; } bundle->algorithm = NX_BD_ALG_ACTIVE_BACKUP; @@ -860,6 +861,28 @@ put_load(port_key, MFF_LOG_INPORT, 0, 32, ofpacts_p); } +static const struct sbrec_port_binding * +get_binding_peer(struct ovsdb_idl_index *sbrec_port_binding_by_name, + const struct sbrec_port_binding *binding) +{ + const char *peer_name = smap_get(&binding->options, "peer"); + if (!peer_name) { + return NULL; + } + + const struct sbrec_port_binding *peer = lport_lookup_by_name( + sbrec_port_binding_by_name, peer_name); + if (!peer || strcmp(peer->type, binding->type)) { + return NULL; + } + const char *peer_peer_name = smap_get(&peer->options, "peer"); + if (!peer_peer_name || strcmp(peer_peer_name, binding->logical_port)) { + return NULL; + } + + return peer; +} + static void consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, enum mf_field_id mff_ovn_geneve, @@ -882,18 +905,10 @@ if (!strcmp(binding->type, "patch") || (!strcmp(binding->type, "l3gateway") && binding->chassis == chassis)) { - const char *peer_name = smap_get(&binding->options, "peer"); - if (!peer_name) { - return; - } - const struct sbrec_port_binding *peer = lport_lookup_by_name( - sbrec_port_binding_by_name, peer_name); - if (!peer || strcmp(peer->type, binding->type)) { - return; - } - const char *peer_peer_name = smap_get(&peer->options, "peer"); - if (!peer_peer_name || strcmp(peer_peer_name, binding->logical_port)) { + const struct sbrec_port_binding *peer = get_binding_peer( + sbrec_port_binding_by_name, binding); + if (!peer) { return; } @@ -1874,3 +1889,22 @@ ofctrl_remove_flows(flow_table, hc_uuid); } } + +void +physical_clear_dp_flows(struct physical_ctx *p_ctx, + struct hmapx *ct_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; + } + 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); + } + } +} diff -Nru ovn-20.09.0/controller/physical.h ovn-20.12.0/controller/physical.h --- ovn-20.09.0/controller/physical.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/physical.h 2020-12-17 17:53:32.000000000 +0000 @@ -56,12 +56,16 @@ const struct simap *ct_zones; enum mf_field_id mff_ovn_geneve; struct shash *local_bindings; + struct hmapx *ct_updated_datapaths; }; void physical_register_ovs_idl(struct ovsdb_idl *); void physical_run(struct physical_ctx *, struct ovn_desired_flow_table *); 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 ovn_desired_flow_table *flow_table); void physical_handle_port_binding_changes(struct physical_ctx *, struct ovn_desired_flow_table *); void physical_handle_mc_group_changes(struct physical_ctx *, diff -Nru ovn-20.09.0/controller/pinctrl.c ovn-20.12.0/controller/pinctrl.c --- ovn-20.09.0/controller/pinctrl.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/pinctrl.c 2020-12-17 17:53:32.000000000 +0000 @@ -200,8 +200,10 @@ static void destroy_send_garps_rarps(void); static void send_garp_rarp_wait(long long int send_garp_rarp_time); static void send_garp_rarp_prepare( + struct ovsdb_idl_txn *ovnsb_idl_txn, struct ovsdb_idl_index *sbrec_port_binding_by_datapath, struct ovsdb_idl_index *sbrec_port_binding_by_name, + struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, const struct ovsrec_bridge *, const struct sbrec_chassis *, const struct hmap *local_datapaths, @@ -465,6 +467,7 @@ sbrec_controller_event_set_seq_num(event, ++event_seq_num); sbrec_controller_event_set_event_info(event, &event_info); sbrec_controller_event_set_chassis(event, chassis); + smap_destroy(&event_info); } } @@ -573,11 +576,22 @@ PREFIX_REQUEST, PREFIX_PENDING, PREFIX_DONE, + PREFIX_RENEW, + PREFIX_REBIND, }; struct ipv6_prefixd_state { long long int next_announce; + long long int last_complete; long long int last_used; + /* IPv6 PD server info */ + struct in6_addr server_addr; + struct eth_addr sa; + /* server_id_info */ + struct { + uint8_t *data; + uint8_t len; + } uuid; struct in6_addr ipv6_addr; struct eth_addr ea; struct eth_addr cmac; @@ -781,20 +795,26 @@ static void pinctrl_prefixd_state_handler(const struct flow *ip_flow, struct in6_addr addr, unsigned aid, + struct eth_addr sa, struct in6_addr server_addr, char prefix_len, unsigned t1, unsigned t2, - unsigned plife_time, unsigned vlife_time) + unsigned plife_time, unsigned vlife_time, + uint8_t *uuid, uint8_t uuid_len) { struct ipv6_prefixd_state *pfd; pfd = pinctrl_find_prefixd_state(ip_flow, aid); if (pfd) { pfd->state = PREFIX_PENDING; - pfd->plife_time = plife_time; - pfd->vlife_time = vlife_time; + pfd->server_addr = server_addr; + pfd->sa = sa; + pfd->uuid.data = uuid; + pfd->uuid.len = uuid_len; + pfd->plife_time = plife_time * 1000; + pfd->vlife_time = vlife_time * 1000; pfd->plen = prefix_len; pfd->prefix = addr; - pfd->t1 = t1; - pfd->t2 = t2; + pfd->t1 = t1 * 1000; + pfd->t2 = t2 * 1000; notify_pinctrl_main(); } } @@ -804,19 +824,21 @@ const struct flow *ip_flow) OVS_REQUIRES(pinctrl_mutex) { + struct eth_header *eth = dp_packet_eth(pkt_in); + struct ip6_hdr *in_ip = dp_packet_l3(pkt_in); struct udp_header *udp_in = dp_packet_l4(pkt_in); unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1); size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in)); unsigned t1 = 0, t2 = 0, vlife_time = 0, plife_time = 0; - uint8_t *end = (uint8_t *)udp_in + dlen; - uint8_t prefix_len = 0; - struct in6_addr ipv6; + uint8_t *end = (uint8_t *)udp_in + dlen, *uuid = NULL; + uint8_t prefix_len = 0, uuid_len = 0; + struct in6_addr ipv6 = in6addr_any; bool status = false; unsigned aid = 0; - memset(&ipv6, 0, sizeof (struct in6_addr)); /* skip DHCPv6 common header */ in_dhcpv6_data += 4; + while (in_dhcpv6_data < end) { struct dhcpv6_opt_header *in_opt = (struct dhcpv6_opt_header *)in_dhcpv6_data; @@ -867,14 +889,22 @@ } break; } + case DHCPV6_OPT_SERVER_ID_CODE: + uuid_len = ntohs(in_opt->len); + uuid = xmalloc(uuid_len); + memcpy(uuid, in_opt + 1, uuid_len); + break; default: break; } in_dhcpv6_data += opt_len; } if (status) { - pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, prefix_len, - t1, t2, plife_time, vlife_time); + pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, eth->eth_src, + in_ip->ip6_src, prefix_len, t1, t2, + plife_time, vlife_time, uuid, uuid_len); + } else if (uuid) { + free(uuid); } } @@ -904,27 +934,42 @@ } static void -compose_prefixd_solicit(struct dp_packet *b, - struct ipv6_prefixd_state *pfd, - const struct eth_addr eth_dst, - const struct in6_addr *ipv6_dst) +compose_prefixd_packet(struct dp_packet *b, struct ipv6_prefixd_state *pfd) { - eth_compose(b, eth_dst, pfd->ea, ETH_TYPE_IPV6, IPV6_HEADER_LEN); + struct in6_addr ipv6_dst; + struct eth_addr eth_dst; int payload = sizeof(struct dhcpv6_opt_server_id) + sizeof(struct dhcpv6_opt_ia_na); + if (pfd->uuid.len) { + payload += pfd->uuid.len + sizeof(struct dhcpv6_opt_header); + ipv6_dst = pfd->server_addr; + eth_dst = pfd->sa; + } else { + eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02); + ipv6_parse("ff02::1:2", &ipv6_dst); + } if (ipv6_addr_is_set(&pfd->prefix)) { payload += sizeof(struct dhcpv6_opt_ia_prefix); } + + eth_compose(b, eth_dst, pfd->ea, ETH_TYPE_IPV6, IPV6_HEADER_LEN); + int len = UDP_HEADER_LEN + 4 + payload; struct udp_header *udp_h = compose_ipv6(b, IPPROTO_UDP, &pfd->ipv6_addr, - ipv6_dst, 0, 0, 255, len); + &ipv6_dst, 0, 0, 255, len); udp_h->udp_len = htons(len); udp_h->udp_csum = 0; packet_set_udp_port(b, htons(546), htons(547)); unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1); - *dhcp_hdr = DHCPV6_MSG_TYPE_SOLICIT; + if (pfd->state == PREFIX_RENEW) { + *dhcp_hdr = DHCPV6_MSG_TYPE_RENEW; + } else if (pfd->state == PREFIX_REBIND) { + *dhcp_hdr = DHCPV6_MSG_TYPE_REBIND; + } else { + *dhcp_hdr = DHCPV6_MSG_TYPE_SOLICIT; + } struct dhcpv6_opt_server_id *opt_client_id = (struct dhcpv6_opt_server_id *)(dhcp_hdr + 4); @@ -935,11 +980,21 @@ opt_client_id->hw_type = htons(DHCPV6_HW_TYPE_ETH); opt_client_id->mac = pfd->cmac; + unsigned char *ptr = (unsigned char *)(opt_client_id + 1); + if (pfd->uuid.len) { + struct dhcpv6_opt_header *in_opt = (struct dhcpv6_opt_header *)ptr; + in_opt->code = htons(DHCPV6_OPT_SERVER_ID_CODE); + in_opt->len = htons(pfd->uuid.len); + + ptr += sizeof *in_opt; + memcpy(ptr, pfd->uuid.data, pfd->uuid.len); + ptr += pfd->uuid.len; + } + if (!ipv6_addr_is_set(&pfd->prefix)) { pfd->aid = random_uint16(); } - struct dhcpv6_opt_ia_na *ia_pd = - (struct dhcpv6_opt_ia_na *)(opt_client_id + 1); + struct dhcpv6_opt_ia_na *ia_pd = (struct dhcpv6_opt_ia_na *)ptr; ia_pd->opt.code = htons(DHCPV6_OPT_IA_PD); int opt_len = sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_header); @@ -981,16 +1036,15 @@ return pfd->next_announce; } + if (pfd->state == PREFIX_DONE) { + goto out; + } + uint64_t packet_stub[256 / 8]; struct dp_packet packet; - struct eth_addr eth_dst; - eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02); - struct in6_addr ipv6_dst; - ipv6_parse("ff02::1:2", &ipv6_dst); - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); - compose_prefixd_solicit(&packet, pfd, eth_dst, &ipv6_dst); + compose_prefixd_packet(&packet, pfd); uint64_t ofpacts_stub[4096 / 8]; struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); @@ -1019,8 +1073,9 @@ queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); dp_packet_uninit(&packet); ofpbuf_uninit(&ofpacts); + +out: pfd->next_announce = cur_time + random_range(IPV6_PREFIXD_TIMEOUT); - pfd->state = PREFIX_SOLICIT; return pfd->next_announce; } @@ -1031,10 +1086,28 @@ SHASH_FOR_EACH (iter, &ipv6_prefixd) { struct ipv6_prefixd_state *pfd = iter->data; + long long int cur_time = time_msec(); + if (pfd->state == PREFIX_SOLICIT) { return true; } - if (pfd->state && pfd->next_announce < time_msec()) { + if (pfd->state == PREFIX_DONE && + cur_time > pfd->last_complete + pfd->t1) { + pfd->state = PREFIX_RENEW; + return true; + } + if (pfd->state == PREFIX_RENEW && + cur_time > pfd->last_complete + pfd->t2) { + pfd->state = PREFIX_REBIND; + if (pfd->uuid.len) { + free(pfd->uuid.data); + pfd->uuid.len = 0; + } + return true; + } + if (pfd->state == PREFIX_REBIND && + cur_time > pfd->last_complete + pfd->vlife_time) { + pfd->state = PREFIX_SOLICIT; return true; } } @@ -1120,7 +1193,8 @@ struct smap options; pfd->state = PREFIX_DONE; - pfd->next_announce = time_msec() + pfd->t1 * 1000; + pfd->last_complete = time_msec(); + pfd->next_announce = pfd->last_complete + pfd->t1; ipv6_string_mapped(prefix_str, &pfd->prefix); smap_clone(&options, &pb->options); smap_add_format(&options, "ipv6_ra_pd_list", "%d:%s/%d", @@ -1219,6 +1293,10 @@ SHASH_FOR_EACH_SAFE (iter, next, &ipv6_prefixd) { struct ipv6_prefixd_state *pfd = iter->data; if (pfd->last_used + IPV6_PREFIXD_STALE_TIMEOUT < time_msec()) { + if (pfd->uuid.len) { + free(pfd->uuid.data); + pfd->uuid.len = 0; + } free(pfd); shash_delete(&ipv6_prefixd, iter); } @@ -1467,7 +1545,7 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, struct dp_packet *pkt_in, const struct match *md, struct ofpbuf *userdata, - bool set_icmp_code) + bool set_icmp_code, bool loopback) { /* This action only works for IP packets, and the switch should only send * us IP packets this way, but check here just to be sure. */ @@ -1488,8 +1566,8 @@ packet.packet_type = htonl(PT_ETH); struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh); - eh->eth_dst = ip_flow->dl_dst; - eh->eth_src = ip_flow->dl_src; + eh->eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst; + eh->eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src; if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) { struct ip_header *in_ip = dp_packet_l3(pkt_in); @@ -1511,8 +1589,9 @@ sizeof(struct icmp_header)); nh->ip_proto = IPPROTO_ICMP; nh->ip_frag_off = htons(IP_DF); - packet_set_ipv4(&packet, ip_flow->nw_src, ip_flow->nw_dst, - ip_flow->nw_tos, 255); + ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src; + ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst; + packet_set_ipv4(&packet, nw_src, nw_dst, ip_flow->nw_tos, 255); uint8_t icmp_code = 1; if (set_icmp_code && in_ip->ip_proto == IPPROTO_UDP) { @@ -1559,8 +1638,12 @@ nh->ip6_vfc = 0x60; nh->ip6_nxt = IPPROTO_ICMPV6; nh->ip6_plen = htons(ICMP6_DATA_HEADER_LEN); - packet_set_ipv6(&packet, &ip_flow->ipv6_src, &ip_flow->ipv6_dst, - ip_flow->nw_tos, ip_flow->ipv6_label, 255); + const struct in6_addr *ip6_src = + loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src; + const struct in6_addr *ip6_dst = + loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst; + packet_set_ipv6(&packet, ip6_src, ip6_dst, ip_flow->nw_tos, + ip_flow->ipv6_label, 255); ih = dp_packet_put_zeros(&packet, sizeof *ih); dp_packet_set_l4(&packet, ih); @@ -1617,7 +1700,8 @@ static void pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, struct dp_packet *pkt_in, - const struct match *md, struct ofpbuf *userdata) + const struct match *md, struct ofpbuf *userdata, + bool loopback) { /* This action only works for TCP segments, and the switch should only send * us TCP segments this way, but check here just to be sure. */ @@ -1632,14 +1716,23 @@ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); packet.packet_type = htonl(PT_ETH); + + struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src; + struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst; + if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) { - pinctrl_compose_ipv6(&packet, ip_flow->dl_src, ip_flow->dl_dst, - (struct in6_addr *)&ip_flow->ipv6_src, - (struct in6_addr *)&ip_flow->ipv6_dst, + const struct in6_addr *ip6_src = + loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src; + const struct in6_addr *ip6_dst = + loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst; + pinctrl_compose_ipv6(&packet, eth_src, eth_dst, + (struct in6_addr *) ip6_src, + (struct in6_addr *) ip6_dst, IPPROTO_TCP, 63, TCP_HEADER_LEN); } else { - pinctrl_compose_ipv4(&packet, ip_flow->dl_src, ip_flow->dl_dst, - ip_flow->nw_src, ip_flow->nw_dst, + ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src; + ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst; + pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst, IPPROTO_TCP, 63, TCP_HEADER_LEN); } @@ -1670,6 +1763,18 @@ dp_packet_uninit(&packet); } +static void +pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow, + struct dp_packet *pkt_in, + const struct match *md, struct ofpbuf *userdata) +{ + if (ip_flow->nw_proto == IPPROTO_TCP) { + pinctrl_handle_tcp_reset(swconn, ip_flow, pkt_in, md, userdata, true); + } else { + pinctrl_handle_icmp(swconn, ip_flow, pkt_in, md, userdata, true, true); + } +} + static bool is_dhcp_flags_broadcast(ovs_be16 flags) { @@ -1766,6 +1871,7 @@ } in_dhcp_ptr += sizeof magic_cookie; + bool ipxe_req = false; const uint8_t *in_dhcp_msg_type = NULL; ovs_be32 request_ip = in_dhcp_data->ciaddr; while (in_dhcp_ptr < end) { @@ -1798,6 +1904,9 @@ request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); } break; + case DHCP_OPT_ETHERBOOT: + ipxe_req = true; + break; default: break; } @@ -1916,6 +2025,32 @@ *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding| * -------------------------------------------------------------- */ + struct dhcp_opt_header *in_dhcp_opt = + (struct dhcp_opt_header *)reply_dhcp_opts_ptr->data; + if (in_dhcp_opt->code == DHCP_OPT_BOOTFILE_CODE) { + unsigned char *ptr = (unsigned char *)in_dhcp_opt; + int len = sizeof *in_dhcp_opt + in_dhcp_opt->len; + struct dhcp_opt_header *next_dhcp_opt = + (struct dhcp_opt_header *)(ptr + len); + + if (next_dhcp_opt->code == DHCP_OPT_BOOTFILE_ALT_CODE) { + if (!ipxe_req) { + ofpbuf_pull(reply_dhcp_opts_ptr, len); + next_dhcp_opt->code = DHCP_OPT_BOOTFILE_CODE; + } else { + char *buf = xmalloc(len); + + memcpy(buf, in_dhcp_opt, len); + ofpbuf_pull(reply_dhcp_opts_ptr, + sizeof *in_dhcp_opt + next_dhcp_opt->len); + memcpy(reply_dhcp_opts_ptr->data, buf, len); + free(buf); + } + } + } else if (in_dhcp_opt->code == DHCP_OPT_BOOTFILE_ALT_CODE) { + in_dhcp_opt->code = DHCP_OPT_BOOTFILE_CODE; + } + uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16; if (msg_type != DHCP_MSG_NAK) { new_l4_size += reply_dhcp_opts_ptr->size; @@ -2374,7 +2509,7 @@ dns_data->delete = false; if (!smap_equal(&dns_data->records, &sbrec_dns->records)) { - smap_clear(&dns_data->records); + smap_destroy(&dns_data->records); smap_clone(&dns_data->records, &sbrec_dns->records); } @@ -2390,6 +2525,8 @@ struct dns_data *d = iter->data; if (d->delete) { shash_delete(&dns_cache, iter); + smap_destroy(&d->records); + free(d->dps); free(d); } } @@ -2402,6 +2539,8 @@ SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) { struct dns_data *d = iter->data; shash_delete(&dns_cache, iter); + smap_destroy(&d->records); + free(d->dps); free(d); } } @@ -2773,18 +2912,23 @@ case ACTION_OPCODE_ICMP: pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata, - &userdata, true); + &userdata, true, false); break; case ACTION_OPCODE_ICMP4_ERROR: case ACTION_OPCODE_ICMP6_ERROR: pinctrl_handle_icmp(swconn, &headers, &packet, &pin.flow_metadata, - &userdata, false); + &userdata, false, false); break; case ACTION_OPCODE_TCP_RESET: pinctrl_handle_tcp_reset(swconn, &headers, &packet, &pin.flow_metadata, - &userdata); + &userdata, false); + break; + + case ACTION_OPCODE_REJECT: + pinctrl_handle_reject(swconn, &headers, &packet, &pin.flow_metadata, + &userdata); break; case ACTION_OPCODE_PUT_ICMP4_FRAG_MTU: @@ -2867,6 +3011,23 @@ seq_change(pinctrl_main_seq); } +static void +pinctrl_rconn_setup(struct rconn *swconn, const char *br_int_name) + OVS_REQUIRES(pinctrl_mutex) +{ + if (br_int_name) { + char *target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int_name); + + if (strcmp(target, rconn_get_target(swconn))) { + VLOG_INFO("%s: connecting to switch", target); + rconn_connect(swconn, target, target); + } + free(target); + } else { + rconn_disconnect(swconn); + } +} + /* pinctrl_handler pthread function. */ static void * pinctrl_handler(void *arg_) @@ -2878,7 +3039,6 @@ * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */ unsigned int conn_seq_no = 0; - char *br_int_name = NULL; uint64_t new_seq; /* Next IPV6 RA in seconds. */ @@ -2893,27 +3053,8 @@ swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP15_VERSION); while (!latch_is_set(&pctrl->pinctrl_thread_exit)) { - if (pctrl->br_int_name) { - if (!br_int_name || strcmp(br_int_name, pctrl->br_int_name)) { - free(br_int_name); - br_int_name = xstrdup(pctrl->br_int_name); - } - } - - if (br_int_name) { - char *target; - - target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int_name); - if (strcmp(target, rconn_get_target(swconn))) { - VLOG_INFO("%s: connecting to switch", target); - rconn_connect(swconn, target, target); - } - free(target); - } else { - rconn_disconnect(swconn); - } - ovs_mutex_lock(&pinctrl_mutex); + pinctrl_rconn_setup(swconn, pctrl->br_int_name); ip_mcast_snoop_run(); ovs_mutex_unlock(&pinctrl_mutex); @@ -2969,11 +3110,32 @@ poll_block(); } - free(br_int_name); rconn_destroy(swconn); return NULL; } +static void +pinctrl_set_br_int_name_(char *br_int_name) + OVS_REQUIRES(pinctrl_mutex) +{ + if (br_int_name && (!pinctrl.br_int_name || strcmp(pinctrl.br_int_name, + br_int_name))) { + free(pinctrl.br_int_name); + pinctrl.br_int_name = xstrdup(br_int_name); + /* Notify pinctrl_handler that integration bridge is + * set/changed. */ + notify_pinctrl_handler(); + } +} + +void +pinctrl_set_br_int_name(char *br_int_name) +{ + ovs_mutex_lock(&pinctrl_mutex); + pinctrl_set_br_int_name_(br_int_name); + ovs_mutex_unlock(&pinctrl_mutex); +} + /* Called by ovn-controller. */ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, @@ -2993,23 +3155,15 @@ const struct sset *active_tunnels) { ovs_mutex_lock(&pinctrl_mutex); - if (br_int && (!pinctrl.br_int_name || strcmp(pinctrl.br_int_name, - br_int->name))) { - if (pinctrl.br_int_name) { - free(pinctrl.br_int_name); - } - pinctrl.br_int_name = xstrdup(br_int->name); - /* Notify pinctrl_handler that integration bridge is - * set/changed. */ - notify_pinctrl_handler(); - } + pinctrl_set_br_int_name_(br_int->name); run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, sbrec_mac_binding_by_lport_ip); run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, chassis); - send_garp_rarp_prepare(sbrec_port_binding_by_datapath, - sbrec_port_binding_by_name, br_int, chassis, + send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath, + sbrec_port_binding_by_name, + sbrec_mac_binding_by_lport_ip, br_int, chassis, local_datapaths, active_tunnels); prepare_ipv6_ras(local_datapaths); prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name, @@ -3700,6 +3854,77 @@ return retval; } +/* Update or add an IP-MAC binding for 'logical_port'. + * Caller should make sure that 'ovnsb_idl_txn' is valid. */ +static void +mac_binding_add(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, + const char *logical_port, + const struct sbrec_datapath_binding *dp, + struct eth_addr ea, const char *ip, + bool update_only) +{ + /* Convert ethernet argument to string form for database. */ + char mac_string[ETH_ADDR_STRLEN + 1]; + snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea)); + + const struct sbrec_mac_binding *b = + mac_binding_lookup(sbrec_mac_binding_by_lport_ip, logical_port, ip); + if (!b) { + if (update_only) { + return; + } + b = sbrec_mac_binding_insert(ovnsb_idl_txn); + sbrec_mac_binding_set_logical_port(b, logical_port); + sbrec_mac_binding_set_ip(b, ip); + sbrec_mac_binding_set_mac(b, mac_string); + sbrec_mac_binding_set_datapath(b, dp); + } else if (strcmp(b->mac, mac_string)) { + sbrec_mac_binding_set_mac(b, mac_string); + } +} + +/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings + * on peer router datapaths. + */ +static void +send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, + const struct hmap *local_datapaths, + const struct sbrec_port_binding *in_pb, + struct eth_addr ea, ovs_be32 ip) +{ + if (!ovnsb_idl_txn) { + return; + } + + const struct local_datapath *ldp = + get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key); + + ovs_assert(ldp); + for (size_t i = 0; i < ldp->n_peer_ports; i++) { + const struct sbrec_port_binding *local = ldp->peer_ports[i].local; + const struct sbrec_port_binding *remote = ldp->peer_ports[i].remote; + + /* Skip "ingress" port. */ + if (local == in_pb) { + continue; + } + + bool update_only = !smap_get_bool(&remote->datapath->external_ids, + "always_learn_from_arp_request", + true); + + struct ds ip_s = DS_EMPTY_INITIALIZER; + + ip_format_masked(ip, OVS_BE32_MAX, &ip_s); + mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, + remote->logical_port, remote->datapath, + ea, ds_cstr(&ip_s), update_only); + ds_destroy(&ip_s); + } +} + static void run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn, struct ovsdb_idl_index *sbrec_datapath_binding_by_key, @@ -3726,20 +3951,9 @@ struct ds ip_s = DS_EMPTY_INITIALIZER; ipv6_format_mapped(&pmb->ip_key, &ip_s); - - /* Update or add an IP-MAC binding for this logical port. */ - const struct sbrec_mac_binding *b = - mac_binding_lookup(sbrec_mac_binding_by_lport_ip, pb->logical_port, - ds_cstr(&ip_s)); - if (!b) { - b = sbrec_mac_binding_insert(ovnsb_idl_txn); - sbrec_mac_binding_set_logical_port(b, pb->logical_port); - sbrec_mac_binding_set_ip(b, ds_cstr(&ip_s)); - sbrec_mac_binding_set_mac(b, mac_string); - sbrec_mac_binding_set_datapath(b, pb->datapath); - } else if (strcmp(b->mac, mac_string)) { - sbrec_mac_binding_set_mac(b, mac_string); - } + mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, + pb->logical_port, pb->datapath, pmb->mac, ds_cstr(&ip_s), + false); ds_destroy(&ip_s); } @@ -3881,7 +4095,10 @@ /* Add or update a vif for which GARPs need to be announced. */ static void -send_garp_rarp_update(const struct sbrec_port_binding *binding_rec, +send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, + const struct hmap *local_datapaths, + const struct sbrec_port_binding *binding_rec, struct shash *nat_addresses) { volatile struct garp_rarp_data *garp_rarp = NULL; @@ -3907,6 +4124,11 @@ laddrs->ipv4_addrs[i].addr, binding_rec->datapath->tunnel_key, binding_rec->tunnel_key); + send_garp_locally(ovnsb_idl_txn, + sbrec_mac_binding_by_lport_ip, + local_datapaths, binding_rec, laddrs->ea, + laddrs->ipv4_addrs[i].addr); + } free(name); } @@ -3942,6 +4164,10 @@ laddrs.ea, ip, binding_rec->datapath->tunnel_key, binding_rec->tunnel_key); + if (ip) { + send_garp_locally(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, + local_datapaths, binding_rec, laddrs.ea, ip); + } destroy_lport_addresses(&laddrs); break; @@ -5218,8 +5444,10 @@ /* Called by pinctrl_run(). Runs with in the main ovn-controller * thread context. */ static void -send_garp_rarp_prepare(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, +send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn, + struct ovsdb_idl_index *sbrec_port_binding_by_datapath, struct ovsdb_idl_index *sbrec_port_binding_by_name, + struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, const struct ovsrec_bridge *br_int, const struct sbrec_chassis *chassis, const struct hmap *local_datapaths, @@ -5258,7 +5486,8 @@ const struct sbrec_port_binding *pb = lport_lookup_by_name( sbrec_port_binding_by_name, iface_id); if (pb) { - send_garp_rarp_update(pb, &nat_addresses); + send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, + local_datapaths, pb, &nat_addresses); } } @@ -5268,7 +5497,8 @@ const struct sbrec_port_binding *pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port); if (pb) { - send_garp_rarp_update(pb, &nat_addresses); + send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, + local_datapaths, pb, &nat_addresses); } } @@ -5606,6 +5836,9 @@ if (!vip || !protocol || !load_balancer) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_WARN_RL(&rl, "missing lb parameters in userdata"); + free(vip); + free(protocol); + free(load_balancer); return false; } diff -Nru ovn-20.09.0/controller/pinctrl.h ovn-20.12.0/controller/pinctrl.h --- ovn-20.09.0/controller/pinctrl.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller/pinctrl.h 2020-12-17 17:53:32.000000000 +0000 @@ -49,5 +49,5 @@ const struct sset *active_tunnels); void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn); void pinctrl_destroy(void); - +void pinctrl_set_br_int_name(char *br_int_name); #endif /* controller/pinctrl.h */ diff -Nru ovn-20.09.0/controller-vtep/vtep.c ovn-20.12.0/controller-vtep/vtep.c --- ovn-20.09.0/controller-vtep/vtep.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/controller-vtep/vtep.c 2020-12-17 17:53:32.000000000 +0000 @@ -18,8 +18,10 @@ #include "vtep.h" #include "lib/hash.h" +#include "lib/hmapx.h" #include "openvswitch/hmap.h" #include "openvswitch/shash.h" +#include "lib/ovn-util.h" #include "lib/smap.h" #include "lib/sset.h" #include "lib/util.h" @@ -366,7 +368,13 @@ for (i = 0; i < port_binding_rec->n_mac; i++) { const struct vteprec_ucast_macs_remote *umr; const struct sbrec_port_binding *conflict; - char *mac = port_binding_rec->mac[i]; + struct lport_addresses laddrs; + + if (!extract_lsp_addresses(port_binding_rec->mac[i], &laddrs)) { + continue; + }; + + char *mac = laddrs.ea_s; /* Checks for duplicate MAC in the same vtep logical switch. */ conflict = shash_find_data(&ls_node->added_macs, mac); @@ -384,7 +392,7 @@ tnl_key); umr = shash_find_data(ucast_macs_rmts, mac_ip_tnlkey); /* If finds the 'umr' entry for the mac, ip, and tnl_key, deletes - * the entry from shash so that it is not gargage collected. + * the entry from shash so that it is not garbage collected. * * If not found, creates a new 'umr' entry. */ if (umr && umr->logical_switch == ls_node->vtep_ls) { @@ -395,6 +403,7 @@ vteprec_ucast_macs_remote_set_locator(new_umr, pl); } free(mac_ip_tnlkey); + destroy_lport_addresses(&laddrs); } } @@ -488,12 +497,12 @@ struct shash physical_locators = SHASH_INITIALIZER(&physical_locators); struct shash vtep_pbs = SHASH_INITIALIZER(&vtep_pbs); struct shash non_vtep_pbs = SHASH_INITIALIZER(&non_vtep_pbs); + struct hmapx mcast_macs_ptrs = HMAPX_INITIALIZER(&mcast_macs_ptrs); const struct vteprec_physical_switch *vtep_ps; const struct vteprec_logical_switch *vtep_ls; const struct vteprec_ucast_macs_remote *umr; const struct sbrec_port_binding *port_binding_rec; const struct vteprec_mcast_macs_remote *mmr; - struct shash_node *node; /* Collects 'Physical_Switch's. */ VTEPREC_PHYSICAL_SWITCH_FOR_EACH (vtep_ps, ctx->vtep_idl) { @@ -519,7 +528,8 @@ /* Collects 'Mcast_Macs_Remote's. */ VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mmr, ctx->vtep_idl) { - struct mmr_hash_node_data *mmr_ext = xmalloc(sizeof *mmr_ext);; + struct mmr_hash_node_data *mmr_ext = xmalloc(sizeof *mmr_ext); + hmapx_add(&mcast_macs_ptrs, mmr_ext); char *mac_tnlkey = xasprintf("%s_%"PRId64, mmr->MAC, mmr->logical_switch && mmr->logical_switch->n_tunnel_key @@ -567,11 +577,13 @@ sset_destroy(&vtep_pswitches); shash_destroy(&vtep_lswitches); shash_destroy(&ucast_macs_rmts); - SHASH_FOR_EACH (node, &mcast_macs_rmts) { + struct hmapx_node *node; + HMAPX_FOR_EACH (node, &mcast_macs_ptrs) { struct mmr_hash_node_data *mmr_ext = node->data; shash_destroy(&mmr_ext->physical_locators); free(mmr_ext); } + hmapx_destroy(&mcast_macs_ptrs); shash_destroy(&mcast_macs_rmts); shash_destroy(&physical_locators); shash_destroy(&vtep_pbs); diff -Nru ovn-20.09.0/debian/changelog ovn-20.12.0/debian/changelog --- ovn-20.09.0/debian/changelog 2020-11-06 16:44:10.000000000 +0000 +++ ovn-20.12.0/debian/changelog 2021-01-11 10:39:19.000000000 +0000 @@ -1,3 +1,16 @@ +ovn (20.12.0-0ubuntu2) hirsute; urgency=medium + + * d/rules: Disable test 168 on arm architectures. + + -- James Page Mon, 11 Jan 2021 10:39:19 +0000 + +ovn (20.12.0-0ubuntu1) hirsute; urgency=medium + + * New upstream release. + * d/control: Bump minimum openvswitch-source version to >= 2.15.0~. + + -- James Page Mon, 04 Jan 2021 16:54:31 +0000 + ovn (20.09.0-0ubuntu1) hirsute; urgency=medium * New upstream release. diff -Nru ovn-20.09.0/debian/control ovn-20.12.0/debian/control --- ovn-20.09.0/debian/control 2020-11-06 10:55:49.000000000 +0000 +++ ovn-20.12.0/debian/control 2021-01-04 16:55:07.000000000 +0000 @@ -17,7 +17,7 @@ libtool, libunbound-dev, openssl, - openvswitch-source (>= 2.14.0), + openvswitch-source (>= 2.15.0~), pkg-config, procps, python3-all-dev, diff -Nru ovn-20.09.0/debian/rules ovn-20.12.0/debian/rules --- ovn-20.09.0/debian/rules 2020-11-06 09:33:26.000000000 +0000 +++ ovn-20.12.0/debian/rules 2021-01-08 13:45:27.000000000 +0000 @@ -30,6 +30,15 @@ --with-ovs-source=$(CURDIR)/ovs \ --enable-ssl +# NOTE(jamespage): by default, just run all tests +TEST_LIST = + +# arm64,armhf: +# 168: ovn -- ovn-controller incremental processing FAILED (ovn-performance.at:483) +ifneq (,$(filter arm64 armhf, $(DEB_HOST_ARCH))) +TEST_LIST = 1-167 169- +endif # arm64,armhf + override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) if $(MAKE) check TESTSUITEFLAGS='$(PARALLEL) $(TEST_LIST)' || \ @@ -70,8 +79,8 @@ # Helper target for creating snapshots from upstream git DATE=$(shell date +%Y%m%d) # Upstream branch to track -BRANCH=branch-20.03 -VERSION=20.03.0 +BRANCH=branch-20.12 +VERSION=20.12.0 get-orig-snapshot: rm -Rf ovn-upstream diff -Nru ovn-20.09.0/Documentation/internals/contributing/submitting-patches.rst ovn-20.12.0/Documentation/internals/contributing/submitting-patches.rst --- ovn-20.09.0/Documentation/internals/contributing/submitting-patches.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/Documentation/internals/contributing/submitting-patches.rst 2020-12-17 17:53:32.000000000 +0000 @@ -397,6 +397,18 @@ deprecation grace period has expired and users had opportunity to use at least one OVN release that would have informed them about feature deprecation! +OVN upgrades +------------ + +If the patch introduces any new OVN actions or updates existing OVN actions, +then make sure to check the function ovn_get_internal_version() in +lib/ovn-util.c and increment the macro - OVN_INTERNAL_MINOR_VER. + +Adding new OVN actions or changing existing OVN actions can have datapath +disruptions during OVN upgrades. To minimize disruptions, OVN supports +version matching between ovn-northd and ovn-controller and it is important +to update the internal OVN version when the patch introduces such changes. + Comments -------- diff -Nru ovn-20.09.0/Documentation/intro/install/general.rst ovn-20.12.0/Documentation/intro/install/general.rst --- ovn-20.09.0/Documentation/intro/install/general.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/Documentation/intro/install/general.rst 2020-12-17 17:53:32.000000000 +0000 @@ -30,7 +30,7 @@ platform, refer to one of the other installation guides listed in :doc:`index`. Obtaining OVN Sources ------------------------------- +--------------------- The canonical location for OVN source code is its Git repository, which you can clone into a directory named "ovn" with:: @@ -39,12 +39,23 @@ Cloning the repository leaves the "master" branch initially checked out. This is the right branch for general development. - -As of now there are no official OVN releases. - -Before building OVN you should configure and build OVS. -Please see the Open vSwitch documentation (https://docs.openvswitch.org/en/latest/intro/install/) -to build and install OVS https://github.com/openvswitch/ovs.git +If, on the other hand, if you want to build a particular released +version, you can check it out by running a command such as the +following from the "ovn" directory:: + + $ git checkout v20.09.0 + +The repository also has a branch for each release series. For +example, to obtain the latest fixes in the OVN 20.09.x release series, +which might include bug fixes that have not yet been in any released +version, you can check it out from the "ovn" directory with:: + + $ git checkout origin/branch-20.09 + +If you do not want to use Git, you can also obtain tarballs for `OVN +release versions `, or download a +ZIP file for any snapshot from the `GitHub web interface +`. .. _general-build-reqs: @@ -54,9 +65,11 @@ To compile the userspace programs in the OVN distribution, you will need the following software: +- Open vSwitch (https://docs.openvswitch.org/en/latest/intro/install/). + - GNU make -- A C compiler, such as: +- One of the following C compilers: - GCC 4.6 or later. @@ -65,10 +78,6 @@ - MSVC 2013. Refer to :doc:`windows` for additional Windows build instructions. -- While OVN may be compatible with other compilers, optimal support for atomic - operations may be missing, making OVN very slow - (see ``ovs/lib/ovs-atomic.h``). - - libssl, from OpenSSL, is optional but recommended if you plan to connect the OVN services to the OVN DB ovsdb-servers securely. If libssl is installed, then OVN will automatically build with support for it. diff -Nru ovn-20.09.0/Documentation/topics/testing.rst ovn-20.12.0/Documentation/topics/testing.rst --- ovn-20.09.0/Documentation/topics/testing.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/Documentation/topics/testing.rst 2020-12-17 17:53:32.000000000 +0000 @@ -89,6 +89,30 @@ $ make check TESTSUITEFLAGS=-j8 RECHECK=yes +Debugging unit tests +++++++++++++++++++++ + +To initiate debugging from artifacts generated from `make check` run, set the +``OVS_PAUSE_TEST`` environment variable to 1. For example, to run test case +139 and pause on error:: + + $ OVS_PAUSE_TEST=1 make check TESTSUITEFLAGS='-v 139' + +When error occurs, above command would display something like this:: + + Set environment variable to use various ovs utilities + export OVS_RUNDIR=/ovs/_build-gcc/tests/testsuite.dir/0139 + Press ENTER to continue: + +And from another window, one can execute ovs-xxx commands like:: + + export OVS_RUNDIR=/opt/vdasari/Developer/ovs/_build-gcc/tests/testsuite.dir/0139 + $ ovs-ofctl dump-ports br0 + . + . + +Once done with investigation, press ENTER to perform cleanup operation. + .. _testing-coverage: Coverage @@ -188,3 +212,47 @@ 4. Pushing a commit to the repository which breaks the build or the testsuite will now trigger a email sent to mylist@mydomain.org + +Datapath testing +~~~~~~~~~~~~~~~~ + +OVN includes a suite of tests specifically for datapath functionality. +The datapath tests make some assumptions about the environment. They +must be run under root privileges on a Linux system with support for +network namespaces. Make sure no other Open vSwitch instance is +running the test suite. These tests may take several minutes to +complete, and cannot be run in parallel. + +To invoke the datapath testsuite with the OVS userspace datapath, +run:: + + $ make check-system-userspace + +The results of the userspace testsuite appear in +``tests/system-userspace-testsuite.dir``. + +To invoke the datapath testsuite with the OVS kernel datapath, run:: + + $ make check-kernel + +The results of the kernel testsuite appear in +``tests/system-kmod-testsuite.dir``. + +The tests themselves must run as root. If you do not run ``make`` as +root, then you can specify a program to get superuser privileges as +``SUDO=``, e.g. the following uses ``sudo`` (the ``-E`` +option is needed to pass through environment variables):: + + $ make check-system-userspace SUDO='sudo -E' + +The testsuite creates and destroys tap devices named ``ovs-netdev`` +and ``br0``. If it is interrupted during a test, then before it can +be restarted, you may need to destroy these devices with commands like +the following:: + + $ ip tuntap del dev ovs-netdev mode tap + $ ip tuntap del dev br0 mode tap + +All the features documented under `Unit Tests`_ are available for the +datapath testsuites, except that the datapath testsuites do not +support running tests in parallel. diff -Nru ovn-20.09.0/.github/workflows/test.yml ovn-20.12.0/.github/workflows/test.yml --- ovn-20.09.0/.github/workflows/test.yml 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/.github/workflows/test.yml 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,113 @@ +name: Build and Test + +on: [push, pull_request] + +jobs: + build-linux: + env: + dependencies: | + automake libtool gcc bc libjemalloc1 libjemalloc-dev \ + libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \ + python3-openssl python3-pip python3-sphinx \ + selinux-policy-dev + m32_dependecies: gcc-multilib + CC: ${{ matrix.compiler }} + LIBS: ${{ matrix.libs }} + M32: ${{ matrix.m32 }} + OPTS: ${{ matrix.opts }} + TESTSUITE: ${{ matrix.testsuite }} + + name: linux ${{ join(matrix.*, ' ') }} + runs-on: ubuntu-18.04 + + strategy: + fail-fast: false + matrix: + include: + - compiler: gcc + opts: --disable-ssl + - compiler: clang + opts: --disable-ssl + + - compiler: gcc + testsuite: test + - compiler: clang + testsuite: test + + - compiler: gcc + testsuite: test + libs: -ljemalloc + - compiler: clang + testsuite: test + libs: -ljemalloc + + - compiler: gcc + m32: m32 + opts: --disable-ssl + + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: install required dependencies + run: sudo apt install -y ${{ env.dependencies }} + + - name: install libunbound libunwind + if: matrix.m32 == '' + run: sudo apt install -y libunbound-dev libunwind-dev + + - name: install 32-bit dependencies + if: matrix.m32 != '' + run: sudo apt install -y ${{ env.m32_dependecies }} + + - name: prepare + run: ./.ci/linux-prepare.sh + + - name: build + run: PATH="$PATH:$HOME/bin" ./.ci/linux-build.sh + + - name: copy logs on failure + if: failure() || cancelled() + run: | + # upload-artifact@v2 throws exceptions if it tries to upload socket + # files and we could have some socket files in testsuite.dir. + # Also, upload-artifact@v2 doesn't work well enough with wildcards. + # So, we're just archiving everything here to avoid any issues. + mkdir logs + cp config.log ./logs/ + cp -r ./*/_build/sub/tests/testsuite.* ./logs/ || true + tar -czvf logs.tgz logs/ + + - name: upload logs on failure + if: failure() || cancelled() + uses: actions/upload-artifact@v2 + with: + name: logs-linux-${{ join(matrix.*, '-') }} + path: logs.tgz + + build-osx: + env: + CC: clang + OPTS: --disable-ssl + + name: osx clang --disable-ssl + runs-on: macos-latest + + strategy: + fail-fast: false + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: install dependencies + run: brew install automake libtool + - name: prepare + run: ./.ci/osx-prepare.sh + - name: build + run: PATH="$PATH:$HOME/bin" ./.ci/osx-build.sh + - name: upload logs on failure + if: failure() + uses: actions/upload-artifact@v2 + with: + name: logs-osx-clang---disable-ssl + path: config.log diff -Nru ovn-20.09.0/ic/ovn-ic.c ovn-20.12.0/ic/ovn-ic.c --- ovn-20.09.0/ic/ovn-ic.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/ic/ovn-ic.c 2020-12-17 17:53:32.000000000 +0000 @@ -136,6 +136,10 @@ az_name = xstrdup(nb_global->name); } + if (ctx->ovnisb_txn) { + ovsdb_idl_txn_add_comment(ctx->ovnisb_txn, "AZ %s", az_name); + } + ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_idl) { if (!strcmp(az->name, az_name)) { return az; @@ -817,9 +821,9 @@ /* Represents an interconnection route entry. */ struct ic_route_info { struct hmap_node node; - struct v46_ip prefix; + struct in6_addr prefix; unsigned int plen; - struct v46_ip nexthop; + struct in6_addr nexthop; /* Either nb_route or nb_lrp is set and the other one must be NULL. * - For a route that is learned from IC-SB, or a static route that is @@ -832,23 +836,23 @@ }; static uint32_t -ic_route_hash(const struct v46_ip *prefix, unsigned int plen, - const struct v46_ip *nexthop) +ic_route_hash(const struct in6_addr *prefix, unsigned int plen, + const struct in6_addr *nexthop) { uint32_t basis = hash_bytes(prefix, sizeof *prefix, (uint32_t)plen); return hash_bytes(nexthop, sizeof *nexthop, basis); } static struct ic_route_info * -ic_route_find(struct hmap *routes, const struct v46_ip *prefix, - unsigned int plen, const struct v46_ip *nexthop) +ic_route_find(struct hmap *routes, const struct in6_addr *prefix, + unsigned int plen, const struct in6_addr *nexthop) { struct ic_route_info *r; uint32_t hash = ic_route_hash(prefix, plen, nexthop); HMAP_FOR_EACH_WITH_HASH (r, node, hash, routes) { - if (ip46_equals(&r->prefix, prefix) && + if (ipv6_addr_equals(&r->prefix, prefix) && r->plen == plen && - ip46_equals(&r->nexthop, nexthop)) { + ipv6_addr_equals(&r->nexthop, nexthop)) { return r; } } @@ -870,8 +874,8 @@ static bool parse_route(const char *s_prefix, const char *s_nexthop, - struct v46_ip *prefix, unsigned int *plen, - struct v46_ip *nexthop) + struct in6_addr *prefix, unsigned int *plen, + struct in6_addr *nexthop) { if (!ip46_parse_cidr(s_prefix, prefix, plen)) { return false; @@ -886,7 +890,7 @@ add_to_routes_learned(struct hmap *routes_learned, const struct nbrec_logical_router_static_route *nb_route) { - struct v46_ip prefix, nexthop; + struct in6_addr prefix, nexthop; unsigned int plen; if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, &prefix, &plen, &nexthop)) { @@ -903,61 +907,60 @@ } static bool -get_nexthop_from_lport_addresses(int family, +get_nexthop_from_lport_addresses(bool is_v4, const struct lport_addresses *laddr, - struct v46_ip *nexthop) + struct in6_addr *nexthop) { - nexthop->family = family; - if (family == AF_INET) { + if (is_v4) { if (!laddr->n_ipv4_addrs) { return false; } - nexthop->ipv4 = laddr->ipv4_addrs[0].addr; + in6_addr_set_mapped_ipv4(nexthop, laddr->ipv4_addrs[0].addr); return true; } /* ipv6 */ if (laddr->n_ipv6_addrs) { - nexthop->ipv6 = laddr->ipv6_addrs[0].addr; + *nexthop = laddr->ipv6_addrs[0].addr; return true; } /* ipv6 link local */ - in6_generate_lla(laddr->ea, &nexthop->ipv6); + in6_generate_lla(laddr->ea, nexthop); return true; } static bool -prefix_is_link_local(struct v46_ip *prefix, unsigned int plen) +prefix_is_link_local(struct in6_addr *prefix, unsigned int plen) { - if (prefix->family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(prefix)) { /* Link local range is "169.254.0.0/16". */ if (plen < 16) { return false; } ovs_be32 lla; inet_pton(AF_INET, "169.254.0.0", &lla); - return ((prefix->ipv4 & htonl(0xffff0000)) == lla); + return ((in6_addr_get_mapped_ipv4(prefix) & htonl(0xffff0000)) == lla); } /* ipv6, link local range is "fe80::/10". */ if (plen < 10) { return false; } - return (((prefix->ipv6.s6_addr[0] & 0xff) == 0xfe) && - ((prefix->ipv6.s6_addr[1] & 0xc0) == 0x80)); + return (((prefix->s6_addr[0] & 0xff) == 0xfe) && + ((prefix->s6_addr[1] & 0xc0) == 0x80)); } static bool prefix_is_black_listed(const struct smap *nb_options, - struct v46_ip *prefix, + struct in6_addr *prefix, unsigned int plen) { const char *blacklist = smap_get(nb_options, "ic-route-blacklist"); if (!blacklist || !blacklist[0]) { return false; } - struct v46_ip bl_prefix; + struct in6_addr bl_prefix; unsigned int bl_plen; char *cur, *next, *start; next = start = xstrdup(blacklist); @@ -970,7 +973,7 @@ continue; } - if (bl_prefix.family != prefix->family) { + if (IN6_IS_ADDR_V4MAPPED(&bl_prefix) != IN6_IS_ADDR_V4MAPPED(prefix)) { continue; } @@ -979,16 +982,19 @@ continue; } - if (prefix->family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(prefix)) { + ovs_be32 bl_prefix_v4 = in6_addr_get_mapped_ipv4(&bl_prefix); + ovs_be32 prefix_v4 = in6_addr_get_mapped_ipv4(prefix); ovs_be32 mask = be32_prefix_mask(bl_plen); - if ((prefix->ipv4 & mask) != (bl_prefix.ipv4 & mask)) { + + if ((prefix_v4 & mask) != (bl_prefix_v4 & mask)) { continue; } } else { struct in6_addr mask = ipv6_create_mask(bl_plen); for (int i = 0; i < 16 && mask.s6_addr[i] != 0; i++) { - if ((prefix->ipv6.s6_addr[i] & mask.s6_addr[i]) - != (bl_prefix.ipv6.s6_addr[i] & mask.s6_addr[i])) { + if ((prefix->s6_addr[i] & mask.s6_addr[i]) + != (bl_prefix.s6_addr[i] & mask.s6_addr[i])) { continue; } } @@ -1002,7 +1008,7 @@ static bool route_need_advertise(const char *policy, - struct v46_ip *prefix, + struct in6_addr *prefix, unsigned int plen, const struct smap *nb_options) { @@ -1035,7 +1041,7 @@ const struct lport_addresses *nexthop_addresses, const struct smap *nb_options) { - struct v46_ip prefix, nexthop; + struct in6_addr prefix, nexthop; unsigned int plen; if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, &prefix, &plen, &nexthop)) { @@ -1046,7 +1052,7 @@ return; } - if (!get_nexthop_from_lport_addresses(prefix.family, + if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), nexthop_addresses, &nexthop)) { return; @@ -1067,7 +1073,7 @@ const struct lport_addresses *nexthop_addresses, const struct smap *nb_options) { - struct v46_ip prefix, nexthop; + struct in6_addr prefix, nexthop; unsigned int plen; if (!ip46_parse_cidr(network, &prefix, &plen)) { return; @@ -1079,14 +1085,29 @@ return; } - if (!get_nexthop_from_lport_addresses(prefix.family, + if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), nexthop_addresses, &nexthop)) { return; } - VLOG_DBG("Route ad: direct network %s of lrp %s, nexthop "IP_FMT, - network, nb_lrp->name, IP_ARGS(nexthop.ipv4)); + if (VLOG_IS_DBG_ENABLED()) { + struct ds msg = DS_EMPTY_INITIALIZER; + + ds_put_format(&msg, "Route ad: direct network %s of lrp %s, nexthop ", + network, nb_lrp->name); + + if (IN6_IS_ADDR_V4MAPPED(&nexthop)) { + ds_put_format(&msg, IP_FMT, + IP_ARGS(in6_addr_get_mapped_ipv4(&nexthop))); + } else { + ipv6_format_addr(&nexthop, &msg); + } + + VLOG_DBG("%s", ds_cstr(&msg)); + ds_destroy(&msg); + } + struct ic_route_info *ic_route = xzalloc(sizeof *ic_route); ic_route->prefix = prefix; ic_route->plen = plen; @@ -1097,7 +1118,7 @@ } static bool -route_need_learn(struct v46_ip *prefix, +route_need_learn(struct in6_addr *prefix, unsigned int plen, const struct smap *nb_options) { @@ -1132,7 +1153,7 @@ if (isb_route->availability_zone == az) { continue; } - struct v46_ip prefix, nexthop; + struct in6_addr prefix, nexthop; unsigned int plen; if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, &prefix, &plen, &nexthop)) { @@ -1227,7 +1248,7 @@ continue; } - struct v46_ip prefix, nexthop; + struct in6_addr prefix, nexthop; unsigned int plen; if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, @@ -1263,17 +1284,17 @@ icsbrec_route_set_availability_zone(isb_route, az); char *prefix_s, *nexthop_s; - if (route_adv->prefix.family == AF_INET) { - prefix_s = xasprintf(IP_FMT "/%d", - IP_ARGS(route_adv->prefix.ipv4), - route_adv->plen); - nexthop_s = xasprintf(IP_FMT, IP_ARGS(route_adv->nexthop.ipv4)); + if (IN6_IS_ADDR_V4MAPPED(&route_adv->prefix)) { + ovs_be32 ipv4 = in6_addr_get_mapped_ipv4(&route_adv->prefix); + ovs_be32 nh = in6_addr_get_mapped_ipv4(&route_adv->nexthop); + prefix_s = xasprintf(IP_FMT "/%d", IP_ARGS(ipv4), route_adv->plen); + nexthop_s = xasprintf(IP_FMT, IP_ARGS(nh)); } else { char network_s[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, &route_adv->prefix.ipv6, network_s, + inet_ntop(AF_INET6, &route_adv->prefix, network_s, INET6_ADDRSTRLEN); prefix_s = xasprintf("%s/%d", network_s, route_adv->plen); - inet_ntop(AF_INET6, &route_adv->nexthop.ipv6, network_s, + inet_ntop(AF_INET6, &route_adv->nexthop, network_s, INET6_ADDRSTRLEN); nexthop_s = xstrdup(network_s); } diff -Nru ovn-20.09.0/include/ovn/actions.h ovn-20.12.0/include/ovn/actions.h --- ovn-20.09.0/include/ovn/actions.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/include/ovn/actions.h 2020-12-17 17:53:32.000000000 +0000 @@ -48,6 +48,10 @@ * action. Its first member must have type "struct ovnact" and name * "ovnact". The structure must have a fixed length, that is, it may not * end with a flexible array member. + * + * These OVNACTS are used to generate the OVN internal version. See + * ovn_get_internal_version() in lib/ovn-util.c. If any OVNACT is updated, + * increment the OVN_INTERNAL_MINOR_VER macro in lib/ovn-util.c. */ #define OVNACTS \ OVNACT(OUTPUT, ovnact_null) \ @@ -57,7 +61,8 @@ OVNACT(EXCHANGE, ovnact_move) \ OVNACT(DEC_TTL, ovnact_null) \ OVNACT(CT_NEXT, ovnact_ct_next) \ - OVNACT(CT_COMMIT, ovnact_nest) \ + OVNACT(CT_COMMIT_V1, ovnact_ct_commit_v1) \ + OVNACT(CT_COMMIT_V2, ovnact_nest) \ OVNACT(CT_DNAT, ovnact_ct_nat) \ OVNACT(CT_SNAT, ovnact_ct_nat) \ OVNACT(CT_LB, ovnact_ct_lb) \ @@ -83,7 +88,7 @@ OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \ OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ OVNACT(SET_QUEUE, ovnact_set_queue) \ - OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ + OVNACT(DNS_LOOKUP, ovnact_result) \ OVNACT(LOG, ovnact_log) \ OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) \ OVNACT(ND_NS, ovnact_nest) \ @@ -95,7 +100,11 @@ OVNACT(HANDLE_SVC_CHECK, ovnact_handle_svc_check) \ OVNACT(FWD_GROUP, ovnact_fwd_group) \ OVNACT(DHCP6_REPLY, ovnact_null) \ - OVNACT(ICMP6_ERROR, ovnact_nest) + OVNACT(ICMP6_ERROR, ovnact_nest) \ + OVNACT(REJECT, ovnact_nest) \ + OVNACT(CHK_LB_HAIRPIN, ovnact_result) \ + OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \ + OVNACT(CT_SNAT_TO_VIP, ovnact_null) \ /* enum ovnact_type, with a member OVNACT_ for each action. */ enum OVS_PACKED_ENUM ovnact_type { @@ -222,6 +231,13 @@ uint8_t ltable; /* Logical table ID of next table. */ }; +/* OVNACT_CT_COMMIT_V1. */ +struct ovnact_ct_commit_v1 { + struct ovnact ovnact; + uint32_t ct_mark, ct_mark_mask; + ovs_be128 ct_label, ct_label_mask; +}; + /* OVNACT_CT_DNAT, OVNACT_CT_SNAT. */ struct ovnact_ct_nat { struct ovnact ovnact; @@ -337,8 +353,8 @@ uint16_t queue_id; }; -/* OVNACT_DNS_LOOKUP. */ -struct ovnact_dns_lookup { +/* OVNACT_DNS_LOOKUP, OVNACT_CHK_LB_HAIRPIN, OVNACT_CHK_LB_HAIRPIN_REPLY. */ +struct ovnact_result { struct ovnact ovnact; struct expr_field dst; /* 1-bit destination field. */ }; @@ -605,6 +621,12 @@ /* MTU value (to put in the icmp6 header field - frag_mtu) follow the * action header. */ ACTION_OPCODE_PUT_ICMP6_FRAG_MTU, + + /* "reject { ...actions... }". + * + * The actions, in OpenFlow 1.3 format, follow the action_header. + */ + ACTION_OPCODE_REJECT, }; /* Header. */ @@ -720,6 +742,12 @@ resubmit. */ uint8_t mac_lookup_ptable; /* OpenFlow table for 'lookup_arp'/'lookup_nd' to resubmit. */ + uint8_t lb_hairpin_ptable; /* OpenFlow table for + * 'chk_lb_hairpin' to resubmit. */ + uint8_t lb_hairpin_reply_ptable; /* OpenFlow table for + * 'chk_lb_hairpin_reply' to resubmit. */ + uint8_t ct_snat_vip_ptable; /* OpenFlow table for + * 'ct_snat_to_vip' to resubmit. */ }; void ovnacts_encode(const struct ovnact[], size_t ovnacts_len, diff -Nru ovn-20.09.0/include/ovn/expr.h ovn-20.12.0/include/ovn/expr.h --- ovn-20.09.0/include/ovn/expr.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/include/ovn/expr.h 2020-12-17 17:53:32.000000000 +0000 @@ -452,6 +452,7 @@ unsigned int *portp), const void *aux, struct flow *uflow) OVS_WARN_UNUSED_RESULT; +char *expr_find_inport(const struct expr *, char **errorp); bool expr_evaluate(const struct expr *, const struct flow *uflow, bool (*lookup_port)(const void *aux, const char *port_name, diff -Nru ovn-20.09.0/include/ovn/lex.h ovn-20.12.0/include/ovn/lex.h --- ovn-20.09.0/include/ovn/lex.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/include/ovn/lex.h 2020-12-17 17:53:32.000000000 +0000 @@ -137,6 +137,7 @@ bool lexer_match(struct lexer *, enum lex_type); bool lexer_force_match(struct lexer *, enum lex_type); bool lexer_match_id(struct lexer *, const char *id); +bool lexer_match_string(struct lexer *, const char *s); bool lexer_is_int(const struct lexer *); bool lexer_get_int(struct lexer *, int *value); bool lexer_force_int(struct lexer *, int *value); diff -Nru ovn-20.09.0/include/ovn/logical-fields.h ovn-20.12.0/include/ovn/logical-fields.h --- ovn-20.09.0/include/ovn/logical-fields.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/include/ovn/logical-fields.h 2020-12-17 17:53:32.000000000 +0000 @@ -17,6 +17,7 @@ #define OVN_LOGICAL_FIELDS_H 1 #include "openvswitch/meta-flow.h" +#include "openvswitch/util.h" struct shash; @@ -57,6 +58,7 @@ MLF_LOCAL_ONLY_BIT = 4, MLF_NESTED_CONTAINER_BIT = 5, MLF_LOOKUP_MAC_BIT = 6, + MLF_LOOKUP_LB_HAIRPIN_BIT = 7, }; /* MFF_LOG_FLAGS_REG flag assignments */ @@ -88,6 +90,8 @@ /* Indicate that the lookup in the mac binding table was successful. */ MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT), + + MLF_LOOKUP_LB_HAIRPIN = (1 << MLF_LOOKUP_LB_HAIRPIN_BIT), }; /* OVN logical fields @@ -137,4 +141,23 @@ const char *event_to_string(enum ovn_controller_event event); int string_to_event(const char *s); const struct ovn_field *ovn_field_from_name(const char *name); + +/* OVN CT label values + * =================== + * These are specific ct.label bit values OVN uses to track different types + * of traffic. + */ + +#define OVN_CT_BLOCKED_BIT 0 +#define OVN_CT_NATTED_BIT 1 + +#define OVN_CT_BLOCKED 1 +#define OVN_CT_NATTED 2 + +#define OVN_CT_STR(LABEL_VALUE) OVS_STRINGIZE(LABEL_VALUE) +#define OVN_CT_MASKED_STR(LABEL_VALUE) \ + OVS_STRINGIZE(LABEL_VALUE) "/" OVS_STRINGIZE(LABEL_VALUE) + +#define OVN_CT_LABEL_STR(LABEL_VALUE) "ct_label[" OVN_CT_STR(LABEL_VALUE) "]" + #endif /* ovn/lib/logical-fields.h */ diff -Nru ovn-20.09.0/lib/actions.c ovn-20.12.0/lib/actions.c --- ovn-20.09.0/lib/actions.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/actions.c 2020-12-17 17:53:32.000000000 +0000 @@ -628,15 +628,74 @@ } static void +parse_ct_commit_v1_arg(struct action_context *ctx, + struct ovnact_ct_commit_v1 *cc) +{ + if (lexer_match_id(ctx->lexer, "ct_mark")) { + if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { + return; + } + if (ctx->lexer->token.type == LEX_T_INTEGER) { + cc->ct_mark = ntohll(ctx->lexer->token.value.integer); + cc->ct_mark_mask = UINT32_MAX; + } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) { + cc->ct_mark = ntohll(ctx->lexer->token.value.integer); + cc->ct_mark_mask = ntohll(ctx->lexer->token.mask.integer); + } else { + lexer_syntax_error(ctx->lexer, "expecting integer"); + return; + } + lexer_get(ctx->lexer); + } else if (lexer_match_id(ctx->lexer, "ct_label")) { + if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { + return; + } + if (ctx->lexer->token.type == LEX_T_INTEGER) { + cc->ct_label = ctx->lexer->token.value.be128_int; + cc->ct_label_mask = OVS_BE128_MAX; + } else if (ctx->lexer->token.type == LEX_T_MASKED_INTEGER) { + cc->ct_label = ctx->lexer->token.value.be128_int; + cc->ct_label_mask = ctx->lexer->token.mask.be128_int; + } else { + lexer_syntax_error(ctx->lexer, "expecting integer"); + return; + } + lexer_get(ctx->lexer); + } else { + lexer_syntax_error(ctx->lexer, NULL); + } +} + +static void +parse_CT_COMMIT_V1(struct action_context *ctx) +{ + add_prerequisite(ctx, "ip"); + + struct ovnact_ct_commit_v1 *ct_commit = + ovnact_put_CT_COMMIT_V1(ctx->ovnacts); + if (lexer_match(ctx->lexer, LEX_T_LPAREN)) { + while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { + parse_ct_commit_v1_arg(ctx, ct_commit); + if (ctx->lexer->error) { + return; + } + lexer_match(ctx->lexer, LEX_T_COMMA); + } + } +} + +static void parse_CT_COMMIT(struct action_context *ctx) { if (ctx->lexer->token.type == LEX_T_LCURLY) { - parse_nested_action(ctx, OVNACT_CT_COMMIT, "ip", + parse_nested_action(ctx, OVNACT_CT_COMMIT_V2, "ip", WR_CT_COMMIT); + } else if (ctx->lexer->token.type == LEX_T_LPAREN) { + parse_CT_COMMIT_V1(ctx); } else { /* Add an empty nested action to allow for "ct_commit;" syntax */ add_prerequisite(ctx, "ip"); - struct ovnact_nest *on = ovnact_put(ctx->ovnacts, OVNACT_CT_COMMIT, + struct ovnact_nest *on = ovnact_put(ctx->ovnacts, OVNACT_CT_COMMIT_V2, OVNACT_ALIGN(sizeof *on)); on->nested_len = 0; on->nested = NULL; @@ -644,7 +703,73 @@ } static void -format_CT_COMMIT(const struct ovnact_nest *on, struct ds *s) +format_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc, struct ds *s) +{ + ds_put_cstr(s, "ct_commit("); + if (cc->ct_mark_mask) { + ds_put_format(s, "ct_mark=%#"PRIx32, cc->ct_mark); + if (cc->ct_mark_mask != UINT32_MAX) { + ds_put_format(s, "/%#"PRIx32, cc->ct_mark_mask); + } + } + if (!ovs_be128_is_zero(cc->ct_label_mask)) { + if (ds_last(s) != '(') { + ds_put_cstr(s, ", "); + } + + ds_put_format(s, "ct_label="); + ds_put_hex(s, &cc->ct_label, sizeof cc->ct_label); + if (!ovs_be128_equals(cc->ct_label_mask, OVS_BE128_MAX)) { + ds_put_char(s, '/'); + ds_put_hex(s, &cc->ct_label_mask, sizeof cc->ct_label_mask); + } + } + if (!ds_chomp(s, '(')) { + ds_put_char(s, ')'); + } + ds_put_char(s, ';'); +} + +static void +encode_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc, + const struct ovnact_encode_params *ep OVS_UNUSED, + struct ofpbuf *ofpacts) +{ + struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); + ct->flags = NX_CT_F_COMMIT; + ct->recirc_table = NX_CT_RECIRC_NONE; + ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE); + ct->zone_src.ofs = 0; + ct->zone_src.n_bits = 16; + + size_t set_field_offset = ofpacts->size; + ofpbuf_pull(ofpacts, set_field_offset); + + if (cc->ct_mark_mask) { + const ovs_be32 value = htonl(cc->ct_mark); + const ovs_be32 mask = htonl(cc->ct_mark_mask); + ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_MARK), &value, &mask); + } + + if (!ovs_be128_is_zero(cc->ct_label_mask)) { + ofpact_put_set_field(ofpacts, mf_from_id(MFF_CT_LABEL), &cc->ct_label, + &cc->ct_label_mask); + } + + ofpacts->header = ofpbuf_push_uninit(ofpacts, set_field_offset); + ct = ofpacts->header; + ofpact_finish(ofpacts, &ct->ofpact); +} + +static void +ovnact_ct_commit_v1_free(struct ovnact_ct_commit_v1 *cc OVS_UNUSED) +{ +} + + + +static void +format_CT_COMMIT_V2(const struct ovnact_nest *on, struct ds *s) { if (on->nested_len) { format_nested_action(on, "ct_commit", s); @@ -654,9 +779,9 @@ } static void -encode_CT_COMMIT(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep OVS_UNUSED, - struct ofpbuf *ofpacts) +encode_CT_COMMIT_V2(const struct ovnact_nest *on, + const struct ovnact_encode_params *ep OVS_UNUSED, + struct ofpbuf *ofpacts) { struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); ct->flags = NX_CT_F_COMMIT; @@ -1099,7 +1224,9 @@ ds_put_format(&ds, ":%"PRIu16, dst->port); } ds_put_format(&ds, "),commit,table=%d,zone=NXM_NX_REG%d[0..15]," - "exec(set_field:2/2->ct_label))", + "exec(set_field:" + OVN_CT_MASKED_STR(OVN_CT_NATTED) + "->ct_label))", recirc_table, zone_reg); } @@ -1188,6 +1315,7 @@ } if (n_dsts <= 1) { lexer_syntax_error(ctx->lexer, "expecting at least 2 group members"); + free(dsts); return; } @@ -1387,6 +1515,12 @@ } static void +parse_REJECT(struct action_context *ctx) +{ + parse_nested_action(ctx, OVNACT_REJECT, NULL, ctx->scope); +} + +static void format_nested_action(const struct ovnact_nest *on, const char *name, struct ds *s) { @@ -1480,6 +1614,12 @@ } static void +format_REJECT(const struct ovnact_nest *nest, struct ds *s) +{ + format_nested_action(nest, "reject", s); +} + +static void encode_nested_actions(const struct ovnact_nest *on, const struct ovnact_encode_params *ep, enum action_opcode opcode, @@ -1561,6 +1701,14 @@ } static void +encode_REJECT(const struct ovnact_nest *on, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + encode_nested_actions(on, ep, ACTION_OPCODE_REJECT, ofpacts); +} + +static void encode_ND_NA(const struct ovnact_nest *on, const struct ovnact_encode_params *ep, struct ofpbuf *ofpacts) @@ -2066,10 +2214,10 @@ } static const struct ovnact_gen_option * -find_offerip(const struct ovnact_gen_option *options, size_t n) +find_opt(const struct ovnact_gen_option *options, size_t n, size_t code) { for (const struct ovnact_gen_option *o = options; o < &options[n]; o++) { - if (o->option->code == 0) { + if (o->option->code == code) { return o; } } @@ -2268,7 +2416,7 @@ parse_put_opts(ctx, dst, po, dhcp_opts, opts_type); if (!ctx->lexer->error && po->ovnact.type == OVNACT_PUT_DHCPV4_OPTS - && !find_offerip(po->options, po->n_options)) { + && !find_opt(po->options, po->n_options, 0)) { lexer_error(ctx->lexer, "put_dhcp_opts requires offerip to be specified."); return; @@ -2517,14 +2665,35 @@ /* Encode the offerip option first, because it's a special case and needs * to be first in the actual DHCP response, and then encode the rest * (skipping offerip the second time around). */ - const struct ovnact_gen_option *offerip_opt = find_offerip( - pdo->options, pdo->n_options); + const struct ovnact_gen_option *offerip_opt = find_opt( + pdo->options, pdo->n_options, 0); ovs_be32 offerip = offerip_opt->value.values[0].value.ipv4; ofpbuf_put(ofpacts, &offerip, sizeof offerip); + /* Encode bootfile_name opt (67) */ + const struct ovnact_gen_option *boot_opt = + find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_CODE); + if (boot_opt) { + uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2); + const union expr_constant *c = boot_opt->value.values; + opt_header[0] = boot_opt->option->code; + opt_header[1] = strlen(c->string); + ofpbuf_put(ofpacts, c->string, opt_header[1]); + } + /* Encode bootfile_name_alt opt (254) */ + const struct ovnact_gen_option *boot_alt_opt = + find_opt(pdo->options, pdo->n_options, DHCP_OPT_BOOTFILE_ALT_CODE); + if (boot_alt_opt) { + uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2); + const union expr_constant *c = boot_alt_opt->value.values; + opt_header[0] = boot_alt_opt->option->code; + opt_header[1] = strlen(c->string); + ofpbuf_put(ofpacts, c->string, opt_header[1]); + } + for (const struct ovnact_gen_option *o = pdo->options; o < &pdo->options[pdo->n_options]; o++) { - if (o != offerip_opt) { + if (o != offerip_opt && o != boot_opt && o != boot_alt_opt) { encode_put_dhcpv4_option(o, ofpacts); } } @@ -2614,13 +2783,14 @@ } static void -parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst, - struct ovnact_dns_lookup *dl) +parse_ovnact_result(struct action_context *ctx, const char *name, + const char *prereq, const struct expr_field *dst, + struct ovnact_result *res) { - lexer_get(ctx->lexer); /* Skip dns_lookup. */ + lexer_get(ctx->lexer); /* Skip action name. */ lexer_get(ctx->lexer); /* Skip '('. */ if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { - lexer_error(ctx->lexer, "dns_lookup doesn't take any parameters"); + lexer_error(ctx->lexer, "%s doesn't take any parameters", name); return; } /* Validate that the destination is a 1-bit, modifiable field. */ @@ -2630,19 +2800,29 @@ free(error); return; } - dl->dst = *dst; - add_prerequisite(ctx, "udp"); + res->dst = *dst; + + if (prereq) { + add_prerequisite(ctx, prereq); + } +} + +static void +parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst, + struct ovnact_result *dl) +{ + parse_ovnact_result(ctx, "dns_lookup", "udp", dst, dl); } static void -format_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, struct ds *s) +format_DNS_LOOKUP(const struct ovnact_result *dl, struct ds *s) { expr_field_format(&dl->dst, s); ds_put_cstr(s, " = dns_lookup();"); } static void -encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, +encode_DNS_LOOKUP(const struct ovnact_result *dl, const struct ovnact_encode_params *ep OVS_UNUSED, struct ofpbuf *ofpacts) { @@ -2659,7 +2839,7 @@ static void -ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) +ovnact_result_free(struct ovnact_result *dl OVS_UNUSED) { } @@ -3303,15 +3483,17 @@ if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { return; } - if (ctx->lexer->token.type != LEX_T_STRING) { + if (lexer_match_string(ctx->lexer, "true") || + lexer_match_id(ctx->lexer, "true")) { + liveness = true; + } else if (lexer_match_string(ctx->lexer, "false") || + lexer_match_id(ctx->lexer, "false")) { + liveness = false; + } else { lexer_syntax_error(ctx->lexer, - "expecting true/false"); + "expecting true or false"); return; } - if (!strcmp(ctx->lexer->token.s, "true")) { - liveness = true; - lexer_get(ctx->lexer); - } lexer_force_match(ctx->lexer, LEX_T_COMMA); } if (lexer_match_id(ctx->lexer, "childports")) { @@ -3323,6 +3505,9 @@ lexer_syntax_error(ctx->lexer, "expecting logical switch port"); if (child_port_list) { + for (int i = 0; i < n_child_ports; i++) { + free(child_port_list[i]); + } free(child_port_list); } return; @@ -3392,6 +3577,7 @@ /* Find the tunnel key of the logical port */ if (!ep->lookup_port(ep->aux, port_name, &port_tunnel_key)) { + ds_destroy(&ds); return; } ds_put_format(&ds, ",bucket="); @@ -3399,6 +3585,7 @@ if (fwd_group->liveness) { /* Find the openflow port number of the tunnel port */ if (!ep->tunnel_ofport(ep->aux, port_name, &ofport)) { + ds_destroy(&ds); return; } @@ -3428,9 +3615,89 @@ static void ovnact_fwd_group_free(struct ovnact_fwd_group *fwd_group) { + for (int i = 0; i < fwd_group->n_child_ports; i++) { + free(fwd_group->child_ports[i]); + } free(fwd_group->child_ports); } +static void +parse_chk_lb_hairpin(struct action_context *ctx, const struct expr_field *dst, + struct ovnact_result *res) +{ + parse_ovnact_result(ctx, "chk_lb_hairpin", NULL, dst, res); +} + +static void +parse_chk_lb_hairpin_reply(struct action_context *ctx, + const struct expr_field *dst, + struct ovnact_result *res) +{ + parse_ovnact_result(ctx, "chk_lb_hairpin_reply", NULL, dst, res); +} + + +static void +format_CHK_LB_HAIRPIN(const struct ovnact_result *res, struct ds *s) +{ + expr_field_format(&res->dst, s); + ds_put_cstr(s, " = chk_lb_hairpin();"); +} + +static void +format_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res, struct ds *s) +{ + expr_field_format(&res->dst, s); + ds_put_cstr(s, " = chk_lb_hairpin_reply();"); +} + +static void +encode_chk_lb_hairpin__(const struct ovnact_result *res, + uint8_t hairpin_table, + struct ofpbuf *ofpacts) +{ + struct mf_subfield dst = expr_resolve_field(&res->dst); + ovs_assert(dst.field); + put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_LB_HAIRPIN_BIT, 1, ofpacts); + emit_resubmit(ofpacts, hairpin_table); + + struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts); + orm->dst = dst; + orm->src.field = mf_from_id(MFF_LOG_FLAGS); + orm->src.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT; + orm->src.n_bits = 1; +} + +static void +encode_CHK_LB_HAIRPIN(const struct ovnact_result *res, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + encode_chk_lb_hairpin__(res, ep->lb_hairpin_ptable, ofpacts); +} + +static void +encode_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + encode_chk_lb_hairpin__(res, ep->lb_hairpin_reply_ptable, ofpacts); +} + +static void +format_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED, struct ds *s) +{ + ds_put_cstr(s, "ct_snat_to_vip;"); +} + +static void +encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + emit_resubmit(ofpacts, ep->ct_snat_vip_ptable); +} + /* Parses an assignment or exchange or put_dhcp_opts action. */ static void parse_set_action(struct action_context *ctx) @@ -3483,6 +3750,14 @@ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { parse_lookup_mac_bind_ip(ctx, &lhs, 128, ovnact_put_LOOKUP_ND_IP(ctx->ovnacts)); + } else if (!strcmp(ctx->lexer->token.s, "chk_lb_hairpin") + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { + parse_chk_lb_hairpin(ctx, &lhs, + ovnact_put_CHK_LB_HAIRPIN(ctx->ovnacts)); + } else if (!strcmp(ctx->lexer->token.s, "chk_lb_hairpin_reply") + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { + parse_chk_lb_hairpin_reply( + ctx, &lhs, ovnact_put_CHK_LB_HAIRPIN_REPLY(ctx->ovnacts)); } else { parse_assignment_action(ctx, false, &lhs); } @@ -3567,6 +3842,10 @@ parse_fwd_group_action(ctx); } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) { ovnact_put_DHCP6_REPLY(ctx->ovnacts); + } else if (lexer_match_id(ctx->lexer, "reject")) { + parse_REJECT(ctx); + } else if (lexer_match_id(ctx->lexer, "ct_snat_to_vip")) { + ovnact_put_CT_SNAT_TO_VIP(ctx->ovnacts); } else { lexer_syntax_error(ctx->lexer, "expecting action"); } diff -Nru ovn-20.09.0/lib/automake.mk ovn-20.12.0/lib/automake.mk --- ovn-20.09.0/lib/automake.mk 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/automake.mk 2020-12-17 17:53:32.000000000 +0000 @@ -19,11 +19,14 @@ lib/mcast-group-index.h \ lib/lex.c \ lib/ovn-l7.h \ + lib/ovn-l7.c \ lib/ovn-util.c \ lib/ovn-util.h \ lib/logical-fields.c \ lib/inc-proc-eng.c \ - lib/inc-proc-eng.h + lib/inc-proc-eng.h \ + lib/lb.c \ + lib/lb.h nodist_lib_libovn_la_SOURCES = \ lib/ovn-dirs.c \ lib/ovn-nb-idl.c \ diff -Nru ovn-20.09.0/lib/expr.c ovn-20.12.0/lib/expr.c --- ovn-20.09.0/lib/expr.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/expr.c 2020-12-17 17:53:32.000000000 +0000 @@ -1317,9 +1317,9 @@ } /* Parses an expression from 'lexer' using the symbols in 'symtab' and - * address set table in 'addr_sets'. If successful, returns the new - * expression; on failure, returns NULL. Returns nonnull if and only if - * lexer->error is NULL. */ + * address set table in 'addr_sets' and 'port_groups'. If successful, returns + * the new expression; on failure, returns NULL. Returns nonnull if and only + * if lexer->error is NULL. */ struct expr * expr_parse(struct lexer *lexer, const struct shash *symtab, const struct shash *addr_sets, @@ -1339,9 +1339,9 @@ } /* Parses the expression in 's' using the symbols in 'symtab' and - * address set table in 'addr_sets'. If successful, returns the new - * expression and sets '*errorp' to NULL. On failure, returns NULL and - * sets '*errorp' to an explanatory error message. The caller must + * address set table in 'addr_sets' and 'port_groups'. If successful, returns + * the new expression and sets '*errorp' to NULL. On failure, returns NULL + * and sets '*errorp' to an explanatory error message. The caller must * eventually free the returned expression (with expr_destroy()) or * error (with free()). */ struct expr * @@ -3481,9 +3481,9 @@ } /* Parses 's' as a microflow, using symbols from 'symtab', address set - * table from 'addr_sets', and looking up port numbers using 'lookup_port' - * and 'aux'. On success, stores the result in 'uflow' and returns - * NULL, otherwise zeros 'uflow' and returns an error message that the + * table from 'addr_sets' and 'port_groups', and looking up port numbers using + * 'lookup_port' and 'aux'. On success, stores the result in 'uflow' and + * returns NULL, otherwise zeros 'uflow' and returns an error message that the * caller must free(). * * A "microflow" is a description of a single stream of packets, such as half a @@ -3538,3 +3538,55 @@ } return error; } + +static void +expr_find_inports(const struct expr *e, struct sset *inports) +{ + const struct expr *sub; + + switch (e->type) { + case EXPR_T_CMP: + if (!strcmp(e->cmp.symbol->name, "inport") + && !e->cmp.symbol->width + && e->cmp.relop == EXPR_R_EQ) { + sset_add(inports, e->cmp.string); + } + break; + + case EXPR_T_AND: + case EXPR_T_OR: + LIST_FOR_EACH (sub, node, &e->andor) { + expr_find_inports(sub, inports); + } + break; + + case EXPR_T_BOOLEAN: + case EXPR_T_CONDITION: + /* Nothing to do. */ + break; + } +} + +/* Traverses 'e' looking for a match against inport. If found, returns a copy + * of its name. If no matches or more than one (different) match is found, + * returns NULL and stores an error message in '*errorp'. The caller must free + * both the error message and the port name. */ +char * +expr_find_inport(const struct expr *e, char **errorp) +{ + struct sset inports = SSET_INITIALIZER(&inports); + expr_find_inports(e, &inports); + + char *retval = NULL; + if (sset_count(&inports) == 1) { + retval = sset_pop(&inports); + *errorp = NULL; + } else if (sset_is_empty(&inports)) { + *errorp = xstrdup("flow match expression does not match on inport"); + } else { + *errorp = xstrdup("flow match expression matches on multiple inports"); + } + + sset_destroy(&inports); + return retval; +} diff -Nru ovn-20.09.0/lib/inc-proc-eng.c ovn-20.12.0/lib/inc-proc-eng.c --- ovn-20.09.0/lib/inc-proc-eng.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/inc-proc-eng.c 2020-12-17 17:53:32.000000000 +0000 @@ -38,10 +38,10 @@ static size_t engine_n_nodes; static const char *engine_node_state_name[EN_STATE_MAX] = { - [EN_STALE] = "Stale", - [EN_UPDATED] = "Updated", - [EN_VALID] = "Valid", - [EN_ABORTED] = "Aborted", + [EN_STALE] = "Stale", + [EN_UPDATED] = "Updated", + [EN_UNCHANGED] = "Unchanged", + [EN_ABORTED] = "Aborted", }; void @@ -210,7 +210,7 @@ static bool engine_node_valid(struct engine_node *node) { - if (node->state == EN_UPDATED || node->state == EN_VALID) { + if (node->state == EN_UPDATED || node->state == EN_UNCHANGED) { return true; } @@ -358,7 +358,7 @@ * still valid. */ if (!engine_node_changed(node)) { - engine_set_node_state(node, EN_VALID); + engine_set_node_state(node, EN_UNCHANGED); } } diff -Nru ovn-20.09.0/lib/inc-proc-eng.h ovn-20.12.0/lib/inc-proc-eng.h --- ovn-20.09.0/lib/inc-proc-eng.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/inc-proc-eng.h 2020-12-17 17:53:32.000000000 +0000 @@ -98,7 +98,7 @@ EN_UPDATED, /* Data in the node is valid but was updated during the * last run. */ - EN_VALID, /* Data in the node is valid and didn't change during the + EN_UNCHANGED, /* Data in the node is valid and didn't change during the * last run. */ EN_ABORTED, /* During the last run, processing was aborted for @@ -314,7 +314,7 @@ engine_set_node_state(node, EN_UPDATED); \ return; \ } \ - engine_set_node_state(node, EN_VALID); \ + engine_set_node_state(node, EN_UNCHANGED); \ } \ static void *en_##DB_NAME##_##TBL_NAME##_init( \ struct engine_node *node OVS_UNUSED, \ diff -Nru ovn-20.09.0/lib/lb.c ovn-20.12.0/lib/lb.c --- ovn-20.09.0/lib/lb.c 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/lib/lb.c 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,301 @@ +/* Copyright (c) 2020, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "lb.h" +#include "lib/ovn-nb-idl.h" +#include "lib/ovn-sb-idl.h" +#include "lib/ovn-util.h" + +/* OpenvSwitch lib includes. */ +#include "openvswitch/vlog.h" +#include "lib/smap.h" + +VLOG_DEFINE_THIS_MODULE(lb); + +static +bool ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key, + const char *lb_value) +{ + int addr_family; + + if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str, + &lb_vip->vip_port, &addr_family)) { + return false; + } + + if (addr_family == AF_INET) { + ovs_be32 vip4; + ip_parse(lb_vip->vip_str, &vip4); + in6_addr_set_mapped_ipv4(&lb_vip->vip, vip4); + } else { + ipv6_parse(lb_vip->vip_str, &lb_vip->vip); + } + + /* Format for backend ips: "IP1:port1,IP2:port2,...". */ + size_t n_backends = 0; + size_t n_allocated_backends = 0; + char *tokstr = xstrdup(lb_value); + char *save_ptr = NULL; + for (char *token = strtok_r(tokstr, ",", &save_ptr); + token != NULL; + token = strtok_r(NULL, ",", &save_ptr)) { + + if (n_backends == n_allocated_backends) { + lb_vip->backends = x2nrealloc(lb_vip->backends, + &n_allocated_backends, + sizeof *lb_vip->backends); + } + + struct ovn_lb_backend *backend = &lb_vip->backends[n_backends]; + int backend_addr_family; + if (!ip_address_and_port_from_lb_key(token, &backend->ip_str, + &backend->port, + &backend_addr_family)) { + continue; + } + + if (addr_family != backend_addr_family) { + free(backend->ip_str); + continue; + } + + if (addr_family == AF_INET) { + ovs_be32 ip4; + ip_parse(backend->ip_str, &ip4); + in6_addr_set_mapped_ipv4(&backend->ip, ip4); + } else { + ipv6_parse(backend->ip_str, &backend->ip); + } + n_backends++; + } + free(tokstr); + lb_vip->n_backends = n_backends; + return true; +} + +static +void ovn_lb_vip_destroy(struct ovn_lb_vip *vip) +{ + free(vip->vip_str); + for (size_t i = 0; i < vip->n_backends; i++) { + free(vip->backends[i].ip_str); + } + free(vip->backends); +} + +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)) +{ + lb_vip_nb->vip_port_str = xstrdup(vip_port_str); + lb_vip_nb->backend_ips = xstrdup(backend_ips); + lb_vip_nb->n_backends = lb_vip->n_backends; + lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends, + sizeof *lb_vip_nb->backends_nb); + + struct nbrec_load_balancer_health_check *lb_health_check = NULL; + if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) { + if (nbrec_lb->n_health_check > 0) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, + "SCTP load balancers do not currently support " + "health checks. Not creating health checks for " + "load balancer " UUID_FMT, + UUID_ARGS(&nbrec_lb->header_.uuid)); + } + } else { + for (size_t j = 0; j < nbrec_lb->n_health_check; j++) { + if (!strcmp(nbrec_lb->health_check[j]->vip, + lb_vip_nb->vip_port_str)) { + lb_health_check = nbrec_lb->health_check[j]; + break; + } + } + } + + 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 +void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip) +{ + free(vip->vip_port_str); + free(vip->backend_ips); + for (size_t i = 0; i < vip->n_backends; i++) { + free(vip->backends_nb[i].svc_mon_src_ip); + } + free(vip->backends_nb); +} + +struct ovn_northd_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)) +{ + struct ovn_northd_lb *lb = xzalloc(sizeof *lb); + + lb->nlb = 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); + struct smap_node *node; + size_t n_vips = 0; + + SMAP_FOR_EACH (node, &nbrec_lb->vips) { + struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; + struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips]; + + if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { + continue; + } + ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb, + node->key, node->value, ports, ovn_port_find); + n_vips++; + } + + /* It's possible that parsing VIPs fails. Update the lb->n_vips to the + * correct value. + */ + lb->n_vips = n_vips; + + if (nbrec_lb->n_selection_fields) { + char *proto = NULL; + if (nbrec_lb->protocol && nbrec_lb->protocol[0]) { + proto = nbrec_lb->protocol; + } + + struct ds sel_fields = DS_EMPTY_INITIALIZER; + for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) { + char *field = lb->nlb->selection_fields[i]; + if (!strcmp(field, "tp_src") && proto) { + ds_put_format(&sel_fields, "%s_src,", proto); + } else if (!strcmp(field, "tp_dst") && proto) { + ds_put_format(&sel_fields, "%s_dst,", proto); + } else { + ds_put_format(&sel_fields, "%s,", field); + } + } + ds_chomp(&sel_fields, ','); + lb->selection_fields = ds_steal_cstr(&sel_fields); + } + return lb; +} + +struct ovn_northd_lb * +ovn_northd_lb_find(struct hmap *lbs, const struct uuid *uuid) +{ + struct ovn_northd_lb *lb; + size_t hash = uuid_hash(uuid); + HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) { + if (uuid_equals(&lb->nlb->header_.uuid, uuid)) { + return lb; + } + } + return NULL; +} + +void +ovn_northd_lb_add_datapath(struct ovn_northd_lb *lb, + const struct sbrec_datapath_binding *sb) +{ + if (lb->n_allocated_dps == lb->n_dps) { + lb->dps = x2nrealloc(lb->dps, &lb->n_allocated_dps, sizeof *lb->dps); + } + lb->dps[lb->n_dps++] = sb; +} + +void +ovn_northd_lb_destroy(struct ovn_northd_lb *lb) +{ + for (size_t i = 0; i < lb->n_vips; i++) { + ovn_lb_vip_destroy(&lb->vips[i]); + ovn_northd_lb_vip_destroy(&lb->vips_nb[i]); + } + free(lb->vips); + free(lb->vips_nb); + free(lb->selection_fields); + free(lb->dps); + free(lb); +} + +struct ovn_controller_lb * +ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) +{ + struct ovn_controller_lb *lb = xzalloc(sizeof *lb); + + lb->slb = sbrec_lb; + lb->n_vips = smap_count(&sbrec_lb->vips); + lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips); + + struct smap_node *node; + size_t n_vips = 0; + + SMAP_FOR_EACH (node, &sbrec_lb->vips) { + struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; + + if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { + continue; + } + n_vips++; + } + + /* It's possible that parsing VIPs fails. Update the lb->n_vips to the + * correct value. + */ + lb->n_vips = n_vips; + return lb; +} + +void +ovn_controller_lb_destroy(struct ovn_controller_lb *lb) +{ + for (size_t i = 0; i < lb->n_vips; i++) { + ovn_lb_vip_destroy(&lb->vips[i]); + } + free(lb->vips); + free(lb); +} diff -Nru ovn-20.09.0/lib/lb.h ovn-20.12.0/lib/lb.h --- ovn-20.09.0/lib/lb.h 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/lib/lb.h 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,97 @@ +/* Copyright (c) 2020, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef OVN_LIB_LB_H +#define OVN_LIB_LB_H 1 + +#include +#include +#include "openvswitch/hmap.h" + +struct nbrec_load_balancer; +struct sbrec_load_balancer; +struct sbrec_datapath_binding; +struct ovn_port; +struct uuid; + +struct ovn_northd_lb { + struct hmap_node hmap_node; + + const struct nbrec_load_balancer *nlb; /* May be NULL. */ + const struct sbrec_load_balancer *slb; /* May be NULL. */ + char *selection_fields; + struct ovn_lb_vip *vips; + struct ovn_northd_lb_vip *vips_nb; + size_t n_vips; + + size_t n_dps; + size_t n_allocated_dps; + const struct sbrec_datapath_binding **dps; +}; + +struct ovn_lb_vip { + struct in6_addr vip; + char *vip_str; + uint16_t vip_port; + + struct ovn_lb_backend *backends; + size_t n_backends; +}; + +struct ovn_lb_backend { + struct in6_addr ip; + char *ip_str; + uint16_t port; +}; + +/* ovn-northd specific backend information. */ +struct ovn_northd_lb_vip { + char *vip_port_str; + char *backend_ips; + struct ovn_northd_lb_backend *backends_nb; + size_t n_backends; + + struct nbrec_load_balancer_health_check *lb_health_check; +}; + +struct ovn_northd_lb_backend { + struct ovn_port *op; /* Logical port to which the ip belong to. */ + bool health_check; + char *svc_mon_src_ip; /* Source IP to use for monitoring. */ + 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_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 *, + const struct sbrec_datapath_binding *); + +struct ovn_controller_lb { + const struct sbrec_load_balancer *slb; /* May be NULL. */ + + struct ovn_lb_vip *vips; + size_t n_vips; +}; + +struct ovn_controller_lb *ovn_controller_lb_create( + const struct sbrec_load_balancer *); +void ovn_controller_lb_destroy(struct ovn_controller_lb *); + +#endif /* OVN_LIB_LB_H 1 */ diff -Nru ovn-20.09.0/lib/lex.c ovn-20.12.0/lib/lex.c --- ovn-20.09.0/lib/lex.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/lex.c 2020-12-17 17:53:32.000000000 +0000 @@ -906,6 +906,19 @@ } } +/* If 'lexer''s current token is the string 's', advances 'lexer' to the next + * token and returns true. Otherwise returns false. */ +bool +lexer_match_string(struct lexer *lexer, const char *s) +{ + if (lexer->token.type == LEX_T_STRING && !strcmp(lexer->token.s, s)) { + lexer_get(lexer); + return true; + } else { + return false; + } +} + bool lexer_is_int(const struct lexer *lexer) { diff -Nru ovn-20.09.0/lib/logical-fields.c ovn-20.12.0/lib/logical-fields.c --- ovn-20.09.0/lib/logical-fields.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/logical-fields.c 2020-12-17 17:53:32.000000000 +0000 @@ -129,9 +129,15 @@ expr_symtab_add_field_scoped(symtab, "ct_label", MFF_CT_LABEL, NULL, false, WR_CT_COMMIT); expr_symtab_add_subfield_scoped(symtab, "ct_label.blocked", NULL, - "ct_label[0]", WR_CT_COMMIT); + "ct_label[" + OVN_CT_STR(OVN_CT_BLOCKED_BIT) + "]", + WR_CT_COMMIT); expr_symtab_add_subfield_scoped(symtab, "ct_label.natted", NULL, - "ct_label[1]", WR_CT_COMMIT); + "ct_label[" + OVN_CT_STR(OVN_CT_NATTED_BIT) + "]", + WR_CT_COMMIT); expr_symtab_add_subfield_scoped(symtab, "ct_label.ecmp_reply_eth", NULL, "ct_label[32..79]", WR_CT_COMMIT); expr_symtab_add_subfield_scoped(symtab, "ct_label.ecmp_reply_port", NULL, diff -Nru ovn-20.09.0/lib/mcast-group-index.h ovn-20.12.0/lib/mcast-group-index.h --- ovn-20.09.0/lib/mcast-group-index.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/mcast-group-index.h 2020-12-17 17:53:32.000000000 +0000 @@ -30,6 +30,7 @@ OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY, OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY, OVN_MCAST_STATIC_TUNNEL_KEY, + OVN_MCAST_FLOOD_L2_TUNNEL_KEY, OVN_MIN_IP_MULTICAST, OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST, }; diff -Nru ovn-20.09.0/lib/ovn-dirs.c.in ovn-20.12.0/lib/ovn-dirs.c.in --- ovn-20.09.0/lib/ovn-dirs.c.in 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/ovn-dirs.c.in 2020-12-17 17:53:32.000000000 +0000 @@ -1,4 +1,4 @@ -#line 2 "@srcdir@/lib/dirs.c.in" +#line 2 "@srcdir@/lib/ovn-dirs.c.in" /* * Copyright (c) 2019 * diff -Nru ovn-20.09.0/lib/ovn-l7.c ovn-20.12.0/lib/ovn-l7.c --- ovn-20.09.0/lib/ovn-l7.c 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/lib/ovn-l7.c 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Red Hat. + * + * 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-l7.h" + +bool +ipv6_addr_is_routable_multicast(const struct in6_addr *ip) +{ + if (!ipv6_addr_is_multicast(ip)) { + return false; + } + + /* Check multicast group scope, RFC 4291, 2.7. */ + switch (ip->s6_addr[1] & 0x0F) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x0F: + return false; + default: + return true; + } +} diff -Nru ovn-20.09.0/lib/ovn-l7.h ovn-20.12.0/lib/ovn-l7.h --- ovn-20.09.0/lib/ovn-l7.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/ovn-l7.h 2020-12-17 17:53:32.000000000 +0000 @@ -93,6 +93,20 @@ #define DHCP_OPT_DOMAIN_SEARCH_LIST \ DHCP_OPTION("domain_search_list", 119, "domains") +#define DHCP_OPT_BROADCAST_ADDRESS \ + DHCP_OPTION("broadcast_address", 28, "ipv4") + +#define DHCP_OPT_BOOTFILE_CODE 67 + +/* Use unused 254 option for iPXE bootfile_name_alt userdata DHCP option. + * This option code is replaced by 67 when sending the DHCP reply. + */ +#define DHCP_OPT_BOOTFILE_ALT_CODE 254 +#define DHCP_OPT_BOOTFILE_ALT DHCP_OPTION("bootfile_name_alt", \ + DHCP_OPT_BOOTFILE_ALT_CODE, "str") + +#define DHCP_OPT_ETHERBOOT 175 + #define DHCP_OPT_ARP_CACHE_TIMEOUT \ DHCP_OPTION("arp_cache_timeout", 35, "uint32") #define DHCP_OPT_TCP_KEEPALIVE_INTERVAL \ @@ -189,6 +203,9 @@ #define DHCPV6_MSG_TYPE_ADVT 2 #define DHCPV6_MSG_TYPE_REQUEST 3 #define DHCPV6_MSG_TYPE_CONFIRM 4 +#define DHCPV6_MSG_TYPE_RENEW 5 +#define DHCPV6_MSG_TYPE_REBIND 6 + #define DHCPV6_MSG_TYPE_REPLY 7 #define DHCPV6_MSG_TYPE_DECLINE 9 #define DHCPV6_MSG_TYPE_INFO_REQ 11 @@ -406,23 +423,17 @@ } } +bool ipv6_addr_is_routable_multicast(const struct in6_addr *); + static inline bool -ipv6_addr_is_routable_multicast(const struct in6_addr *ip) { - if (!ipv6_addr_is_multicast(ip)) { - return false; - } +ipv6_addr_is_host_zero(const struct in6_addr *prefix, + const struct in6_addr *mask) +{ + /* host-bits-non-zero <=> (prefix ^ mask) & prefix. */ + struct in6_addr tmp = ipv6_addr_bitxor(prefix, mask); - /* Check multicast group scope, RFC 4291, 2.7. */ - switch (ip->s6_addr[1] & 0x0F) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x0F: - return false; - default: - return true; - } + tmp = ipv6_addr_bitand(&tmp, prefix); + return ipv6_is_zero(&tmp); } #define IPV6_EXT_HEADER_LEN 8 diff -Nru ovn-20.09.0/lib/ovn-util.c ovn-20.12.0/lib/ovn-util.c --- ovn-20.09.0/lib/ovn-util.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/ovn-util.c 2020-12-17 17:53:32.000000000 +0000 @@ -13,17 +13,22 @@ */ #include + +#include "ovn-util.h" + +#include #include #include "daemon.h" -#include "ovn-util.h" -#include "ovn-dirs.h" -#include "openvswitch/vlog.h" +#include "include/ovn/actions.h" #include "openvswitch/ofp-parse.h" +#include "openvswitch/vlog.h" +#include "ovn-dirs.h" #include "ovn-nb-idl.h" #include "ovn-sb-idl.h" +#include "socket-util.h" +#include "svec.h" #include "unixctl.h" -#include VLOG_DEFINE_THIS_MODULE(ovn_util); @@ -241,26 +246,36 @@ extract_lrp_networks(const struct nbrec_logical_router_port *lrp, struct lport_addresses *laddrs) { + return extract_lrp_networks__(lrp->mac, lrp->networks, lrp->n_networks, + laddrs); +} + +/* Separate out the body of 'extract_lrp_networks()' for use from DDlog, + * which does not know the 'nbrec_logical_router_port' type. */ +bool +extract_lrp_networks__(char *mac, char **networks, size_t n_networks, + struct lport_addresses *laddrs) +{ memset(laddrs, 0, sizeof *laddrs); - if (!eth_addr_from_string(lrp->mac, &laddrs->ea)) { + if (!eth_addr_from_string(mac, &laddrs->ea)) { laddrs->ea = eth_addr_zero; return false; } snprintf(laddrs->ea_s, sizeof laddrs->ea_s, ETH_ADDR_FMT, ETH_ADDR_ARGS(laddrs->ea)); - for (int i = 0; i < lrp->n_networks; i++) { + for (int i = 0; i < n_networks; i++) { ovs_be32 ip4; struct in6_addr ip6; unsigned int plen; char *error; - error = ip_parse_cidr(lrp->networks[i], &ip4, &plen); + error = ip_parse_cidr(networks[i], &ip4, &plen); if (!error) { if (!ip4) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'networks' %s", lrp->networks[i]); + VLOG_WARN_RL(&rl, "bad 'networks' %s", networks[i]); continue; } @@ -269,13 +284,13 @@ } free(error); - error = ipv6_parse_cidr(lrp->networks[i], &ip6, &plen); + error = ipv6_parse_cidr(networks[i], &ip6, &plen); if (!error) { add_ipv6_netaddr(laddrs, ip6, plen); } else { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); VLOG_INFO_RL(&rl, "invalid syntax '%s' in networks", - lrp->networks[i]); + networks[i]); free(error); } } @@ -320,6 +335,12 @@ return ret; } +bool +lport_addresses_is_empty(struct lport_addresses *laddrs) +{ + return !laddrs->n_ipv4_addrs && !laddrs->n_ipv6_addrs; +} + void destroy_lport_addresses(struct lport_addresses *laddrs) { @@ -327,6 +348,23 @@ free(laddrs->ipv6_addrs); } +/* Go through 'addresses' and add found IPv4 addresses to 'ipv4_addrs' and + * IPv6 addresses to 'ipv6_addrs'. */ +void +split_addresses(const char *addresses, struct svec *ipv4_addrs, + struct svec *ipv6_addrs) +{ + struct lport_addresses laddrs; + extract_lsp_addresses(addresses, &laddrs); + for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) { + svec_add(ipv4_addrs, laddrs.ipv4_addrs[k].addr_s); + } + for (size_t k = 0; k < laddrs.n_ipv6_addrs; k++) { + svec_add(ipv6_addrs, laddrs.ipv6_addrs[k].addr_s); + } + destroy_lport_addresses(&laddrs); +} + /* Allocates a key for NAT conntrack zone allocation for a provided * 'key' record and a 'type'. * @@ -468,33 +506,43 @@ sbrec_logical_flow_hash(const struct sbrec_logical_flow *lf) { const struct sbrec_datapath_binding *ld = lf->logical_datapath; - if (!ld) { - return 0; - } + uint32_t hash = ovn_logical_flow_hash(lf->table_id, lf->pipeline, + lf->priority, lf->match, + lf->actions); - return ovn_logical_flow_hash(&ld->header_.uuid, - lf->table_id, lf->pipeline, - lf->priority, lf->match, lf->actions); + return ld ? ovn_logical_flow_hash_datapath(&ld->header_.uuid, hash) : hash; } uint32_t -ovn_logical_flow_hash(const struct uuid *logical_datapath, - uint8_t table_id, const char *pipeline, +ovn_logical_flow_hash(uint8_t table_id, const char *pipeline, uint16_t priority, const char *match, const char *actions) { - size_t hash = uuid_hash(logical_datapath); - hash = hash_2words((table_id << 16) | priority, hash); + size_t hash = hash_2words((table_id << 16) | priority, 0); hash = hash_string(pipeline, hash); hash = hash_string(match, hash); return hash_string(actions, hash); } +uint32_t +ovn_logical_flow_hash_datapath(const struct uuid *logical_datapath, + uint32_t hash) +{ + return hash_add(hash, uuid_hash(logical_datapath)); +} + bool datapath_is_switch(const struct sbrec_datapath_binding *ldp) { return smap_get(&ldp->external_ids, "logical-switch") != NULL; } + +int +datapath_snat_ct_zone(const struct sbrec_datapath_binding *dp) +{ + return smap_get_int(&dp->external_ids, "snat-ct-zone", -1); +} + struct tnlid_node { struct hmap_node hmap_node; @@ -511,24 +559,21 @@ hmap_destroy(tnlids); } -void -ovn_add_tnlid(struct hmap *set, uint32_t tnlid) -{ - struct tnlid_node *node = xmalloc(sizeof *node); - hmap_insert(set, &node->hmap_node, hash_int(tnlid, 0)); - node->tnlid = tnlid; -} - bool -ovn_tnlid_in_use(const struct hmap *set, uint32_t tnlid) +ovn_add_tnlid(struct hmap *set, uint32_t tnlid) { - const struct tnlid_node *node; - HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash_int(tnlid, 0), set) { + uint32_t hash = hash_int(tnlid, 0); + struct tnlid_node *node; + HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, set) { if (node->tnlid == tnlid) { - return true; + return false; } } - return false; + + node = xmalloc(sizeof *node); + hmap_insert(set, &node->hmap_node, hash); + node->tnlid = tnlid; + return true; } static uint32_t @@ -543,8 +588,7 @@ { for (uint32_t tnlid = next_tnlid(*hint, min, max); tnlid != *hint; tnlid = next_tnlid(tnlid, min, max)) { - if (!ovn_tnlid_in_use(set, tnlid)) { - ovn_add_tnlid(set, tnlid); + if (ovn_add_tnlid(set, tnlid)) { *hint = tnlid; return tnlid; } @@ -562,33 +606,24 @@ } bool -ip46_parse_cidr(const char *str, struct v46_ip *prefix, unsigned int *plen) +ip46_parse_cidr(const char *str, struct in6_addr *prefix, unsigned int *plen) { - memset(prefix, 0, sizeof *prefix); + ovs_be32 ipv4; + char *error = ip_parse_cidr(str, &ipv4, plen); - char *error = ip_parse_cidr(str, &prefix->ipv4, plen); if (!error) { - prefix->family = AF_INET; + in6_addr_set_mapped_ipv4(prefix, ipv4); return true; } free(error); - error = ipv6_parse_cidr(str, &prefix->ipv6, plen); + error = ipv6_parse_cidr(str, prefix, plen); if (!error) { - prefix->family = AF_INET6; return true; } free(error); return false; } -bool -ip46_equals(const struct v46_ip *addr1, const struct v46_ip *addr2) -{ - return (addr1->family == addr2->family && - (addr1->family == AF_INET ? addr1->ipv4 == addr2->ipv4 : - IN6_ARE_ADDR_EQUAL(&addr1->ipv6, &addr2->ipv6))); -} - /* The caller must free the returned string. */ char * normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen) @@ -603,12 +638,12 @@ /* The caller must free the returned string. */ char * -normalize_ipv6_prefix(struct in6_addr ipv6, unsigned int plen) +normalize_ipv6_prefix(const struct in6_addr *ipv6, unsigned int plen) { char network_s[INET6_ADDRSTRLEN]; struct in6_addr mask = ipv6_create_mask(plen); - struct in6_addr network = ipv6_addr_bitand(&ipv6, &mask); + struct in6_addr network = ipv6_addr_bitand(ipv6, &mask); inet_ntop(AF_INET6, &network, network_s, INET6_ADDRSTRLEN); if (plen == 128) { @@ -619,12 +654,12 @@ } char * -normalize_v46_prefix(const struct v46_ip *prefix, unsigned int plen) +normalize_v46_prefix(const struct in6_addr *prefix, unsigned int plen) { - if (prefix->family == AF_INET) { - return normalize_ipv4_prefix(prefix->ipv4, plen); + if (IN6_IS_ADDR_V4MAPPED(prefix)) { + return normalize_ipv4_prefix(in6_addr_get_mapped_ipv4(prefix), plen); } else { - return normalize_ipv6_prefix(prefix->ipv6, plen); + return normalize_ipv6_prefix(prefix, plen); } } @@ -661,3 +696,44 @@ return u_value; } + +/* For a 'key' of the form "IP:port" or just "IP", sets 'port' and + * 'ip_address'. The caller must free() the memory allocated for + * 'ip_address'. + * Returns true if parsing of 'key' was successful, false otherwise. + */ +bool +ip_address_and_port_from_lb_key(const char *key, char **ip_address, + uint16_t *port, int *addr_family) +{ + struct sockaddr_storage ss; + if (!inet_parse_active(key, 0, &ss, false)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s", + key); + *ip_address = NULL; + *port = 0; + *addr_family = 0; + return false; + } + + struct ds s = DS_EMPTY_INITIALIZER; + ss_format_address_nobracks(&ss, &s); + *ip_address = ds_steal_cstr(&s); + *port = ss_get_port(&ss); + *addr_family = ss.ss_family; + return true; +} + +/* Increment this for any logical flow changes or if existing OVN action is + * modified. */ +#define OVN_INTERNAL_MINOR_VER 0 + +/* Returns the OVN version. The caller must free the returned value. */ +char * +ovn_get_internal_version(void) +{ + return xasprintf("%s-%s-%d.%d", OVN_PACKAGE_VERSION, + sbrec_get_db_version(), + N_OVNACTS, OVN_INTERNAL_MINOR_VER); +} diff -Nru ovn-20.09.0/lib/ovn-util.h ovn-20.12.0/lib/ovn-util.h --- ovn-20.09.0/lib/ovn-util.h 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/lib/ovn-util.h 2020-12-17 17:53:32.000000000 +0000 @@ -27,6 +27,7 @@ struct nbrec_logical_router_port; struct sbrec_logical_flow; +struct svec; struct uuid; struct eth_addr; struct sbrec_port_binding; @@ -76,8 +77,15 @@ bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding, struct eth_addr *ea); +bool extract_lrp_networks__(char *mac, char **networks, size_t n_networks, + struct lport_addresses *laddrs); + +bool lport_addresses_is_empty(struct lport_addresses *); void destroy_lport_addresses(struct lport_addresses *); +void split_addresses(const char *addresses, struct svec *ipv4_addrs, + struct svec *ipv6_addrs); + char *alloc_nat_zone_key(const struct uuid *key, const char *type); const char *default_nb_db(void); @@ -94,11 +102,13 @@ bool ovn_is_known_nb_lsp_type(const char *type); uint32_t sbrec_logical_flow_hash(const struct sbrec_logical_flow *); -uint32_t ovn_logical_flow_hash(const struct uuid *logical_datapath, - uint8_t table_id, const char *pipeline, +uint32_t ovn_logical_flow_hash(uint8_t table_id, const char *pipeline, uint16_t priority, const char *match, const char *actions); +uint32_t ovn_logical_flow_hash_datapath(const struct uuid *logical_datapath, + uint32_t hash); bool datapath_is_switch(const struct sbrec_datapath_binding *); +int datapath_snat_ct_zone(const struct sbrec_datapath_binding *ldp); void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *idl_); @@ -114,8 +124,7 @@ struct hmap; void ovn_destroy_tnlids(struct hmap *tnlids); -void ovn_add_tnlid(struct hmap *set, uint32_t tnlid); -bool ovn_tnlid_in_use(const struct hmap *set, uint32_t tnlid); +bool ovn_add_tnlid(struct hmap *set, uint32_t tnlid); uint32_t ovn_allocate_tnlid(struct hmap *set, const char *name, uint32_t min, uint32_t max, uint32_t *hint); @@ -137,21 +146,12 @@ char *ovn_chassis_redirect_name(const char *port_name); void ovn_set_pidfile(const char *name); -/* An IPv4 or IPv6 address */ -struct v46_ip { - int family; - union { - ovs_be32 ipv4; - struct in6_addr ipv6; - }; -}; -bool ip46_parse_cidr(const char *str, struct v46_ip *prefix, +bool ip46_parse_cidr(const char *str, struct in6_addr *prefix, unsigned int *plen); -bool ip46_equals(const struct v46_ip *addr1, const struct v46_ip *addr2); char *normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen); -char *normalize_ipv6_prefix(struct in6_addr ipv6, unsigned int plen); -char *normalize_v46_prefix(const struct v46_ip *prefix, unsigned int plen); +char *normalize_ipv6_prefix(const struct in6_addr *ipv6, unsigned int plen); +char *normalize_v46_prefix(const struct in6_addr *prefix, unsigned int plen); /* Temporary util function until ovs library has smap_get_unit. */ unsigned int ovn_smap_get_uint(const struct smap *smap, const char *key, @@ -220,4 +220,11 @@ case OVN_OPT_MONITOR: \ case OVN_OPT_USER_GROUP: +bool ip_address_and_port_from_lb_key(const char *key, char **ip_address, + uint16_t *port, int *addr_family); + +/* Returns the internal OVN version. The caller must free the returned + * value. */ +char *ovn_get_internal_version(void); + #endif diff -Nru ovn-20.09.0/Makefile.am ovn-20.12.0/Makefile.am --- ovn-20.09.0/Makefile.am 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/Makefile.am 2020-12-17 17:53:32.000000000 +0000 @@ -22,10 +22,6 @@ AM_CPPFLAGS += -I $(top_srcdir)/include if WIN32 -AM_CPPFLAGS += -I $(OVS_SRCDIR)/include -AM_CPPFLAGS += -I $(OVS_SRCDIR)/lib -AM_CPPFLAGS += -I $(OVS_SRCDIR) -AM_CPPFLAGS += -I $(top_srcdir)/lib AM_CPPFLAGS += $(PTHREAD_INCLUDES) AM_CPPFLAGS += $(MSVC_CFLAGS) AM_LDFLAGS += $(PTHREAD_LDFLAGS) @@ -36,6 +32,8 @@ AM_CPPFLAGS += -I $(top_srcdir)/include AM_CPPFLAGS += -I $(top_srcdir)/ovn AM_CPPFLAGS += -I $(top_builddir)/include +AM_CPPFLAGS += -I $(top_srcdir)/lib +AM_CPPFLAGS += -I $(top_builddir)/lib AM_CPPFLAGS += -I $(OVS_SRCDIR)/include AM_CPPFLAGS += -I $(OVS_BUILDDIR)/include @@ -43,8 +41,6 @@ AM_CPPFLAGS += -I $(OVS_BUILDDIR)/lib AM_CPPFLAGS += -I $(OVS_SRCDIR) AM_CPPFLAGS += -I $(OVS_BUILDDIR) -AM_CPPFLAGS += -I $(top_srcdir)/lib -AM_CPPFLAGS += -I $(top_builddir)/lib AM_CPPFLAGS += $(SSL_INCLUDES) @@ -88,11 +84,11 @@ README.rst \ NOTICE \ .cirrus.yml \ - .travis.yml \ - .travis/linux-build.sh \ - .travis/linux-prepare.sh \ - .travis/osx-build.sh \ - .travis/osx-prepare.sh \ + .ci/linux-build.sh \ + .ci/linux-prepare.sh \ + .ci/osx-build.sh \ + .ci/osx-prepare.sh \ + .github/workflows/test.yml \ boot.sh \ $(MAN_FRAGMENTS) \ $(MAN_ROOTS) \ diff -Nru ovn-20.09.0/NEWS ovn-20.12.0/NEWS --- ovn-20.09.0/NEWS 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/NEWS 2020-12-17 17:53:32.000000000 +0000 @@ -1,3 +1,26 @@ +OVN v20.12.0 - 18 Dec 2020 +-------------------------- + - The "datapath" argument to ovn-trace is now optional, since the + datapath can be inferred from the inport (which is required). + - The obsolete "redirect-chassis" way to configure gateways has been + removed. See ovn-nb(5) for advice on how to update your config if needed. + - Add IPv4 iPXE support introducing "bootfile_name_alt" option to ovn dhcp + server. + - Support other_config:vlan-passthru=true to allow VLAN tagged incoming + traffic. + - Propagate currently processed SB_Global.nb_cfg in ovn-controller to the + local OVS DB integration bridge external_ids:ovn-nb-cfg. + - Support version pinning between ovn-northd and ovn-controller as an + option. If the option is enabled and the versions don't match, + ovn-controller will not process any DB changes. + - Add "fair" column in Meter table to allow multiple ACL logs to use the + same Meter while being rate-limited independently. + - New configuration option for northd 'options:use_logical_dp_groups=true' + to enable combining of logical flows by logical datapath. This should + significantly decrease size of a Southbound DB. However, in some cases, + it could have performance penalty for ovn-controller. Disabled by + default. + OVN v20.09.0 - 28 Sep 2020 -------------------------- - Added packet marking support for traffic routed with diff -Nru ovn-20.09.0/northd/ovn-northd.8.xml ovn-20.12.0/northd/ovn-northd.8.xml --- ovn-20.09.0/northd/ovn-northd.8.xml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/northd/ovn-northd.8.xml 2020-12-17 17:53:32.000000000 +0000 @@ -718,24 +718,55 @@

Ingress Table 12: Pre-Hairpin

  • - For all configured load balancer VIPs a priority-2 flow that - matches on traffic that needs to be hairpinned, i.e., after load - balancing the destination IP matches the source IP, which sets - reg0[6] = 1 and executes ct_snat(VIP) - to force replies to these packets to come back through OVN. + If the logical switch has load balancer(s) configured, then a + priorirty-100 flow is added with the match + ip && ct.trk&& ct.dnat to check if the + packet needs to be hairpinned (if after load balancing the destination + IP matches the source IP) or not by executing the action + reg0[6] = chk_lb_hairpin(); and advances the packet to + the next table. +
  • + +
  • + If the logical switch has load balancer(s) configured, then a + priorirty-90 flow is added with the match ip to check if + the packet is a reply for a hairpinned connection or not by executing + the action reg0[6] = chk_lb_hairpin_reply(); and advances + the packet to the next table. +
  • + +
  • + A priority-0 flow that simply moves traffic to the next table.
  • +
+ +

Ingress Table 13: Nat-Hairpin

+
    +
  • + If the logical switch has load balancer(s) configured, then a + priorirty-100 flow is added with the match + ip && (ct.new || ct.est) && ct.trk && + ct.dnat && reg0[6] == 1 which hairpins the traffic by + NATting source IP to the load balancer VIP by executing the action + ct_snat_to_vip and advances the packet to the next table. +
  • +
  • - For all configured load balancer VIPs a priority-1 flow that - matches on replies to hairpinned traffic, i.e., destination IP is VIP, - source IP is the backend IP and source L4 port is backend port, which - sets reg0[6] = 1 and executes ct_snat;. + If the logical switch has load balancer(s) configured, then a + priorirty-90 flow is added with the match + ip && reg0[6] == 1 which matches on the replies + of hairpinned traffic (i.e., destination IP is VIP, + source IP is the backend IP and source L4 port is backend port for L4 + load balancers) and executes ct_snat and advances the + packet to the next table.
  • +
  • A priority-0 flow that simply moves traffic to the next table.
-

Ingress Table 13: Hairpin

+

Ingress Table 14: Hairpin

  • A priority-1 flow that hairpins traffic matched by non-default @@ -748,7 +779,7 @@
-

Ingress Table 14: ARP/ND responder

+

Ingress Table 15: ARP/ND responder

This table implements ARP/ND responder in a logical switch for known @@ -1038,7 +1069,7 @@ -

Ingress Table 15: DHCP option processing

+

Ingress Table 16: DHCP option processing

This table adds the DHCPv4 options to a DHCPv4 packet from the @@ -1099,7 +1130,7 @@ -

Ingress Table 16: DHCP responses

+

Ingress Table 17: DHCP responses

This table implements DHCP responder for the DHCP replies generated by @@ -1180,7 +1211,7 @@ -

Ingress Table 17 DNS Lookup

+

Ingress Table 18 DNS Lookup

This table looks up and resolves the DNS names to the corresponding @@ -1209,7 +1240,7 @@ -

Ingress Table 18 DNS Responses

+

Ingress Table 19 DNS Responses

This table implements DNS responder for the DNS replies generated by @@ -1244,7 +1275,7 @@ -

Ingress table 19 External ports

+

Ingress table 20 External ports

Traffic from the external logical ports enter the ingress @@ -1287,7 +1318,7 @@ -

Ingress Table 20 Destination Lookup

+

Ingress Table 21 Destination Lookup

This table implements switching behavior. It contains these logical @@ -1367,18 +1398,19 @@

  • - Priority-80 flows for each port connected to a logical router - matching self originated GARP/ARP request/ND packets. These packets - are flooded to the MC_FLOOD which contains all logical - ports. + Priority-80 flows for each IP address/VIP/NAT address owned by a + router port connected to the switch. These flows match ARP requests + and ND packets for the specific IP addresses. Matched packets are + forwarded only to the router that owns the IP address and to the + MC_FLOOD_L2 multicast group which contains all non-router + logical ports.
  • - Priority-75 flows for each IP address/VIP/NAT address owned by a - router port connected to the switch. These flows match ARP requests - and ND packets for the specific IP addresses. Matched packets are - forwarded only to the router that owns the IP address and, if - present, to the localnet port of the logical switch. + Priority-75 flows for each port connected to a logical router + matching self originated ARP request/ND packets. These packets + are flooded to the MC_FLOOD_L2 which contains all + non-router logical ports.
  • @@ -1399,14 +1431,13 @@ router, when that logical switch port's column is set to router and - the connected logical router port specifies a - redirect-chassis: + the connected logical router port has a gateway chassis:

    • The flow for the connected logical router port's Ethernet - address is only programmed on the redirect-chassis. + address is only programmed on the gateway chassis.
    • @@ -1428,14 +1459,13 @@ the connected logical router port specifies a reside-on-redirect-chassis and the logical router to which the connected logical router port belongs to has a - redirect-chassis distributed gateway logical router - port: + distributed gateway LRP:

      • The flow for the connected logical router port's Ethernet - address is only programmed on the redirect-chassis. + address is only programmed on the gateway chassis.
      @@ -1650,10 +1680,9 @@

      For the gateway port on a distributed logical router (where one of the logical router ports specifies a - redirect-chassis), the above flow matching + gateway chassis), the above flow matching eth.dst == E is only programmed on - the gateway port instance on the - redirect-chassis. + the gateway port instance on the gateway chassis.

    • @@ -2013,23 +2042,21 @@

      For the gateway port on a distributed logical router (where one of the logical router ports specifies a - redirect-chassis), the above flows are only + gateway chassis), the above flows are only programmed on the gateway port instance on the - redirect-chassis. This behavior avoids generation + gateway chassis. This behavior avoids generation of multiple ARP responses from different chassis, and allows - upstream MAC learning to point to the - redirect-chassis. + upstream MAC learning to point to the gateway chassis.

      For the logical router port with the option reside-on-redirect-chassis set (which is centralized), the above flows are only programmed on the gateway port instance on - the redirect-chassis (if the logical router has a + the gateway chassis (if the logical router has a distributed gateway port). This behavior avoids generation of multiple ARP responses from different chassis, and allows - upstream MAC learning to point to the - redirect-chassis. + upstream MAC learning to point to the gateway chassis.

      @@ -2064,12 +2091,12 @@

      For the gateway port on a distributed logical router (where one of the logical router ports specifies a - redirect-chassis), the above flows replying to + gateway chassis), the above flows replying to IPv6 Neighbor Solicitations are only programmed on the - gateway port instance on the redirect-chassis. + gateway port instance on the gateway chassis. This behavior avoids generation of multiple replies from different chassis, and allows upstream MAC learning to point - to the redirect-chassis. + to the gateway chassis.

      @@ -2147,7 +2174,7 @@

      For the gateway port on a distributed logical router with NAT (where one of the logical router ports specifies a - redirect-chassis): + gateway chassis):

        @@ -2155,12 +2182,12 @@ If the corresponding NAT rule cannot be handled in a distributed manner, then a priority-92 flow is programmed on the gateway port instance on the - redirect-chassis. A priority-91 drop flow is + gateway chassis. A priority-91 drop flow is programmed on the other chassis when ARP requests/NS packets are received on the gateway port. This behavior avoids generation of multiple ARP responses from different chassis, and allows upstream MAC learning to point to the - redirect-chassis. + gateway chassis.
      • @@ -2470,7 +2497,7 @@

        If the NAT rule cannot be handled in a distributed manner, then the priority-100 flow above is only programmed on the - redirect-chassis. + gateway chassis.

        @@ -2493,7 +2520,7 @@

        Following load balancing DNAT flows are added for Gateway router or Router with gateway port. These flows are programmed only on the - redirect-chassis. These flows do not get programmed for + gateway chassis. These flows do not get programmed for load balancers with IPv6 VIPs.

        @@ -2642,7 +2669,7 @@

        If the NAT rule cannot be handled in a distributed manner, then the priority-100 flow above is only programmed on the - redirect-chassis. + gateway chassis.

        @@ -2668,7 +2695,25 @@

      -

      Ingress Table 7: IPv6 ND RA option processing

      +

      Ingress Table 7: ECMP symmetric reply processing

      +
        +
      • + If ECMP routes with symmetric reply are configured in the + OVN_Northbound database for a gateway router, a + priority-100 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 destination routing policy will instead match if the + source IP address matches the static route's prefix. The flow uses + the action ct_commit { ct_label.ecmp_reply_eth = eth.src;" + " ct_label.ecmp_reply_port = K;}; next; to commit + the connection and storing eth.src and the ECMP + reply port binding tunnel key K in the + ct_label. +
      • +
      + +

      Ingress Table 8: IPv6 ND RA option processing

      • @@ -2698,7 +2743,7 @@
      -

      Ingress Table 8: IPv6 ND RA responder

      +

      Ingress Table 9: IPv6 ND RA responder

      This table implements IPv6 ND RA responder for the IPv6 ND RA replies @@ -2743,7 +2788,7 @@

    -

    Ingress Table 9: IP Routing

    +

    Ingress Table 10: IP Routing

    A packet that arrives at this table is an IP packet that should be @@ -2906,7 +2951,7 @@

  • -

    Ingress Table 10: IP_ROUTING_ECMP

    +

    Ingress Table 11: IP_ROUTING_ECMP

    This table implements the second part of IP routing for ECMP routes @@ -2958,7 +3003,57 @@ -

    Ingress Table 12: ARP/ND Resolution

    +

    Ingress Table 12: Router policies

    +

    + This table adds flows for the logical router policies configured + on the logical router. Please see the + OVN_Northbound database Logical_Router_Policy + table documentation in ovn-nb for supported actions. +

    + +
      +
    • +

      + For each router policy configured on the logical router, a + logical flow is added with specified priority, match and + actions. +

      +
    • + +
    • +

      + If the policy action is reroute, then the logical + flow is added with the following actions: +

      + +
      +[xx]reg0 = H;
      +eth.src = E;
      +outport = P;
      +flags.loopback = 1;
      +next;
      +        
      + +

      + where H is the nexthop defined in the + router policy, E is the ethernet address of the + logical router port from which the nexthop is + reachable and P is the logical router port from + which the nexthop is reachable. +

      +
    • + +
    • +

      + If a router policy has the option pkt_mark=m + set and if the action is not drop, then the action also + includes pkt.mark = m to mark the packet + with the marker m. +

      +
    • +
    + +

    Ingress Table 13: ARP/ND Resolution

    Any packet that reaches this table is an IP packet whose next-hop @@ -3133,8 +3228,8 @@

  • - For logical router port with redirect-chassis and redirect-type - being set as bridged, a priority-50 flow will match + For a distributed gateway LRP with redirect-type + set to bridged, a priority-50 flow will match outport == "ROUTER_PORT" and !is_chassis_resident ("cr-ROUTER_PORT") has actions eth.dst = E; next;, where E is the ethernet address of the @@ -3144,7 +3239,7 @@ -

    Ingress Table 13: Check packet length

    +

    Ingress Table 14: Check packet length

    For distributed logical routers with distributed gateway port configured @@ -3174,7 +3269,7 @@ and advances to the next table.

    -

    Ingress Table 14: Handle larger packets

    +

    Ingress Table 15: Handle larger packets

    For distributed logical routers with distributed gateway port configured @@ -3235,13 +3330,13 @@ and advances to the next table.

    -

    Ingress Table 15: Gateway Redirect

    +

    Ingress Table 16: Gateway Redirect

    For distributed logical routers where one of the logical router - ports specifies a redirect-chassis, this table redirects + ports specifies a gateway chassis, this table redirects certain packets to the distributed gateway port instance on the - redirect-chassis. This table has the following flows: + gateway chassis. This table has the following flows:

      @@ -3266,7 +3361,7 @@ port and CR is the chassisredirect port representing the instance of the logical router distributed gateway port on the - redirect-chassis. + gateway chassis.
    • @@ -3275,7 +3370,7 @@
    -

    Ingress Table 16: ARP Request

    +

    Ingress Table 17: ARP Request

    In the common case where the Ethernet destination has been resolved, this @@ -3371,7 +3466,7 @@ port in OVN_Northbound database that includes an IPv4 address VIP, for every backend IPv4 address B defined for the VIP a priority-120 flow is programmed on - redirect-chassis that matches + gateway chassis that matches ip && ip4.src == B && outport == GW, where GW is the logical router gateway port with an action ct_dnat;. If the @@ -3405,7 +3500,7 @@

    If the NAT rule cannot be handled in a distributed manner, then the priority-100 flow above is only programmed on the - redirect-chassis. + gateway chassis.

    @@ -3518,7 +3613,7 @@

    If the NAT rule cannot be handled in a distributed manner, then the flow above is only programmed on the - redirect-chassis increasing flow priority by 128 in + gateway chassis increasing flow priority by 128 in order to be run first

    @@ -3559,7 +3654,7 @@

    For distributed logical routers where one of the logical router - ports specifies a redirect-chassis. + ports specifies a gateway chassis.

    diff -Nru ovn-20.09.0/northd/ovn-northd.c ovn-20.12.0/northd/ovn-northd.c --- ovn-20.09.0/northd/ovn-northd.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/northd/ovn-northd.c 2020-12-17 17:53:32.000000000 +0000 @@ -25,6 +25,7 @@ #include "openvswitch/dynamic-string.h" #include "fatal-signal.h" #include "hash.h" +#include "hmapx.h" #include "openvswitch/hmap.h" #include "openvswitch/json.h" #include "ovn/lex.h" @@ -35,6 +36,7 @@ #include "lib/ovn-nb-idl.h" #include "lib/ovn-sb-idl.h" #include "lib/ovn-util.h" +#include "lib/lb.h" #include "ovn/actions.h" #include "ovn/logical-fields.h" #include "packets.h" @@ -100,8 +102,8 @@ /* Default probe interval for NB and SB DB connections. */ #define DEFAULT_PROBE_INTERVAL_MSEC 5000 -static int northd_probe_interval_nb = DEFAULT_PROBE_INTERVAL_MSEC; -static int northd_probe_interval_sb = DEFAULT_PROBE_INTERVAL_MSEC; +static int northd_probe_interval_nb = 0; +static int northd_probe_interval_sb = 0; #define MAX_OVN_TAGS 4096 @@ -149,14 +151,15 @@ PIPELINE_STAGE(SWITCH, IN, LB, 10, "ls_in_lb") \ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 11, "ls_in_stateful") \ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 12, "ls_in_pre_hairpin") \ - PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 13, "ls_in_hairpin") \ - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 14, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 15, "ls_in_dhcp_options") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 16, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 17, "ls_in_dns_lookup") \ - PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 18, "ls_in_dns_response") \ - PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 19, "ls_in_external_port") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 20, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 13, "ls_in_nat_hairpin") \ + PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 14, "ls_in_hairpin") \ + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 15, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 16, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 17, "ls_in_dhcp_response") \ + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 18, "ls_in_dns_lookup") \ + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 19, "ls_in_dns_response") \ + PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 20, "ls_in_external_port") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 21, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -596,6 +599,8 @@ struct ovs_list list; /* In list of similar records. */ + uint32_t tunnel_key; + /* Logical switch data. */ struct ovn_port **router_ports; size_t n_router_ports; @@ -613,18 +618,21 @@ /* OVN northd only needs to know about the logical router gateway port for * NAT on a distributed router. This "distributed gateway port" is - * populated only when there is a "redirect-chassis" specified for one of + * populated only when there is a gateway chassis specified for one of * the ports on the logical router. Otherwise this will be NULL. */ struct ovn_port *l3dgw_port; /* The "derived" OVN port representing the instance of l3dgw_port on - * the "redirect-chassis". */ + * the gateway chassis. */ struct ovn_port *l3redirect_port; /* NAT entries configured on the router. */ struct ovn_nat *nat_entries; - /* SNAT IPs used by the router. */ - struct sset snat_ips; + /* SNAT IPs owned by the router (shash of 'struct ovn_snat_ip'). */ + struct shash snat_ips; + + struct lport_addresses dnat_force_snat_addrs; + struct lport_addresses lb_force_snat_addrs; struct ovn_port **localnet_ports; size_t n_localnet_ports; @@ -642,6 +650,18 @@ struct ovn_nat { const struct nbrec_nat *nb; struct lport_addresses ext_addrs; + struct ovs_list ext_addr_list_node; /* Linkage in the per-external IP + * list of nat entries. Currently + * only used for SNAT. + */ +}; + +/* Stores the list of SNAT entries referencing a unique SNAT IP address. + * The 'snat_entries' list will be empty if the SNAT IP is used only for + * dnat_force_snat_ip or lb_force_snat_ip. + */ +struct ovn_snat_ip { + struct ovs_list snat_entries; }; static bool @@ -668,34 +688,50 @@ } static void -init_nat_entries(struct ovn_datapath *od) +snat_ip_add(struct ovn_datapath *od, const char *ip, struct ovn_nat *nat_entry) { - struct lport_addresses snat_addrs; + struct ovn_snat_ip *snat_ip = shash_find_data(&od->snat_ips, ip); + + if (!snat_ip) { + snat_ip = xzalloc(sizeof *snat_ip); + ovs_list_init(&snat_ip->snat_entries); + shash_add(&od->snat_ips, ip, snat_ip); + } + if (nat_entry) { + ovs_list_push_back(&snat_ip->snat_entries, + &nat_entry->ext_addr_list_node); + } +} + +static void +init_nat_entries(struct ovn_datapath *od) +{ if (!od->nbr) { return; } - sset_init(&od->snat_ips); - if (get_force_snat_ip(od, "dnat", &snat_addrs)) { - if (snat_addrs.n_ipv4_addrs) { - sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s); + shash_init(&od->snat_ips); + if (get_force_snat_ip(od, "dnat", &od->dnat_force_snat_addrs)) { + if (od->dnat_force_snat_addrs.n_ipv4_addrs) { + snat_ip_add(od, od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, + NULL); + } + if (od->dnat_force_snat_addrs.n_ipv6_addrs) { + snat_ip_add(od, od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, + NULL); } - if (snat_addrs.n_ipv6_addrs) { - sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s); - } - destroy_lport_addresses(&snat_addrs); } - memset(&snat_addrs, 0, sizeof(snat_addrs)); - if (get_force_snat_ip(od, "lb", &snat_addrs)) { - if (snat_addrs.n_ipv4_addrs) { - sset_add(&od->snat_ips, snat_addrs.ipv4_addrs[0].addr_s); - } - if (snat_addrs.n_ipv6_addrs) { - sset_add(&od->snat_ips, snat_addrs.ipv6_addrs[0].addr_s); + if (get_force_snat_ip(od, "lb", &od->lb_force_snat_addrs)) { + if (od->lb_force_snat_addrs.n_ipv4_addrs) { + snat_ip_add(od, od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, + NULL); + } + if (od->lb_force_snat_addrs.n_ipv6_addrs) { + snat_ip_add(od, od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, + NULL); } - destroy_lport_addresses(&snat_addrs); } if (!od->nbr->n_nat) { @@ -720,10 +756,15 @@ continue; } - if (!nat_entry_is_v6(nat_entry)) { - sset_add(&od->snat_ips, nat_entry->ext_addrs.ipv4_addrs[0].addr_s); - } else { - sset_add(&od->snat_ips, nat_entry->ext_addrs.ipv6_addrs[0].addr_s); + /* If this is a SNAT rule add the IP to the set of unique SNAT IPs. */ + if (!strcmp(nat->type, "snat")) { + if (!nat_entry_is_v6(nat_entry)) { + snat_ip_add(od, nat_entry->ext_addrs.ipv4_addrs[0].addr_s, + nat_entry); + } else { + snat_ip_add(od, nat_entry->ext_addrs.ipv6_addrs[0].addr_s, + nat_entry); + } } } } @@ -735,7 +776,10 @@ return; } - sset_destroy(&od->snat_ips); + shash_destroy_free_data(&od->snat_ips); + destroy_lport_addresses(&od->dnat_force_snat_addrs); + destroy_lport_addresses(&od->lb_force_snat_addrs); + for (size_t i = 0; i < od->nbr->n_nat; i++) { destroy_lport_addresses(&od->nat_entries[i].ext_addrs); } @@ -1138,6 +1182,23 @@ smap_add(&ids, "interconn-ts", ts); } } + + /* Set snat-ct-zone */ + if (od->nbr) { + int nat_default_ct = smap_get_int(&od->nbr->options, + "snat-ct-zone", -1); + if (nat_default_ct >= 0) { + smap_add_format(&ids, "snat-ct-zone", "%d", nat_default_ct); + } + + bool learn_from_arp_request = + smap_get_bool(&od->nbr->options, "always_learn_from_arp_request", + true); + if (!learn_from_arp_request) { + smap_add(&ids, "always_learn_from_arp_request", "false"); + } + } + sbrec_datapath_binding_set_external_ids(od->sb, &ids); smap_destroy(&ids); } @@ -1257,12 +1318,45 @@ return OVN_MAX_DP_KEY - OVN_MAX_DP_GLOBAL_NUM; } -static uint32_t -ovn_datapath_allocate_key(struct northd_context *ctx, struct hmap *dp_tnlids) +static void +ovn_datapath_allocate_key(struct northd_context *ctx, + struct hmap *datapaths, struct hmap *dp_tnlids, + struct ovn_datapath *od, uint32_t *hint) +{ + if (!od->tunnel_key) { + od->tunnel_key = ovn_allocate_tnlid(dp_tnlids, "datapath", + OVN_MIN_DP_KEY_LOCAL, + get_ovn_max_dp_key_local(ctx), + hint); + if (!od->tunnel_key) { + if (od->sb) { + sbrec_datapath_binding_delete(od->sb); + } + ovs_list_remove(&od->list); + ovn_datapath_destroy(datapaths, od); + } + } +} + +static void +ovn_datapath_assign_requested_tnl_id(struct hmap *dp_tnlids, + struct ovn_datapath *od) { - static uint32_t hint; - return ovn_allocate_tnlid(dp_tnlids, "datapath", OVN_MIN_DP_KEY_LOCAL, - get_ovn_max_dp_key_local(ctx), &hint); + const struct smap *other_config = (od->nbs + ? &od->nbs->other_config + : &od->nbr->options); + uint32_t tunnel_key = smap_get_int(other_config, "requested-tnl-key", 0); + if (tunnel_key) { + if (ovn_add_tnlid(dp_tnlids, tunnel_key)) { + od->tunnel_key = tunnel_key; + } else { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Logical %s %s requests same tunnel key " + "%"PRIu32" as another logical switch or router", + od->nbs ? "switch" : "router", od->nbs->name, + tunnel_key); + } + } } /* Updates the southbound Datapath_Binding table so that it contains the @@ -1278,65 +1372,44 @@ join_datapaths(ctx, datapaths, &sb_only, &nb_only, &both, lr_list); - /* First index the in-use datapath tunnel IDs. */ + /* Assign explicitly requested tunnel ids first. */ struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); struct ovn_datapath *od, *next; - if (!ovs_list_is_empty(&nb_only) || !ovs_list_is_empty(&both)) { - LIST_FOR_EACH (od, list, &both) { - ovn_add_tnlid(&dp_tnlids, od->sb->tunnel_key); - } + LIST_FOR_EACH (od, list, &both) { + ovn_datapath_assign_requested_tnl_id(&dp_tnlids, od); } - - /* Add southbound record for each unmatched northbound record. */ LIST_FOR_EACH (od, list, &nb_only) { - int64_t tunnel_key = 0; - if (od->nbs) { - tunnel_key = smap_get_int(&od->nbs->other_config, - "requested-tnl-key", - 0); - if (tunnel_key && ovn_tnlid_in_use(&dp_tnlids, tunnel_key)) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Cannot create datapath binding for " - "logical switch %s due to duplicate key set " - "in other_config:requested-tnl-key: %"PRId64, - od->nbs->name, tunnel_key); - continue; - } - } - if (!tunnel_key) { - tunnel_key = ovn_datapath_allocate_key(ctx, &dp_tnlids); - if (!tunnel_key) { - break; - } + ovn_datapath_assign_requested_tnl_id(&dp_tnlids, od); + } + + /* Keep nonconflicting tunnel IDs that are already assigned. */ + LIST_FOR_EACH (od, list, &both) { + if (!od->tunnel_key && ovn_add_tnlid(&dp_tnlids, od->sb->tunnel_key)) { + od->tunnel_key = od->sb->tunnel_key; } + } - od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn); - ovn_datapath_update_external_ids(od); - sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key); + /* Assign new tunnel ids where needed. */ + uint32_t hint = 0; + LIST_FOR_EACH_SAFE (od, next, list, &both) { + ovn_datapath_allocate_key(ctx, datapaths, &dp_tnlids, od, &hint); + } + LIST_FOR_EACH_SAFE (od, next, list, &nb_only) { + ovn_datapath_allocate_key(ctx, datapaths, &dp_tnlids, od, &hint); } - /* Sync from northbound to southbound record for od existed in both. */ + /* Sync tunnel ids from nb to sb. */ LIST_FOR_EACH (od, list, &both) { - if (od->nbs) { - int64_t tunnel_key = smap_get_int(&od->nbs->other_config, - "requested-tnl-key", - 0); - if (tunnel_key && tunnel_key != od->sb->tunnel_key) { - if (ovn_tnlid_in_use(&dp_tnlids, tunnel_key)) { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Cannot update datapath binding key for " - "logical switch %s due to duplicate key set " - "in other_config:requested-tnl-key: %"PRId64, - od->nbs->name, tunnel_key); - continue; - } - sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key); - } + if (od->sb->tunnel_key != od->tunnel_key) { + sbrec_datapath_binding_set_tunnel_key(od->sb, od->tunnel_key); } + ovn_datapath_update_external_ids(od); + } + LIST_FOR_EACH (od, list, &nb_only) { + od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn); + ovn_datapath_update_external_ids(od); + sbrec_datapath_binding_set_tunnel_key(od->sb, od->tunnel_key); } - ovn_destroy_tnlids(&dp_tnlids); /* Delete southbound records without northbound matches. */ @@ -1347,13 +1420,35 @@ } } +/* A logical switch port or logical router port. + * + * In steady state, an ovn_port points to a northbound Logical_Switch_Port + * record (via 'nbsp') *or* a Logical_Router_Port record (via 'nbrp'), and to a + * southbound Port_Binding record (via 'sb'). As the state of the system + * changes, join_logical_ports() may determine that there is a new LSP or LRP + * that has no corresponding Port_Binding record (in which case build_ports()) + * will create the missing Port_Binding) or that a Port_Binding record exists + * that has no coresponding LSP (in which case build_ports() will delete the + * spurious Port_Binding). Thus, after build_ports() runs, any given ovn_port + * will have 'sb' nonnull, and 'nbsp' xor 'nbrp' nonnull. + * + * Ordinarily there is only one ovn_port that points to a given LSP or LRP (but + * distributed gateway ports point a "derived" ovn_port to a duplicate LRP). + */ struct ovn_port { + /* Port name aka key. + * + * This is ordinarily the same as nbsp->name or nbrp->name and + * sb->logical_port. (A distributed gateway port creates a "derived" + * ovn_port with key "cr-%s" % nbrp->name.) */ struct hmap_node key_node; /* Index on 'key'. */ - char *key; /* nbs->name, nbr->name, sb->logical_port. */ + char *key; /* nbsp->name, nbrp->name, sb->logical_port. */ char *json_key; /* 'key', quoted for use in JSON. */ const struct sbrec_port_binding *sb; /* May be NULL. */ + uint32_t tunnel_key; + /* Logical switch port data. */ const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */ @@ -1371,15 +1466,20 @@ /* Logical port multicast data. */ struct mcast_port_info mcast_info; - bool derived; /* Indicates whether this is an additional port - * derived from nbsp or nbrp. */ + /* This is ordinarily false. It is true if and only if this ovn_port is + * derived from a chassis-redirect port. */ + bool derived; + bool has_unknown; /* If the addresses have 'unknown' defined. */ + /* The port's peer: * * - A switch port S of type "router" has a router port R as a peer, * and R in turn has S has its peer. * - * - Two connected logical router ports have each other as peer. */ + * - Two connected logical router ports have each other as peer. + * + * - Other kinds of ports have no peer. */ struct ovn_port *peer; struct ovn_datapath *od; @@ -1388,13 +1488,6 @@ }; static void -ovn_port_set_sb(struct ovn_port *op, - const struct sbrec_port_binding *sb) -{ - op->sb = sb; -} - -static void ovn_port_set_nb(struct ovn_port *op, const struct nbrec_logical_switch_port *nbsp, const struct nbrec_logical_router_port *nbrp) @@ -1417,7 +1510,7 @@ op->json_key = ds_steal_cstr(&json_key); op->key = xstrdup(key); - ovn_port_set_sb(op, sb); + op->sb = sb; ovn_port_set_nb(op, nbsp, nbrp); op->derived = false; hmap_insert(ports, &op->key_node, hash_string(op->key, 0)); @@ -1463,13 +1556,6 @@ return NULL; } -static uint32_t -ovn_port_allocate_key(struct ovn_datapath *od) -{ - return ovn_allocate_tnlid(&od->port_tnlids, "port", - 1, (1u << 15) - 1, &od->port_key_hint); -} - /* Returns true if the logical switch port 'enabled' column is empty or * set to true. Otherwise, returns false. */ static bool @@ -1493,6 +1579,12 @@ } static bool +lsp_is_router(const struct nbrec_logical_switch_port *nbsp) +{ + return !strcmp(nbsp->type, "router"); +} + +static bool lrport_is_enabled(const struct nbrec_logical_router_port *lrport) { return !lrport->enabled || *lrport->enabled; @@ -1754,7 +1846,7 @@ } uint32_t index = ip4 - ipam->start_ipv4; - if (index > ipam->total_ipv4s || + if (index >= ipam->total_ipv4s - 1 || bitmap_is_set(ipam->allocated_ipv4s, index)) { /* Previously assigned dynamic IPv4 address can no longer be used. * It's either outside the subnet, conflicts with an excluded IP, @@ -2369,27 +2461,25 @@ op->lrp_networks = lrp_networks; op->od = od; - const char *redirect_chassis = smap_get(&op->nbrp->options, - "redirect-chassis"); - if (op->nbrp->ha_chassis_group || redirect_chassis || + if (op->nbrp->ha_chassis_group || op->nbrp->n_gateway_chassis) { /* Additional "derived" ovn_port crp represents the - * instance of op on the "redirect-chassis". */ + * instance of op on the gateway chassis. */ const char *gw_chassis = smap_get(&op->od->nbr->options, "chassis"); if (gw_chassis) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Bad configuration: " - "redirect-chassis configured on port %s " + VLOG_WARN_RL(&rl, "Bad configuration: distributed " + "gateway port configured on port %s " "on L3 gateway router", nbrp->name); continue; } if (od->l3dgw_port || od->l3redirect_port) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Bad configuration: multiple ports " - "with redirect-chassis on same logical " + VLOG_WARN_RL(&rl, "Bad configuration: multiple " + "distributed gateway ports on logical " "router %s", od->nbr->name); continue; } @@ -2424,7 +2514,7 @@ * to their peers. */ struct ovn_port *op; HMAP_FOR_EACH (op, key_node, ports) { - if (op->nbsp && !strcmp(op->nbsp->type, "router") && !op->derived) { + 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; @@ -2488,10 +2578,6 @@ } } -static bool -ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port, int *addr_family); - static void get_router_load_balancer_ips(const struct ovn_datapath *od, struct sset *all_ips_v4, struct sset *all_ips_v6) @@ -2635,7 +2721,7 @@ if (central_ip_address) { /* Gratuitous ARP for centralized NAT rules on distributed gateway - * ports should be restricted to the "redirect-chassis". */ + * ports should be restricted to the gateway chassis. */ if (op->od->l3redirect_port) { ds_put_format(&c_addresses, " is_chassis_resident(%s)", op->od->l3redirect_port->json_key); @@ -2903,15 +2989,6 @@ free(sb_ha_chassis); } -static int64_t -op_get_requested_tnl_key(const struct ovn_port *op) -{ - ovs_assert(op->nbsp || op->nbrp); - const struct smap *op_options = op->nbsp ? &op->nbsp->options - : &op->nbrp->options; - return smap_get_int(op_options, "requested-tnl-key", 0); -} - static const char* op_get_name(const struct ovn_port *op) { @@ -2971,33 +3048,19 @@ struct smap new; smap_init(&new); if (op->derived) { - const char *redirect_chassis = smap_get(&op->nbrp->options, - "redirect-chassis"); const char *redirect_type = smap_get(&op->nbrp->options, "redirect-type"); - int n_gw_options_set = 0; if (op->nbrp->ha_chassis_group) { - n_gw_options_set++; - } - if (op->nbrp->n_gateway_chassis) { - n_gw_options_set++; - } - if (redirect_chassis) { - n_gw_options_set++; - } - if (n_gw_options_set > 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL( - &rl, "Multiple gatway options set for the logical router " - "port %s. The first preferred option is " - "ha_chassis_group; the second is gateway_chassis; " - "and the last is redirect-chassis.", op->nbrp->name); - } + if (op->nbrp->n_gateway_chassis) { + static struct vlog_rate_limit rl + = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Both ha_chassis_group and " + "gateway_chassis configured on port %s; " + "ignoring the latter.", op->nbrp->name); + } - if (op->nbrp->ha_chassis_group) { - /* HA Chassis group is set. Ignore 'gateway_chassis' - * column and redirect-chassis option. */ + /* HA Chassis group is set. Ignore 'gateway_chassis'. */ sync_ha_chassis_group_for_sbpb(ctx, op->nbrp->ha_chassis_group, sbrec_chassis_by_name, op->sb); sset_add(active_ha_chassis_grps, @@ -3014,47 +3077,6 @@ } sset_add(active_ha_chassis_grps, op->nbrp->name); - } else if (redirect_chassis) { - /* Handle ports that had redirect-chassis option attached - * to them, and for backwards compatibility convert them - * to a single HA Chassis group entry */ - const struct sbrec_chassis *chassis = - chassis_lookup_by_name(sbrec_chassis_by_name, - redirect_chassis); - if (chassis) { - /* If we found the chassis, and the gw chassis on record - * differs from what we expect go ahead and update */ - char *gwc_name = xasprintf("%s_%s", op->nbrp->name, - chassis->name); - const struct sbrec_ha_chassis_group *sb_ha_ch_grp; - sb_ha_ch_grp = ha_chassis_group_lookup_by_name( - ctx->sbrec_ha_chassis_grp_by_name, gwc_name); - if (!sb_ha_ch_grp) { - sb_ha_ch_grp = - sbrec_ha_chassis_group_insert(ctx->ovnsb_txn); - sbrec_ha_chassis_group_set_name(sb_ha_ch_grp, - gwc_name); - } - - if (sb_ha_ch_grp->n_ha_chassis != 1) { - struct sbrec_ha_chassis *sb_ha_ch = - create_sb_ha_chassis(ctx, chassis, - chassis->name, 0); - sbrec_ha_chassis_group_set_ha_chassis(sb_ha_ch_grp, - &sb_ha_ch, 1); - } - sbrec_port_binding_set_ha_chassis_group(op->sb, - sb_ha_ch_grp); - sset_add(active_ha_chassis_grps, gwc_name); - free(gwc_name); - } else { - VLOG_WARN("chassis name '%s' from redirect from logical " - " router port '%s' redirect-chassis not found", - redirect_chassis, op->nbrp->name); - if (op->sb->ha_chassis_group) { - sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } - } } else { /* Nothing is set. Clear ha_chassis_group from pb. */ if (op->sb->ha_chassis_group) { @@ -3105,7 +3127,7 @@ sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0); } else { - if (strcmp(op->nbsp->type, "router")) { + if (!lsp_is_router(op->nbsp)) { uint32_t queue_id = smap_get_int( &op->sb->options, "qdisc_queue_id", 0); bool has_qos = port_has_qos_params(&op->nbsp->options); @@ -3219,7 +3241,7 @@ * IPs by the ovn-controller on which the distributed gateway * router port resides if: * - * - op->peer has 'reside-on-gateway-chassis' set and the + * - op->peer has 'reside-on-redirect-chassis' set and the * the logical router datapath has distributed router port. * * - op->peer is distributed gateway router port. @@ -3283,17 +3305,8 @@ sbrec_port_binding_set_external_ids(op->sb, &ids); smap_destroy(&ids); } - int64_t tnl_key = op_get_requested_tnl_key(op); - if (tnl_key && tnl_key != op->sb->tunnel_key) { - if (ovn_tnlid_in_use(&op->od->port_tnlids, tnl_key)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Cannot update port binding for " - "%s due to duplicate key set " - "in options:requested-tnl-key: %"PRId64, - op_get_name(op), tnl_key); - } else { - sbrec_port_binding_set_tunnel_key(op->sb, tnl_key); - } + if (op->tunnel_key != op->sb->tunnel_key) { + sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); } } @@ -3327,53 +3340,6 @@ } } -struct ovn_lb { - struct hmap_node hmap_node; - - const struct nbrec_load_balancer *nlb; /* May be NULL. */ - char *selection_fields; - struct lb_vip *vips; - size_t n_vips; -}; - -struct lb_vip { - char *vip; - uint16_t vip_port; - int addr_family; - char *backend_ips; - - bool health_check; - struct lb_vip_backend *backends; - size_t n_backends; -}; - -struct lb_vip_backend { - char *ip; - uint16_t port; - int addr_family; - - struct ovn_port *op; /* Logical port to which the ip belong to. */ - bool health_check; - char *svc_mon_src_ip; /* Source IP to use for monitoring. */ - const struct sbrec_service_monitor *sbrec_monitor; -}; - - -static inline struct ovn_lb * -ovn_lb_find(struct hmap *lbs, struct uuid *uuid) -{ - struct ovn_lb *lb; - size_t hash = uuid_hash(uuid); - HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) { - if (uuid_equals(&lb->nlb->header_.uuid, uuid)) { - return lb; - } - } - - return NULL; -} - - struct service_monitor_info { struct hmap_node hmap_node; const struct sbrec_service_monitor *sbrec_mon; @@ -3413,126 +3379,39 @@ return mon_info; } -static struct ovn_lb * -ovn_lb_create(struct northd_context *ctx, struct hmap *lbs, - const struct nbrec_load_balancer *nbrec_lb, - struct hmap *ports, struct hmap *monitor_map) +static void +ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_lb *lb, + struct hmap *monitor_map) { - struct ovn_lb *lb = xzalloc(sizeof *lb); - - size_t hash = uuid_hash(&nbrec_lb->header_.uuid); - lb->nlb = nbrec_lb; - hmap_insert(lbs, &lb->hmap_node, hash); - - lb->n_vips = smap_count(&nbrec_lb->vips); - lb->vips = xcalloc(lb->n_vips, sizeof (struct lb_vip)); - struct smap_node *node; - size_t n_vips = 0; - - SMAP_FOR_EACH (node, &nbrec_lb->vips) { - char *vip; - uint16_t port; - int addr_family; + 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 (!ip_address_and_port_from_lb_key(node->key, &vip, &port, - &addr_family)) { + if (!lb_vip_nb->lb_health_check) { continue; } - lb->vips[n_vips].vip = vip; - lb->vips[n_vips].vip_port = port; - lb->vips[n_vips].addr_family = addr_family; - lb->vips[n_vips].backend_ips = xstrdup(node->value); - - struct nbrec_load_balancer_health_check *lb_health_check = NULL; - if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) { - if (nbrec_lb->n_health_check > 0) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, - "SCTP load balancers do not currently support " - "health checks. Not creating health checks for " - "load balancer " UUID_FMT, - UUID_ARGS(&nbrec_lb->header_.uuid)); - } - } else { - for (size_t i = 0; i < nbrec_lb->n_health_check; i++) { - if (!strcmp(nbrec_lb->health_check[i]->vip, node->key)) { - lb_health_check = nbrec_lb->health_check[i]; - break; - } - } - } - - char *tokstr = xstrdup(node->value); - char *save_ptr = NULL; - char *token; - size_t n_backends = 0; - /* Format for a backend ips : IP1:port1,IP2:port2,...". */ - for (token = strtok_r(tokstr, ",", &save_ptr); - token != NULL; - token = strtok_r(NULL, ",", &save_ptr)) { - n_backends++; - } - - free(tokstr); - tokstr = xstrdup(node->value); - save_ptr = NULL; - - lb->vips[n_vips].n_backends = n_backends; - lb->vips[n_vips].backends = xcalloc(n_backends, - sizeof (struct lb_vip_backend)); - lb->vips[n_vips].health_check = lb_health_check ? true: false; - - size_t i = 0; - for (token = strtok_r(tokstr, ",", &save_ptr); - token != NULL; - token = strtok_r(NULL, ",", &save_ptr)) { - char *backend_ip; - uint16_t backend_port; + 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 (!ip_address_and_port_from_lb_key(token, &backend_ip, - &backend_port, - &addr_family)) { - continue; - } - - /* Get the logical port to which this ip belongs to. */ - struct ovn_port *op = NULL; - char *svc_mon_src_ip = NULL; - const char *s = smap_get(&nbrec_lb->ip_port_mappings, - backend_ip); - 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); - } - - lb->vips[n_vips].backends[i].ip = backend_ip; - lb->vips[n_vips].backends[i].port = backend_port; - lb->vips[n_vips].backends[i].addr_family = addr_family; - lb->vips[n_vips].backends[i].op = op; - lb->vips[n_vips].backends[i].svc_mon_src_ip = svc_mon_src_ip; - - if (lb_health_check && op && svc_mon_src_ip) { - const char *protocol = nbrec_lb->protocol; + if (backend_nb->op && backend_nb->svc_mon_src_ip) { + const char *protocol = lb->nlb->protocol; if (!protocol || !protocol[0]) { protocol = "tcp"; } - lb->vips[n_vips].backends[i].health_check = true; + backend_nb->health_check = true; struct service_monitor_info *mon_info = - create_or_get_service_mon(ctx, monitor_map, backend_ip, - op->nbsp->name, backend_port, + 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_health_check->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) || @@ -3542,89 +3421,49 @@ } if (!mon_info->sbrec_mon->src_ip || - strcmp(mon_info->sbrec_mon->src_ip, svc_mon_src_ip)) { - sbrec_service_monitor_set_src_ip(mon_info->sbrec_mon, - svc_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); } - lb->vips[n_vips].backends[i].sbrec_monitor = - mon_info->sbrec_mon; + backend_nb->sbrec_monitor = mon_info->sbrec_mon; mon_info->required = true; - } else { - lb->vips[n_vips].backends[i].health_check = false; - } - - i++; - } - - free(tokstr); - n_vips++; - } - - char *proto = NULL; - if (nbrec_lb->protocol && nbrec_lb->protocol[0]) { - proto = nbrec_lb->protocol; - } - - if (lb->nlb->n_selection_fields) { - struct ds sel_fields = DS_EMPTY_INITIALIZER; - for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) { - char *field = lb->nlb->selection_fields[i]; - if (!strcmp(field, "tp_src") && proto) { - ds_put_format(&sel_fields, "%s_src,", proto); - } else if (!strcmp(field, "tp_dst") && proto) { - ds_put_format(&sel_fields, "%s_dst,", proto); - } else { - ds_put_format(&sel_fields, "%s,", field); } } - ds_chomp(&sel_fields, ','); - lb->selection_fields = ds_steal_cstr(&sel_fields); } - - return lb; } -static void -ovn_lb_destroy(struct ovn_lb *lb) +static +void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, + struct ovn_northd_lb_vip *lb_vip_nb, + struct ds *action, + char *selection_fields) { - for (size_t i = 0; i < lb->n_vips; i++) { - free(lb->vips[i].vip); - free(lb->vips[i].backend_ips); - - for (size_t j = 0; j < lb->vips[i].n_backends; j++) { - free(lb->vips[i].backends[j].ip); - free(lb->vips[i].backends[j].svc_mon_src_ip); - } + bool skip_hash_fields = false; - free(lb->vips[i].backends); - } - free(lb->vips); - free(lb->selection_fields); -} - -static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip, - struct ds *action, - char *selection_fields) -{ - if (lb_vip->health_check) { + if (lb_vip_nb->lb_health_check) { ds_put_cstr(action, "ct_lb(backends="); size_t n_active_backends = 0; - for (size_t k = 0; k < lb_vip->n_backends; k++) { - struct lb_vip_backend *backend = &lb_vip->backends[k]; - if (backend->health_check && backend->sbrec_monitor && - backend->sbrec_monitor->status && - strcmp(backend->sbrec_monitor->status, "online")) { + for (size_t i = 0; i < lb_vip->n_backends; i++) { + struct ovn_lb_backend *backend = &lb_vip->backends[i]; + struct ovn_northd_lb_backend *backend_nb = + &lb_vip_nb->backends_nb[i]; + if (backend_nb->health_check && backend_nb->sbrec_monitor && + backend_nb->sbrec_monitor->status && + strcmp(backend_nb->sbrec_monitor->status, "online")) { continue; } n_active_backends++; ds_put_format(action, "%s:%"PRIu16",", - backend->ip, backend->port); + backend->ip_str, backend->port); } if (!n_active_backends) { + skip_hash_fields = true; ds_clear(action); ds_put_cstr(action, "drop;"); } else { @@ -3632,10 +3471,10 @@ ds_put_cstr(action, ");"); } } else { - ds_put_format(action, "ct_lb(backends=%s);", lb_vip->backend_ips); + ds_put_format(action, "ct_lb(backends=%s);", lb_vip_nb->backend_ips); } - if (selection_fields && selection_fields[0]) { + if (!skip_hash_fields && selection_fields && selection_fields[0]) { ds_chomp(action, ';'); ds_chomp(action, ')'); ds_put_format(action, "; hash_fields=\"%s\");", selection_fields); @@ -3643,8 +3482,8 @@ } static void -build_ovn_lbs(struct northd_context *ctx, struct hmap *ports, - struct hmap *lbs) +build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, + struct hmap *ports, struct hmap *lbs) { hmap_init(lbs); struct hmap monitor_map = HMAP_INITIALIZER(&monitor_map); @@ -3662,7 +3501,94 @@ const struct nbrec_load_balancer *nbrec_lb; NBREC_LOAD_BALANCER_FOR_EACH (nbrec_lb, ctx->ovnnb_idl) { - ovn_lb_create(ctx, lbs, nbrec_lb, ports, &monitor_map); + 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_datapath *od; + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + for (size_t i = 0; i < od->nbs->n_load_balancer; i++) { + const struct uuid *lb_uuid = + &od->nbs->load_balancer[i]->header_.uuid; + lb = ovn_northd_lb_find(lbs, lb_uuid); + + ovn_northd_lb_add_datapath(lb, od->sb); + } + } + + /* Delete any stale SB load balancer rows. */ + const struct sbrec_load_balancer *sbrec_lb, *next; + SBREC_LOAD_BALANCER_FOR_EACH_SAFE (sbrec_lb, next, ctx->ovnsb_idl) { + const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id"); + struct uuid lb_uuid; + if (!nb_lb_uuid || !uuid_from_string(&lb_uuid, nb_lb_uuid)) { + sbrec_load_balancer_delete(sbrec_lb); + continue; + } + + lb = ovn_northd_lb_find(lbs, &lb_uuid); + if (lb && lb->n_dps) { + lb->slb = sbrec_lb; + } else { + sbrec_load_balancer_delete(sbrec_lb); + } + } + + /* Create SB Load balancer records if not present and sync + * the SB load balancer columns. */ + HMAP_FOR_EACH (lb, hmap_node, lbs) { + if (!lb->n_dps) { + continue; + } + + if (!lb->slb) { + sbrec_lb = sbrec_load_balancer_insert(ctx->ovnsb_txn); + lb->slb = sbrec_lb; + char *lb_id = xasprintf( + UUID_FMT, UUID_ARGS(&lb->nlb->header_.uuid)); + const struct smap external_ids = + SMAP_CONST1(&external_ids, "lb_id", lb_id); + sbrec_load_balancer_set_external_ids(sbrec_lb, &external_ids); + free(lb_id); + } + sbrec_load_balancer_set_name(lb->slb, lb->nlb->name); + sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips); + sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol); + sbrec_load_balancer_set_datapaths( + lb->slb, (struct sbrec_datapath_binding **)lb->dps, + lb->n_dps); + } + + /* Set the list of associated load balanacers to a logical switch + * datapath binding in the SB DB. */ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + const struct sbrec_load_balancer **sbrec_lbs = + xmalloc(od->nbs->n_load_balancer * sizeof *sbrec_lbs); + for (size_t i = 0; i < od->nbs->n_load_balancer; i++) { + const struct uuid *lb_uuid = + &od->nbs->load_balancer[i]->header_.uuid; + lb = ovn_northd_lb_find(lbs, lb_uuid); + sbrec_lbs[i] = lb->slb; + } + + sbrec_datapath_binding_set_load_balancers( + od->sb, (struct sbrec_load_balancer **)sbrec_lbs, + od->nbs->n_load_balancer); + free(sbrec_lbs); } struct service_monitor_info *mon_info; @@ -3676,13 +3602,49 @@ hmap_destroy(&monitor_map); } +static bool +ovn_port_add_tnlid(struct ovn_port *op, uint32_t tunnel_key) +{ + bool added = ovn_add_tnlid(&op->od->port_tnlids, tunnel_key); + if (added) { + op->tunnel_key = tunnel_key; + if (tunnel_key > op->od->port_key_hint) { + op->od->port_key_hint = tunnel_key; + } + } + return added; +} + +static void +ovn_port_assign_requested_tnl_id(struct ovn_port *op) +{ + const struct smap *options = (op->nbsp + ? &op->nbsp->options + : &op->nbrp->options); + uint32_t tunnel_key = smap_get_int(options, "requested-tnl-key", 0); + if (tunnel_key && !ovn_port_add_tnlid(op, tunnel_key)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Logical %s port %s requests same tunnel key " + "%"PRIu32" as another LSP or LRP", + op->nbsp ? "switch" : "router", + op_get_name(op), tunnel_key); + } +} + static void -destroy_ovn_lbs(struct hmap *lbs) +ovn_port_allocate_key(struct hmap *ports, struct ovn_port *op) { - struct ovn_lb *lb; - HMAP_FOR_EACH_POP (lb, hmap_node, lbs) { - ovn_lb_destroy(lb); - free(lb); + if (!op->tunnel_key) { + op->tunnel_key = ovn_allocate_tnlid(&op->od->port_tnlids, "port", + 1, (1u << 15) - 1, + &op->od->port_key_hint); + if (!op->tunnel_key) { + if (op->sb) { + sbrec_port_binding_delete(op->sb); + } + ovs_list_remove(&op->list); + ovn_port_destroy(ports, op); + } } } @@ -3711,15 +3673,30 @@ /* Purge stale Mac_Bindings if ports are deleted. */ bool remove_mac_bindings = !ovs_list_is_empty(&sb_only); + /* Assign explicitly requested tunnel ids first. */ struct ovn_port *op, *next; - /* For logical ports that are in both databases, index the in-use - * tunnel_keys. */ LIST_FOR_EACH (op, list, &both) { - ovn_add_tnlid(&op->od->port_tnlids, op->sb->tunnel_key); - if (op->sb->tunnel_key > op->od->port_key_hint) { - op->od->port_key_hint = op->sb->tunnel_key; + ovn_port_assign_requested_tnl_id(op); + } + LIST_FOR_EACH (op, list, &nb_only) { + ovn_port_assign_requested_tnl_id(op); + } + + /* Keep nonconflicting tunnel IDs that are already assigned. */ + LIST_FOR_EACH (op, list, &both) { + if (!op->tunnel_key) { + ovn_port_add_tnlid(op, op->sb->tunnel_key); } } + + /* Assign new tunnel ids where needed. */ + LIST_FOR_EACH_SAFE (op, next, list, &both) { + ovn_port_allocate_key(ports, op); + } + LIST_FOR_EACH_SAFE (op, next, list, &nb_only) { + ovn_port_allocate_key(ports, op); + } + /* For logical ports that are in both databases, update the southbound * record based on northbound data. * For logical ports that are in NB database, do any tag allocation @@ -3741,38 +3718,21 @@ /* Add southbound record for each unmatched northbound record. */ LIST_FOR_EACH_SAFE (op, next, list, &nb_only) { - int64_t tunnel_key = op_get_requested_tnl_key(op); - if (tunnel_key && ovn_tnlid_in_use(&op->od->port_tnlids, tunnel_key)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Cannot create port binding for " - "%s due to duplicate key set " - "in options:requested-tnl-key: %"PRId64, - op_get_name(op), tunnel_key); - continue; - } - - if (!tunnel_key) { - tunnel_key = ovn_port_allocate_key(op->od); - if (!tunnel_key) { - continue; - } - } - - ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn)); + op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn); ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op, &chassis_qdisc_queues, &active_ha_chassis_grps); sbrec_port_binding_set_logical_port(op->sb, op->key); - sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key); } /* Delete southbound records without northbound matches. */ - LIST_FOR_EACH_SAFE(op, next, list, &sb_only) { - ovs_list_remove(&op->list); - sbrec_port_binding_delete(op->sb); - ovn_port_destroy(ports, op); + if (!ovs_list_is_empty(&sb_only)) { + LIST_FOR_EACH_SAFE (op, next, list, &sb_only) { + ovs_list_remove(&op->list); + sbrec_port_binding_delete(op->sb); + ovn_port_destroy(ports, op); + } } - if (remove_mac_bindings) { cleanup_mac_bindings(ctx, datapaths, ports); } @@ -3783,6 +3743,9 @@ sset_destroy(&active_ha_chassis_grps); } +/* XXX: The 'ovn_lflow_add_unique*()' functions should be used for logical + * flows using a multicast group. + * See the comment on 'ovn_lflow_add_unique()' for details. */ struct multicast_group { const char *name; uint16_t key; /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */ @@ -3808,6 +3771,10 @@ static const struct multicast_group mc_unknown = { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY }; +#define MC_FLOOD_L2 "_MC_flood_l2" +static const struct multicast_group mc_flood_l2 = + { MC_FLOOD_L2, OVN_MCAST_FLOOD_L2_TUNNEL_KEY }; + static bool multicast_group_equal(const struct multicast_group *a, const struct multicast_group *b) @@ -4095,6 +4062,13 @@ ovn_igmp_group_destroy_entry(entry); free(entry); } + + if (igmp_group->datapath->n_localnet_ports) { + ovn_multicast_add_ports(mcast_groups, igmp_group->datapath, + &igmp_group->mcgroup, + igmp_group->datapath->localnet_ports, + igmp_group->datapath->n_localnet_ports); + } } static void @@ -4123,7 +4097,8 @@ struct ovn_lflow { struct hmap_node hmap_node; - struct ovn_datapath *od; + struct ovn_datapath *od; /* 'logical_datapath' in SB schema. */ + struct hmapx od_group; /* Hash map of 'struct ovn_datapath *'. */ enum ovn_stage stage; uint16_t priority; char *match; @@ -4132,11 +4107,15 @@ const char *where; }; -static size_t +static void ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow); +static struct ovn_lflow * ovn_lflow_find_by_lflow(const struct hmap *, + const struct ovn_lflow *, + uint32_t hash); + +static uint32_t ovn_lflow_hash(const struct ovn_lflow *lflow) { - return ovn_logical_flow_hash(&lflow->od->sb->header_.uuid, - ovn_stage_get_table(lflow->stage), + return ovn_logical_flow_hash(ovn_stage_get_table(lflow->stage), ovn_stage_get_pipeline_name(lflow->stage), lflow->priority, lflow->match, lflow->actions); @@ -4167,6 +4146,7 @@ char *match, char *actions, char *stage_hint, const char *where) { + hmapx_init(&lflow->od_group); lflow->od = od; lflow->stage = stage; lflow->priority = priority; @@ -4176,31 +4156,72 @@ lflow->where = where; } +/* If this option is 'true' northd will combine logical flows that differs by + * logical datapath only by creating a datapah group. */ +static bool use_logical_dp_groups = false; + /* Adds a row with the specified contents to the Logical_Flow table. */ static void ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od, enum ovn_stage stage, uint16_t priority, - const char *match, const char *actions, + const char *match, const char *actions, bool shared, const struct ovsdb_idl_row *stage_hint, const char *where) { ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od)); - struct ovn_lflow *lflow = xmalloc(sizeof *lflow); - ovn_lflow_init(lflow, od, stage, priority, + struct ovn_lflow *old_lflow, *lflow; + uint32_t hash; + + lflow = xmalloc(sizeof *lflow); + /* While adding new logical flows we're not setting single datapath, but + * collecting a group. 'od' will be updated later for all flows with only + * one datapath in a group, so it could be hashed correctly. */ + ovn_lflow_init(lflow, NULL, stage, priority, xstrdup(match), xstrdup(actions), ovn_lflow_hint(stage_hint), where); - hmap_insert(lflow_map, &lflow->hmap_node, ovn_lflow_hash(lflow)); + + hash = ovn_lflow_hash(lflow); + if (shared && use_logical_dp_groups) { + old_lflow = ovn_lflow_find_by_lflow(lflow_map, lflow, hash); + if (old_lflow) { + ovn_lflow_destroy(NULL, lflow); + hmapx_add(&old_lflow->od_group, od); + return; + } + } + + hmapx_add(&lflow->od_group, od); + hmap_insert(lflow_map, &lflow->hmap_node, hash); } /* Adds a row with the specified contents to the Logical_Flow table. */ #define ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \ ACTIONS, STAGE_HINT) \ - ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \ + ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, true, \ STAGE_HINT, OVS_SOURCE_LOCATOR) #define ovn_lflow_add(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \ - ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \ - ACTIONS, NULL) + ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, true, \ + NULL, OVS_SOURCE_LOCATOR) + +/* Adds a row with the specified contents to the Logical_Flow table. + * Combining of this logical flow with already existing ones, e.g., by using + * Logical Datapath Groups, is forbidden. + * + * XXX: ovn-controller assumes that a logical flow using multicast group always + * comes after or in the same database update with the corresponding + * multicast group. That will not be the case with datapath groups. + * For this reason, the 'ovn_lflow_add_unique*()' functions should be used + * for such logical flows. + */ +#define ovn_lflow_add_unique_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \ + ACTIONS, STAGE_HINT) \ + ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, false, \ + STAGE_HINT, OVS_SOURCE_LOCATOR) + +#define ovn_lflow_add_unique(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \ + ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, false, \ + NULL, OVS_SOURCE_LOCATOR) static struct ovn_lflow * ovn_lflow_find(struct hmap *lflows, struct ovn_datapath *od, @@ -4212,20 +4233,17 @@ CONST_CAST(char *, match), CONST_CAST(char *, actions), NULL, NULL); - struct ovn_lflow *lflow; - HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) { - if (ovn_lflow_equal(lflow, &target)) { - return lflow; - } - } - return NULL; + return ovn_lflow_find_by_lflow(lflows, &target, hash); } static void ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow) { if (lflow) { - hmap_remove(lflows, &lflow->hmap_node); + if (lflows) { + hmap_remove(lflows, &lflow->hmap_node); + } + hmapx_destroy(&lflow->od_group); free(lflow->match); free(lflow->actions); free(lflow->stage_hint); @@ -4233,6 +4251,19 @@ } } +static struct ovn_lflow * +ovn_lflow_find_by_lflow(const struct hmap *lflows, + const struct ovn_lflow *target, uint32_t hash) +{ + struct ovn_lflow *lflow; + HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) { + if (ovn_lflow_equal(lflow, target)) { + return lflow; + } + } + return NULL; +} + /* Appends port security constraints on L2 address field 'eth_addr_field' * (e.g. "eth.src" or "eth.dst") to 'match'. 'ps_addrs', with 'n_ps_addrs' * elements, is the collection of port_security constraints from an @@ -4278,10 +4309,20 @@ ipv6_string_mapped(ip6_str, &lla); ds_put_format(match, " && (nd.target == %s", ip6_str); - for(int i = 0; i < n_ipv6_addrs; i++) { - memset(ip6_str, 0, sizeof(ip6_str)); - ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); - ds_put_format(match, " || nd.target == %s", ip6_str); + for (size_t i = 0; i < n_ipv6_addrs; i++) { + /* When the netmask is applied, if the host portion is + * non-zero, the host can only use the specified + * address in the nd.target. If zero, the host is allowed + * to use any address in the subnet. + */ + if (ipv6_addrs[i].plen == 128 + || !ipv6_addr_is_host_zero(&ipv6_addrs[i].addr, + &ipv6_addrs[i].mask)) { + ds_put_format(match, " || nd.target == %s", ipv6_addrs[i].addr_s); + } else { + ds_put_format(match, " || nd.target == %s/%d", + ipv6_addrs[i].network_s, ipv6_addrs[i].plen); + } } ds_put_format(match, ")))"); @@ -4307,9 +4348,20 @@ if (pipeline == P_OUT) { ds_put_cstr(match, "ff00::/8, "); } - for(int i = 0; i < n_ipv6_addrs; i++) { - ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); - ds_put_format(match, "%s, ", ip6_str); + for (size_t i = 0; i < n_ipv6_addrs; i++) { + /* When the netmask is applied, if the host portion is + * non-zero, the host can only use the specified + * address. If zero, the host is allowed to use any + * address in the subnet. + */ + if (ipv6_addrs[i].plen == 128 + || !ipv6_addr_is_host_zero(&ipv6_addrs[i].addr, + &ipv6_addrs[i].mask)) { + ds_put_format(match, "%s, ", ipv6_addrs[i].addr_s); + } else { + ds_put_format(match, "%s/%d, ", ipv6_addrs[i].network_s, + ipv6_addrs[i].plen); + } } /* Replace ", " by "}". */ ds_chomp(match, ' '); @@ -4792,70 +4844,64 @@ return false; } +/* Logical switch ingress table 0: Ingress port security - L2 + * (priority 50). + * Ingress table 1: Ingress port security - IP (priority 90 and 80) + * Ingress table 2: Ingress port security - ND (priority 90 and 80) + */ static void -build_lswitch_input_port_sec(struct hmap *ports, struct hmap *datapaths, - struct hmap *lflows) +build_lswitch_input_port_sec_op( + struct ovn_port *op, struct hmap *lflows, + struct ds *actions, struct ds *match) { - /* Logical switch ingress table 0: Ingress port security - L2 - * (priority 50). - * Ingress table 1: Ingress port security - IP (priority 90 and 80) - * Ingress table 2: Ingress port security - ND (priority 90 and 80) - */ - struct ds actions = DS_EMPTY_INITIALIZER; - struct ds match = DS_EMPTY_INITIALIZER; - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { - continue; - } + if (!op->nbsp) { + return; + } - if (!lsp_is_enabled(op->nbsp)) { - /* Drop packets from disabled logical ports (since logical flow - * tables are default-drop). */ - continue; - } + if (!lsp_is_enabled(op->nbsp)) { + /* Drop packets from disabled logical ports (since logical flow + * tables are default-drop). */ + return; + } - if (lsp_is_external(op->nbsp)) { - continue; - } + if (lsp_is_external(op->nbsp)) { + return; + } - ds_clear(&match); - ds_clear(&actions); - ds_put_format(&match, "inport == %s", op->json_key); - build_port_security_l2("eth.src", op->ps_addrs, op->n_ps_addrs, - &match); - - const char *queue_id = smap_get(&op->sb->options, "qdisc_queue_id"); - if (queue_id) { - ds_put_format(&actions, "set_queue(%s); ", queue_id); - } - ds_put_cstr(&actions, "next;"); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50, - ds_cstr(&match), ds_cstr(&actions), - &op->nbsp->header_); + ds_clear(match); + ds_clear(actions); + ds_put_format(match, "inport == %s", op->json_key); + build_port_security_l2("eth.src", op->ps_addrs, op->n_ps_addrs, + match); + + const char *queue_id = smap_get(&op->sb->options, "qdisc_queue_id"); + if (queue_id) { + ds_put_format(actions, "set_queue(%s); ", queue_id); + } + ds_put_cstr(actions, "next;"); + ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50, + ds_cstr(match), ds_cstr(actions), + &op->nbsp->header_); - if (op->nbsp->n_port_security) { - build_port_security_ip(P_IN, op, lflows, &op->nbsp->header_); - build_port_security_nd(op, lflows, &op->nbsp->header_); - } + if (op->nbsp->n_port_security) { + build_port_security_ip(P_IN, op, lflows, &op->nbsp->header_); + build_port_security_nd(op, lflows, &op->nbsp->header_); } +} - /* Ingress table 1 and 2: Port security - IP and ND, by default - * goto next. (priority 0) - */ - struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } +/* Ingress table 1 and 2: Port security - IP and ND, by default + * goto next. (priority 0) + */ +static void +build_lswitch_input_port_sec_od( + struct ovn_datapath *od, struct hmap *lflows) +{ + if (od->nbs) { ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); } - - ds_destroy(&match); - ds_destroy(&actions); } static void @@ -4969,15 +5015,11 @@ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 0, "1", "next;"); - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, svc_check_match, - "next;"); - free(svc_check_match); + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, + "eth.dst == $svc_monitor_mac", "next;"); - svc_check_match = xasprintf("eth.src == %s", svc_monitor_mac); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, svc_check_match, - "next;"); - free(svc_check_match); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, + "eth.src == $svc_monitor_mac", "next;"); /* If there are any stateful ACL rules in this datapath, we must * send all IP packets through the conntrack action, which handles @@ -5020,34 +5062,6 @@ } } -/* For a 'key' of the form "IP:port" or just "IP", sets 'port' and - * 'ip_address'. The caller must free() the memory allocated for - * 'ip_address'. - * Returns true if parsing of 'key' was successful, false otherwise. - */ -static bool -ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port, int *addr_family) -{ - struct sockaddr_storage ss; - if (!inet_parse_active(key, 0, &ss, false)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s", - key); - *ip_address = NULL; - *port = 0; - *addr_family = 0; - return false; - } - - struct ds s = DS_EMPTY_INITIALIZER; - ss_format_address_nobracks(&ss, &s); - *ip_address = ds_steal_cstr(&s); - *port = ss_get_port(&ss); - *addr_family = ss.ss_family; - return true; -} - /* * Returns true if logical switch is configured with DNS records, false * otherwise. @@ -5066,7 +5080,7 @@ static void build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - struct lb_vip *lb_vip, + struct ovn_lb_vip *lb_vip, struct nbrec_load_balancer *lb, int pl, struct shash *meter_groups) { @@ -5074,7 +5088,7 @@ return; } - bool ipv4 = (lb_vip->addr_family == AF_INET); + bool ipv4 = IN6_IS_ADDR_V4MAPPED(&lb_vip->vip); struct ds match = DS_EMPTY_INITIALIZER; char *meter = "", *action; @@ -5083,13 +5097,13 @@ } ds_put_format(&match, "ip%s.dst == %s && %s", - ipv4 ? "4": "6", lb_vip->vip, lb->protocol); + ipv4 ? "4": "6", lb_vip->vip_str, lb->protocol); - char *vip = lb_vip->vip; + char *vip = lb_vip->vip_str; if (lb_vip->vip_port) { ds_put_format(&match, " && %s.dst == %u", lb->protocol, lb_vip->vip_port); - vip = xasprintf("%s%s%s:%u", ipv4 ? "" : "[", lb_vip->vip, + vip = xasprintf("%s%s%s:%u", ipv4 ? "" : "[", lb_vip->vip_str, ipv4 ? "" : "]", lb_vip->vip_port); } @@ -5135,15 +5149,10 @@ "next;"); /* Do not send service monitor packets to conntrack. */ - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110, - svc_check_match, "next;"); - free(svc_check_match); - - svc_check_match = xasprintf("eth.src == %s", svc_monitor_mac); + "eth.dst == $svc_monitor_mac", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110, - svc_check_match, "next;"); - free(svc_check_match); + "eth.src == $svc_monitor_mac", "next;"); /* Allow all packets to go to next tables by default. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;"); @@ -5163,12 +5172,12 @@ bool vip_configured = false; for (int i = 0; i < od->nbs->n_load_balancer; i++) { struct nbrec_load_balancer *nb_lb = od->nbs->load_balancer[i]; - struct ovn_lb *lb = - ovn_lb_find(lbs, &nb_lb->header_.uuid); + struct ovn_northd_lb *lb = + ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); ovs_assert(lb); for (size_t j = 0; j < lb->n_vips; j++) { - struct lb_vip *lb_vip = &lb->vips[j]; + struct ovn_lb_vip *lb_vip = &lb->vips[j]; build_empty_lb_event_flow(od, lflows, lb_vip, nb_lb, S_SWITCH_IN_PRE_LB, meter_groups); @@ -5324,8 +5333,46 @@ } } +static const struct nbrec_meter* +fair_meter_lookup_by_name(const struct shash *meter_groups, + const char *meter_name) +{ + const struct nbrec_meter *nb_meter = + meter_name ? shash_find_data(meter_groups, meter_name) : NULL; + if (nb_meter) { + return (nb_meter->fair && *nb_meter->fair) ? nb_meter : NULL; + } + return NULL; +} + +static char* +alloc_acl_log_unique_meter_name(const struct nbrec_acl *acl) +{ + return xasprintf("%s__" UUID_FMT, + acl->meter, UUID_ARGS(&acl->header_.uuid)); +} + +static void +build_acl_log_meter(struct ds *actions, const struct nbrec_acl *acl, + const struct shash *meter_groups) +{ + if (!acl->meter) { + return; + } + + /* If ACL log meter uses a fair meter, use unique Meter name. */ + if (fair_meter_lookup_by_name(meter_groups, acl->meter)) { + char *meter_name = alloc_acl_log_unique_meter_name(acl); + ds_put_format(actions, "meter=\"%s\", ", meter_name); + free(meter_name); + } else { + ds_put_format(actions, "meter=\"%s\", ", acl->meter); + } +} + static void -build_acl_log(struct ds *actions, const struct nbrec_acl *acl) +build_acl_log(struct ds *actions, const struct nbrec_acl *acl, + const struct shash *meter_groups) { if (!acl->log) { return; @@ -5353,9 +5400,7 @@ ds_put_cstr(actions, "verdict=allow, "); } - if (acl->meter) { - ds_put_format(actions, "meter=\"%s\", ", acl->meter); - } + build_acl_log_meter(actions, acl, meter_groups); ds_chomp(actions, ' '); ds_chomp(actions, ','); @@ -5366,87 +5411,46 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, enum ovn_stage stage, struct nbrec_acl *acl, struct ds *extra_match, struct ds *extra_actions, - const struct ovsdb_idl_row *stage_hint) + const struct ovsdb_idl_row *stage_hint, + const struct shash *meter_groups) { struct ds match = DS_EMPTY_INITIALIZER; struct ds actions = DS_EMPTY_INITIALIZER; bool ingress = (stage == S_SWITCH_IN_ACL); - /* TCP */ - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip4 && tcp && (%s)", acl->match); - ds_put_format(&actions, "reg0 = 0; " - "eth.dst <-> eth.src; ip4.dst <-> ip4.src; " - "tcp_reset { outport <-> inport; %s };", - ingress ? "next(pipeline=egress,table=5);" - : "next(pipeline=ingress,table=20);"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions), stage_hint); - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip6 && tcp && (%s)", acl->match); - ds_put_format(&actions, "reg0 = 0; " - "eth.dst <-> eth.src; ip6.dst <-> ip6.src; " - "tcp_reset { outport <-> inport; %s };", - ingress ? "next(pipeline=egress,table=5);" - : "next(pipeline=ingress,table=20);"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions), stage_hint); + char *next_action = + xasprintf("next(pipeline=%s,table=%d);", + ingress ? "egress": "ingress", + ingress ? ovn_stage_get_table(S_SWITCH_OUT_QOS_MARK) + : ovn_stage_get_table(S_SWITCH_IN_L2_LKUP)); - /* IP traffic */ - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); if (extra_match->length > 0) { ds_put_format(&match, "(%s) && ", extra_match->string); } - ds_put_format(&match, "ip4 && (%s)", acl->match); + ds_put_cstr(&match, acl->match); + if (extra_actions->length > 0) { ds_put_format(&actions, "%s ", extra_actions->string); } + ds_put_format(&actions, "reg0 = 0; " - "icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; " - "outport <-> inport; %s };", - ingress ? "next(pipeline=egress,table=5);" - : "next(pipeline=ingress,table=20);"); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET, - ds_cstr(&match), ds_cstr(&actions), stage_hint); - ds_clear(&match); - ds_clear(&actions); - build_acl_log(&actions, acl); - if (extra_match->length > 0) { - ds_put_format(&match, "(%s) && ", extra_match->string); - } - ds_put_format(&match, "ip6 && (%s)", acl->match); - if (extra_actions->length > 0) { - ds_put_format(&actions, "%s ", extra_actions->string); - } - ds_put_format(&actions, "reg0 = 0; icmp6 { " - "eth.dst <-> eth.src; ip6.dst <-> ip6.src; " - "outport <-> inport; %s };", - ingress ? "next(pipeline=egress,table=5);" - : "next(pipeline=ingress,table=20);"); + "reject { " + "/* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ " + "outport <-> inport; %s };", next_action); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, ds_cstr(&match), ds_cstr(&actions), stage_hint); + free(next_action); ds_destroy(&match); ds_destroy(&actions); } static void consider_acl(struct hmap *lflows, struct ovn_datapath *od, - struct nbrec_acl *acl, bool has_stateful) + struct nbrec_acl *acl, bool has_stateful, + const struct shash *meter_groups) { bool ingress = !strcmp(acl->direction, "from-lport") ? true :false; enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL; @@ -5460,7 +5464,7 @@ * associated conntrack entry and would return "+invalid". */ if (!has_stateful) { struct ds actions = DS_EMPTY_INITIALIZER; - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "next;"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5486,7 +5490,7 @@ ds_put_format(&match, REGBIT_ACL_HINT_ALLOW_NEW " == 1 && (%s)", acl->match); ds_put_cstr(&actions, REGBIT_CONNTRACK_COMMIT" = 1; "); - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "next;"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5505,7 +5509,7 @@ ds_put_format(&match, REGBIT_ACL_HINT_ALLOW " == 1 && (%s)", acl->match); - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "next;"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5530,10 +5534,10 @@ ds_put_cstr(&match, REGBIT_ACL_HINT_DROP " == 1"); if (!strcmp(acl->action, "reject")) { build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions, &acl->header_); + &actions, &acl->header_, meter_groups); } else { ds_put_format(&match, " && (%s)", acl->match); - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "/* drop */"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5557,10 +5561,10 @@ ds_put_cstr(&actions, "ct_commit { ct_label.blocked = 1; }; "); if (!strcmp(acl->action, "reject")) { build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions, &acl->header_); + &actions, &acl->header_, meter_groups); } else { ds_put_format(&match, " && (%s)", acl->match); - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "/* drop */"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5573,9 +5577,9 @@ * logical flow action in all cases. */ if (!strcmp(acl->action, "reject")) { build_reject_acl_rules(od, lflows, stage, acl, &match, - &actions, &acl->header_); + &actions, &acl->header_, meter_groups); } else { - build_acl_log(&actions, acl); + build_acl_log(&actions, acl, meter_groups); ds_put_cstr(&actions, "/* drop */"); ovn_lflow_add_with_hint(lflows, od, stage, acl->priority + OVN_ACL_PRI_OFFSET, @@ -5655,7 +5659,7 @@ static void build_acls(struct ovn_datapath *od, struct hmap *lflows, - struct hmap *port_groups) + struct hmap *port_groups, const struct shash *meter_groups) { bool has_stateful = (has_stateful_acl(od) || has_lb_vip(od)); @@ -5758,13 +5762,14 @@ /* Ingress or Egress ACL Table (Various priorities). */ for (size_t i = 0; i < od->nbs->n_acls; i++) { struct nbrec_acl *acl = od->nbs->acls[i]; - consider_acl(lflows, od, acl, has_stateful); + consider_acl(lflows, od, acl, has_stateful, meter_groups); } struct ovn_port_group *pg; HMAP_FOR_EACH (pg, key_node, port_groups) { if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) { for (size_t i = 0; i < pg->nb_pg->n_acls; i++) { - consider_acl(lflows, od, pg->nb_pg->acls[i], has_stateful); + consider_acl(lflows, od, pg->nb_pg->acls[i], has_stateful, + meter_groups); } } } @@ -5839,17 +5844,13 @@ /* Add a 34000 priority flow to advance the service monitor reply * packets to skip applying ingress ACLs. */ - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000, svc_check_match, - "next;"); - free(svc_check_match); + ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000, + "eth.dst == $svc_monitor_mac", "next;"); /* Add a 34000 priority flow to advance the service monitor packets * generated by ovn-controller to skip applying egress ACLs. */ - svc_check_match = xasprintf("eth.src == %s", svc_monitor_mac); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000, svc_check_match, - "next;"); - free(svc_check_match); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000, + "eth.src == $svc_monitor_mac", "next;"); } static void @@ -5945,91 +5946,15 @@ } static void -build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - struct ovn_lb *lb, struct lb_vip *lb_vip, - const char *ip_match, const char *proto) -{ - if (lb_vip->n_backends == 0) { - return; - } - - struct ds action = DS_EMPTY_INITIALIZER; - struct ds match_initiator = DS_EMPTY_INITIALIZER; - struct ds match_reply = DS_EMPTY_INITIALIZER; - struct ds proto_match = DS_EMPTY_INITIALIZER; - - /* Ingress Pre-Hairpin table. - * - Priority 2: SNAT load balanced traffic that needs to be hairpinned: - * - Both SRC and DST IP match backend->ip and destination port - * matches backend->port. - * - Priority 1: unSNAT replies to hairpinned load balanced traffic. - * - SRC IP matches backend->ip, DST IP matches LB VIP and source port - * matches backend->port. - */ - ds_put_char(&match_reply, '('); - for (size_t i = 0; i < lb_vip->n_backends; i++) { - struct lb_vip_backend *backend = &lb_vip->backends[i]; - - /* Packets that after load balancing have equal source and - * destination IPs should be hairpinned. - */ - if (lb_vip->vip_port) { - ds_put_format(&proto_match, " && %s.dst == %"PRIu16, - proto, backend->port); - } - ds_put_format(&match_initiator, "(%s.src == %s && %s.dst == %s%s)", - ip_match, backend->ip, ip_match, backend->ip, - ds_cstr(&proto_match)); - - /* Replies to hairpinned traffic are originated by backend->ip:port. */ - ds_clear(&proto_match); - if (lb_vip->vip_port) { - ds_put_format(&proto_match, " && %s.src == %"PRIu16, proto, - backend->port); - } - ds_put_format(&match_reply, "(%s.src == %s%s)", ip_match, backend->ip, - ds_cstr(&proto_match)); - ds_clear(&proto_match); - - if (i < lb_vip->n_backends - 1) { - ds_put_cstr(&match_initiator, " || "); - ds_put_cstr(&match_reply, " || "); - } - } - ds_put_char(&match_reply, ')'); - - /* SNAT hairpinned initiator traffic so that the reply traffic is - * also directed through OVN. - */ - ds_put_format(&action, REGBIT_HAIRPIN " = 1; ct_snat(%s);", - lb_vip->vip); - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 2, - ds_cstr(&match_initiator), ds_cstr(&action), - &lb->nlb->header_); - - /* Replies to hairpinned traffic are destined to the LB VIP. */ - ds_put_format(&match_reply, " && %s.dst == %s", ip_match, lb_vip->vip); - - /* UNSNAT replies for hairpinned traffic. */ - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 1, - ds_cstr(&match_reply), - REGBIT_HAIRPIN " = 1; ct_snat;", - &lb->nlb->header_); - - ds_destroy(&action); - ds_destroy(&match_initiator); - ds_destroy(&match_reply); - ds_destroy(&proto_match); -} - -static void -build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, struct ovn_lb *lb) +build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, + struct ovn_northd_lb *lb) { for (size_t i = 0; i < lb->n_vips; i++) { - struct lb_vip *lb_vip = &lb->vips[i]; + struct ovn_lb_vip *lb_vip = &lb->vips[i]; + struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; const char *ip_match = NULL; - if (lb_vip->addr_family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ip_match = "ip4"; } else { ip_match = "ip6"; @@ -6049,10 +5974,12 @@ /* New connections in Ingress table. */ struct ds action = DS_EMPTY_INITIALIZER; - build_lb_vip_ct_lb_actions(lb_vip, &action, lb->selection_fields); + build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &action, + lb->selection_fields); struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, lb_vip->vip); + ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, + lb_vip->vip_str); if (lb_vip->vip_port) { ds_put_format(&match, " && %s.dst == %d", proto, lb_vip->vip_port); ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_STATEFUL, 120, @@ -6066,12 +5993,6 @@ ds_destroy(&match); ds_destroy(&action); - - /* Also install flows that allow hairpinning of traffic (i.e., if - * a load balancer VIP is DNAT-ed to a backend that happens to be - * the source of the traffic). - */ - build_lb_hairpin_rules(od, lflows, lb, lb_vip, ip_match, proto); } } @@ -6112,90 +6033,126 @@ * connection, so it is okay if we do not hit the above match on * REGBIT_CONNTRACK_COMMIT. */ for (int i = 0; i < od->nbs->n_load_balancer; i++) { - struct ovn_lb *lb = - ovn_lb_find(lbs, &od->nbs->load_balancer[i]->header_.uuid); + struct ovn_northd_lb *lb = + ovn_northd_lb_find(lbs, &od->nbs->load_balancer[i]->header_.uuid); ovs_assert(lb); build_lb_rules(od, lflows, lb); } +} - /* Ingress Pre-Hairpin table (Priority 0). Packets that don't need - * hairpinning should continue processing. +static void +build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) +{ + /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0). + * Packets that don't need hairpinning should continue processing. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 0, "1", "next;"); - - /* Ingress Hairpin table. - * - Priority 0: Packets that don't need hairpinning should continue - * processing. - * - Priority 1: Packets that were SNAT-ed for hairpinning should be - * looped back (i.e., swap ETH addresses and send back on inport). - */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1, REGBIT_HAIRPIN " == 1", - "eth.dst <-> eth.src;" - "outport = inport;" - "flags.loopback = 1;" - "output;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;"); + + if (has_lb_vip(od)) { + /* Check if the packet needs to be hairpinned. */ + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, + "ip && ct.trk && ct.dnat", + REGBIT_HAIRPIN " = chk_lb_hairpin(); next;", + &od->nbs->header_); + + /* Check if the packet is a reply of hairpinned traffic. */ + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90, "ip", + REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); " + "next;", &od->nbs->header_); + + /* If packet needs to be hairpinned, snat the src ip with the VIP. */ + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100, + "ip && (ct.new || ct.est) && ct.trk && ct.dnat" + " && "REGBIT_HAIRPIN " == 1", + "ct_snat_to_vip; next;", + &od->nbs->header_); + + /* For the reply of hairpinned traffic, snat the src ip to the VIP. */ + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90, + "ip && "REGBIT_HAIRPIN " == 1", "ct_snat;", + &od->nbs->header_); + + /* Ingress Hairpin table. + * - Priority 1: Packets that were SNAT-ed for hairpinning should be + * looped back (i.e., swap ETH addresses and send back on inport). + */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1, + REGBIT_HAIRPIN " == 1", + "eth.dst <-> eth.src;" + "outport = inport;" + "flags.loopback = 1;" + "output;"); + } } +/* Build logical flows for the forwarding groups */ static void build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows) { - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) { - const struct nbrec_forwarding_group *fwd_group = NULL; - fwd_group = od->nbs->forwarding_groups[i]; - if (!fwd_group->n_child_port) { - continue; - } + if (!(!od->nbs || !od->nbs->n_forwarding_groups)) { + struct ds match = DS_EMPTY_INITIALIZER; + struct ds actions = DS_EMPTY_INITIALIZER; + struct ds group_ports = DS_EMPTY_INITIALIZER; - /* ARP responder for the forwarding group's virtual IP */ - ds_put_format(&match, "arp.tpa == %s && arp.op == 1", - fwd_group->vip); - ds_put_format(&actions, - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " - "arp.tha = arp.sha; " - "arp.sha = %s; " - "arp.tpa = arp.spa; " - "arp.spa = %s; " - "outport = inport; " - "flags.loopback = 1; " - "output;", - fwd_group->vmac, fwd_group->vmac, fwd_group->vip); + for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) { + const struct nbrec_forwarding_group *fwd_group = NULL; + fwd_group = od->nbs->forwarding_groups[i]; + if (!fwd_group->n_child_port) { + continue; + } - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 50, - ds_cstr(&match), ds_cstr(&actions), - &fwd_group->header_); + /* ARP responder for the forwarding group's virtual IP */ + ds_put_format(&match, "arp.tpa == %s && arp.op == 1", + fwd_group->vip); + ds_put_format(&actions, + "eth.dst = eth.src; " + "eth.src = %s; " + "arp.op = 2; /* ARP reply */ " + "arp.tha = arp.sha; " + "arp.sha = %s; " + "arp.tpa = arp.spa; " + "arp.spa = %s; " + "outport = inport; " + "flags.loopback = 1; " + "output;", + fwd_group->vmac, fwd_group->vmac, fwd_group->vip); - /* L2 lookup for the forwarding group's virtual MAC */ - ds_clear(&match); - ds_put_format(&match, "eth.dst == %s", fwd_group->vmac); + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 50, + ds_cstr(&match), ds_cstr(&actions), + &fwd_group->header_); - /* Create a comma separated string of child ports */ - struct ds group_ports = DS_EMPTY_INITIALIZER; - if (fwd_group->liveness) { - ds_put_cstr(&group_ports, "liveness=\"true\","); - } - ds_put_cstr(&group_ports, "childports="); - for (i = 0; i < (fwd_group->n_child_port - 1); ++i) { - ds_put_format(&group_ports, "\"%s\",", fwd_group->child_port[i]); + /* L2 lookup for the forwarding group's virtual MAC */ + ds_clear(&match); + ds_put_format(&match, "eth.dst == %s", fwd_group->vmac); + + /* Create a comma separated string of child ports */ + ds_clear(&group_ports); + if (fwd_group->liveness) { + ds_put_cstr(&group_ports, "liveness=\"true\","); + } + ds_put_cstr(&group_ports, "childports="); + for (i = 0; i < (fwd_group->n_child_port - 1); ++i) { + ds_put_format(&group_ports, "\"%s\",", + fwd_group->child_port[i]); + } + ds_put_format(&group_ports, "\"%s\"", + fwd_group->child_port[fwd_group->n_child_port - 1]); + + ds_clear(&actions); + ds_put_format(&actions, "fwd_group(%s);", ds_cstr(&group_ports)); + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, 50, + ds_cstr(&match), ds_cstr(&actions), + &fwd_group->header_); } - ds_put_format(&group_ports, "\"%s\"", - fwd_group->child_port[fwd_group->n_child_port - 1]); - ds_clear(&actions); - ds_put_format(&actions, "fwd_group(%s);", ds_cstr(&group_ports)); - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, 50, - ds_cstr(&match), ds_cstr(&actions), - &fwd_group->header_); + ds_destroy(&match); + ds_destroy(&actions); + ds_destroy(&group_ports); } - - ds_destroy(&match); - ds_destroy(&actions); } static void @@ -6387,12 +6344,11 @@ sset_add(&all_eth_addrs, nat->external_mac); } - - /* Self originated (G)ARP requests/ND need to be flooded as usual. - * Determine that packets are self originated by also matching on - * source MAC. Matching on ingress port is not reliable in case this - * is a VLAN-backed network. - * Priority: 80. + /* Self originated ARP requests/ND need to be flooded to the L2 domain + * (except on router ports). Determine that packets are self originated + * by also matching on source MAC. Matching on ingress port is not + * reliable in case this is a VLAN-backed network. + * Priority: 75. */ const char *eth_addr; @@ -6406,9 +6362,9 @@ ds_put_format(&match, "eth.src == %s && (arp.op == 1 || nd_ns)", ds_cstr(ð_src)); - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority, - ds_cstr(&match), - "outport = \""MC_FLOOD"\"; output;"); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, priority, + ds_cstr(&match), + "outport = \""MC_FLOOD_L2"\"; output;"); sset_destroy(&all_eth_addrs); ds_destroy(ð_src); @@ -6454,16 +6410,22 @@ ds_chomp(&match, ','); ds_put_cstr(&match, "}"); - /* Send a the packet only to the router pipeline and skip flooding it - * in the broadcast domain (except for the localnet port). + /* Send a the packet to the router pipeline. If the switch has non-router + * ports then flood it there as well. */ - for (size_t i = 0; i < od->n_localnet_ports; i++) { - ds_put_format(&actions, "clone { outport = %s; output; }; ", - od->localnet_ports[i]->json_key); + if (od->n_router_ports != od->nbs->n_ports) { + ds_put_format(&actions, "clone {outport = %s; output; }; " + "outport = \""MC_FLOOD_L2"\"; output;", + patch_op->json_key); + ovn_lflow_add_unique_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, + priority, ds_cstr(&match), + ds_cstr(&actions), stage_hint); + } else { + ds_put_format(&actions, "outport = %s; output;", patch_op->json_key); + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority, + ds_cstr(&match), ds_cstr(&actions), + stage_hint); } - ds_put_format(&actions, "outport = %s; output;", patch_op->json_key); - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority, - ds_cstr(&match), ds_cstr(&actions), stage_hint); ds_destroy(&match); ds_destroy(&actions); @@ -6491,14 +6453,9 @@ return; } - /* Self originated (G)ARP requests/ND need to be flooded as usual. - * Priority: 80. - */ - build_lswitch_rport_arp_req_self_orig_flow(op, 80, sw_od, lflows); - /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this * router port. - * Priority: 75. + * Priority: 80. */ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); @@ -6573,17 +6530,28 @@ if (!sset_is_empty(&all_ips_v4)) { build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v4, AF_INET, sw_op, - sw_od, 75, lflows, + sw_od, 80, 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, 75, lflows, + 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 + * even try to flood. + * + * Priority: 75. + */ + if (sw_od->n_router_ports != sw_od->nbs->n_ports) { + build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lflows); + } } static void @@ -6779,73 +6747,30 @@ ds_destroy(&match); } +static bool +is_vlan_transparent(const struct ovn_datapath *od) +{ + return smap_get_bool(&od->nbs->other_config, "vlan-passthru", false); +} + static void build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct hmap *port_groups, struct hmap *lflows, - struct hmap *mcgroups, struct hmap *igmp_groups, - struct shash *meter_groups, - struct hmap *lbs) + struct hmap *lflows, struct hmap *mcgroups, + struct hmap *igmp_groups, struct hmap *lbs) { /* This flow table structure is documented in ovn-northd(8), so please * update ovn-northd.8.xml if you change anything. */ struct ds match = DS_EMPTY_INITIALIZER; struct ds actions = DS_EMPTY_INITIALIZER; - - /* Build pre-ACL and ACL tables for both ingress and egress. - * Ingress tables 3 through 10. Egress tables 0 through 7. */ struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - build_pre_acls(od, lflows); - build_pre_lb(od, lflows, meter_groups, lbs); - build_pre_stateful(od, lflows); - build_acl_hints(od, lflows); - build_acls(od, lflows, port_groups); - build_qos(od, lflows); - build_lb(od, lflows); - build_stateful(od, lflows, lbs); - } - - /* Build logical flows for the forwarding groups */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs || !od->nbs->n_forwarding_groups) { - continue; - } - - build_fwd_group_lflows(od, lflows); - } - - /* Logical switch ingress table 0: Admission control framework (priority - * 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; - } - - /* Logical VLANs not supported. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present", - "drop;"); - - /* Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", - "drop;"); - - /* Port security flows have priority 50 (see below) and will continue - * to the next table if packet source is acceptable. */ - } - - build_lswitch_input_port_sec(ports, datapaths, lflows); - - /* Ingress table 13: ARP/ND responder, skip requests coming from localnet - * and vtep ports. (priority 100); see ovn-northd.8.xml for the - * rationale. */ - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp) { + + /* Ingress table 13: ARP/ND responder, skip requests coming from localnet + * and vtep ports. (priority 100); see ovn-northd.8.xml for the + * rationale. */ + struct ovn_port *op; + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbsp) { continue; } @@ -6923,7 +6848,7 @@ * - port type is localport */ if (check_lsp_is_up && - !lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") && + !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) && strcmp(op->nbsp->type, "localport")) { continue; } @@ -6998,8 +6923,7 @@ "flags.loopback = 1; " "output; " "};", - !strcmp(op->nbsp->type, "router") ? - "nd_na_router" : "nd_na", + lsp_is_router(op->nbsp) ? "nd_na_router" : "nd_na", op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ipv6_addrs[j].addr_s, op->lsp_addrs[i].ipv6_addrs[j].addr_s, @@ -7034,22 +6958,24 @@ /* Ingress table 13: ARP/ND responder for service monitor source ip. * (priority 110)*/ - struct ovn_lb *lb; + struct ovn_northd_lb *lb; HMAP_FOR_EACH (lb, hmap_node, lbs) { for (size_t i = 0; i < lb->n_vips; i++) { - if (!lb->vips[i].health_check) { + 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->vips[i].n_backends; j++) { - if (!lb->vips[i].backends[j].op || - !lb->vips[i].backends[j].svc_mon_src_ip) { + for (size_t j = 0; j < lb_vip_nb->n_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) { continue; } ds_clear(&match); ds_put_format(&match, "arp.tpa == %s && arp.op == 1", - lb->vips[i].backends[j].svc_mon_src_ip); + backend_nb->svc_mon_src_ip); ds_clear(&actions); ds_put_format(&actions, "eth.dst = eth.src; " @@ -7063,9 +6989,9 @@ "flags.loopback = 1; " "output;", svc_monitor_mac, svc_monitor_mac, - lb->vips[i].backends[j].svc_mon_src_ip); + backend_nb->svc_mon_src_ip); ovn_lflow_add_with_hint(lflows, - lb->vips[i].backends[j].op->od, + backend_nb->op->od, S_SWITCH_IN_ARP_ND_RSP, 110, ds_cstr(&match), ds_cstr(&actions), &lb->nlb->header_); @@ -7081,7 +7007,7 @@ continue; } - if (!lsp_is_enabled(op->nbsp) || !strcmp(op->nbsp->type, "router")) { + if (!lsp_is_enabled(op->nbsp) || lsp_is_router(op->nbsp)) { /* Don't add the DHCP flows if the port is not enabled or if the * port is a router port. */ continue; @@ -7180,7 +7106,6 @@ } } - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); /* Ingress table 19: Destination lookup, broadcast and multicast handling * (priority 70 - 100). */ HMAP_FOR_EACH (od, key_node, datapaths) { @@ -7188,7 +7113,8 @@ continue; } - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, svc_check_match, + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, + "eth.dst == $svc_monitor_mac", "handle_svc_check(inport);"); struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw; @@ -7204,26 +7130,26 @@ } ds_put_cstr(&actions, "igmp;"); /* Punt IGMP traffic to controller. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, - "ip4 && ip.proto == 2", ds_cstr(&actions)); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100, + "ip4 && ip.proto == 2", ds_cstr(&actions)); /* Punt MLD traffic to controller. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, - "mldv1 || mldv2", ds_cstr(&actions)); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100, + "mldv1 || mldv2", ds_cstr(&actions)); /* Flood all IP multicast traffic destined to 224.0.0.X to all * ports - RFC 4541, section 2.1.2, item 2. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85, - "ip4.mcast && ip4.dst == 224.0.0.0/24", - "outport = \""MC_FLOOD"\"; output;"); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 85, + "ip4.mcast && ip4.dst == 224.0.0.0/24", + "outport = \""MC_FLOOD"\"; output;"); /* Flood all IPv6 multicast traffic destined to reserved * multicast IPs (RFC 4291, 2.7.1). */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85, - "ip6.mcast_flood", - "outport = \""MC_FLOOD"\"; output;"); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 85, + "ip6.mcast_flood", + "outport = \""MC_FLOOD"\"; output;"); /* Forward uregistered IP multicast to routers with relay enabled * and to any ports configured to flood IP multicast traffic. @@ -7253,15 +7179,15 @@ ds_put_cstr(&actions, "drop;"); } - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80, - "ip4.mcast || ip6.mcast", ds_cstr(&actions)); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 80, + "ip4.mcast || ip6.mcast", + ds_cstr(&actions)); } } - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast", - "outport = \""MC_FLOOD"\"; output;"); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast", + "outport = \""MC_FLOOD"\"; output;"); } - free(svc_check_match); /* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD * (priority 90). */ @@ -7327,8 +7253,8 @@ ds_put_format(&actions, "outport = \"%s\"; output; ", igmp_group->mcgroup.name); - ovn_lflow_add(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, 90, - ds_cstr(&match), ds_cstr(&actions)); + ovn_lflow_add_unique(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, + 90, ds_cstr(&match), ds_cstr(&actions)); } /* Ingress table 19: Destination lookup, unicast handling (priority 50), */ @@ -7341,7 +7267,7 @@ * broadcast flooding of ARP/ND requests in table 19. We direct the * requests only to the router port that owns the IP address. */ - if (!strcmp(op->nbsp->type, "router")) { + if (lsp_is_router(op->nbsp)) { build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows, &op->nbsp->header_); } @@ -7401,7 +7327,7 @@ /* The peer of this port represents a distributed * gateway port. The destination lookup flow for the * router's distributed gateway port MAC address should - * only be programmed on the "redirect-chassis". */ + * only be programmed on the gateway chassis. */ add_chassis_resident_check = true; } else { /* Check if the option 'reside-on-redirect-chassis' @@ -7474,8 +7400,8 @@ } if (od->has_unknown) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", - "outport = \""MC_UNKNOWN"\"; output;"); + ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", + "outport = \""MC_UNKNOWN"\"; output;"); } } @@ -7485,6 +7411,53 @@ ds_destroy(&actions); } +/* Build pre-ACL and ACL tables for both ingress and egress. + * Ingress tables 3 through 10. Egress tables 0 through 7. */ +static void +build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od, + struct hmap *port_groups, + struct hmap *lflows, + struct shash *meter_groups, + struct hmap *lbs) +{ + if (od->nbs) { + build_pre_acls(od, lflows); + build_pre_lb(od, lflows, meter_groups, lbs); + build_pre_stateful(od, lflows); + build_acl_hints(od, lflows); + build_acls(od, lflows, port_groups, meter_groups); + build_qos(od, lflows); + build_lb(od, lflows); + build_stateful(od, lflows, lbs); + build_lb_hairpin(od, lflows); + } +} + +/* Logical switch ingress table 0: Admission control framework (priority + * 100). */ +static void +build_lswitch_lflows_admission_control(struct ovn_datapath *od, + struct hmap *lflows) +{ + if (od->nbs) { + /* Logical VLANs not supported. */ + if (!is_vlan_transparent(od)) { + /* Block logical VLANs. */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, + "vlan.present", "drop;"); + } + + /* Broadcast/multicast source address is invalid. */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", + "drop;"); + + /* Port security flows have priority 50 + * (see build_lswitch_input_port_sec()) and will continue + * to the next table if packet source is acceptable. */ + } +} + + /* Returns a string of the IP address of the router port 'op' that * overlaps with 'ip_s". If one is not found, returns NULL. * @@ -7627,7 +7600,7 @@ struct parsed_route { struct ovs_list list_node; - struct v46_ip prefix; + struct in6_addr prefix; unsigned int plen; bool is_src_route; uint32_t hash; @@ -7649,7 +7622,7 @@ const struct nbrec_logical_router_static_route *route) { /* Verify that the next hop is an IP address with an all-ones mask. */ - struct v46_ip nexthop; + struct in6_addr nexthop; unsigned int plen; if (!ip46_parse_cidr(route->nexthop, &nexthop, &plen)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); @@ -7658,8 +7631,8 @@ UUID_ARGS(&route->header_.uuid)); return NULL; } - if ((nexthop.family == AF_INET && plen != 32) || - (nexthop.family == AF_INET6 && plen != 128)) { + if ((IN6_IS_ADDR_V4MAPPED(&nexthop) && plen != 32) || + (!IN6_IS_ADDR_V4MAPPED(&nexthop) && plen != 128)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "bad next hop mask %s in static route" UUID_FMT, route->nexthop, @@ -7668,7 +7641,7 @@ } /* Parse ip_prefix */ - struct v46_ip prefix; + struct in6_addr prefix; if (!ip46_parse_cidr(route->ip_prefix, &prefix, &plen)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "bad 'ip_prefix' %s in static route" @@ -7678,7 +7651,7 @@ } /* Verify that ip_prefix and nexthop have same address familiy. */ - if (prefix.family != nexthop.family) { + if (IN6_IS_ADDR_V4MAPPED(&prefix) != IN6_IS_ADDR_V4MAPPED(&nexthop)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "Address family doesn't match between 'ip_prefix' %s" " and 'nexthop' %s in static route"UUID_FMT, @@ -7719,7 +7692,7 @@ struct ecmp_groups_node { struct hmap_node hmap_node; /* In ecmp_groups */ uint16_t id; /* starts from 1 */ - struct v46_ip prefix; + struct in6_addr prefix; unsigned int plen; bool is_src_route; uint16_t route_count; @@ -7770,7 +7743,7 @@ { struct ecmp_groups_node *eg; HMAP_FOR_EACH_WITH_HASH (eg, hmap_node, route->hash, ecmp_groups) { - if (ip46_equals(&eg->prefix, &route->prefix) && + if (ipv6_addr_equals(&eg->prefix, &route->prefix) && eg->plen == route->plen && eg->is_src_route == route->is_src_route) { return eg; @@ -7817,7 +7790,7 @@ { struct unique_routes_node *ur; HMAP_FOR_EACH_WITH_HASH (ur, hmap_node, route->hash, unique_routes) { - if (ip46_equals(&route->prefix, &ur->route->prefix) && + if (ipv6_addr_equals(&route->prefix, &ur->route->prefix) && route->plen == ur->route->plen && route->is_src_route == ur->route->is_src_route) { hmap_remove(unique_routes, &ur->hmap_node); @@ -7841,15 +7814,15 @@ } static char * -build_route_prefix_s(const struct v46_ip *prefix, unsigned int plen) +build_route_prefix_s(const struct in6_addr *prefix, unsigned int plen) { char *prefix_s; - if (prefix->family == AF_INET) { - prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix->ipv4 & + if (IN6_IS_ADDR_V4MAPPED(prefix)) { + prefix_s = xasprintf(IP_FMT, IP_ARGS(in6_addr_get_mapped_ipv4(prefix) & be32_prefix_mask(plen))); } else { struct in6_addr mask = ipv6_create_mask(plen); - struct in6_addr network = ipv6_addr_bitand(&prefix->ipv6, &mask); + struct in6_addr network = ipv6_addr_bitand(prefix, &mask); prefix_s = xmalloc(INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &network, prefix_s, INET6_ADDRSTRLEN); } @@ -7963,9 +7936,10 @@ */ ds_put_format(&match, "inport == %s && ip%s.%s == %s", out_port->json_key, - route->prefix.family == AF_INET ? "4" : "6", + IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "4" : "6", route->is_src_route ? "dst" : "src", cidr); + free(cidr); ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100, ds_cstr(&match), "ct_next;", &st_route->header_); @@ -8002,7 +7976,7 @@ ds_put_format(&actions, "ip.ttl--; flags.loopback = 1; " "eth.src = %s; %sreg1 = %s; outport = %s; next;", out_port->lrp_networks.ea_s, - route->prefix.family == AF_INET ? "" : "xx", + 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, ds_cstr(&match), ds_cstr(&actions), @@ -8017,6 +7991,10 @@ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 200, ds_cstr(&ecmp_reply), action, &st_route->header_); + + ds_destroy(&match); + ds_destroy(&actions); + ds_destroy(&ecmp_reply); } static void @@ -8024,7 +8002,7 @@ struct hmap *ports, struct ecmp_groups_node *eg) { - bool is_ipv4 = (eg->prefix.family == AF_INET); + bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(&eg->prefix); uint16_t priority; struct ecmp_route_list_node *er; struct ds route_match = DS_EMPTY_INITIALIZER; @@ -8158,13 +8136,13 @@ { const char *lrp_addr_s = NULL; struct ovn_port *out_port = NULL; - bool is_ipv4 = (route_->prefix.family == AF_INET); const struct nbrec_logical_router_static_route *route = route_->route; /* Find the outgoing port. */ - if (!find_static_route_outport(od, ports, route, is_ipv4, &lrp_addr_s, - &out_port)) { + if (!find_static_route_outport(od, ports, route, + IN6_IS_ADDR_V4MAPPED(&route_->prefix), + &lrp_addr_s, &out_port)) { return; } @@ -8243,7 +8221,7 @@ static void add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, struct ds *match, struct ds *actions, int priority, - bool lb_force_snat_ip, struct lb_vip *lb_vip, + bool lb_force_snat_ip, struct ovn_lb_vip *lb_vip, const char *proto, struct nbrec_load_balancer *lb, struct shash *meter_groups, struct sset *nat_entries) { @@ -8279,13 +8257,13 @@ free(est_match); const char *ip_match = NULL; - if (lb_vip->addr_family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ip_match = "ip4"; } else { ip_match = "ip6"; } - if (sset_contains(nat_entries, lb_vip->vip)) { + if (sset_contains(nat_entries, lb_vip->vip_str)) { /* The load balancer vip is also present in the NAT entries. * So add a high priority lflow to advance the the packet * destined to the vip (and the vip port if defined) @@ -8299,7 +8277,7 @@ * S_ROUTER_IN_DNAT stage. */ struct ds unsnat_match = DS_EMPTY_INITIALIZER; ds_put_format(&unsnat_match, "%s && %s.dst == %s && %s", - ip_match, ip_match, lb_vip->vip, proto); + ip_match, ip_match, lb_vip->vip_str, proto); if (lb_vip->vip_port) { ds_put_format(&unsnat_match, " && %s.dst == %d", proto, lb_vip->vip_port); @@ -8323,8 +8301,9 @@ ds_put_format(&undnat_match, "%s && (", ip_match); for (size_t i = 0; i < lb_vip->n_backends; i++) { - struct lb_vip_backend *backend = &lb_vip->backends[i]; - ds_put_format(&undnat_match, "(%s.src == %s", ip_match, backend->ip); + struct ovn_lb_backend *backend = &lb_vip->backends[i]; + ds_put_format(&undnat_match, "(%s.src == %s", ip_match, + backend->ip_str); if (backend->port) { ds_put_format(&undnat_match, " && %s.src == %d) || ", @@ -8636,6 +8615,153 @@ } static void +build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od, + struct ovn_nat *nat_entry, + struct hmap *lflows) +{ + struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; + const struct nbrec_nat *nat = nat_entry->nb; + + if (nat_entry_is_v6(nat_entry)) { + build_lrouter_nd_flow(od, NULL, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + REG_INPORT_ETH_ADDR, NULL, false, 90, + &nat->header_, lflows); + } else { + build_lrouter_arp_flow(od, NULL, + ext_addrs->ipv4_addrs[0].addr_s, + REG_INPORT_ETH_ADDR, NULL, false, 90, + &nat->header_, lflows); + } +} + +static void +build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op, + struct ovn_nat *nat_entry, + struct hmap *lflows) +{ + struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; + const struct nbrec_nat *nat = nat_entry->nb; + struct ds match = DS_EMPTY_INITIALIZER; + + /* Mac address to use when replying to ARP/NS. */ + const char *mac_s = REG_INPORT_ETH_ADDR; + struct eth_addr mac; + + if (nat->external_mac && + eth_addr_from_string(nat->external_mac, &mac) + && nat->logical_port) { + /* distributed NAT case, use nat->external_mac */ + mac_s = nat->external_mac; + /* Traffic with eth.src = nat->external_mac should only be + * sent from the chassis where nat->logical_port is + * resident, so that upstream MAC learning points to the + * correct chassis. Also need to avoid generation of + * multiple ARP responses from different chassis. */ + ds_put_format(&match, "is_chassis_resident(\"%s\")", + nat->logical_port); + } else { + mac_s = REG_INPORT_ETH_ADDR; + /* 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. */ + if (op->od->l3redirect_port) { + ds_put_format(&match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + } + + /* Respond to ARP/NS requests on the chassis that binds the gw + * port. Drop the ARP/NS requests on other chassis. + */ + if (nat_entry_is_v6(nat_entry)) { + build_lrouter_nd_flow(op->od, op, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + mac_s, &match, false, 92, + &nat->header_, lflows); + build_lrouter_nd_flow(op->od, op, "nd_na", + ext_addrs->ipv6_addrs[0].addr_s, + ext_addrs->ipv6_addrs[0].sn_addr_s, + mac_s, NULL, true, 91, + &nat->header_, lflows); + } else { + build_lrouter_arp_flow(op->od, op, + ext_addrs->ipv4_addrs[0].addr_s, + mac_s, &match, false, 92, + &nat->header_, lflows); + build_lrouter_arp_flow(op->od, op, + ext_addrs->ipv4_addrs[0].addr_s, + mac_s, NULL, true, 91, + &nat->header_, lflows); + } + + ds_destroy(&match); +} + +static void +build_lrouter_drop_own_dest(struct ovn_port *op, enum ovn_stage stage, + uint16_t priority, bool drop_snat_ip, + struct hmap *lflows) +{ + struct ds match_ips = DS_EMPTY_INITIALIZER; + + if (op->lrp_networks.n_ipv4_addrs) { + for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + const char *ip = op->lrp_networks.ipv4_addrs[i].addr_s; + + bool router_ip_in_snat_ips = !!shash_find(&op->od->snat_ips, ip); + bool drop_router_ip = (drop_snat_ip == router_ip_in_snat_ips); + + if (drop_router_ip) { + ds_put_format(&match_ips, "%s, ", ip); + } + } + + if (ds_last(&match_ips) != EOF) { + ds_chomp(&match_ips, ' '); + ds_chomp(&match_ips, ','); + + char *match = xasprintf("ip4.dst == {%s}", ds_cstr(&match_ips)); + ovn_lflow_add_with_hint(lflows, op->od, stage, priority, + match, "drop;", + &op->nbrp->header_); + free(match); + } + } + + if (op->lrp_networks.n_ipv6_addrs) { + ds_clear(&match_ips); + + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + const char *ip = op->lrp_networks.ipv6_addrs[i].addr_s; + + bool router_ip_in_snat_ips = !!shash_find(&op->od->snat_ips, ip); + bool drop_router_ip = (drop_snat_ip == router_ip_in_snat_ips); + + if (drop_router_ip) { + ds_put_format(&match_ips, "%s, ", ip); + } + } + + if (ds_last(&match_ips) != EOF) { + ds_chomp(&match_ips, ' '); + ds_chomp(&match_ips, ','); + + char *match = xasprintf("ip6.dst == {%s}", ds_cstr(&match_ips)); + ovn_lflow_add_with_hint(lflows, op->od, stage, priority, + match, "drop;", + &op->nbrp->header_); + free(match); + } + } + ds_destroy(&match_ips); +} + +static void build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, const char *ip_version, const char *ip_addr, const char *context) @@ -8661,101 +8787,6 @@ ds_destroy(&actions); } - -/* Logical router ingress table 0: Admission control framework. */ -static void -build_adm_ctrl_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows); - -/* Logical router ingress table 0: Admission control framework. */ -static void -build_adm_ctrl_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Logical router ingress table 1: LOOKUP_NEIGHBOR and - * table 2: LEARN_NEIGHBOR. */ -static void -build_neigh_learning_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions); - -static void -build_neigh_learning_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -* responder. */ -static void -build_ND_RA_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows); - -/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router - * Adv (RA) options and response. */ -static void -build_ND_RA_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Logical router ingress table IP_ROUTING: IP Routing. - */ -static void -build_ip_routing_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows); - -/* Convert the static routes to flows. */ -static void -build_static_route_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct hmap *ports); - -static void -build_mcast_lookup_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Logical router ingress table POLICY. */ -static void -build_ingress_policy_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct hmap *ports); - -/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ -static void -build_arp_resolve_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows); -static void -build_arp_resolve_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct hmap *ports, - struct ds *match, struct ds *actions); - -/* Local router ingress table CHK_PKT_LEN: Check packet length. */ -static void -build_check_pkt_len_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct hmap *ports, - struct ds *match, struct ds *actions); - -/* Logical router ingress table GW_REDIRECT: Gateway redirect. */ -static void -build_gateway_redirect_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Local router ingress table ARP_REQUEST: ARP request. */ -static void -build_arp_request_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions); - -/* Logical router egress table DELIVERY: Delivery (priority 100-110). */ -static void -build_egress_delivery_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions); - static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups, @@ -8768,183 +8799,52 @@ struct ds actions = DS_EMPTY_INITIALIZER; struct ovn_datapath *od; - HMAP_FOR_EACH (od, key_node, datapaths) { - build_adm_ctrl_flows_for_lrouter(od, lflows); - } - struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - build_adm_ctrl_flows_for_lrouter_port(op, lflows, &match, &actions); - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_neigh_learning_flows_for_lrouter( - od, lflows, &match, &actions); - } - - HMAP_FOR_EACH (op, key_node, ports) { - build_neigh_learning_flows_for_lrouter_port( - op, lflows, &match, &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. - */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->lrp_networks.n_ipv4_addrs) { - ds_clear(&match); - for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - if (!sset_find(&op->od->snat_ips, - op->lrp_networks.ipv4_addrs[i].addr_s)) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv4_addrs[i].addr_s); - } - - if (ds_last(&match) != EOF) { - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - - char *drop_match = xasprintf("ip4.dst == {%s}", - ds_cstr(&match)); - /* Drop traffic with IP.dest == router-ip. */ - ovn_lflow_add_with_hint(lflows, op->od, - S_ROUTER_IN_ARP_RESOLVE, 1, - drop_match, "drop;", - &op->nbrp->header_); - free(drop_match); - } - } - - if (op->lrp_networks.n_ipv6_addrs) { - ds_clear(&match); - for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - if (!sset_find(&op->od->snat_ips, - op->lrp_networks.ipv6_addrs[i].addr_s)) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv6_addrs[i].addr_s); - } - - if (ds_last(&match) != EOF) { - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - - char *drop_match = xasprintf("ip6.dst == {%s}", - ds_cstr(&match)); - /* Drop traffic with IP.dest == router-ip. */ - ovn_lflow_add_with_hint(lflows, op->od, - S_ROUTER_IN_ARP_RESOLVE, 1, - drop_match, "drop;", - &op->nbrp->header_); - free(drop_match); - } - } - } HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbr) { continue; } - /* L3 admission control: drop multicast and broadcast source, localhost - * source or destination, and zero network source or destination - * (priority 100). */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100, - "ip4.src_mcast ||" - "ip4.src == 255.255.255.255 || " - "ip4.src == 127.0.0.0/8 || " - "ip4.dst == 127.0.0.0/8 || " - "ip4.src == 0.0.0.0/8 || " - "ip4.dst == 0.0.0.0/8", - "drop;"); - /* 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. + * + * Priority 91 and 92 flows are added for each gateway router + * port to handle the special cases. In case we get the packet + * on a regular port, just reply with the port's ETH address. */ - struct sset snat_ips = SSET_INITIALIZER(&snat_ips); for (int i = 0; i < od->nbr->n_nat; i++) { struct ovn_nat *nat_entry = &od->nat_entries[i]; - const struct nbrec_nat *nat = nat_entry->nb; /* Skip entries we failed to parse. */ if (!nat_entry_is_valid(nat_entry)) { continue; } - struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; - char *ext_addr = nat_entry_is_v6(nat_entry) ? - ext_addrs->ipv6_addrs[0].addr_s : - ext_addrs->ipv4_addrs[0].addr_s; - - if (!strcmp(nat->type, "snat")) { - if (!sset_add(&snat_ips, ext_addr)) { - 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); + } - /* Priority 91 and 92 flows are added for each gateway router - * port to handle the special cases. In case we get the packet - * on a regular port, just reply with the port's ETH address. - */ - if (nat_entry_is_v6(nat_entry)) { - build_lrouter_nd_flow(od, NULL, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - REG_INPORT_ETH_ADDR, NULL, false, 90, - &nat->header_, lflows); - } else { - build_lrouter_arp_flow(od, NULL, - ext_addrs->ipv4_addrs[0].addr_s, - REG_INPORT_ETH_ADDR, NULL, false, 90, - &nat->header_, lflows); - } - } - sset_destroy(&snat_ips); - - /* 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 - * IPs are handled with priority-90 flows. - */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, - "arp || nd", "drop;"); - - /* Allow IPv6 multicast traffic that's supposed to reach the - * router pipeline (e.g., router solicitations). - */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra", - "next;"); - - /* Drop other reserved multicast. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83, - "ip6.mcast_rsvd", "drop;"); - - /* 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;"); + /* 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; - /* TTL discard */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, - "ip4 && ip.ttl == {0, 1}", "drop;"); + if (ovs_list_is_empty(&snat_ip->snat_entries)) { + continue; + } - /* 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;"); + 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); + } } /* Logical router ingress table 3: IP Input for IPv4. */ @@ -9027,8 +8927,8 @@ 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 "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". + * 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; @@ -9139,296 +9039,58 @@ } } - /* A gateway router can have 4 SNAT IP addresses to force DNATed and - * LBed traffic respectively to be SNATed. In addition, there can be - * a number of SNAT rules in the NAT table. - * Skip all of them for drop flows. */ - ds_clear(&match); - ds_put_cstr(&match, "ip4.dst == {"); - bool has_drop_ips = false; - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - if (sset_find(&op->od->snat_ips, - op->lrp_networks.ipv4_addrs[i].addr_s)) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv4_addrs[i].addr_s); - has_drop_ips = true; - } - if (has_drop_ips) { - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "} || ip6.dst == {"); - } else { - ds_clear(&match); - ds_put_cstr(&match, "ip6.dst == {"); - } - - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - if (sset_find(&op->od->snat_ips, - op->lrp_networks.ipv6_addrs[i].addr_s)) { - continue; - } - ds_put_format(&match, "%s, ", - op->lrp_networks.ipv6_addrs[i].addr_s); - has_drop_ips = true; - } - - ds_chomp(&match, ' '); - ds_chomp(&match, ','); - ds_put_cstr(&match, "}"); - - if (has_drop_ips) { - /* Drop IP traffic to this router. */ - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60, - ds_cstr(&match), "drop;", - &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); - /* ARP/NS packets are taken care of per router. The only exception - * is on the l3dgw_port where we might need to use a different - * ETH address. + /* 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; } - struct sset sset_snat_ips = SSET_INITIALIZER(&sset_snat_ips); for (size_t i = 0; i < op->od->nbr->n_nat; i++) { struct ovn_nat *nat_entry = &op->od->nat_entries[i]; - const struct nbrec_nat *nat = nat_entry->nb; /* Skip entries we failed to parse. */ if (!nat_entry_is_valid(nat_entry)) { continue; } - struct lport_addresses *ext_addrs = &nat_entry->ext_addrs; - char *ext_addr = (nat_entry_is_v6(nat_entry) - ? ext_addrs->ipv6_addrs[0].addr_s - : ext_addrs->ipv4_addrs[0].addr_s); - if (!strcmp(nat->type, "snat")) { - if (!sset_add(&sset_snat_ips, ext_addr)) { - continue; - } - } - - /* Mac address to use when replying to ARP/NS. */ - const char *mac_s = REG_INPORT_ETH_ADDR; - - /* ARP / ND handling for external IP addresses. - * - * DNAT IP addresses are external IP addresses that need ARP - * handling. */ - - struct eth_addr mac; - - ds_clear(&match); - if (nat->external_mac && - eth_addr_from_string(nat->external_mac, &mac) - && nat->logical_port) { - /* distributed NAT case, use nat->external_mac */ - mac_s = nat->external_mac; - /* Traffic with eth.src = nat->external_mac should only be - * sent from the chassis where nat->logical_port is - * resident, so that upstream MAC learning points to the - * correct chassis. Also need to avoid generation of - * multiple ARP responses from different chassis. */ - ds_put_format(&match, "is_chassis_resident(\"%s\")", - nat->logical_port); - } else { - mac_s = REG_INPORT_ETH_ADDR; - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ARP responses - * from different chassis. */ - if (op->od->l3redirect_port) { - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); - } - } - - /* Respond to ARP/NS requests on the chassis that binds the gw - * port. Drop the ARP/NS requests on other chassis. + /* Skip SNAT entries for now, we handle unique SNAT IPs separately + * below. */ - if (nat_entry_is_v6(nat_entry)) { - build_lrouter_nd_flow(op->od, op, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - mac_s, &match, false, 92, - &nat->header_, lflows); - build_lrouter_nd_flow(op->od, op, "nd_na", - ext_addrs->ipv6_addrs[0].addr_s, - ext_addrs->ipv6_addrs[0].sn_addr_s, - mac_s, NULL, true, 91, - &nat->header_, lflows); - } else { - build_lrouter_arp_flow(op->od, op, - ext_addrs->ipv4_addrs[0].addr_s, - mac_s, &match, false, 92, - &nat->header_, lflows); - build_lrouter_arp_flow(op->od, op, - ext_addrs->ipv4_addrs[0].addr_s, - mac_s, NULL, true, 91, - &nat->header_, lflows); - } - } - sset_destroy(&sset_snat_ips); - } - - /* DHCPv6 reply handling */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - continue; - } - - for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(&match); - ds_put_format(&match, "ip6.dst == %s && udp.src == 547 &&" - " udp.dst == 546", - op->lrp_networks.ipv6_addrs[i].addr_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, - ds_cstr(&match), - "reg0 = 0; handle_dhcpv6_reply;"); - } - } - - /* Logical router ingress table 1: IP Input for IPv6. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - - if (op->derived) { - /* No ingress packets are accepted on a chassisredirect - * port, so no need to program flows for that port. */ - continue; - } - - if (op->lrp_networks.n_ipv6_addrs) { - /* ICMPv6 echo reply. These flows reply to echo requests - * received for the router's IP address. */ - ds_clear(&match); - ds_put_cstr(&match, "ip6.dst == "); - op_put_v6_networks(&match, op); - ds_put_cstr(&match, " && icmp6.type == 128 && icmp6.code == 0"); - - const char *lrp_actions = - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 129; " - "flags.loopback = 1; " - "next; "; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, - ds_cstr(&match), lrp_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++) { - ds_clear(&match); - if (op->od->l3dgw_port && op == op->od->l3dgw_port - && op->od->l3redirect_port) { - /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s - * should only be sent from the "redirect-chassis", so that - * upstream MAC learning points to the "redirect-chassis". - * Also need to avoid generation of multiple ND replies - * from different chassis. */ - ds_put_format(&match, "is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); + if (!strcmp(nat_entry->nb->type, "snat")) { + continue; } - - 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); + build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); } - /* UDP/TCP 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, - "ip6 && ip6.dst == %s && !ip.later_frag && tcp", - op->lrp_networks.ipv6_addrs[i].addr_s); - const char *action = "tcp_reset {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag && udp", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 4; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 80, ds_cstr(&match), action, - &op->nbrp->header_); - - ds_clear(&match); - ds_put_format(&match, - "ip6 && ip6.dst == %s && !ip.later_frag", - op->lrp_networks.ipv6_addrs[i].addr_s); - action = "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst <-> ip6.src; " - "ip.ttl = 255; " - "icmp6.type = 1; " - "icmp6.code = 3; " - "next; };"; - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, - 70, ds_cstr(&match), action, - &op->nbrp->header_); - } - } + /* 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; - /* ICMPv6 time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - /* skip link-local address */ - if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { + if (ovs_list_is_empty(&snat_ip->snat_entries)) { continue; } - ds_clear(&match); - ds_clear(&actions); - - ds_put_format(&match, - "inport == %s && ip6 && " - "ip6.src == %s/%d && " - "ip.ttl == {0, 1} && !ip.later_frag", - op->json_key, - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen); - ds_put_format(&actions, - "icmp6 {" - "eth.dst <-> eth.src; " - "ip6.dst = ip6.src; " - "ip6.src = %s; " - "ip.ttl = 255; " - "icmp6.type = 3; /* Time exceeded */ " - "icmp6.code = 0; /* TTL exceeded in transit */ " - "next; };", - op->lrp_networks.ipv6_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_); + 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); } } @@ -9453,7 +9115,7 @@ 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 "redirect-chassis" + * l3dgw_port (router has a port with gateway chassis * specified). */ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { continue; @@ -9461,12 +9123,10 @@ struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - struct lport_addresses dnat_force_snat_addrs; - struct lport_addresses lb_force_snat_addrs; - bool dnat_force_snat_ip = get_force_snat_ip(od, "dnat", - &dnat_force_snat_addrs); - bool lb_force_snat_ip = get_force_snat_ip(od, "lb", - &lb_force_snat_addrs); + 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); for (int i = 0; i < od->nbr->n_nat; i++) { const struct nbrec_nat *nat; @@ -9484,7 +9144,7 @@ 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" + VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " "both allowed and exempt external ips set", UUID_ARGS(&(nat->header_.uuid))); continue; @@ -9511,10 +9171,13 @@ /* 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) { @@ -9600,7 +9263,7 @@ od->l3dgw_port->json_key); if (!distributed && od->l3redirect_port) { /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ + * programmed on the gateway chassis. */ ds_put_format(&match, " && is_chassis_resident(%s)", od->l3redirect_port->json_key); } @@ -9677,7 +9340,7 @@ od->l3dgw_port->json_key); if (!distributed && od->l3redirect_port) { /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ + * programmed on the gateway chassis. */ ds_put_format(&match, " && is_chassis_resident(%s)", od->l3redirect_port->json_key); } @@ -9762,7 +9425,7 @@ od->l3dgw_port->json_key); if (!distributed && od->l3redirect_port) { /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ + * programmed on the gateway chassis. */ ds_put_format(&match, " && is_chassis_resident(%s)", od->l3redirect_port->json_key); } @@ -9820,11 +9483,11 @@ * nat->logical_ip with the longest mask gets a higher * priority. */ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, - count_1bits(ntohl(mask)) + 1, + cidr_bits + 1, ds_cstr(&match), ds_cstr(&actions), &nat->header_); } else { - uint16_t priority = count_1bits(ntohl(mask)) + 1; + uint16_t priority = cidr_bits + 1; /* Distributed router. */ ds_clear(&match); @@ -9835,7 +9498,7 @@ od->l3dgw_port->json_key); if (!distributed && od->l3redirect_port) { /* Flows for NAT rules that are centralized are only - * programmed on the "redirect-chassis". */ + * programmed on the gateway chassis. */ priority += 128; ds_put_format(&match, " && is_chassis_resident(%s)", od->l3redirect_port->json_key); @@ -9956,7 +9619,8 @@ ds_put_format(&actions, "reg%d = 0; ", j); } ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; " - "next(pipeline=ingress, table=0); };"); + "next(pipeline=ingress, table=%d); };", + ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, ds_cstr(&match), ds_cstr(&actions), &nat->header_); @@ -9966,23 +9630,25 @@ /* Handle force SNAT options set in the gateway router. */ if (!od->l3dgw_port) { if (dnat_force_snat_ip) { - if (dnat_force_snat_addrs.n_ipv4_addrs) { + if (od->dnat_force_snat_addrs.n_ipv4_addrs) { build_lrouter_force_snat_flows(lflows, od, "4", - dnat_force_snat_addrs.ipv4_addrs[0].addr_s, "dnat"); + od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, + "dnat"); } - if (dnat_force_snat_addrs.n_ipv6_addrs) { + if (od->dnat_force_snat_addrs.n_ipv6_addrs) { build_lrouter_force_snat_flows(lflows, od, "6", - dnat_force_snat_addrs.ipv6_addrs[0].addr_s, "dnat"); + od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, + "dnat"); } } if (lb_force_snat_ip) { - if (lb_force_snat_addrs.n_ipv4_addrs) { + if (od->lb_force_snat_addrs.n_ipv4_addrs) { build_lrouter_force_snat_flows(lflows, od, "4", - lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); + od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); } - if (lb_force_snat_addrs.n_ipv6_addrs) { + if (od->lb_force_snat_addrs.n_ipv6_addrs) { build_lrouter_force_snat_flows(lflows, od, "6", - lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); + od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); } } @@ -9999,13 +9665,6 @@ "ip", "flags.loopback = 1; ct_dnat;"); } - if (dnat_force_snat_ip) { - destroy_lport_addresses(&dnat_force_snat_addrs); - } - if (lb_force_snat_ip) { - destroy_lport_addresses(&lb_force_snat_addrs); - } - /* Load balancing and packet defrag are only valid on * Gateway routers or router with gateway port. */ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { @@ -10018,18 +9677,19 @@ for (int i = 0; i < od->nbr->n_load_balancer; i++) { struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; - struct ovn_lb *lb = - ovn_lb_find(lbs, &nb_lb->header_.uuid); + struct ovn_northd_lb *lb = + ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); ovs_assert(lb); for (size_t j = 0; j < lb->n_vips; j++) { - struct lb_vip *lb_vip = &lb->vips[j]; + struct ovn_lb_vip *lb_vip = &lb->vips[j]; + struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; ds_clear(&actions); - build_lb_vip_ct_lb_actions(lb_vip, &actions, + build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &actions, lb->selection_fields); - if (!sset_contains(&all_ips, lb_vip->vip)) { - sset_add(&all_ips, lb_vip->vip); + if (!sset_contains(&all_ips, lb_vip->vip_str)) { + sset_add(&all_ips, lb_vip->vip_str); /* If there are any load balancing rules, we should send * the packet to conntrack for defragmentation and * tracking. This helps with two things. @@ -10039,12 +9699,12 @@ * 2. If there are L4 ports in load balancing rules, we * need the defragmentation to match on L4 ports. */ ds_clear(&match); - if (lb_vip->addr_family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ds_put_format(&match, "ip && ip4.dst == %s", - lb_vip->vip); - } else if (lb_vip->addr_family == AF_INET6) { + lb_vip->vip_str); + } else { ds_put_format(&match, "ip && ip6.dst == %s", - lb_vip->vip); + lb_vip->vip_str); } ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100, ds_cstr(&match), "ct_next;", @@ -10057,12 +9717,12 @@ * on ct.new with an action of "ct_lb($targets);". The other * flow is for ct.est with an action of "ct_dnat;". */ ds_clear(&match); - if (lb_vip->addr_family == AF_INET) { + if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ds_put_format(&match, "ip && ip4.dst == %s", - lb_vip->vip); - } else if (lb_vip->addr_family == AF_INET6) { + lb_vip->vip_str); + } else { ds_put_format(&match, "ip && ip6.dst == %s", - lb_vip->vip); + lb_vip->vip_str); } int prio = 110; @@ -10090,81 +9750,24 @@ sset_destroy(&nat_entries); } - HMAP_FOR_EACH (op, key_node, ports) { - build_ND_RA_flows_for_lrouter_port(op, lflows, &match, &actions); - } - - /* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS - * responder, by default goto next. (priority 0). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - build_ND_RA_flows_for_lrouter(od, lflows); - } - - HMAP_FOR_EACH (op, key_node, ports) { - build_ip_routing_flows_for_lrouter_port(op, lflows); - } + ds_destroy(&match); + ds_destroy(&actions); +} - /* Convert the static routes to flows. */ - HMAP_FOR_EACH (od, key_node, datapaths) { - build_static_route_flows_for_lrouter(od, lflows, ports); +/* Logical router ingress Table 0: L2 Admission Control + * Generic admission control flows (without inport check). + */ +static void +build_adm_ctrl_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows) +{ + if (od->nbr) { + /* Logical VLANs not supported. + * Broadcast/multicast source address is invalid. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100, + "vlan.present || eth.src[40]", "drop;"); } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_mcast_lookup_flows_for_lrouter(od, lflows, &match, &actions); - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_ingress_policy_flows_for_lrouter(od, lflows, ports); - } - - /* XXX destination unreachable */ - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_arp_resolve_flows_for_lrouter(od, lflows); - } - - HMAP_FOR_EACH (op, key_node, ports) { - build_arp_resolve_flows_for_lrouter_port( - op, lflows, ports, &match, &actions); - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_check_pkt_len_flows_for_lrouter( - od, lflows, ports, &match, &actions); - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_gateway_redirect_flows_for_lrouter( - od, lflows, &match, &actions); - } - - HMAP_FOR_EACH (od, key_node, datapaths) { - build_arp_request_flows_for_lrouter(od, lflows, &match, &actions); - } - - HMAP_FOR_EACH (op, key_node, ports) { - build_egress_delivery_flows_for_lrouter_port( - op, lflows, &match, &actions); - } - - ds_destroy(&match); - ds_destroy(&actions); -} - -/* Logical router ingress Table 0: L2 Admission Control - * Generic admission control flows (without inport check). - */ -static void -build_adm_ctrl_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows) -{ - if (od->nbr) { - /* Logical VLANs not supported. - * Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100, - "vlan.present || eth.src[40]", "drop;"); - } -} +} /* Logical router ingress Table 0: L2 Admission Control * This table drops packets that the router shouldn’t see at all based @@ -10208,7 +9811,7 @@ if (op->od->l3dgw_port && op == op->od->l3dgw_port && op->od->l3redirect_port) { /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s - * should only be received on the "redirect-chassis". */ + * should only be received on the gateway chassis. */ ds_put_format(match, " && is_chassis_resident(%s)", op->od->l3redirect_port->json_key); } @@ -10407,7 +10010,6 @@ } smap_add(&options, "ipv6_prefix_delegation", prefix_delegation ? "true" : "false"); - sbrec_port_binding_set_options(op->sb, &options); bool ipv6_prefix = smap_get_bool(&op->nbrp->options, "prefix", false); @@ -10645,15 +10247,15 @@ } ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", igmp_group->mcgroup.name); - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, - ds_cstr(match), ds_cstr(actions)); + 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(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, + ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, "ip4.mcast || ip6.mcast", "clone { " "outport = \""MC_STATIC"\"; " @@ -10800,7 +10402,16 @@ &op->nbrp->header_); } } - } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router") + + /* 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 @@ -10884,7 +10495,7 @@ } } } - } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router") + } 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 * the mac of the present active virtual parent. @@ -10988,7 +10599,7 @@ } } } - } else if (!strcmp(op->nbsp->type, "router")) { + } else if (lsp_is_router(op->nbsp)) { /* This is a logical switch port that connects to a router. */ /* The peer of this switch port is the router port for which @@ -11138,10 +10749,11 @@ "icmp4.type = 3; /* Destination Unreachable. */ " "icmp4.code = 4; /* Frag Needed and DF was Set. */ " "icmp4.frag_mtu = %d; " - "next(pipeline=ingress, table=0); };", + "next(pipeline=ingress, table=%d); };", rp->lrp_networks.ea_s, rp->lrp_networks.ipv4_addrs[0].addr_s, - gw_mtu); + gw_mtu, + ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_LARGER_PKTS, 50, ds_cstr(match), ds_cstr(actions), @@ -11166,10 +10778,11 @@ "icmp6.type = 2; /* Packet Too Big. */ " "icmp6.code = 0; " "icmp6.frag_mtu = %d; " - "next(pipeline=ingress, table=0); };", + "next(pipeline=ingress, table=%d); };", rp->lrp_networks.ea_s, rp->lrp_networks.ipv6_addrs[0].addr_s, - gw_mtu); + gw_mtu, + ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_LARGER_PKTS, 50, ds_cstr(match), ds_cstr(actions), @@ -11180,162 +10793,560 @@ } } -/* Logical router ingress table GW_REDIRECT: Gateway redirect. - * - * For traffic with outport equal to the l3dgw_port - * on a distributed router, this table redirects a subset - * of the traffic to the l3redirect_port which represents - * the central instance of the l3dgw_port. +/* Logical router ingress table GW_REDIRECT: Gateway redirect. + * + * For traffic with outport equal to the l3dgw_port + * on a distributed router, this table redirects a subset + * of the traffic to the l3redirect_port which represents + * the central instance of the l3dgw_port. + */ +static void +build_gateway_redirect_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (od->nbr) { + if (od->l3dgw_port && od->l3redirect_port) { + const struct ovsdb_idl_row *stage_hint = NULL; + + if (od->l3dgw_port->nbrp) { + stage_hint = &od->l3dgw_port->nbrp->header_; + } + + /* 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 + * instance of the l3dgw_port. */ + ds_clear(match); + ds_put_format(match, "outport == %s", + od->l3dgw_port->json_key); + ds_clear(actions); + ds_put_format(actions, "outport = %s; next;", + od->l3redirect_port->json_key); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50, + ds_cstr(match), ds_cstr(actions), + stage_hint); + } + + /* Packets are allowed by default. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); + } +} + +/* 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_request_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (od->nbr) { + for (int i = 0; i < od->nbr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *route; + + route = od->nbr->static_routes[i]; + struct in6_addr gw_ip6; + unsigned int plen; + char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen); + if (error || plen != 128) { + free(error); + continue; + } + + ds_clear(match); + ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && " + "ip6 && " REG_NEXT_HOP_IPV6 " == %s", + route->nexthop); + struct in6_addr sn_addr; + struct eth_addr eth_dst; + in6_addr_solicited_node(&sn_addr, &gw_ip6); + ipv6_multicast_to_ethernet(ð_dst, &sn_addr); + + char sn_addr_s[INET6_ADDRSTRLEN + 1]; + ipv6_string_mapped(sn_addr_s, &sn_addr); + + ds_clear(actions); + ds_put_format(actions, + "nd_ns { " + "eth.dst = "ETH_ADDR_FMT"; " + "ip6.dst = %s; " + "nd.target = %s; " + "output; " + "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s, + route->nexthop); + + 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", + "arp { " + "eth.dst = ff:ff:ff:ff:ff:ff; " + "arp.spa = " REG_SRC_IPV4 "; " + "arp.tpa = " REG_NEXT_HOP_IPV4 "; " + "arp.op = 1; " /* ARP request */ + "output; " + "};"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, + "eth.dst == 00:00:00:00:00:00 && ip6", + "nd_ns { " + "nd.target = " REG_NEXT_HOP_IPV6 "; " + "output; " + "};"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); + } +} + +/* Logical router egress table DELIVERY: Delivery (priority 100-110). + * + * 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 +build_egress_delivery_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (op->nbrp) { + if (!lrport_is_enabled(op->nbrp)) { + /* Drop packets to disabled logical ports (since logical flow + * tables are default-drop). */ + return; + } + + if (op->derived) { + /* No egress packets should be processed in the context of + * a chassisredirect port. The chassisredirect port should + * be replaced by the l3dgw port in the local output + * pipeline stage before egress processing. */ + return; + } + + /* If multicast relay is enabled then also adjust source mac for IP + * multicast traffic. + */ + if (op->od->mcast_info.rtr.relay) { + ds_clear(match); + ds_clear(actions); + ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s", + op->json_key); + ds_put_format(actions, "eth.src = %s; output;", + 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 +build_misc_local_traffic_drop_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows) +{ + if (od->nbr) { + /* L3 admission control: drop multicast and broadcast source, localhost + * source or destination, and zero network source or destination + * (priority 100). */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100, + "ip4.src_mcast ||" + "ip4.src == 255.255.255.255 || " + "ip4.src == 127.0.0.0/8 || " + "ip4.dst == 127.0.0.0/8 || " + "ip4.src == 0.0.0.0/8 || " + "ip4.dst == 0.0.0.0/8", + "drop;"); + + /* 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 + * IPs are handled with priority-90 flows. + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, + "arp || nd", "drop;"); + + /* Allow IPv6 multicast traffic that's supposed to reach the + * router pipeline (e.g., router solicitations). + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra", + "next;"); + + /* Drop other reserved multicast. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83, + "ip6.mcast_rsvd", "drop;"); + + /* 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;"); + + /* 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;"); + } +} + +static void +build_dhcpv6_reply_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match) +{ + if (op->nbrp && (!op->derived)) { + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_clear(match); + ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&" + " udp.dst == 546", + op->lrp_networks.ipv6_addrs[i].addr_s); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, + ds_cstr(match), + "reg0 = 0; handle_dhcpv6_reply;"); + } + } + +} + +static void +build_ipv6_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) +{ + if (op->nbrp && (!op->derived)) { + /* No ingress packets are accepted on a chassisredirect + * port, so no need to program flows for that port. */ + if (op->lrp_networks.n_ipv6_addrs) { + /* ICMPv6 echo reply. These flows reply to echo requests + * received for the router's IP address. */ + ds_clear(match); + ds_put_cstr(match, "ip6.dst == "); + op_put_v6_networks(match, op); + ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0"); + + const char *lrp_actions = + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 129; " + "flags.loopback = 1; " + "next; "; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, + ds_cstr(match), lrp_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++) { + ds_clear(match); + if (op->od->l3dgw_port && op == op->od->l3dgw_port + && op->od->l3redirect_port) { + /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s + * should only be sent from the gateway chassi, so that + * upstream MAC learning points to the gateway chassis. + * Also need to avoid generation of multiple ND replies + * from different chassis. */ + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } + + 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); + } + + /* UDP/TCP 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, + "ip6 && ip6.dst == %s && !ip.later_frag && tcp", + op->lrp_networks.ipv6_addrs[i].addr_s); + const char *action = "tcp_reset {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && udp", + op->lrp_networks.ipv6_addrs[i].addr_s); + action = "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 1; " + "icmp6.code = 4; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, + &op->nbrp->header_); + + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag", + op->lrp_networks.ipv6_addrs[i].addr_s); + action = "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " + "icmp6.type = 1; " + "icmp6.code = 3; " + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 70, ds_cstr(match), action, + &op->nbrp->header_); + } + } + + /* ICMPv6 time exceeded */ + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + /* skip link-local address */ + if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { + continue; + } + + ds_clear(match); + ds_clear(actions); + + ds_put_format(match, + "inport == %s && ip6 && " + "ip6.src == %s/%d && " + "ip.ttl == {0, 1} && !ip.later_frag", + op->json_key, + op->lrp_networks.ipv6_addrs[i].network_s, + op->lrp_networks.ipv6_addrs[i].plen); + ds_put_format(actions, + "icmp6 {" + "eth.dst <-> eth.src; " + "ip6.dst = ip6.src; " + "ip6.src = %s; " + "ip.ttl = 255; " + "icmp6.type = 3; /* Time exceeded */ " + "icmp6.code = 0; /* TTL exceeded in transit */ " + "next; };", + op->lrp_networks.ipv6_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_); + } + } + +} + +struct lswitch_flow_build_info { + struct hmap *datapaths; + struct hmap *ports; + struct hmap *port_groups; + struct hmap *lflows; + struct hmap *mcgroups; + struct hmap *igmp_groups; + struct shash *meter_groups; + struct hmap *lbs; + char *svc_check_match; + struct ds match; + struct ds actions; +}; + +/* Helper function to combine all lflow generation which is iterated by + * datapath. */ + static void -build_gateway_redirect_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions) +build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, + struct lswitch_flow_build_info *lsi) { - if (od->nbr) { - if (od->l3dgw_port && od->l3redirect_port) { - const struct ovsdb_idl_row *stage_hint = NULL; - - if (od->l3dgw_port->nbrp) { - stage_hint = &od->l3dgw_port->nbrp->header_; - } + /* Build Logical Switch Flows. */ + build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows, + lsi->meter_groups, lsi->lbs); + + build_fwd_group_lflows(od, lsi->lflows); + build_lswitch_lflows_admission_control(od, lsi->lflows); + build_lswitch_input_port_sec_od(od, lsi->lflows); + + /* Build Logical Router Flows. */ + build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); + build_neigh_learning_flows_for_lrouter(od, lsi->lflows, &lsi->match, + &lsi->actions); + build_ND_RA_flows_for_lrouter(od, lsi->lflows); + build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports); + build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match, + &lsi->actions); + build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports); + build_arp_resolve_flows_for_lrouter(od, lsi->lflows); + build_check_pkt_len_flows_for_lrouter(od, lsi->lflows, lsi->ports, + &lsi->match, &lsi->actions); + build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match, + &lsi->actions); + build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match, + &lsi->actions); + build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); +} - /* 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 - * instance of the l3dgw_port. */ - ds_clear(match); - ds_put_format(match, "outport == %s", - od->l3dgw_port->json_key); - ds_clear(actions); - ds_put_format(actions, "outport = %s; next;", - od->l3redirect_port->json_key); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50, - ds_cstr(match), ds_cstr(actions), - stage_hint); - } +/* Helper function to combine all lflow generation which is iterated by port. + */ - /* Packets are allowed by default. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); - } +static void +build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, + struct lswitch_flow_build_info *lsi) +{ + /* Build Logical Switch Flows. */ + build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions, + &lsi->match); + + /* Build Logical Router Flows. */ + build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, + &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_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, + &lsi->match, &lsi->actions); + build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, + &lsi->actions); + 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); } -/* 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_request_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, - struct ds *match, struct ds *actions) +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, + struct shash *meter_groups, struct hmap *lbs) { - if (od->nbr) { - for (int i = 0; i < od->nbr->n_static_routes; i++) { - const struct nbrec_logical_router_static_route *route; + struct ovn_datapath *od; + struct ovn_port *op; - route = od->nbr->static_routes[i]; - struct in6_addr gw_ip6; - unsigned int plen; - char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen); - if (error || plen != 128) { - free(error); - continue; - } + char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - ds_clear(match); - ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && " - "ip6 && " REG_NEXT_HOP_IPV6 " == %s", - route->nexthop); - struct in6_addr sn_addr; - struct eth_addr eth_dst; - in6_addr_solicited_node(&sn_addr, &gw_ip6); - ipv6_multicast_to_ethernet(ð_dst, &sn_addr); + struct lswitch_flow_build_info lsi = { + .datapaths = datapaths, + .ports = ports, + .port_groups = port_groups, + .lflows = lflows, + .mcgroups = mcgroups, + .igmp_groups = igmp_groups, + .meter_groups = meter_groups, + .lbs = lbs, + .svc_check_match = svc_check_match, + .match = DS_EMPTY_INITIALIZER, + .actions = DS_EMPTY_INITIALIZER, + }; - char sn_addr_s[INET6_ADDRSTRLEN + 1]; - ipv6_string_mapped(sn_addr_s, &sn_addr); + /* Combined build - all lflow generation from lswitch and lrouter + * will move here and will be reogranized by iterator type. + */ + HMAP_FOR_EACH (od, key_node, datapaths) { + build_lswitch_and_lrouter_iterate_by_od(od, &lsi); + } + HMAP_FOR_EACH (op, key_node, ports) { + build_lswitch_and_lrouter_iterate_by_op(op, &lsi); + } + free(svc_check_match); - ds_clear(actions); - ds_put_format(actions, - "nd_ns { " - "eth.dst = "ETH_ADDR_FMT"; " - "ip6.dst = %s; " - "nd.target = %s; " - "output; " - "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s, - route->nexthop); + ds_destroy(&lsi.match); + ds_destroy(&lsi.actions); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200, - ds_cstr(match), ds_cstr(actions), - &route->header_); + /* Legacy lswitch build - to be migrated. */ + build_lswitch_flows(datapaths, ports, lflows, mcgroups, + igmp_groups, lbs); + + /* Legacy lrouter build - to be migrated. */ + build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs); +} + +struct ovn_dp_group { + struct hmapx map; + struct sbrec_logical_dp_group *dp_group; + struct hmap_node node; +}; + +static struct ovn_dp_group * +ovn_dp_group_find(const struct hmap *dp_groups, + const struct hmapx *od, uint32_t hash) +{ + struct ovn_dp_group *dpg; + + HMAP_FOR_EACH_WITH_HASH (dpg, node, hash, dp_groups) { + if (hmapx_equals(&dpg->map, od)) { + return dpg; } + } + return NULL; +} - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, - "eth.dst == 00:00:00:00:00:00 && ip4", - "arp { " - "eth.dst = ff:ff:ff:ff:ff:ff; " - "arp.spa = " REG_SRC_IPV4 "; " - "arp.tpa = " REG_NEXT_HOP_IPV4 "; " - "arp.op = 1; " /* ARP request */ - "output; " - "};"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, - "eth.dst == 00:00:00:00:00:00 && ip6", - "nd_ns { " - "nd.target = " REG_NEXT_HOP_IPV6 "; " - "output; " - "};"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); +static struct sbrec_logical_dp_group * +ovn_sb_insert_logical_dp_group(struct northd_context *ctx, + const struct hmapx *od) +{ + struct sbrec_logical_dp_group *dp_group; + const struct sbrec_datapath_binding **sb; + const struct hmapx_node *node; + int n = 0; + + sb = xmalloc(hmapx_count(od) * sizeof *sb); + HMAPX_FOR_EACH (node, od) { + sb[n++] = ((struct ovn_datapath *) node->data)->sb; } + dp_group = sbrec_logical_dp_group_insert(ctx->ovnsb_txn); + sbrec_logical_dp_group_set_datapaths( + dp_group, (struct sbrec_datapath_binding **) sb, n); + free(sb); + + return dp_group; } -/* Logical router egress table DELIVERY: Delivery (priority 100-110). - * - * 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 -build_egress_delivery_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions) +ovn_sb_set_lflow_logical_dp_group( + struct northd_context *ctx, + struct hmap *dp_groups, + const struct sbrec_logical_flow *sbflow, + const struct hmapx *od_group) { - if (op->nbrp) { - if (!lrport_is_enabled(op->nbrp)) { - /* Drop packets to disabled logical ports (since logical flow - * tables are default-drop). */ - return; - } + struct ovn_dp_group *dpg; - if (op->derived) { - /* No egress packets should be processed in the context of - * a chassisredirect port. The chassisredirect port should - * be replaced by the l3dgw port in the local output - * pipeline stage before egress processing. */ - return; - } + if (!hmapx_count(od_group)) { + sbrec_logical_flow_set_logical_dp_group(sbflow, NULL); + return; + } - /* If multicast relay is enabled then also adjust source mac for IP - * multicast traffic. - */ - if (op->od->mcast_info.rtr.relay) { - ds_clear(match); - ds_clear(actions); - ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s", - op->json_key); - ds_put_format(actions, "eth.src = %s; output;", - op->lrp_networks.ea_s); - ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110, - ds_cstr(match), ds_cstr(actions)); - } + ovs_assert(hmapx_count(od_group) != 1); - 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;"); - } + dpg = ovn_dp_group_find(dp_groups, od_group, + hash_int(hmapx_count(od_group), 0)); + ovs_assert(dpg != NULL); + if (!dpg->dp_group) { + dpg->dp_group = ovn_sb_insert_logical_dp_group(ctx, &dpg->map); + } + sbrec_logical_flow_set_logical_dp_group(sbflow, dpg->dp_group); } /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database, @@ -11349,40 +11360,143 @@ { struct hmap lflows = HMAP_INITIALIZER(&lflows); - build_lswitch_flows(datapaths, ports, port_groups, &lflows, mcgroups, - igmp_groups, meter_groups, lbs); - build_lrouter_flows(datapaths, ports, &lflows, meter_groups, lbs); + build_lswitch_and_lrouter_flows(datapaths, ports, + port_groups, &lflows, mcgroups, + igmp_groups, meter_groups, lbs); + + /* Collecting all unique datapath groups. */ + struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups); + struct hmapx single_dp_lflows = HMAPX_INITIALIZER(&single_dp_lflows); + struct ovn_lflow *lflow; + HMAP_FOR_EACH (lflow, hmap_node, &lflows) { + uint32_t hash = hash_int(hmapx_count(&lflow->od_group), 0); + struct ovn_dp_group *dpg; + + ovs_assert(hmapx_count(&lflow->od_group)); + + if (hmapx_count(&lflow->od_group) == 1) { + /* There is only one datapath, so it should be moved out of the + * group to a single 'od'. */ + const struct hmapx_node *node; + HMAPX_FOR_EACH (node, &lflow->od_group) { + lflow->od = node->data; + break; + } + hmapx_clear(&lflow->od_group); + /* Logical flow should be re-hashed later to allow lookups. */ + hmapx_add(&single_dp_lflows, lflow); + continue; + } + + dpg = ovn_dp_group_find(&dp_groups, &lflow->od_group, hash); + if (!dpg) { + dpg = xzalloc(sizeof *dpg); + hmapx_clone(&dpg->map, &lflow->od_group); + hmap_insert(&dp_groups, &dpg->node, hash); + } + } + + /* Adding datapath to the flow hash for logical flows that have only one, + * so they could be found by the southbound db record. */ + const struct hmapx_node *node; + uint32_t hash; + HMAPX_FOR_EACH (node, &single_dp_lflows) { + lflow = node->data; + hash = hmap_node_hash(&lflow->hmap_node); + hmap_remove(&lflows, &lflow->hmap_node); + hash = ovn_logical_flow_hash_datapath(&lflow->od->sb->header_.uuid, + hash); + hmap_insert(&lflows, &lflow->hmap_node, hash); + } + hmapx_destroy(&single_dp_lflows); /* Push changes to the Logical_Flow table to database. */ const struct sbrec_logical_flow *sbflow, *next_sbflow; SBREC_LOGICAL_FLOW_FOR_EACH_SAFE (sbflow, next_sbflow, ctx->ovnsb_idl) { - struct ovn_datapath *od - = ovn_datapath_from_sbrec(datapaths, sbflow->logical_datapath); + struct sbrec_logical_dp_group *dp_group = sbflow->logical_dp_group; + struct ovn_datapath **od, *logical_datapath_od = NULL; + int n_datapaths = 0; + size_t i; + + od = xmalloc((dp_group ? dp_group->n_datapaths + 1 : 1) * sizeof *od); + /* Check all logical datapaths from the group. */ + for (i = 0; dp_group && i < dp_group->n_datapaths; i++) { + od[n_datapaths] = ovn_datapath_from_sbrec(datapaths, + dp_group->datapaths[i]); + if (!od[n_datapaths] || ovn_datapath_is_stale(od[n_datapaths])) { + continue; + } + n_datapaths++; + } - if (!od || ovn_datapath_is_stale(od)) { + struct sbrec_datapath_binding *dp = sbflow->logical_datapath; + if (dp) { + logical_datapath_od = ovn_datapath_from_sbrec(datapaths, dp); + if (logical_datapath_od + && ovn_datapath_is_stale(logical_datapath_od)) { + logical_datapath_od = NULL; + } + } + + if (!n_datapaths && !logical_datapath_od) { + /* This lflow has no valid logical datapaths. */ sbrec_logical_flow_delete(sbflow); + free(od); continue; } - enum ovn_datapath_type dp_type = od->nbs ? DP_SWITCH : DP_ROUTER; enum ovn_pipeline pipeline = !strcmp(sbflow->pipeline, "ingress") ? P_IN : P_OUT; - struct ovn_lflow *lflow = ovn_lflow_find( - &lflows, od, ovn_stage_build(dp_type, pipeline, sbflow->table_id), + enum ovn_datapath_type dp_type; + + if (n_datapaths) { + dp_type = od[0]->nbs ? DP_SWITCH : DP_ROUTER; + } else { + dp_type = logical_datapath_od->nbs ? DP_SWITCH : DP_ROUTER; + } + lflow = ovn_lflow_find( + &lflows, logical_datapath_od, + ovn_stage_build(dp_type, pipeline, sbflow->table_id), sbflow->priority, sbflow->match, sbflow->actions, sbflow->hash); if (lflow) { + /* This is a valid lflow. Checking if the datapath group needs + * updates. */ + bool update_dp_group = false; + + if (n_datapaths != hmapx_count(&lflow->od_group)) { + update_dp_group = true; + } else { + for (i = 0; i < n_datapaths; i++) { + if (od[i] && !hmapx_contains(&lflow->od_group, od[i])) { + update_dp_group = true; + break; + } + } + } + + if (update_dp_group) { + ovn_sb_set_lflow_logical_dp_group(ctx, &dp_groups, + sbflow, &lflow->od_group); + } + /* This lflow updated. Not needed anymore. */ ovn_lflow_destroy(&lflows, lflow); } else { sbrec_logical_flow_delete(sbflow); } + free(od); } - struct ovn_lflow *lflow, *next_lflow; + + struct ovn_lflow *next_lflow; HMAP_FOR_EACH_SAFE (lflow, next_lflow, hmap_node, &lflows) { const char *pipeline = ovn_stage_get_pipeline_name(lflow->stage); uint8_t table = ovn_stage_get_table(lflow->stage); sbflow = sbrec_logical_flow_insert(ctx->ovnsb_txn); - sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->sb); + if (lflow->od) { + sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->sb); + } + ovn_sb_set_lflow_logical_dp_group(ctx, &dp_groups, + sbflow, &lflow->od_group); sbrec_logical_flow_set_pipeline(sbflow, pipeline); sbrec_logical_flow_set_table_id(sbflow, table); sbrec_logical_flow_set_priority(sbflow, lflow->priority); @@ -11414,6 +11528,13 @@ } hmap_destroy(&lflows); + struct ovn_dp_group *dpg; + HMAP_FOR_EACH_POP (dpg, node, &dp_groups) { + hmapx_destroy(&dpg->map); + free(dpg); + } + hmap_destroy(&dp_groups); + /* Push changes to the Multicast_Group table to database. */ const struct sbrec_multicast_group *sbmc, *next_sbmc; SBREC_MULTICAST_GROUP_FOR_EACH_SAFE (sbmc, next_sbmc, ctx->ovnsb_idl) { @@ -11467,24 +11588,6 @@ addrs, n_addrs); } -/* Go through 'addresses' and add found IPv4 addresses to 'ipv4_addrs' and IPv6 - * addresses to 'ipv6_addrs'. - */ -static void -split_addresses(const char *addresses, struct svec *ipv4_addrs, - struct svec *ipv6_addrs) -{ - struct lport_addresses laddrs; - extract_lsp_addresses(addresses, &laddrs); - for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) { - svec_add(ipv4_addrs, laddrs.ipv4_addrs[k].addr_s); - } - for (size_t k = 0; k < laddrs.n_ipv6_addrs; k++) { - svec_add(ipv6_addrs, laddrs.ipv6_addrs[k].addr_s); - } - destroy_lport_addresses(&laddrs); -} - /* OVN_Southbound Address_Set table contains same records as in north * bound, plus the records generated from Port_Group table in north bound. * @@ -11505,6 +11608,11 @@ shash_add(&sb_address_sets, sb_address_set->name, sb_address_set); } + /* Service monitor MAC. */ + const char *svc_monitor_macp = svc_monitor_mac; + sync_address_set(ctx, "svc_monitor_mac", &svc_monitor_macp, 1, + &sb_address_sets); + /* sync port group generated address sets first */ const struct nbrec_port_group *nb_port_group; NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) { @@ -11689,12 +11797,50 @@ return need_update; } +static void +sync_meters_iterate_nb_meter(struct northd_context *ctx, + const char *meter_name, + const struct nbrec_meter *nb_meter, + struct shash *sb_meters) +{ + bool new_sb_meter = false; + + const struct sbrec_meter *sb_meter = shash_find_and_delete(sb_meters, + meter_name); + if (!sb_meter) { + sb_meter = sbrec_meter_insert(ctx->ovnsb_txn); + sbrec_meter_set_name(sb_meter, meter_name); + new_sb_meter = true; + } + + if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { + struct sbrec_meter_band **sb_bands; + sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands); + for (size_t i = 0; i < nb_meter->n_bands; i++) { + const struct nbrec_meter_band *nb_band = nb_meter->bands[i]; + + sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn); + + sbrec_meter_band_set_action(sb_bands[i], nb_band->action); + sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate); + sbrec_meter_band_set_burst_size(sb_bands[i], + nb_band->burst_size); + } + sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands); + free(sb_bands); + } + + sbrec_meter_set_unit(sb_meter, nb_meter->unit); +} + /* 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. + * OVN_Southbound. Additionally, ACL logs that use fair meters have + * a private copy of its meter in the SB table. */ static void -sync_meters(struct northd_context *ctx) +sync_meters(struct northd_context *ctx, struct hmap *datapaths, + struct shash *meter_groups) { struct shash sb_meters = SHASH_INITIALIZER(&sb_meters); @@ -11705,33 +11851,32 @@ const struct nbrec_meter *nb_meter; NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) { - bool new_sb_meter = false; + sync_meters_iterate_nb_meter(ctx, nb_meter->name, nb_meter, + &sb_meters); + } - sb_meter = shash_find_and_delete(&sb_meters, nb_meter->name); - if (!sb_meter) { - sb_meter = sbrec_meter_insert(ctx->ovnsb_txn); - sbrec_meter_set_name(sb_meter, nb_meter->name); - new_sb_meter = true; + /* + * In addition to creating Meters in the SB from the block above, check + * and see if additional rows are needed to get ACLs logs individually + * rate-limited. + */ + struct ovn_datapath *od; + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; } - - if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { - struct sbrec_meter_band **sb_bands; - sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands); - for (size_t i = 0; i < nb_meter->n_bands; i++) { - const struct nbrec_meter_band *nb_band = nb_meter->bands[i]; - - sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn); - - sbrec_meter_band_set_action(sb_bands[i], nb_band->action); - sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate); - sbrec_meter_band_set_burst_size(sb_bands[i], - nb_band->burst_size); + for (size_t i = 0; i < od->nbs->n_acls; i++) { + struct nbrec_acl *acl = od->nbs->acls[i]; + nb_meter = fair_meter_lookup_by_name(meter_groups, acl->meter); + if (!nb_meter) { + continue; } - sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands); - free(sb_bands); - } - sbrec_meter_set_unit(sb_meter, nb_meter->unit); + char *meter_name = alloc_acl_log_unique_meter_name(acl); + sync_meters_iterate_nb_meter(ctx, meter_name, nb_meter, + &sb_meters); + free(meter_name); + } } struct shash_node *node, *next; @@ -11941,6 +12086,10 @@ } else if (op->nbsp && lsp_is_enabled(op->nbsp)) { ovn_multicast_add(mcast_groups, &mc_flood, op); + if (!lsp_is_router(op->nbsp)) { + ovn_multicast_add(mcast_groups, &mc_flood_l2, op); + } + /* If this port is connected to a multicast router then add it * to the MC_MROUTER_FLOOD group. */ @@ -12114,7 +12263,8 @@ struct ovsdb_idl_loop *sb_loop, struct hmap *datapaths, struct hmap *ports, struct ovs_list *lr_list, - int64_t loop_start_time) + int64_t loop_start_time, + const char *ovn_internal_version) { if (!ctx->ovnsb_txn || !ctx->ovnnb_txn) { return; @@ -12191,6 +12341,8 @@ smap_replace(&options, "max_tunid", max_tunid); free(max_tunid); + smap_replace(&options, "northd_internal_version", ovn_internal_version); + nbrec_nb_global_verify_options(nb); nbrec_nb_global_set_options(nb, &options); @@ -12200,6 +12352,8 @@ northd_probe_interval_nb = get_probe_interval(ovnnb_db, nb); northd_probe_interval_sb = get_probe_interval(ovnsb_db, nb); + use_logical_dp_groups = smap_get_bool(&nb->options, + "use_logical_dp_groups", false); controller_event_en = smap_get_bool(&nb->options, "controller_event", false); check_lsp_is_up = !smap_get_bool(&nb->options, @@ -12207,7 +12361,7 @@ build_datapaths(ctx, datapaths, lr_list); build_ports(ctx, sbrec_chassis_by_name, datapaths, ports); - build_ovn_lbs(ctx, ports, &lbs); + build_ovn_lbs(ctx, datapaths, ports, &lbs); build_ipam(datapaths, ports); build_port_group_lswitches(ctx, &port_groups, ports); build_lrouter_groups(ports, lr_list); @@ -12220,9 +12374,13 @@ sync_address_sets(ctx); sync_port_groups(ctx, &port_groups); - sync_meters(ctx); + sync_meters(ctx, datapaths, &meter_groups); sync_dns_entries(ctx, datapaths); - destroy_ovn_lbs(&lbs); + + struct ovn_northd_lb *lb; + HMAP_FOR_EACH_POP (lb, hmap_node, &lbs) { + ovn_northd_lb_destroy(lb); + } hmap_destroy(&lbs); struct ovn_igmp_group *igmp_group, *next_igmp_group; @@ -12384,7 +12542,7 @@ continue; } - bool up = (sb->chassis || !strcmp(op->nbsp->type, "router")); + bool up = (sb->chassis || lsp_is_router(op->nbsp)); if (!op->nbsp->up || *op->nbsp->up != up) { nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); } @@ -12431,6 +12589,8 @@ DHCP_OPT_ARP_CACHE_TIMEOUT, DHCP_OPT_TCP_KEEPALIVE_INTERVAL, DHCP_OPT_DOMAIN_SEARCH_LIST, + DHCP_OPT_BOOTFILE_ALT, + DHCP_OPT_BROADCAST_ADDRESS, }; static struct gen_opts_map supported_dhcpv6_opts[] = { @@ -12515,17 +12675,17 @@ {"name"}; static const char *rbac_chassis_update[] = {"nb_cfg", "external_ids", "encaps", "vtep_logical_switches", - "other_config", "name"}; + "other_config"}; static const char *rbac_chassis_private_auth[] = {"name"}; static const char *rbac_chassis_private_update[] = - {"nb_cfg", "nb_cfg_timestamp", "chassis", "name"}; + {"nb_cfg", "nb_cfg_timestamp", "chassis"}; static const char *rbac_encap_auth[] = {"chassis_name"}; static const char *rbac_encap_update[] = - {"type", "options", "ip", "chassis_name"}; + {"type", "options", "ip"}; static const char *rbac_port_binding_auth[] = {""}; @@ -12796,7 +12956,8 @@ static void ovn_db_run(struct northd_context *ctx, struct ovsdb_idl_index *sbrec_chassis_by_name, - struct ovsdb_idl_loop *ovnsb_idl_loop) + struct ovsdb_idl_loop *ovnsb_idl_loop, + const char *ovn_internal_version) { struct hmap datapaths, ports; struct ovs_list lr_list; @@ -12806,7 +12967,8 @@ int64_t start_time = time_wall_msec(); ovnnb_db_run(ctx, sbrec_chassis_by_name, ovnsb_idl_loop, - &datapaths, &ports, &lr_list, start_time); + &datapaths, &ports, &lr_list, start_time, + ovn_internal_version); ovnsb_db_run(ctx, ovnsb_idl_loop, &ports, start_time); destroy_datapaths_and_ports(&datapaths, &ports, &lr_list); } @@ -12964,12 +13126,19 @@ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_logical_flow); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_logical_datapath); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_logical_flow_col_logical_dp_group); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_pipeline); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_table_id); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_priority); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_match); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_actions); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, + &sbrec_table_logical_dp_group); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_logical_dp_group_col_datapaths); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_multicast_group); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_multicast_group_col_datapath); @@ -12982,6 +13151,8 @@ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_datapath_binding_col_tunnel_key); add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_datapath_binding_col_load_balancers); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_datapath_binding_col_external_ids); ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_binding); @@ -13148,6 +13319,14 @@ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_service_monitor_col_external_ids); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_load_balancer); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_datapaths); + 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); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_load_balancer_col_external_ids); + struct ovsdb_idl_index *sbrec_chassis_by_name = chassis_index_create(ovnsb_idl_loop.idl); @@ -13163,6 +13342,9 @@ unixctl_command_register("sb-connection-status", "", 0, 0, ovn_conn_show, ovnsb_idl_loop.idl); + char *ovn_internal_version = ovn_get_internal_version(); + VLOG_INFO("OVN internal version is : [%s]", ovn_internal_version); + /* Main loop. */ exiting = false; state.had_lock = false; @@ -13204,7 +13386,8 @@ } if (ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) { - ovn_db_run(&ctx, sbrec_chassis_by_name, &ovnsb_idl_loop); + ovn_db_run(&ctx, sbrec_chassis_by_name, &ovnsb_idl_loop, + ovn_internal_version); if (ctx.ovnsb_txn) { check_and_add_supported_dhcp_opts_to_sb_db(&ctx); check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx); @@ -13266,6 +13449,7 @@ } } + free(ovn_internal_version); unixctl_server_destroy(unixctl); ovsdb_idl_loop_destroy(&ovnnb_idl_loop); ovsdb_idl_loop_destroy(&ovnsb_idl_loop); diff -Nru ovn-20.09.0/ovn-nb.ovsschema ovn-20.12.0/ovn-nb.ovsschema --- ovn-20.09.0/ovn-nb.ovsschema 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/ovn-nb.ovsschema 2020-12-17 17:53:32.000000000 +0000 @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.27.0", - "cksum": "3507518247 26773", + "version": "5.28.0", + "cksum": "610359755 26847", "tables": { "NB_Global": { "columns": { @@ -264,6 +264,7 @@ "refType": "strong"}, "min": 1, "max": "unlimited"}}, + "fair": {"type": {"key": "boolean", "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, diff -Nru ovn-20.09.0/ovn-nb.xml ovn-20.12.0/ovn-nb.xml --- ovn-20.09.0/ovn-nb.xml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/ovn-nb.xml 2020-12-17 17:53:32.000000000 +0000 @@ -55,8 +55,19 @@ - The timestamp (ms) when ovn-northd sees the latest - nb_cfg and starts processing. +

    + The timestamp, in milliseconds since the epoch, when + ovn-northd sees the latest nb_cfg and starts + processing. +

    + +

    + To print the timestamp as a human-readable date: +

    + +
    +          date -d "@$(ovn-nbctl get NB_Global . nb_cfg_timestamp | sed 's/...$//')"
    +        
    @@ -66,23 +77,38 @@ - The timestamp (ms) when ovn-northd finishes applying the + The timestamp, in milliseconds since the epoch, when + ovn-northd finishes applying the corresponding configuration changes to the database successfully. - Sequence number that ovn-northd sets to the smallest - sequence number of all the chassis in the system, as reported in the - Chassis_Private table in the southbound database. Thus, - equals if all chassis are - caught up with the northbound configuration (which may never happen, if - any chassis is down). This value can regress, if a chassis was removed - from the system and rejoins before catching up. +

    + Sequence number that ovn-northd sets to the smallest + sequence number of all the chassis in the system, as reported in the + Chassis_Private table in the southbound database. Thus, + equals if all chassis + are caught up with the northbound configuration (which may never + happen, if any chassis is down). This value can regress, if a + chassis was removed from the system and rejoins before catching up. +

    + +

    + If there are no chassis, then ovn-northd copies + nb_cfg to . Thus, in this case, + the (nonexistent) hypervisors are always considered to be caught up. + This means that hypervisors can be "caught up" even in cases where + would show that the southbound database is + not. To detect when both the hypervisors and the southbound database + are caught up, a client should take the smaller of and . +

    - The largest timestamp (ms) of the smallest sequence number of all the + The largest timestamp, in milliseconds since the epoch, of the smallest + sequence number of all the chassis in the system, as reported in the Chassis_Private table in the southbound database. In other words, this timestamp reflects the time when the slowest chassis catches up with the @@ -167,6 +193,26 @@

    + +

    + If set to true, ovn-northd will combine + logical flows that differs only by logical datapath into a single + logical flow with logical datapath group attached. +

    +

    + While this should significantly reduce number of logical flows stored + in Southbound database this could also increase processing complexity + on the ovn-controller side, e.g., + ovn-controller will re-consider logical flow for all + logical datapaths in a group. If the option set to + false, there will be separate logical flow per logical + datapath and only this flow will be re-considered. +

    +

    + The default value is false. +

    +
    +

    If set to false, ARP/ND reply flows for logical switch ports will be @@ -512,6 +558,13 @@ + + + Determines whether VLAN tagged incoming traffic should be allowed. + + + See External IDs at the beginning of this document. @@ -545,6 +598,11 @@ unique identifier. See Containers, below, for more information.

    + +

    + A logical switch port may not have the same name as a logical router + port, but the database schema cannot enforce this. +

    @@ -1049,65 +1107,40 @@
    dynamic
    - Use this keyword to make ovn-northd generate a - globally unique MAC address and choose an unused IPv4 address with - the logical port's subnet and store them in the port's column. ovn-northd will - use the subnet specified in in the port's . -
    - -
    Ethernet address followed by keyword "dynamic"
    -
    -

    - The keyword dynamic after the MAC address indicates - that ovn-northd should choose an unused IPv4 address - from the logical port's subnet and store it with the specified - MAC in the port's column. - ovn-northd will use the subnet specified in in - the port's table. + Use dynamic to make ovn-northd generate + a globally unique MAC address, choose an unused IPv4 address with + the logical port's subnet (if is set in the port's ), and generate an IPv6 address from the + MAC address (if is set in the port's ) and store them in the port's column.

    - Examples: + Only one element containing dynamic may appear in + .

    - -
    -
    80:fa:5b:06:72:b7 dynamic
    -
    - This indicates that the logical port owns the specified - MAC address and ovn-northd should allocate an - unused IPv4 address for the logical port from the corresponding - logical switch subnet. -
    -
    -
    Keyword "dynamic" followed by an IPv4/IPv6 address
    +
    dynamic ip
    +
    dynamic ipv6
    +
    dynamic ip ipv6
    + These act like dynamic alone but specify particular + IPv4 or IPv6 addresses to use. OVN IPAM will still automatically + allocate the other address if configured appropriately. Example: + dynamic 192.168.0.1 2001::1. +
    -

    - The keyword dynamic followed by an IPv4/IPv6 - address indicates that ovn-northd should choose - a dynamic ethernet address and use the provided IPv4/IPv6 address - as network address. -

    - -

    - Examples: -

    - -
    -
    dynamic 192.168.0.1 2001::1
    -
    - This indicates that ovn-northd should allocate - a unique MAC address and use the provided IPv4/IPv6 address - for the related port -
    -
    +
    mac dynamic
    +
    + This acts like dynamic alone but specifies a + particular MAC address to use. OVN IPAM will still automatically + allocate IPv4 or IPv6 addresses, or both, if configured + appropriately. Example: 80:fa:5b:06:72:b7 dynamic
    router
    @@ -1530,36 +1563,45 @@

    - - Load balancer health checks associated with this load balancer. - If health check is desired for a vip's endpoints defined in - the - column, then a row in the table - should - be created and referenced here and L4 port should be defined - for the vip and it's endpoints. Health checks are supported only - for IPv4 load balancers. - - - -

    - This column is used if load balancer health checks are enabled. - This keeps a mapping of endpoint IP to the logical port name. - The source ip to be used for health checks is also expected to be - defined. The key of the mapping is the endpoint IP and the value - is in the format : port_name:SRC_IP + +

    + OVN supports health checks for load balancer endpoints, for IPv4 load + balancers only. When health checks are enabled, the load balancer uses + only healthy endpoints.

    - Eg. If there is a VIP entry: - "10.0.0.10:80=10.0.0.4:8080,20.0.0.4:8080", - then the IP to port mappings should be defined as: - "10.0.0.4"="sw0-p1:10.0.0.2" and - "20.0.0.4"="sw1-p1:20.0.0.2". 10.0.0.2 - and 20.0.0.2 will be used by ovn-controller - as source ip when it sends out health check packets. + Suppose that contains a key-value pair + 10.0.0.10:80=10.0.0.4:8080,20.0.0.4:8080. To + enable health checks for this virtual's endpoints, add two key-value + pairs to , with keys + 10.0.0.4 and 20.0.0.4, and add to a reference to a row whose is set to + 10.0.0.10.

    -
    + + + Load balancer health checks associated with this load balancer. + + + +

    + Maps from endpoint IP to a colon-separated pair of logical port name + and source IP, + e.g. port_name:sourc_ip. Health + checks are sent to this port with the specified source IP. +

    + +

    + For example, in the example above, IP to port mappings might be + defined as 10.0.0.4=sw0-p1:10.0.0.2 and + 20.0.0.4=sw1-p1:20.0.0.2, if the values + given were suitable ports and IP addresses. +

    +
    +

    @@ -1763,7 +1805,9 @@ The name of a meter to rate-limit log messages for the ACL. The string must match the column of a row in the table. By - default, log messages are not rate-limited. + default, log messages are not rate-limited. In order to ensure + that the same rate limits multiple ACL logs + separately, set the column.

    @@ -1931,6 +1975,21 @@ binding table.

    + + + Configures the datapath tunnel key for the logical router. + This is not needed because ovn-northd will assign an + unique key for each datapath by itself. However, if it is configured, + ovn-northd honors the configured value. + + + Use the requested conntrack zone for SNAT with this router. This can be + useful if egress traffic from the host running OVN comes from both OVN + and other sources. This way, OVN and the other sources can make use of + the same conntrack zone. + @@ -2052,6 +2111,18 @@

    + +

    + This column is used to further describe the desired behavior + of the meter when there are multiple references to it. If this + column is empty or is set to false, the rate will + be shared across all rows that refer to the same Meter + . Conversely, when this column + is set to true, each user of the same Meter will be + rate-limited on its own. +

    +
    + See External IDs at the beginning of this document. @@ -2118,6 +2189,11 @@ in or another logical router port in .

    + +

    + A logical router port may not have the same name as a logical switch + port, but the database schema cannot enforce this. +

    @@ -2152,15 +2228,15 @@ architecture guide, provide limited connectivity between logical networks and physical ones. OVN support multiple kinds of gateways. The - table can be used three different ways to configure + table can be used two different ways to configure distributed gateway ports, which are one kind of - gateway. These different forms of configuration exist for - historical reasons. All of them produce the same kind of OVN + gateway. These two forms of configuration exist for + historical reasons. Both of them produce the same kind of OVN southbound records and the same behavior in practice.

    - If any of these are set, this logical router port represents a + If either of these are set, this logical router port represents a distributed gateway port that connects this router to a logical switch with a localnet port or a connection to another OVN deployment. There may be at most @@ -2168,12 +2244,11 @@

    - The newest and most preferred way to configure a gateway is - , followed by . Using is deprecated. At most one of these - should be set at a time on a given LRP, since they configure - the same features. + The preferred way to configure a gateway is , but is also supported for backward + compatibility. Only one of these should be set at a time on a + given LRP, since they configure the same features.

    @@ -2208,6 +2283,17 @@ table="Logical_Switch_Port"/> to router.

    +

    + OVN 20.03 and earlier supported a third way to configure + distributed gateway ports using + options:redirect-chassis to specify the gateway + chassis. This method is no longer supported. Any remaining + users should switch to one of the newer methods instead. A + may be easily configured from + the command line, e.g. ovn-nbctl lrp-set-gateway-chassis + lrp chassis. +

    + Designates an to provide gateway high availability. @@ -2218,10 +2304,6 @@ logical router port. - - Designates the named chassis as the gateway. - -

    MTU issues arise in mixing tunnels with logical networks that are @@ -3096,6 +3178,23 @@ resolving hostnames via the Domain Name System.

    + + +

    +

    + "bootfile_name_alt" option is used to support iPXE. + When both "bootfile_name" and "bootfile_name_alt" are provided + by the CMS, "bootfile_name" will be used for option 67 if the + dhcp request contains etherboot option (175), otherwise + "bootfile_name_alt" will be used. +
    + + +

    + The DHCPv4 option code for this option is 28. This option + specifies the IP address used as a broadcast address. +

    +
    @@ -3511,10 +3610,6 @@ Association of a chassis to a logical router port. The traffic going out through an specific router port will be redirected to a chassis, or a set of them in high availability configurations. - A single is equivalent to setting - . Using - allows associating multiple prioritized - chassis with a single logical router port.

    diff -Nru ovn-20.09.0/ovn-sb.ovsschema ovn-20.12.0/ovn-sb.ovsschema --- ovn-20.09.0/ovn-sb.ovsschema 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/ovn-sb.ovsschema 2020-12-17 17:53:32.000000000 +0000 @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "2.10.0", - "cksum": "2548342632 22615", + "version": "20.12.0", + "cksum": "3969471120 24441", "tables": { "SB_Global": { "columns": { @@ -90,8 +90,14 @@ "isRoot": true}, "Logical_Flow": { "columns": { - "logical_datapath": {"type": {"key": {"type": "uuid", - "refTable": "Datapath_Binding"}}}, + "logical_datapath": + {"type": {"key": {"type": "uuid", + "refTable": "Datapath_Binding"}, + "min": 0, "max": 1}}, + "logical_dp_group": + {"type": {"key": {"type": "uuid", + "refTable": "Logical_DP_Group"}, + "min": 0, "max": 1}}, "pipeline": {"type": {"key": {"type": "string", "enum": ["set", ["ingress", "egress"]]}}}, @@ -107,6 +113,14 @@ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, + "Logical_DP_Group": { + "columns": { + "datapaths": + {"type": {"key": {"type": "uuid", + "refTable": "Datapath_Binding", + "refType": "weak"}, + "min": 0, "max": "unlimited"}}}, + "isRoot": false}, "Multicast_Group": { "columns": { "datapath": {"type": {"key": {"type": "uuid", @@ -152,6 +166,11 @@ "type": {"key": {"type": "integer", "minInteger": 1, "maxInteger": 16777215}}}, + "load_balancers": {"type": {"key": {"type": "uuid", + "refTable": "Load_Balancer", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -447,6 +466,24 @@ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["logical_port", "ip", "port", "protocol"]], + "isRoot": true}, + "Load_Balancer": { + "columns": { + "name": {"type": "string"}, + "vips": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "protocol": { + "type": {"key": {"type": "string", + "enum": ["set", ["tcp", "udp", "sctp"]]}, + "min": 0, "max": 1}}, + "datapaths": { + "type": {"key": {"type": "uuid", + "refTable": "Datapath_Binding"}, + "min": 0, "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, "isRoot": true} } } diff -Nru ovn-20.09.0/ovn-sb.xml ovn-20.12.0/ovn-sb.xml --- ovn-20.09.0/ovn-sb.xml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/ovn-sb.xml 2020-12-17 17:53:32.000000000 +0000 @@ -579,11 +579,12 @@

    In more detail, to start, OVN searches the - table for a row with correct , a of ingress, a - of 0, and a that is true for the packet. If none - is found, OVN drops the packet. If OVN finds more than one, it chooses - the match with the highest . Then OVN executes + table for a row with correct or a + , a of + ingress, a of 0, and a that is true for the packet. If none is found, OVN + drops the packet. If OVN finds more than one, it chooses the match with + the highest . Then OVN executes each of the actions specified in the row's column, in the order specified. Some actions, such as those to modify packet headers, require no further details. The next and @@ -616,12 +617,12 @@

    To execute the egress pipeline, OVN again searches the table for a row with correct , a of 0, a that is true for the packet, but now looking for a of egress. If no matching row is found, - the output becomes a no-op. Otherwise, OVN executes the actions for the - matching flow (which is chosen from multiple, if necessary, as already - described). + column="logical_datapath"/> or a , + a of 0, a that is true for + the packet, but now looking for a of + egress. If no matching row is found, the output becomes a + no-op. Otherwise, OVN executes the actions for the matching flow (which + is chosen from multiple, if necessary, as already described).

    @@ -652,6 +653,11 @@ The logical datapath to which the logical flow belongs. + + The group of logical datapaths to which the logical flow belongs. This + means that the same logical flow belongs to all datapaths in a group. + +

    The primary flows used for deciding on a packet's destination are the @@ -2068,29 +2074,32 @@ The string should reference a entry from the table. The only meter - that is appriopriate + that is appropriate is drop. -

    fwd_group(P);
    +
    fwd_group(liveness=bool, childports=port, ...);

    - Parameters: liveness, list of child ports P. + Parameters: optional liveness, either + true or false, defaulting to false; + childports, a comma-delimited list of strings denoting + logical ports to load balance across.

    - It load balances traffic to one or more child ports in a - logical switch. ovn-controller translates the - fwd_group into openflow group with one bucket - for each child port. If liveness is set to true, it also - integrates the bucket selection with BFD status on the tunnel + Load balance traffic to one or more child ports in a logical + switch. ovn-controller translates the + fwd_group into an OpenFlow group with one bucket for + each child port. If liveness=true is specified, it + also integrates the bucket selection with BFD status on the tunnel interface corresponding to child port.

    -

    Example: fwd_group(liveness=true, childports=p1,p2 -

    +

    Example: fwd_group(liveness=true, childports="p1", + "p2");

    @@ -2191,6 +2200,22 @@

    Prerequisite: tcp

    +
    reject { action; ... };
    +
    +

    + If the original packet is IPv4 or IPv6 TCP packet, it replaces it + with IPv4 or IPv6 TCP RST packet and executes the inner actions. + Otherwise it replaces it with an ICMPv4 or ICMPv6 packet and + executes the inner actions. +

    + +

    + The inner actions should not attempt to swap eth source with eth + destination and IP source with IP destination as this action + implicitly does that. +

    +
    +
    trigger_event;

    @@ -2309,6 +2334,43 @@ Delegation Router and managed IPv6 Prefix delegation state machine

    + +
    R = chk_lb_hairpin();
    +
    +

    + This action checks if the packet under consideration was destined + to a load balancer VIP and it is hairpinned, i.e., after load + balancing the destination IP matches the source IP. If it is so, + then the 1-bit destination register R is set to 1. +

    +
    + +
    R = chk_lb_hairpin_reply();
    +
    +

    + This action checks if the packet under consideration is from + one of the backend IP of a load balancer VIP and the destination IP + is the load balancer VIP. If it is so, then the 1-bit destination + register R is set to 1. +

    +
    + +
    R = ct_snat_to_vip;
    +
    +

    + This action sends the packet through the SNAT zone to change the + source IP address of the packet to the load balancer VIP if the + original destination IP was load balancer VIP and commits the + connection. This action applies successfully only for the + hairpinned traffic i.e if the action chk_lb_hairpin + returned success. This action doesn't take any arguments and it + determines the SNAT IP internally. + + The packet is not automatically sent to the next table. The caller + has to execute the next; action explicitly after this + action to advance the packet to the next stage. +

    +
    @@ -2335,6 +2397,20 @@
    + +

    + Each row in this table represents a group of logical datapaths referenced + by the column + in the table. +

    + + +

    + List of entries. +

    +
    +
    +

    The rows in this table define multicast groups of logical ports. @@ -2481,6 +2557,12 @@ constructed for each supported encapsulation. + +

    + Load balancers associated with the datapath. +

    + +

    Each row in is associated with some @@ -2558,8 +2640,8 @@ For every Logical_Switch_Port record in OVN_Northbound database, ovn-northd creates a record in this table. ovn-northd populates - and maintains every column except the chassis column, - which it leaves empty in new records. + and maintains every column except the chassis and + virtual_parent columns, which it leaves empty in new records.

    @@ -2578,6 +2660,12 @@

    + ovn-controller also populates the + virtual_parent column of records whose type is + virtual. +

    + +

    When a chassis shuts down gracefully, it should clean up the chassis column that it previously had populated. (This is not critical because resources hosted on the chassis are equally @@ -2594,10 +2682,15 @@ - A logical port, taken from in the OVN_Northbound - database's - table. OVN does not prescribe a particular format for the + A logical port. For a logical switch port, this is taken from in the + OVN_Northbound database's table. For a logical router port, this is taken + from in the OVN_Northbound database's table. (This means + that logical switch ports and router port names must share names in an + OVN deployment.) OVN does not prescribe a particular format for the logical port ID. @@ -2999,14 +3092,6 @@ The name of the distributed port for which this chassisredirect port represents a particular instance. - - - The chassis that this chassisredirect port - is bound to. This is taken from - in the OVN_Northbound database's table. - @@ -3879,7 +3964,16 @@ - A list of which references this HA chassis group. + The set of that reference this HA chassis group. + To determine the correct , find the + chassisredirect type that + references this . This is derived from some particular logical router. + Starting from that LR, find the set of all logical switches and routers + connected to it, directly or indirectly, across router ports that link + one LRP to another or to a LSP. For each LSP in these logical switches, + find the corresponding and add its bound (if any) to . @@ -4006,62 +4100,50 @@

    - This table montiors a service for its liveliness. The service - can be an IPv4 tcp or a udp service. ovn-controller - periodically sends out service monitor packets and updates the - status of the service. Service monitoring for IPv6 services is - not supported. + Each row in this table configures monitoring a service for its liveness. + The service can be an IPv4 TCP or UDP + service. ovn-controller periodically sends out service + monitor packets and updates the status of the service. Service monitoring + for IPv6 services is not supported.

    - - IP of the service to be monitored. Only IPv4 is supported. - - - - The protocol of the service. It can be either tcp or - udp. - - - - The tcp or udp port of the service. - - - - The VIF of logical port on which the service is running. The - ovn-controller which binds this logical_port - monitors the service by sending periodic monitor packets. - +

    + ovn-northd uses this feature to implement the load balancer + health check feature offered to the CMS through the northbound database. +

    - +

    - The ovn-controller which binds the - logical_port updates the status to online - offline or error. + ovn-northd sets these columns and values to configure the + service monitor.

    -

    - For tcp service, ovn-controller sends a - TCP SYN packet to the service and expects a - TCP ACK response to consider the service to be - online. -

    + + IP of the service to be monitored. Only IPv4 is supported. + -

    - For udp service, ovn-controller sends a udp - packet to the service and doesn't expect any reply. If it receives - ICMP reply, then it considers the service to be offline. -

    -
    + + The protocol of the service. + - - Source Ethernet address to use in the service monitor packet. - + + The TCP or UDP port of the service. + - - Source IPv4 address to use in the service monitor packet. - + + The VIF of the logical port on which the service is running. The + ovn-controller that binds this logical_port + monitors the service by sending periodic monitor packets. + + + + Source Ethernet address to use in the service monitor packet. + + + + Source IPv4 address to use in the service monitor packet. + - The interval, in seconds, between service monitor checks. @@ -4082,6 +4164,67 @@ + +

    + The ovn-controller on the chassis that hosts the updates this column to report the service's + status. +

    + + +

    + For TCP service, ovn-controller sends a SYN to the + service and expects an ACK response to consider the service to be + online. +

    + +

    + For UDP service, ovn-controller sends a UDP packet to + the service and doesn't expect any reply. If it receives an ICMP + reply, then it considers the service to be offline. +

    +
    +
    + + + + See External IDs at the beginning of this document. + + +
    + + +

    + Each row represents a load balancer. +

    + + + A name for the load balancer. This name has no special meaning or + purpose other than to provide convenience for human interaction with + the ovn-nb database. + + + + A map of virtual IP addresses (and an optional port number with + : as a separator) associated with this load balancer and + their corresponding endpoint IP addresses (and optional port numbers + with : as separators) separated by commas. + + + +

    + Valid protocols are tcp, udp, or + sctp. This column is useful when a port number is + provided as part of the vips column. If this column is + empty and a port number is provided as part of vips + column, OVN assumes the protocol to be tcp. +

    +
    + + + Datapaths to which this load balancer applies to. + + See External IDs at the beginning of this document. diff -Nru ovn-20.09.0/README.rst ovn-20.12.0/README.rst --- ovn-20.09.0/README.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/README.rst 2020-12-17 17:53:32.000000000 +0000 @@ -53,7 +53,7 @@ .. TODO(stephenfin): Update with a link to the hosting site of the docs, once we know where that is -To install Open vSwitch on a regular Linux or FreeBSD host, please read the +To install OVN on a regular Linux or FreeBSD host, please read the `installation guide `__. For specifics around installation on a specific platform, refer to one of the `other installation guides `__ diff -Nru ovn-20.09.0/tests/atlocal.in ovn-20.12.0/tests/atlocal.in --- ovn-20.09.0/tests/atlocal.in 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/atlocal.in 2020-12-17 17:53:32.000000000 +0000 @@ -46,23 +46,7 @@ case `uname` in Linux) MALLOC_PERTURB_=165; export MALLOC_PERTURB_ - - # Before glibc 2.11, the feature enabled by MALLOC_CHECK_ was not - # thread-safe. See https://bugzilla.redhat.com/show_bug.cgi?id=585674 and - # in particular the patch attached there, which was applied to glibc CVS as - # "Restore locking in free_check." between 1.11 and 1.11.1. - binary=$abs_top_builddir/controller/ovn-controller - glibc=`ldd $binary | sed -n 's/^ libc\.[^ ]* => \([^ ]*\) .*/\1/p'` - glibc_version=`$glibc | sed -n '1s/.*version \([0-9]\{1,\}\.[0-9]\{1,\}\).*/\1/p'` - case $glibc_version in - 2.[0-9] | 2.1[01]) mcheck=disabled ;; - *) mcheck=enabled ;; - esac - if test $mcheck = enabled; then - MALLOC_CHECK_=2; export MALLOC_CHECK_ - else - echo >&2 "glibc $glibc_version detected, disabling memory checking" - fi + MALLOC_CHECK_=2; export MALLOC_CHECK_ ;; FreeBSD) case `uname -r` in @@ -221,8 +205,5 @@ # Add some default flags to make the tests run better under Address # Sanitizer, if it was used for the build. -# -# We disable leak detection because otherwise minor leaks that don't -# matter break everything. -ASAN_OPTIONS=detect_leaks=0:abort_on_error=true:log_path=asan:$ASAN_OPTIONS +ASAN_OPTIONS=detect_leaks=1:abort_on_error=true:log_path=asan:$ASAN_OPTIONS export ASAN_OPTIONS diff -Nru ovn-20.09.0/tests/automake.mk ovn-20.12.0/tests/automake.mk --- ovn-20.09.0/tests/automake.mk 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/automake.mk 2020-12-17 17:53:32.000000000 +0000 @@ -20,6 +20,7 @@ TESTSUITE_AT = \ tests/testsuite.at \ tests/checkpatch.at \ + tests/network-functions.at \ tests/ovn.at \ tests/ovn-northd.at \ tests/ovn-nbctl.at \ @@ -40,8 +41,7 @@ SYSTEM_USERSPACE_TESTSUITE_AT = \ tests/system-userspace-testsuite.at \ tests/system-ovn.at \ - tests/system-userspace-macros.at \ - tests/system-userspace-packet-type-aware.at + tests/system-userspace-macros.at SYSTEM_TESTSUITE_AT = \ tests/system-common-macros.at \ @@ -100,6 +100,7 @@ valgrind_wrappers = \ tests/valgrind/ovn-controller \ + tests/valgrind/ovn-controller-vtep \ tests/valgrind/ovn-nbctl \ tests/valgrind/ovn-northd \ tests/valgrind/ovn-sbctl \ @@ -154,11 +155,12 @@ # Run kmod tests. Assume kernel modules has been installed or linked into the kernel check-kernel: all set $(SHELL) '$(SYSTEM_KMOD_TESTSUITE)' -C tests AUTOTEST_PATH='$(AUTOTEST_PATH)'; \ - "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes && "$$@" --recheck) + $(SUDO) "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes && $(SUDO) "$$@" --recheck) - check-system-userspace: all + +check-system-userspace: all set $(SHELL) '$(SYSTEM_USERSPACE_TESTSUITE)' -C tests AUTOTEST_PATH='$(AUTOTEST_PATH)'; \ - "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes && "$$@" --recheck) + $(SUDO) "$$@" $(TESTSUITEFLAGS) -j1 || (test X'$(RECHECK)' = Xyes && $(SUDO) "$$@" --recheck) clean-local: test ! -f '$(TESTSUITE)' || $(SHELL) '$(TESTSUITE)' -C tests --clean diff -Nru ovn-20.09.0/tests/network-functions.at ovn-20.12.0/tests/network-functions.at --- ovn-20.09.0/tests/network-functions.at 1970-01-01 00:00:00.000000000 +0000 +++ ovn-20.12.0/tests/network-functions.at 2020-12-17 17:53:32.000000000 +0000 @@ -0,0 +1,187 @@ +AT_BANNER([test library internal helpers]) + +OVS_START_SHELL_HELPERS +# ip_to_hex 192 168 0 1 -> c0a80001 +# ip_to_hex 192.168.0.1 -> c0a80001 +ip_to_hex() { + if test $# = 1; then + set $(echo $1 | sed 's/\./ /g') + fi + printf "%02x%02x%02x%02x" "$@" +} +OVS_END_SHELL_HELPERS + +AT_SETUP([ip_to_hex]) +AT_KEYWORDS([network-functions]) +AT_CHECK([ip_to_hex 192 168 0 1], [0], [c0a80001]) +AT_CHECK([ip_to_hex 192.168.0.1], [0], [c0a80001]) +AT_CLEANUP + +OVS_START_SHELL_HELPERS +# ip_csum WORDS +# +# Calculates the IP checksum of the provided 16-bit words, which +# should be provided as a sequence of hex digits (a multiple of 4 +# digits in length). Prints the checksum on stdout as 4 hex digits. +ip_csum() { + local csum=0 + while test -n "$1"; do + local head=$(expr "$1" : '\(....\)') + csum=$(expr $csum + $(printf %u 0x$head)) + set -- "${1##????}" + done + while test $csum -gt 65535; do + local a=$(expr $csum / 65536) + local b=$(expr $csum % 65536) + csum=$(expr $a + $b) + done + csum=$(expr 65535 - $csum) + printf "%04x" $csum +} +OVS_END_SHELL_HELPERS + +AT_SETUP([ip_csum]) +AT_KEYWORDS([network-functions]) +test_csum() { + AT_CHECK_UNQUOTED([ip_csum $1], [0], [$2]) +} +test_csum 4500003c1c4640004006b1e600000a63ac100a0c ac10 +test_csum 4500003c1c4640004006b1e6ac100a63ac100a0c 0000 +test_csum 4500007600000000400100000a000003aca80003 c3d9 +test_csum 45000076000000004001c3d90a000003aca80003 0000 +AT_CLEANUP + +OVS_START_SHELL_HELPERS +# ip4_csum_inplace IP4_HEADER +# +# Outputs IP4_HEADER with the checksum filled in properly. +# The checksum must initially be 0000. IP4_HEADER must be +# 40 hex digits. +ip4_csum_inplace() { + local csum=$(ip_csum $1) + echo "$1" | sed "s/^\(....................\)..../\1$csum/" +} +OVS_END_SHELL_HELPERS + +AT_SETUP([ip4_csum_inplace]) +AT_CHECK([ip4_csum_inplace 450000730000400040110000c0a80001c0a800c7], [0], + [45000073000040004011b861c0a80001c0a800c7 +]) +AT_CLEANUP + +OVS_START_SHELL_HELPERS +# ip6_pseudoheader IP6_HEADER NEXT_HEADER PAYLOAD_LEN +# +# where: +# IP6_HEADER is the 40-byte IPv6 header as 80 hex digits +# NEXT_HEADER is Next Header in the pseudoheader as 2 hex digits +# PAYLOAD_LEN is the length of everything that follows the IPv6 +# header, as a decimal count of bytes +ip6_pseudoheader() { + local ip6_srcdst=$(expr "$1" : '................\(................................................................\)') + local len=$(printf "%08x" $3) + printf %s "${ip6_srcdst}${len}000000$2" +} +OVS_END_SHELL_HELPERS + +AT_SETUP([ip6_pseudoheader]) +AT_KEYWORDS([network-functions]) +AT_CHECK([ip6_pseudoheader 6000000000203aff''fe8000000000000088c57541aa0c58ee''ff020000000000000000000000000001 3a 32], + [0], + [fe8000000000000088c57541aa0c58eeff020000000000000000000000000001000000200000003a]) +AT_CLEANUP + +OVS_START_SHELL_HELPERS +# icmp6_csum ICMP6_AND_PAYLOAD IP6HEADER +# +# Outputs the checksum for ICMP6_AND_PAYLOAD given that it is inside +# IP6HEADER. Both arguments must be given as hex digits. +icmp6_csum() { + local payload_len=$(expr ${#1} / 2) + ip_csum $(ip6_pseudoheader "$2" 3a $payload_len)$1 +} +# icmp6_csum_inplace ICMP6_AND_PAYLOAD IP6HEADER +# +# Outputs ICMP6_AND_PAYLOAD with the checksum filled in properly. +icmp6_csum_inplace() { + local csum=$(icmp6_csum "$@") + echo "$1" | sed "s/^\(....\)..../\1$csum/" +} +OVS_END_SHELL_HELPERS + +AT_SETUP([icmp6_csum]) +AT_KEYWORDS([network-functions]) +ipv6_src=10000000000000000000000000000003 +ipv6_dst=20000000000000000000000000000002 +payload=0000000000000000000000000000000000000000 # 20 0-bytes +payload=${payload}${payload} # 40 0-bytes +payload=${payload}${payload} # 80 0-bytes +ip6=6000000000583afe${ipv6_src}${ipv6_dst} +AT_CHECK([icmp6_csum 8000000062f00001${payload} $ip6], [0], [ec76]) +AT_CHECK([icmp6_csum 8000ec7662f00001${payload} $ip6], [0], [0000]) +AT_CHECK_UNQUOTED([icmp6_csum_inplace 8000000062f00001${payload} $ip6], [0], + [8000ec7662f00001${payload} +]) +AT_CLEANUP + +OVS_START_SHELL_HELPERS +# hex_to_binary HEXDIGITS +# +# Converts the pairs of HEXDIGITS into bytes and prints them on stdout. +hex_to_binary() { + printf $(while test -n "$1"; do + printf '\\%03o' 0x$(expr "$1" : '\(..\)') + set -- "${1##??}" + done) +} + +# tcpdump_hex TITLE PACKET +# +# Passes PACKET, expressed as pairs of hex digits, to tcpdump, +# printing "TITLE: " as a prefix. +# +if test $HAVE_TCPDUMP = yes; then + tcpdump_hex() { + if test $# -gt 1; then + printf "%s: " "$1" + shift + fi + + local pkt_len=$(expr ${#1} / 2) + (# File header + hex_to_binary a1b2c3d4 # magic number + printf '\0\2' # major version 2 + printf '\0\4' # minor version 4 + printf '\0\0\0\0' # GMT to local correction + printf '\0\0\0\0' # sigfigs + printf '\0\0\5\356' # snaplen 1518 + printf '\0\0\0\1' # Ethernet data link type + + # Packet header + printf '\0\0\0\0' # timestamp seconds + printf '\0\0\0\0' # timestamp subseconds + hex_to_binary $(printf "%08x" $pkt_len) # incl_len + hex_to_binary $(printf "%08x" $pkt_len) # orig_len + + # Packet + hex_to_binary $1 + ) | tcpdump -vvvve -n -t -r- 2>&1 | grep -v 'reading from file -' + } +else + tcpdump_hex() { + if test $# -gt 1; then + printf "%s: " "$1" + shift + fi + echo "(cannot print packet because tcpdump is not installed)" + } +fi +OVS_END_SHELL_HELPERS + +AT_SETUP([tcpdump_hex]) +AT_KEYWORDS([network-functions]) +AT_SKIP_IF([test $HAVE_TCPDUMP = no]) +AT_CHECK([tcpdump_hex title ffffffffffff0011223344550800], [0], [stdout]) +AT_CHECK([grep 'title:' stdout], [0], [ignore]) +AT_CHECK([grep '00:11:22:33:44:55' stdout], [0], [ignore]) +AT_CLEANUP diff -Nru ovn-20.09.0/tests/ofproto-macros.at ovn-20.12.0/tests/ofproto-macros.at --- ovn-20.09.0/tests/ofproto-macros.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ofproto-macros.at 2020-12-17 17:53:32.000000000 +0000 @@ -1,12 +1,27 @@ m4_divert_push([PREPARE_TESTS]) [ +# Strips 'n_packets=...' from ovs-ofctl output. +strip_n_packets () { + sed 's/ n_packets=[0-9]*,//' +} + +# Strips 'n_bytes=...' from ovs-ofctl output. +strip_n_bytes () { + sed 's/ n_bytes=[0-9]*,//' +} + +# Strips 'cookie=...' from ovs-ofctl output. +strip_cookie () { + sed 's/ cookie=0x[0-9a-fA-F]*,//' +} + # Strips out uninteresting parts of ovs-ofctl output, as well as parts # that vary from one run to another. ofctl_strip () { sed ' s/ (xid=0x[0-9a-fA-F]*)// s/ duration=[0-9.]*s,// -s/ cookie=0x0,// +s/ cookie=0,// s/ table=0,// s/ n_packets=0,// s/ n_bytes=0,// @@ -19,6 +34,12 @@ ' } +# Strips out uninteresting parts of ovs-ofctl output, including n_packets=.. +# n_bytes=.. +ofctl_strip_all () { + ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie +} + # Filter (multiline) vconn debug messages from ovs-vswitchd.log. # Use with vconn_sub() and ofctl_strip() print_vconn_debug () { awk -F\| < ovs-vswitchd.log ' @@ -54,7 +75,9 @@ [OVS_WAIT_UNTIL([$2=`sed -n 's/.*0:.*: listening on port \([[0-9]]*\)$/\1/p' "$1"` && test X != X"[$]$2"])]) start_daemon () { - "$@" -vconsole:off --detach --no-chdir --pidfile --log-file + set "$@" -vconsole:off --detach --no-chdir --pidfile --log-file + echo "$@" + "$@" pidfile="$OVS_RUNDIR"/$1.pid on_exit "test -e \"$pidfile\" && kill \`cat \"$pidfile\"\`" } @@ -99,6 +122,7 @@ # Start ovs-vswitchd as $1 start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl + as $1 ovs-appctl vlog/disable-rate-limit vconn } # "as $1" sets the OVS_*DIR environment variables to point to $ovs_base/$1. diff -Nru ovn-20.09.0/tests/ovn.at ovn-20.12.0/tests/ovn.at --- ovn-20.09.0/tests/ovn.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn.at 2020-12-17 17:53:32.000000000 +0000 @@ -12,7 +12,7 @@ m4_divert_text([PREPARE_TESTS], [ovn_check_packets__ () { echo - echo "checking packets in $1 against $2:" + echo "$3: checking packets in $1 against $2:" rcv_pcap=$1 rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'` exp_text=$2 @@ -25,7 +25,7 @@ sort $exp_text > expout } ovn_check_packets_remove_broadcast__ () { - echo "checking packets in $1 against $2:" + echo "$3: checking packets in $1 against $2:" rcv_pcap=$1 rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'` exp_text=$2 @@ -41,16 +41,16 @@ ]) m4_define([OVN_CHECK_PACKETS], - [ovn_check_packets__ "$1" "$2" + [ovn_check_packets__ "$1" "$2" "__file__:__line__" AT_CHECK([sort $rcv_text], [0], [expout])]) m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST], - [ovn_check_packets_remove_broadcast__ "$1" "$2" + [ovn_check_packets_remove_broadcast__ "$1" "$2" "__file__:__line__" AT_CHECK([sort $rcv_text], [0], [expout])]) m4_define([OVN_CHECK_PACKETS_CONTAIN], - [ovn_check_packets__ "$1" "$2" - AT_CHECK([sort $rcv_text | comm --nocheck-order -2 -3 expout -], [0], [])]) + [ovn_check_packets__ "$1" "$2" "__file__:__line__" + AT_CHECK([sort $rcv_text | comm -2 -3 expout -], [0], [])]) AT_BANNER([OVN components]) @@ -1102,6 +1102,50 @@ ct_commit { ip4.dst = 192.168.0.1; }; Field ip4.dst is not modifiable. +# Legact ct_commit_v1 action. +ct_commit(); + formats as ct_commit; + encodes as ct(commit,zone=NXM_NX_REG13[0..15]) + has prereqs ip +ct_commit(ct_mark=1); + formats as ct_commit(ct_mark=0x1); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark)) + has prereqs ip +ct_commit(ct_mark=1/1); + formats as ct_commit(ct_mark=0x1/0x1); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_mark)) + has prereqs ip +ct_commit(ct_label=1); + formats as ct_commit(ct_label=0x1); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_label)) + has prereqs ip +ct_commit(ct_label=1/1); + formats as ct_commit(ct_label=0x1/0x1); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_label)) + has prereqs ip +ct_commit(ct_mark=1, ct_label=2); + formats as ct_commit(ct_mark=0x1, ct_label=0x2); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1->ct_mark,set_field:0x2->ct_label)) + has prereqs ip + +ct_commit(ct_label=0x01020304050607080910111213141516); + formats as ct_commit(ct_label=0x1020304050607080910111213141516); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1020304050607080910111213141516->ct_label)) + has prereqs ip +ct_commit(ct_label=0x181716151413121110090807060504030201); + formats as ct_commit(ct_label=0x16151413121110090807060504030201); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x16151413121110090807060504030201->ct_label)) + has prereqs ip +ct_commit(ct_label=0x1000000000000000000000000000000/0x1000000000000000000000000000000); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1000000000000000000000000000000/0x1000000000000000000000000000000->ct_label)) + has prereqs ip +ct_commit(ct_label=18446744073709551615); + formats as ct_commit(ct_label=0xffffffffffffffff); + encodes as ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0xffffffffffffffff->ct_label)) + has prereqs ip +ct_commit(ct_label=18446744073709551616); + Decimal constants must be less than 2**64. + ct_mark = 12345 Field ct_mark is not modifiable. ct_label = 0xcafe @@ -1280,22 +1324,22 @@ encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause) reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot"); formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot"); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause) + encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.d2.09.2f.74.66.74.70.62.6f.6f.74,pause) reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10); formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10); encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause) -reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10); - formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause) +reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10,broadcast_address=255.255.255.255); + formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10, broadcast_address = 255.255.255.255); + encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a.1c.04.ff.ff.ff.ff,pause) reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server="tftp_server_test"); formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = "tftp_server_test"); encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause) reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org"); formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org"); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause) + encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause) reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com"); formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com"); - encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause) + encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause) reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); Cannot use 2-bit field reg1[0..1] where 1-bit field is required. @@ -1593,6 +1637,14 @@ encodes as controller(userdata=00.00.00.0b.00.00.00.00) has prereqs tcp +# reject +reject { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; + encodes as controller(userdata=00.00.00.16.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64) + +reject { }; + formats as reject { drop; }; + encodes as controller(userdata=00.00.00.16.00.00.00.00) + # trigger_event trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c"); encodes as controller(userdata=00.00.00.0f.00.00.00.00.00.00.00.00.00.01.00.0b.31.30.2e.30.2e.30.2e.31.3a.38.30.00.02.00.03.74.63.70.00.03.00.24.31.32.33.34.35.36.37.38.2d.61.62.63.64.2d.39.38.37.36.2d.66.65.64.63.2d.31.31.31.31.39.66.38.65.37.64.36.63) @@ -1687,7 +1739,8 @@ reg0[0..14] = select(1, 2, 3); cannot use 15-bit field reg0[0..14] for "select", which requires at least 16 bits. -fwd_group(liveness="true", childports="eth0", "lsp1"); +fwd_group(liveness=true, childports="eth0", "lsp1"); + formats as fwd_group(liveness="true", childports="eth0", "lsp1"); encodes as group:11 uses group: id(11), name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=watch_port:17,load=0x17->NXM_NX_REG15[0..15],resubmit(,64)) @@ -1701,13 +1754,59 @@ fwd_group(); Syntax error at `)' expecting `;'. -fwd_group(liveness="false", childports="eth0", "lsp1"); - Syntax error at `"false"' expecting `,'. +fwd_group(childports="eth0", "lsp1"); + encodes as group:12 + uses group: id(12), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[0..15],resubmit(,64),bucket=load=0x17->NXM_NX_REG15[0..15],resubmit(,64)) + +fwd_group(liveness=xyzzy, childports="eth0", "lsp1"); + Syntax error at `xyzzy' expecting true or false. + +fwd_group(liveness=false childports="eth0", "lsp1"); + Syntax error at `childports' expecting `,'. # prefix delegation handle_dhcpv6_reply; encodes as controller(userdata=00.00.00.13.00.00.00.00) +# chk_lb_hairpin +reg0[0] = chk_lb_hairpin(); + encodes as set_field:0/0x80->reg10,resubmit(,68),move:NXM_NX_REG10[7]->NXM_NX_XXREG0[96] + +reg2[2] = chk_lb_hairpin(); + encodes as set_field:0/0x80->reg10,resubmit(,68),move:NXM_NX_REG10[7]->NXM_NX_XXREG0[34] + +reg0 = chk_lb_hairpin(); + Cannot use 32-bit field reg0[0..31] where 1-bit field is required. + +reg0[0] = chk_lb_hairpin(foo); + chk_lb_hairpin doesn't take any parameters + +chk_lb_hairpin; + Syntax error at `chk_lb_hairpin' expecting action. + +# chk_lb_hairpin_reply +reg0[0] = chk_lb_hairpin_reply(); + encodes as set_field:0/0x80->reg10,resubmit(,69),move:NXM_NX_REG10[7]->NXM_NX_XXREG0[96] + +reg2[2..3] = chk_lb_hairpin_reply(); + Cannot use 2-bit field reg2[2..3] where 1-bit field is required. + +reg0 = chk_lb_hairpin_reply(); + Cannot use 32-bit field reg0[0..31] where 1-bit field is required. + +reg0[0] = chk_lb_hairpin_reply(foo); + chk_lb_hairpin_reply doesn't take any parameters + +chk_lb_hairpin_reply; + Syntax error at `chk_lb_hairpin_reply' expecting action. + +# ct_snat_to_vip +ct_snat_to_vip; + encodes as resubmit(,70) + +ct_snat_to_vip(foo); + Syntax error at `(' expecting `;'. + # Miscellaneous negative tests. ; Syntax error at `;'. @@ -1774,6 +1873,7 @@ ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33` ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop +check ovn-nbctl --wait=hv sync # Pre-populate the hypervisors' ARP tables so that we don't lose any # packets for ARP resolution (native tunneling doesn't queue packets @@ -1851,10 +1951,6 @@ fi } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send packets between all pairs of source and destination ports: # # 1. Unicast packets are delivered to exactly one logical switch port @@ -1997,19 +2093,8 @@ test_arp 11 f00000000011 $sip $tip # dump information and flows with counters -ovn-sbctl dump-flows -- list multicast_group - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int +ovn-sbctl dump-flows -- list multicast_group > sbflows +AT_CAPTURE_FILE([sbflows]) # Now check the packets actually received against the ones expected. for i in 1 2 3; do @@ -2055,20 +2140,12 @@ # explictly # For Chassis hv1 -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp11], [0], [dnl -encap : [[]] -]) -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp12], [0], [dnl -encap : [[]] -]) +check_row_count Port_Binding 1 logical_port=lp11 'encap=[[]]' +check_row_count Port_Binding 1 logical_port=lp12 'encap=[[]]' # For Chassis hv2 -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp21], [0], [dnl -encap : [[]] -]) -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp22], [0], [dnl -encap : [[]] -]) +check_row_count Port_Binding 1 logical_port=lp21 'encap=[[]]' +check_row_count Port_Binding 1 logical_port=lp22 'encap=[[]]' # Bind the ports to the encap-ip for i in 1 2; do @@ -2084,26 +2161,14 @@ # ports to be bound to geneve tunnels. # For Chassis 1 -encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv1 type=geneve ip=192.168.0.1` - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp11], [0], [dnl -encap : ${encap_rec} -]) - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp12], [0], [dnl -encap : ${encap_rec} -]) +encap_rec=$(fetch_column Encap _uuid chassis_name=hv1 type=geneve ip=192.168.0.1) +check_row_count Port_Binding 1 logical_port=lp11 encap=$encap_rec +check_row_count Port_Binding 1 logical_port=lp12 encap=$encap_rec # For Chassis 2 -encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv2 type=geneve ip=192.168.0.2` - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp21], [0], [dnl -encap : ${encap_rec} -]) - -AT_CHECK_UNQUOTED([ovn-sbctl --column encap list port_binding lp22], [0], [dnl -encap : ${encap_rec} -]) +encap_rec=$(fetch_column Encap _uuid chassis_name=hv2 type=geneve ip=192.168.0.2) +check_row_count Port_Binding 1 logical_port=lp21 encap=$encap_rec +check_row_count Port_Binding 1 logical_port=lp22 encap=$encap_rec # Pre-populate the hypervisors' ARP tables so that we don't lose any # packets for ARP resolution (native tunneling doesn't queue packets @@ -2147,10 +2212,6 @@ done } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send packets between all pairs of source and destination ports: # # 1. Unicast packets are delivered to exactly one logical switch port @@ -2235,6 +2296,8 @@ ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp3"' drop ovn-nbctl --wait=sb sync +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) on_exit 'kill `cat ovn-trace.pid`' ovn-trace --detach --pidfile --no-chdir @@ -2505,7 +2568,8 @@ done done ovn-nbctl --wait=sb sync -ovn-sbctl dump-flows +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) OVN_POPULATE_ARP @@ -2621,7 +2685,7 @@ AT_CLEANUP -AT_SETUP([ovn -- 2 HVs, 2 LS, routing works for multiple colacated segments attached to different switches]) +AT_SETUP([ovn -- 2 HVs, 2 LS, routing works for multiple collocated segments attached to different switches]) ovn_start for tag in `seq 10 30`; do @@ -2670,12 +2734,14 @@ ovn-nbctl --wait=sb sync -ovn-nbctl show -ovn-sbctl dump-flows -echo "------ OVN dump ------" -ovn-nbctl show -ovn-sbctl show +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + +ovn-nbctl show > nbctl-show +AT_CAPTURE_FILE([nbctl-show]) +ovn-sbctl show > sbctl-show +AT_CAPTURE_FILE([sbctl-show]) for i in 1 2; do hv=hv-$i @@ -2802,7 +2868,8 @@ ovn-nbctl --wait=sb sync ovn-nbctl show -ovn-sbctl dump-flows +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) echo "------ OVN dump ------" ovn-nbctl show @@ -2938,7 +3005,8 @@ ovn-nbctl --wait=sb sync ovn-nbctl show -ovn-sbctl dump-flows +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) # vif ports for i in 1 2; do @@ -3007,6 +3075,98 @@ AT_CLEANUP +AT_SETUP([ovn -- VLAN transparency, passthru=true]) +ovn_start + +check ovn-nbctl ls-add ls +check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true +for i in 1 2; do + check ovn-nbctl lsp-add ls lsp$i + check ovn-nbctl lsp-set-addresses lsp$i f0:00:00:00:00:0$i +done + +net_add physnet +ovs-vsctl add-br br-phys +ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet:br-phys +ovn_attach physnet br-phys 192.168.0.1 + +for i in 1 2; do + ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \ + options:tx_pcap=vif$i-tx.pcap \ + options:rxq_pcap=vif$i-rx.pcap \ + ofport-request=$i + OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup]) +done + +test_packet() { + local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 + + # First try tracing the packet. + uflow="inport==\"lsp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth && vlan.present==1" + echo "output(\"$lout\");" > expout + AT_CAPTURE_FILE([trace]) + AT_CHECK([ovn-trace --all ls "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout]) + + # Then actually send a packet, for an end-to-end test. + local packet=$(echo $dst$src | sed 's/://g')${eth}fefefefe + vif=vif$inport + ovs-appctl netdev-dummy/receive $vif $packet + echo $packet >> ${eout#lsp}.expected +} + +test_packet 1 f0:00:00:00:00:02 f0:00:00:00:00:01 8100 lsp2 lsp2 +test_packet 2 f0:00:00:00:00:01 f0:00:00:00:00:02 8100 lsp1 lsp1 +for i in 1 2; do + OVN_CHECK_PACKETS_REMOVE_BROADCAST([vif$i-tx.pcap], [$i.expected]) +done + +AT_CLEANUP + +AT_SETUP([ovn -- VLAN transparency, passthru=false]) +ovn_start + +check ovn-nbctl ls-add ls +check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=false +for i in 1 2; do + check ovn-nbctl lsp-add ls lsp$i + check ovn-nbctl lsp-set-addresses lsp$i f0:00:00:00:00:0$i +done + +net_add physnet +ovs-vsctl add-br br-phys +ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet:br-phys +ovn_attach physnet br-phys 192.168.0.1 + +for i in 1 2; do + ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \ + options:tx_pcap=vif$i-tx.pcap \ + options:rxq_pcap=vif$i-rx.pcap \ + ofport-request=$i + OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup]) + + : > $i.expected +done + +test_packet() { + local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 + + # First try tracing the packet. + uflow="inport==\"lsp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth && vlan.present==1" + AT_CHECK([ovn-trace --all ls "$uflow" | grep drop], [0], [ignore]) + + # Then actually send a packet, for an end-to-end test. + local packet=$(echo $dst$src | sed 's/://g')${eth}fefefefe + ovs-appctl netdev-dummy/receive vif$inport $packet +} + +test_packet 1 f0:00:00:00:00:02 f0:00:00:00:00:01 8100 lsp2 lsp2 +test_packet 2 f0:00:00:00:00:01 f0:00:00:00:00:02 8100 lsp1 lsp1 +for i in 1 2; do + OVN_CHECK_PACKETS_REMOVE_BROADCAST([vif$i-tx.pcap], [$i.expected]) +done + +AT_CLEANUP + AT_SETUP([ovn -- 2 HVs, 1 LS, no switching between multiple localnet ports with different tags]) ovn_start @@ -3271,9 +3431,6 @@ # ARP request should not be responded to by logical switch router # type arp responder on HV1 and HV2 and should reach directly to # vif1 and vif2 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} sha=f00000000003 spa=`ip_to_hex 192 168 1 2` tpa=`ip_to_hex 192 168 1 1` @@ -3571,6 +3728,8 @@ done done +check ovn-nbctl --wait=hv sync + # Pre-populate the hypervisors' ARP tables so that we don't lose any # packets for ARP resolution (native tunneling doesn't queue packets # for ARP resolution). @@ -3578,7 +3737,6 @@ # Allow some time for ovn-northd and ovn-controller to catch up. # XXX This should be more systematic. -sleep 1 # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... # @@ -3618,7 +3776,7 @@ done } -# test_arp INPORT SHA SPA TPA FLOOD [REPLY_HA] +# test_arp INPORT SHA SPA TPA [REPLY_HA] # # Causes a packet to be received on INPORT. The packet is an ARP # request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then @@ -3629,25 +3787,22 @@ # SHA and REPLY_HA are each 12 hex digits. # SPA and TPA are each 8 hex digits. test_arp() { - local inport=$1 sha=$2 spa=$3 tpa=$4 flood=$5 reply_ha=$6 + echo "$@" + local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5 local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} hv=hv`vif_to_hv $inport` as $hv ovs-appctl netdev-dummy/receive vif$inport $request - as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request + as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request > trace # Expect to receive the broadcast ARP on the other logical switch ports if - # IP address is not configured on the switch patch port or on the router - # port (i.e, $flood == 1). + # IP address is not configured to the switch patch port. local i=`vif_to_ls $inport` local j k for j in 1 2 3; do for k in 1 2 3; do - # Skip ingress port. - if test $i$j$k == $inport; then - continue - fi - - if test X$flood == X1; then + # 192.168.33.254 is configured to the switch patch port for lrp33, + # so no ARP flooding expected for it. + if test $i$j$k != $inport && test $tpa != `ip_to_hex 192 168 33 254`; then echo $request >> $i$j$k.expected fi done @@ -3661,11 +3816,15 @@ fi } -as hv1 ovs-vsctl --columns=name,ofport list interface -as hv1 ovn-sbctl list port_binding -as hv1 ovn-sbctl list datapath_binding -as hv1 ovn-sbctl dump-flows -as hv1 ovs-ofctl dump-flows br-int +as hv1 ovs-vsctl --columns=name,ofport list interface > interfaces +(as hv1 ovn-sbctl list port_binding + as hv1 ovn-sbctl list datapath_binding) > bindings +as hv1 ovn-sbctl dump-flows > sbflows +as hv1 ovs-ofctl dump-flows br-int > offlows +AT_CAPTURE_FILE([interfaces]) +AT_CAPTURE_FILE([bindings]) +AT_CAPTURE_FILE([sbflows]) +AT_CAPTURE_FILE([offlows]) # Send IP packets between all pairs of source and destination ports: # @@ -3674,9 +3833,6 @@ # # 2. Broadcast IP packets are delivered to all logical switch ports # except the input port. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} for is in 1 2 3; do for js in 1 2 3; do for ks in 1 2 3; do @@ -3692,15 +3848,16 @@ if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi if test $d != $s; then unicast=$d; else unicast=; fi - test_ip $s $smac $dmac $sip $dip $unicast #1 + test_ip $s $smac $dmac $sip $dip $unicast & #1 if test $id = $is && test $d != $s; then bcast="$bcast $d"; fi done done done - test_ip $s $smac ffffffffffff $sip ffffffff $bcast #2 + test_ip $s $smac ffffffffffff $sip ffffffff $bcast & #2 done done + wait done : > mac_bindings.expected @@ -3784,9 +3941,9 @@ otherip=`ip_to_hex 192 168 $i$j 55` # Some other IP in subnet externalip=`ip_to_hex 1 2 3 4` # Some other IP not in subnet - test_arp $i$j$k $smac $sip $rip 0 $rmac #4 - test_arp $i$j$k $smac $otherip $rip 0 $rmac #5 - test_arp $i$j$k $smac $sip $otherip 1 #6 + test_arp $i$j$k $smac $sip $rip $rmac #4 + test_arp $i$j$k $smac $otherip $rip $rmac #5 + test_arp $i$j$k $smac $sip $otherip #6 # When rip is 192.168.33.254, ARP request from externalip won't be # filtered, because 192.168.33.254 is configured to switch peer port @@ -3795,7 +3952,7 @@ if test $i = 3 && test $j = 3; then lrp33_rsp=$rmac fi - test_arp $i$j$k $smac $externalip $rip 0 $lrp33_rsp #7 + test_arp $i$j$k $smac $externalip $rip $lrp33_rsp #7 # MAC binding should be learned from ARP request. host_mac_pretty=f0:00:00:00:0$i:$j$k @@ -3861,22 +4018,34 @@ done done -ovn-sbctl -f csv -d bare --no-heading \ - -- --columns=logical_port,ip,mac list mac_binding > mac_bindings +ovn-sbctl dump-flows > sbflows2 +AT_CAPTURE_FILE([sbflows2]) -# Now check the packets actually received against the ones expected. -for i in 1 2 3; do - for j in 1 2 3; do - for k in 1 2 3; do - OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap], - [$i$j$k.expected]) +AT_CAPTURE_FILE([expected]) +AT_CAPTURE_FILE([received]) +check_packets() { + > expected + > received + for i in 1 2 3; do + for j in 1 2 3; do + for k in 1 2 3; do + pcap=hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap + echo "--- $pcap" | tee -a expected >> received + sort $i$j$k.expected >> expected + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received + echo | tee -a expected >> received + done done done -done -# Check the MAC bindings against those expected. -AT_CHECK_UNQUOTED([sort < mac_bindings], [0], [`sort < mac_bindings.expected` -]) + echo '--- MAC bindings' | tee -a expected >> received + ovn-sbctl -f csv -d bare --no-heading \ + -- --columns=logical_port,ip,mac list mac_binding | sort >> received + sort < mac_bindings.expected >> expected + + $at_diff expected received >/dev/null +} +OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received]) # Gracefully terminate daemons OVN_CLEANUP([hv1], [hv2], [hv3]) @@ -4037,10 +4206,6 @@ fi } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # lp11 send GARP request to announce ownership of 192.168.1.100. sha=f00000000011 @@ -4053,7 +4218,7 @@ test_arp 11 $sha $spa $tpa sleep 1 -AT_CHECK([ovn-sbctl find mac_binding ip="192.168.1.100"], [0], []) +check_row_count MAC_Binding 0 ip="192.168.1.100" # When always_learn_from_arp_request=true, the new mac-binding will be learned. ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=true @@ -4078,7 +4243,7 @@ sha=f00000000012 test_arp 12 $sha $spa $tpa -OVS_WAIT_UNTIL([ovn-sbctl find mac_binding ip="192.168.1.100" | grep f0:00:00:00:00:12]) +wait_row_count MAC_Binding 1 ip="192.168.1.100" mac='"f0:00:00:00:00:12"' ovn-nbctl --wait=hv sync # give to the hv the time to send queued ip packets sleep 1 @@ -4125,13 +4290,14 @@ if test $j = 1; then ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown elif test $j = 2; then - ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" + ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j 4343::00$i$j" ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j else - extra_addr="f0:00:00:00:0$i:$i$j fe80::ea2a:eaff:fe28:$i$j" + extra_addr="f0:00:00:00:0$i:$i$j fe80::ea2a:eaff:fe28:$i$j 4242::00$i$j" ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" ovn-nbctl lsp-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" fi + OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp$i$j` = xup]) done done @@ -4243,10 +4409,6 @@ done } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # no port security sip=`ip_to_hex 192 168 0 12` tip=`ip_to_hex 192 168 0 13` @@ -4344,7 +4506,7 @@ done # lp13 has extra port security with mac f0000000113 and ipv6 addr -# fe80::ea2a:eaff:fe28:0012 +# fe80::ea2a:eaff:fe28:0012 and 4242::0013 # ipv4 packet should be dropped for lp13 with mac f0000000113 sip=`ip_to_hex 192 168 0 13` @@ -4358,20 +4520,24 @@ for i in 1 2 3; do tip=fe80000000000000ea2aeafffe2800${i}3 test_ipv6 11 f00000000011 f00000000${i}${i}3 $sip $tip ${i}3 + tip=424200000000000000000000000000${i}3 + test_ipv6 11 f00000000011 f00000000${i}${i}3 $sip $tip ${i}3 done # ipv6 packet should not be received by lp33 with mac f0000000333 -# and ip6.dst as fe80::ea2a:eaff:fe28:0023 as it is -# configured with fe80::ea2a:eaff:fe28:0033 +# and ip6.dst as fe80::ea2a:eaff:fe28:0023 or 4242::0023 as it is +# configured with fe80::ea2a:eaff:fe28:0033 and 4242::0033 # lp11 can send ipv6 traffic as there is no port security sip=ee800000000000000000000000000000 tip=fe80000000000000ea2aeafffe280023 test_ipv6 11 f00000000011 f00000000333 $sip $tip +tip=42420000000000000000000000000023 +test_ipv6 11 f00000000011 f00000000333 $sip $tip # ipv6 packet should be allowed for lp[123]3 with mac f0000000${i}${i}3 -# and ip6.src fe80::ea2a:eaff:fe28:0${i}${i}3 and ip6.src ::. +# and ip6.src fe80::ea2a:eaff:fe28:0${i}${i}3, 4242::00${i}3 and ip6.src ::. # and should be dropped for any other ip6.src # lp21 can receive ipv6 traffic as there is no port security @@ -4379,6 +4545,8 @@ for i in 1 2 3; do sip=fe80000000000000ea2aeafffe2800${i}3 test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21 + sip=424200000000000000000000000000${i}3 + test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21 # Test ICMPv6 MLD reports (v1 and v2) and NS for DAD sip=00000000000000000000000000000000 @@ -4396,9 +4564,7 @@ done # configure lsp13 to send and received IPv4 packets with an address range -ovn-nbctl lsp-set-port-security lp13 "f0:00:00:00:00:13 192.168.0.13 20.0.0.4/24 10.0.0.0/24" - -sleep 2 +ovn-nbctl --wait=hv lsp-set-port-security lp13 "f0:00:00:00:00:13 192.168.0.13 20.0.0.4/24 10.0.0.0/24 4242::/64" sip=`ip_to_hex 10 0 0 13` tip=`ip_to_hex 192 168 0 22` @@ -4411,12 +4577,24 @@ # with dst ip 192.168.0.23 should be allowed test_ip 13 f00000000013 f00000000023 $sip $tip 23 +sip=42420000000000000000000000000014 +tip=42420000000000000000000000000023 +# IPv6 packet from lsp13 with src ip 4242::14 destined to lsp23 +# with dst ip 4242::23 should be received by lsp23 +test_ipv6 13 f00000000013 f00000000223 $sip $tip 23 + sip=`ip_to_hex 192 168 0 33` tip=`ip_to_hex 10 0 0 15` # IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 # with dst ip 10.0.0.15 should be received by lsp13 test_ip 33 f00000000033 f00000000013 $sip $tip 13 +sip=42420000000000000000000000000033 +tip=42420000000000000000000000000013 +# IPv6 packet from lsp33 with src ip 4242::33 destined to lsp13 +# with dst ip 4242::13 should be received by lsp13 +test_ipv6 33 f00000000333 f00000000013 $sip $tip 13 + sip=`ip_to_hex 192 168 0 33` tip=`ip_to_hex 20 0 0 4` # IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 @@ -4429,6 +4607,12 @@ # with dst ip 20.0.0.5 should not be received by lsp13 test_ip 33 f00000000033 f00000000013 $sip $tip +sip=42420000000000000000000000000033 +tip=42420000000000000000000000000005 +# IPv6 packet from lsp33 with src ip 4242::33 destined to lsp13 +# with dst ip 4242::5 should not be received by lsp13 +test_ipv6 33 f00000000333 f00000000013 $sip $tip 13 + sip=`ip_to_hex 192 168 0 33` tip=`ip_to_hex 20 0 0 255` # IPv4 packet from lsp33 with src ip 192.168.0.33 destined to lsp13 @@ -4672,9 +4856,6 @@ sleep 1 # Send ip packets between the two ports. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} # Packet to send. src_mac="f00000010203" @@ -4789,9 +4970,6 @@ sleep 1 # Send ip packets between the two ports. -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} # Packet to send. src_mac="f00000010203" @@ -4939,10 +5117,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets between foo1 and alice1 src_mac="f00000010203" dst_mac="000000010203" @@ -5162,10 +5336,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets between foo1 and alice1 src_mac="f00000010203" dst_mac="000001010203" @@ -5232,69 +5402,71 @@ AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS]) ovn_start -ovn-nbctl ls-add ls1 +check ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 ls1-lp1 \ +check ovn-nbctl lsp-add ls1 ls1-lp1 \ -- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" -ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" +check ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" -ovn-nbctl lsp-add ls1 ls1-lp2 \ +check ovn-nbctl lsp-add ls1 ls1-lp2 \ -- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" -ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" +check ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" -ovn-nbctl ls-add ls2 -ovn-nbctl lsp-add ls2 ls2-lp1 \ +check ovn-nbctl ls-add ls2 +check ovn-nbctl lsp-add ls2 ls2-lp1 \ -- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" -ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" -ovn-nbctl lsp-add ls2 ls2-lp2 \ +check ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" +check ovn-nbctl lsp-add ls2 ls2-lp2 \ -- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" -ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" +check ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \ options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \ \"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")" -ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1} -ovn-nbctl lsp-set-dhcpv4-options ls1-lp2 ${d1} +check ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1} +check ovn-nbctl lsp-set-dhcpv4-options ls1-lp2 ${d1} d2="$(ovn-nbctl create DHCP_Options cidr=30.0.0.0/24 \ options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \ \"lease_time\"=\"3600\"")" -ovn-nbctl lsp-set-dhcpv4-options ls2-lp2 ${d2} +check ovn-nbctl lsp-set-dhcpv4-options ls2-lp2 ${d2} net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=2 -ovs-vsctl -- add-port br-int hv1-vif3 -- \ +check ovs-vsctl -- add-port br-int hv1-vif3 -- \ set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \ options:tx_pcap=hv1/vif3-tx.pcap \ options:rxq_pcap=hv1/vif3-rx.pcap \ ofport-request=3 -ovs-vsctl -- add-port br-int hv1-vif4 -- \ +check ovs-vsctl -- add-port br-int hv1-vif4 -- \ set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \ options:tx_pcap=hv1/vif4-tx.pcap \ options:rxq_pcap=hv1/vif4-rx.pcap \ ofport-request=4 +as hv1 ovs-appctl vlog/set dbg + OVN_POPULATE_ARP sleep 2 @@ -5302,10 +5474,31 @@ as hv1 ovs-vsctl show # This shell function sends a DHCP request packet -# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP USE_IP ... +# +# The first argument is just the number of calls to this function so +# far (1, 2, ...). This is redundant, but it makes it easier to find +# the failures by searching for the number. +# +# test_dhcp PACKET_NUM INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ... +packet_num=0 test_dhcp() { - local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 use_ip=$8 - shift; shift; shift; shift; shift; shift; shift; shift; + local expect_resume=: + local trace=false + while :; do + case $1 in + (--no-resume) expect_resume=false; shift ;; + # --trace isn't used but it can be useful for debugging: + (--trace) trace=:; shift ;; + (*) break ;; + esac + done + + packet_num=$(expr $packet_num + 1) + AT_FAIL_IF([test $packet_num != $1]) + shift + + local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 eth_boot=$8 use_ip=$9 + shift; shift; shift; shift; shift; shift; shift; shift; shift; if test $use_ip != 0; then src_ip=$1 @@ -5316,12 +5509,35 @@ dst_ip=`ip_to_hex 255 255 255 255` fi + AS_BOX([dhcp test packet $packet_num]) + + as hv1 + if test -f hv1/ovs-ofctl.pid; then + OVS_APP_EXIT_AND_WAIT([ovs-ofctl]) + AT_FAIL_IF([test -f ovs-ofctl.pid]) + fi + AT_CAPTURE_FILE([ofctl_monitor$packet_num.log]) + ovs-ofctl monitor br-int resume --detach --no-chdir \ + --pidfile=ovs-ofctl.pid 2> ofctl_monitor$packet_num.log + + echo "inport=$inport src_mac=$src_mac dhcp_type=$dhcp_type broadcast=$broadcast ciaddr=$ciaddr offer_ip=$offer_ip request_ip=$request_ip use_ip=$use_ip src_ip=$src_ip dst_ip=$dst_ip" + if test $request_ip != 0; then - ip_len=0120 - udp_len=010b + if test $eth_boot != 0; then + ip_len=0124 + udp_len=010f + else + ip_len=0120 + udp_len=010b + fi else - ip_len=011a - udp_len=0106 + if test $eth_boot != 0; then + ip_len=011e + udp_len=010a + else + ip_len=011a + udp_len=0106 + fi fi if test $broadcast != 0; then @@ -5332,7 +5548,7 @@ reply_dst_ip=${offer_ip} fi - if test "$dhcp_type" == "04"; then + if test "$dhcp_type" = "04"; then ciaddr=$offer_ip fi @@ -5362,8 +5578,12 @@ # dhcp requested ip request=${request}3204${request_ip} fi + if test $eth_boot != 0; then + request=${request}af020000 + fi # dhcp end option request=${request}ff + tcpdump_hex "-- sending DHCP request on hv1-vif$inport" $request for port in $inport "$@"; do : >> $port.expected @@ -5383,7 +5603,9 @@ ip_len=$(printf "%x" $ip_len) udp_len=$(printf "%x" $udp_len) # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len - local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${reply_dst_ip} + local reply=${src_mac}${srv_mac}0800 + local ip_header=45100${ip_len}0000000080110000${srv_ip}${reply_dst_ip} + reply=${reply}$(ip4_csum_inplace $ip_header) # udp header and dhcp header. # $udp_len var will be in 3 digits. So adding a '0' before $udp_len reply=${reply}004300440${udp_len}0000020106006359aa760000${flags}${ciaddr} @@ -5409,47 +5631,49 @@ reply=${reply}63825363 reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000 echo $reply >> $inport.expected + tcpdump_hex "-- expecting DHCP reply on $inport" $request else for outport; do echo $request >> $outport.expected done fi - as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request + if $trace; then + as hv1 ovs-appctl ofproto/trace br-int in_port=hv1-vif$inport $request > trace$packet_num + AT_CAPTURE_FILE([trace$packet_num]) + else + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request + fi + + # NXT_RESUMEs should be 1. + if $expect_resume; then + OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor$packet_num.log | grep -c NXT_RESUME`]) + fi +} + +compare_dhcp_packets() { + received=$($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$1-tx.pcap) + expected=$(cat $1.expected) + + if test "$received" != "$expected"; then + AT_CHECK_UNQUOTED([echo "$received"; tcpdump_hex "$received"], [0], + [$(echo "$expected"; tcpdump_hex "$expected")]) + fi } reset_pcap_file() { local iface=$1 local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ options:rxq_pcap=dummy-rx.pcap rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - -AT_CAPTURE_FILE([ofctl_monitor0.log]) -as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ ---pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log - -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list logical_flow -echo "---------------------" - -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" +AT_CAPTURE_FILE([sbflows]) +ovn-sbctl dump-flows > sbflows -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl dump-flows br-int +# ---------------------------------------------------------------------- # Send DHCPDISCOVER. offer_ip=`ip_to_hex 10 0 0 4` @@ -5457,17 +5681,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=0 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts - -# NXT_RESUMEs should be 1. -OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 1 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +compare_dhcp_packets 1 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -cat 1.expected | cut -c -48 > expout -AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 1.expected | cut -c 53- > expout -AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- # ovs-ofctl also resumes the packets and this causes other ports to receive # the DHCP request packet. So reset the pcap files so that its easier to test. @@ -5483,17 +5700,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=$offer_ip expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +test_dhcp 2 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -# NXT_RESUMEs should be 2. -OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5507,18 +5717,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=`ip_to_hex 10 0 0 7` expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts - -# NXT_RESUMEs should be 3. -OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +test_dhcp 3 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 06 $expected_dhcp_opts +compare_dhcp_packets 2 +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected @@ -5531,36 +5733,20 @@ ciaddr=`ip_to_hex 0 0 0 0` offer_ip=0 request_ip=0 -test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1 +test_dhcp 4 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 0 1 1 # NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - # vif1-tx.pcap should have received the DHCPv4 (invalid) request packet OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) +# ---------------------------------------------------------------------- + reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected -# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined. -# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once. - -ciaddr=`ip_to_hex 0 0 0 0` -test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 4 0 - -# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for -# this lport. -ciaddr=`ip_to_hex 0 0 0 0` -test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 3 0 - # NXT_RESUMEs should be 4. -OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected]) -#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected]) - # Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6 # and ip4.dst set to 10.0.0.1. offer_ip=`ip_to_hex 10 0 0 6` @@ -5570,17 +5756,10 @@ src_ip=$offer_ip dst_ip=$server_ip expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 5. -OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 5 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5596,17 +5775,10 @@ src_ip=$offer_ip dst_ip=`ip_to_hex 255 255 255 255` expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 6. -OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 6 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5622,17 +5794,10 @@ src_ip=$offer_ip dst_ip=`ip_to_hex 255 255 255 255` expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts +test_dhcp 7 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts +compare_dhcp_packets 2 -# NXT_RESUMEs should be 7. -OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5648,17 +5813,10 @@ src_ip=$offer_ip dst_ip=`ip_to_hex 255 255 255 255` expected_dhcp_opts="" -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts - -# NXT_RESUMEs should be 8. -OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 8 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts +compare_dhcp_packets 2 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5670,14 +5828,13 @@ ciaddr=`ip_to_hex 0 0 0 0` src_ip=`ip_to_hex 10 0 0 6` dst_ip=`ip_to_hex 10 0 0 4` -test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 1 $src_ip $dst_ip 1 - -# NXT_RESUMEs should be 8. -OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp --no-resume 9 2 f00000000002 03 0 $ciaddr 0 0 0 1 $src_ip $dst_ip 1 # vif1-tx.pcap should have received the DHCPv4 request packet OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) +# ---------------------------------------------------------------------- + reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected @@ -5689,17 +5846,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=0 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts - -# NXT_RESUMEs should be 9. -OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 10 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +compare_dhcp_packets 1 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets -cat 1.expected | cut -c -48 > expout -AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 1.expected | cut -c 53- > expout -AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5712,10 +5862,7 @@ ciaddr=`ip_to_hex 10 0 0 6` request_ip=0 expected_dhcp_opts=0 -test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 - -# NXT_RESUMEs should be 10. -OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) +test_dhcp 11 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 # There is no reply for this. Check for the INFO log in ovn-controller.log AT_CHECK([test 1 = $(cat hv1/ovn-controller.log | \ @@ -5724,6 +5871,8 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets AT_CHECK([cat 2.packets], [0], []) +# ---------------------------------------------------------------------- + reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected @@ -5739,20 +5888,15 @@ # In the expected_dhcp_opts we should not see 330400000e10 which is # dhcp lease time option and 0104ffffff00 which is subnet mask option. expected_dhcp_opts=03040a00000136040a000001 -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 11. -OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +test_dhcp 12 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 # Now add the dhcp option T1 to the dhcp options. -ovn-nbctl set dhcp_options ${d1} options:T1=4000 +check ovn-nbctl --wait=hv set dhcp_options ${d1} options:T1=4000 +AT_CAPTURE_FILE([sbflows2]) +ovn-sbctl dump-flows > sbflows2 + +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5769,17 +5913,10 @@ # In the expected_dhcp_opts we should not see 330400000e10 which is # dhcp lease time option. expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +test_dhcp 13 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -# NXT_RESUMEs should be 12. -OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5796,17 +5933,10 @@ # In the expected_dhcp_opts we should not see 330400000e10 which is # dhcp lease time option and 0104ffffff00 which is subnet mask option. expected_dhcp_opts=03040a00000136040a000001 -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 13. -OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) +test_dhcp 14 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5815,9 +5945,11 @@ # Set tftp server option (IPv4 address) for ls1 echo "------ Set tftp server (IPv4 address) --------" -ovn-nbctl dhcp-options-set-options $d1 server_id=10.0.0.1 \ +ovn-nbctl --wait=hv dhcp-options-set-options $d1 server_id=10.0.0.1 \ server_mac=ff:10:00:00:00:01 lease_time=3600 router=10.0.0.1 \ tftp_server=10.10.10.10 +AT_CAPTURE_FILE([sbflows3]) +ovn-sbctl dump-flows > sbflows3 echo "----------------------------------------------" # Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with the offered IP @@ -5827,17 +5959,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=$offer_ip expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a00000142040a0a0a0a -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +test_dhcp 15 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -# NXT_RESUMEs should be 14. -OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5846,9 +5971,11 @@ # Set tftp server option (Hostname) for ls1 echo "------ Set tftp server (hostname) --------" -ovn-nbctl dhcp-options-set-options $d1 server_id=10.0.0.1 \ +ovn-nbctl --wait=hv dhcp-options-set-options $d1 server_id=10.0.0.1 \ server_mac=ff:10:00:00:00:01 lease_time=3600 router=10.0.0.1 \ tftp_server=\"test_tftp_server\" +AT_CAPTURE_FILE([sbflows4]) +ovn-sbctl dump-flows > sbflows4 echo "------------------------------------------" # Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with the offered IP @@ -5858,17 +5985,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=$offer_ip expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a0000014210746573745f746674705f736572766572 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts - -# NXT_RESUMEs should be 15. -OVS_WAIT_UNTIL([test 15 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) +test_dhcp 16 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- reset_pcap_file hv1-vif1 hv1/vif1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -5877,9 +5997,11 @@ # Set domain search list option for ls1 echo "------ Set domain search list --------" -ovn-nbctl dhcp-options-set-options $d1 server_id=10.0.0.1 \ +ovn-nbctl --wait=hv dhcp-options-set-options $d1 server_id=10.0.0.1 \ server_mac=ff:10:00:00:00:01 lease_time=3600 router=10.0.0.1 \ domain_search_list=\"test1.com,test2.com\" +AT_CAPTURE_FILE([sbflows5]) +ovn-sbctl dump-flows > sbflows5 echo "------------------------------------------" # Send DHCPREQUEST in the SELECTING/INIT-REBOOT state with the offered IP @@ -5889,17 +6011,10 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=$offer_ip expected_dhcp_opts=771305746573743103636f6d00057465737432c006330400000e100104ffffff0003040a00000136040a000001 -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +test_dhcp 17 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts +compare_dhcp_packets 2 -# NXT_RESUMEs should be 16. -OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) - -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets -cat 2.expected | cut -c -48 > expout -AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) -# Skipping the IPv4 checksum. -cat 2.expected | cut -c 53- > expout -AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) +# ---------------------------------------------------------------------- # test DHCPDECLINE offer_ip=`ip_to_hex 10 0 0 4` @@ -5907,10 +6022,94 @@ ciaddr=`ip_to_hex 0 0 0 0` request_ip=0 expected_dhcp_opts="" -test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +test_dhcp 18 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts AT_CHECK([fgrep -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], []) -OVN_CLEANUP([hv1]) +# Send Etherboot. + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +ovn-nbctl --all destroy dhcp-option + +ovn-nbctl dhcp-options-create 10.0.0.0/24 +d3=$(ovn-nbctl --bare --columns=_uuid find dhcp_options cidr="10.0.0.0/24") +ovn-nbctl dhcp-options-set-options $d3 \ + server_id=10.0.0.1 server_mac=ff:10:00:00:00:01 \ + lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"bootfile_name_alt\" \ + bootfile_name=\"bootfile\" + +ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3 + +offer_ip=`ip_to_hex 10 0 0 4` +server_ip=`ip_to_hex 10 0 0 1` +ciaddr=`ip_to_hex 0 0 0 0` +request_ip=0 +boofile=4308626f6f7466696c65 +expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001 +test_dhcp 19 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +compare_dhcp_packets 1 + +# Test that ovn-controller pinctrl thread handles dhcp requests when it +# connects to a wrong version of ovn-northd at startup. + +# Stop ovn-northd so that we can modify the northd_version. +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as northd-backup +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +northd_version=$(ovn-sbctl get SB_Global . options:northd_internal_version | sed s/\"//g) +echo "northd version = $northd_version" + +check ovn-sbctl set SB_Global . options:northd_internal_version=foo + +echo +echo "__file__:__line__: Stop ovn-controller." +as hv1 +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +echo +echo "__file__:__line__: Pin ovn-controller to ovn-northd version." + +as hv1 +check ovs-vsctl set open . external_ids:ovn-match-northd-version=true + +# Start ovn-controller +as hv1 +start_daemon ovn-controller + +OVS_WAIT_UNTIL( + [test 1 = $(grep -c "controller version - $northd_version mismatch with northd version - foo" hv1/ovn-controller.log) +]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# ---------------------------------------------------------------------- + +offer_ip=`ip_to_hex 10 0 0 4` +server_ip=`ip_to_hex 10 0 0 1` +ciaddr=`ip_to_hex 0 0 0 0` +request_ip=0 +boofile=4308626f6f7466696c65 +expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001 +test_dhcp 20 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts +compare_dhcp_packets 1 + +as hv1 +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]) AT_CLEANUP @@ -6275,10 +6474,6 @@ # XXX This should be more systematic. sleep 2 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets between foo1 and alice1 src_mac="f00000010203" dst_mac="000001010203" @@ -6416,9 +6611,6 @@ sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} for i in 1 2; do : > vif$i.expected done @@ -6593,9 +6785,10 @@ ls3_p1_mac=00:00:00:01:02:05 # Create a drop policy -ovn-nbctl lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop +check ovn-nbctl --wait=hv lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop # Check logical flow +ovn-sbctl dump-flows > sbflows AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl 1 ]) @@ -6618,10 +6811,10 @@ # Expected to drop the packet. $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets rcvd_packet=`cat vif2.packets` -AT_FAIL_IF([rcvd_packet = ""]) +AT_FAIL_IF([test "$rcvd_packet" != ""]) # Override drop policy with allow -ovn-nbctl lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow +check ovn-nbctl --wait=hv lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow # Check logical flow AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl @@ -6635,6 +6828,7 @@ as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" # Check if packet hit the allow policy +sleep 1 AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ grep "192.168.1.0" | \ grep "priority=20" | wc -l], [0], [dnl @@ -6650,7 +6844,7 @@ OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected]) # Override allow policy with reroute -ovn-nbctl lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" reroute 20.20.1.2 +check ovn-nbctl --wait=hv lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" reroute 20.20.1.2 # Check logical flow AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ @@ -6664,15 +6858,16 @@ ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip && udp && udp.src==53 && udp.dst==4369" as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" +sleep 1 echo "southbound flows" - -ovn-sbctl dump-flows | grep lr_in_policy +ovn-sbctl --ovs dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) echo "ovs flows" -ovs-ofctl dump-flows br-int +ovs-ofctl dump-flows br-int > brflows +AT_CAPTURE_FILE([brflows]) # Check if packet hit the allow policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" | \ +AT_CHECK([grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" brflows | \ grep "priority=30" | \ grep "n_packets=1" | wc -l], [0], [dnl 1 @@ -6778,10 +6973,12 @@ ls3_p1_mac=00:00:00:01:02:05 # Create a drop policy -ovn-nbctl lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop +check ovn-nbctl --wait=sb lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop # Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) +AT_CHECK([grep lr_in_policy sbflows | grep "2001" | wc -l], [0], [dnl 1 ]) @@ -6802,14 +6999,15 @@ # Expected to drop the packet. $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets -rcvd_packet=`cat vif2.packets` -AT_FAIL_IF([rcvd_packet = ""]) +AT_FAIL_IF([test -s vif2.packets]) # Override drop policy with allow -ovn-nbctl lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow +check ovn-nbctl --wait=sb lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow # Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" | wc -l], [0], [dnl +ovn-sbctl dump-flows > sbflows2 +AT_CAPTURE_FILE([sbflows2]) +AT_CHECK([grep lr_in_policy sbflows2 | grep "2001" | wc -l], [0], [dnl 2 ]) @@ -6820,7 +7018,9 @@ as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" # Check if packet hit the allow policy -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ +ovn-sbctl dump-flows > sbflows3 +AT_CAPTURE_FILE([sbflows3]) +AT_CHECK([grep lr_in_policy sbflows3 | \ grep "2001" | \ grep "priority=20" | wc -l], [0], [dnl 1 @@ -6835,10 +7035,12 @@ OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected]) # Override allow policy with reroute -ovn-nbctl lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2 +check ovn-nbctl --wait=sb lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2 # Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \ +ovn-sbctl dump-flows > sbflows4 +AT_CAPTURE_FILE([sbflows4]) +AT_CHECK([grep lr_in_policy sbflows4 | \ grep "2001" | \ grep "priority=30" | wc -l], [0], [dnl 1 @@ -6849,20 +7051,18 @@ ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip && udp && udp.src==53 && udp.dst==4369" as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet" +sleep 1 -echo "southbound flows" - -ovn-sbctl dump-flows | grep lr_in_policy -echo "ovs flows" -ovs-ofctl dump-flows br-int +ovn-sbctl dump-flows > sbflows5 +ovs-ofctl dump-flows br-int > offlows5 +AT_CAPTURE_FILE([sbflows5]) +AT_CAPTURE_FILE([offlows5]) # Check if packet hit the allow policy -AT_CHECK([ovs-ofctl dump-flows br-int | \ - grep "ipv6_src=2001::/64,ipv6_dst=2002::/64" | \ +AT_CHECK([grep "ipv6_src=2001::/64,ipv6_dst=2002::/64" offlows5 | \ grep "priority=30" | \ grep "n_packets=1" | wc -l], [0], [dnl 1 ]) -echo "packet hit reroute policy" # Expected packet has TTL decreased by 1 expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac && @@ -6958,7 +7158,7 @@ ovn-nbctl ls-del ls1 # wait for earlier changes to take effect -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) +check ovn-nbctl --wait=sb sync # ensure OF rules are no longer present. There used to be a bug here. test_datapath_in_of_rules 0 "after lport+ls removal" @@ -7068,95 +7268,54 @@ AT_SETUP([ovn -- ipam]) ovn_start +check_dynamic_addresses() { + local arg + case $2 in + ('') arg='[[]]' ;; + (*) arg="\"$2\"" ;; + esac + check_row_count nb:Logical_Switch_Port 1 name="$1" dynamic_addresses="$arg" +} + # Add a port to a switch that does not have a subnet set, then set the # subnet which should result in an address being allocated for the port. ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00" ovn-nbctl ls-add sw0 ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic ovn-nbctl --wait=sb add Logical-Switch sw0 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0], - ["0a:00:00:a8:01:03 192.168.1.2" -]) +check_dynamic_addresses p0 "0a:00:00:a8:01:03 192.168.1.2" # Add 9 more ports to sw0, addresses should all be unique. for n in `seq 1 9`; do ovn-nbctl --wait=sb lsp-add sw0 "p$n" -- lsp-set-addresses "p$n" dynamic done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p1 dynamic_addresses], [0], - ["0a:00:00:a8:01:04 192.168.1.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p2 dynamic_addresses], [0], - ["0a:00:00:a8:01:05 192.168.1.4" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p3 dynamic_addresses], [0], - ["0a:00:00:a8:01:06 192.168.1.5" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p4 dynamic_addresses], [0], - ["0a:00:00:a8:01:07 192.168.1.6" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p5 dynamic_addresses], [0], - ["0a:00:00:a8:01:08 192.168.1.7" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p6 dynamic_addresses], [0], - ["0a:00:00:a8:01:09 192.168.1.8" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p7 dynamic_addresses], [0], - ["0a:00:00:a8:01:0a 192.168.1.9" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p8 dynamic_addresses], [0], - ["0a:00:00:a8:01:0b 192.168.1.10" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p9 dynamic_addresses], [0], - ["0a:00:00:a8:01:0c 192.168.1.11" -]) +for i in `seq 1 9`; do + mac=0a:00:00:a8:01:$(printf "%02x" $(expr $i + 3)) + ip=192.168.1.$(expr $i + 2) + check_dynamic_addresses p$i "$mac $ip" +done # Trying similar tests with a second switch. MAC addresses should be unique # across both switches but IP's only need to be unique within the same switch. ovn-nbctl ls-add sw1 ovn-nbctl lsp-add sw1 p10 -- lsp-set-addresses p10 dynamic ovn-nbctl --wait=sb add Logical-Switch sw1 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p10 dynamic_addresses], [0], - ["0a:00:00:a8:01:0d 192.168.1.2" -]) +check_row_count nb:Logical_Switch_Port 1 name=p10 dynamic_addresses='"0a:00:00:a8:01:0d 192.168.1.2"' for n in `seq 11 19`; do ovn-nbctl --wait=sb lsp-add sw1 "p$n" -- lsp-set-addresses "p$n" dynamic done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p11 dynamic_addresses], [0], - ["0a:00:00:a8:01:0e 192.168.1.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p12 dynamic_addresses], [0], - ["0a:00:00:a8:01:0f 192.168.1.4" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p13 dynamic_addresses], [0], - ["0a:00:00:a8:01:10 192.168.1.5" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p14 dynamic_addresses], [0], - ["0a:00:00:a8:01:11 192.168.1.6" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p15 dynamic_addresses], [0], - ["0a:00:00:a8:01:12 192.168.1.7" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p16 dynamic_addresses], [0], - ["0a:00:00:a8:01:13 192.168.1.8" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p17 dynamic_addresses], [0], - ["0a:00:00:a8:01:14 192.168.1.9" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p18 dynamic_addresses], [0], - ["0a:00:00:a8:01:15 192.168.1.10" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p19 dynamic_addresses], [0], - ["0a:00:00:a8:01:16 192.168.1.11" -]) +for i in `seq 11 19`; do + mac=0a:00:00:a8:01:$(printf "%02x" $(expr $i + 3)) + ip=192.168.1.$(expr $i - 8) + check_dynamic_addresses p$i "$mac $ip" +done # Change a port's address to test for multiple ip's for a single address entry # and addresses set by the user. ovn-nbctl lsp-set-addresses p0 "0a:00:00:a8:01:17 192.168.1.2 192.168.1.12 192.168.1.14" ovn-nbctl --wait=sb lsp-add sw0 p20 -- lsp-set-addresses p20 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p20 dynamic_addresses], [0], - ["0a:00:00:a8:01:18 192.168.1.13" -]) +check_dynamic_addresses p20 "0a:00:00:a8:01:18 192.168.1.13" # Test for logical router port address management. ovn-nbctl create Logical_Router name=R1 @@ -7165,36 +7324,26 @@ -- add Logical_Router R1 ports @lrp -- lsp-add sw0 rp-sw0 \ -- set Logical_Switch_Port rp-sw0 type=router options:router-port=sw0 ovn-nbctl --wait=sb lsp-add sw0 p21 -- lsp-set-addresses p21 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p21 dynamic_addresses], [0], - ["0a:00:00:a8:01:1a 192.168.1.15" -]) +check_dynamic_addresses p21 "0a:00:00:a8:01:1a 192.168.1.15" # Test for address reuse after logical port is deleted. ovn-nbctl lsp-del p0 ovn-nbctl --wait=sb lsp-add sw0 p23 -- lsp-set-addresses p23 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p23 dynamic_addresses], [0], - ["0a:00:00:a8:01:03 192.168.1.2" -]) +check_dynamic_addresses p23 "0a:00:00:a8:01:03 192.168.1.2" # Test for multiple addresses to one logical port. ovn-nbctl lsp-add sw0 p25 -- lsp-set-addresses p25 \ "0a:00:00:a8:01:1b 192.168.1.12" "0a:00:00:a8:01:1c 192.168.1.14" ovn-nbctl --wait=sb lsp-add sw0 p26 -- lsp-set-addresses p26 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p26 dynamic_addresses], [0], - ["0a:00:00:a8:01:17 192.168.1.16" -]) +check_dynamic_addresses p26 "0a:00:00:a8:01:17 192.168.1.16" # Test for exhausting subnet address space. ovn-nbctl ls-add sw2 -- add Logical-Switch sw2 other_config subnet=172.16.1.0/30 ovn-nbctl --wait=sb lsp-add sw2 p27 -- lsp-set-addresses p27 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p27 dynamic_addresses], [0], - ["0a:00:00:10:01:03 172.16.1.2" -]) +check_dynamic_addresses p27 "0a:00:00:10:01:03 172.16.1.2" ovn-nbctl --wait=sb lsp-add sw2 p28 -- lsp-set-addresses p28 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p28 dynamic_addresses], [0], - ["0a:00:00:00:00:01" -]) +check_dynamic_addresses p28 "0a:00:00:00:00:01" # Test that address management does not add duplicate MAC for lsp/lrp peers. ovn-nbctl create Logical_Router name=R2 @@ -7206,119 +7355,83 @@ -- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \ -- set Logical_Switch_Port rp-sw3 type=router options:router-port=sw3 ovn-nbctl --wait=sb lsp-add sw0 p30 -- lsp-set-addresses p30 dynamic -AT_CHECK([ovn-nbctl get Logical-Switch-Port p30 dynamic_addresses], [0], - ["0a:00:00:a8:01:1d 192.168.1.17" -]) +check_dynamic_addresses p30 "0a:00:00:a8:01:1d 192.168.1.17" # Test static MAC address with dynamically allocated IP ovn-nbctl --wait=sb lsp-add sw0 p31 -- lsp-set-addresses p31 \ "fe:dc:ba:98:76:54 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:54 192.168.1.18" -]) +check_dynamic_addresses p31 "fe:dc:ba:98:76:54 192.168.1.18" # Update the static MAC address with dynamically allocated IP and check # if the MAC address is updated in 'Logical_Switch_Port.dynamic_adddresses' ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:55 dynamic" - -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:55 192.168.1.18" -]) +check_dynamic_addresses p31 "fe:dc:ba:98:76:55 192.168.1.18" ovn-nbctl --wait=sb lsp-set-addresses p31 "dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["0a:00:00:a8:01:1e 192.168.1.18" -]) +check_dynamic_addresses p31 "0a:00:00:a8:01:1e 192.168.1.18" ovn-nbctl --wait=sb lsp-set-addresses p31 "fe:dc:ba:98:76:56 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p31 dynamic_addresses], [0], - ["fe:dc:ba:98:76:56 192.168.1.18" -]) - +check_dynamic_addresses p31 "fe:dc:ba:98:76:56 192.168.1.18" # Test the exclude_ips from the IPAM list ovn-nbctl --wait=sb set logical_switch sw0 \ other_config:exclude_ips="192.168.1.19 192.168.1.21 192.168.1.23..192.168.1.50" -ovn-nbctl --wait=sb lsp-add sw0 p32 -- lsp-set-addresses p32 \ -"dynamic" +ovn-nbctl --wait=sb lsp-add sw0 p32 -- lsp-set-addresses p32 "dynamic" # 192.168.1.20 should be assigned as 192.168.1.19 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p32 dynamic_addresses], [0], - ["0a:00:00:a8:01:1e 192.168.1.20" -]) +check_dynamic_addresses p32 "0a:00:00:a8:01:1e 192.168.1.20" ovn-nbctl --wait=sb lsp-add sw0 p33 -- lsp-set-addresses p33 \ "dynamic" # 192.168.1.22 should be assigned as 192.168.1.21 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p33 dynamic_addresses], [0], - ["0a:00:00:a8:01:1f 192.168.1.22" -]) +check_dynamic_addresses p33 "0a:00:00:a8:01:1f 192.168.1.22" ovn-nbctl --wait=sb lsp-add sw0 p34 -- lsp-set-addresses p34 \ "dynamic" # 192.168.1.51 should be assigned as 192.168.1.23-192.168.1.50 is excluded. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p34 dynamic_addresses], [0], - ["0a:00:00:a8:01:34 192.168.1.51" -]) +check_dynamic_addresses p34 "0a:00:00:a8:01:34 192.168.1.51" # Now clear the exclude_ips list. 192.168.1.19 should be assigned. ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="invalid" -ovn-nbctl --wait=sb lsp-add sw0 p35 -- lsp-set-addresses p35 \ -"dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p35 dynamic_addresses], [0], - ["0a:00:00:a8:01:20 192.168.1.19" -]) +ovn-nbctl --wait=sb lsp-add sw0 p35 -- lsp-set-addresses p35 "dynamic" +check_dynamic_addresses p35 "0a:00:00:a8:01:20 192.168.1.19" # Set invalid data in exclude_ips list. It should be ignored. ovn-nbctl --wait=sb set Logical-switch sw0 other_config:exclude_ips="182.168.1.30" ovn-nbctl --wait=sb lsp-add sw0 p36 -- lsp-set-addresses p36 \ "dynamic" # 192.168.1.21 should be assigned as that's the next free one. -AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0], - ["0a:00:00:a8:01:21 192.168.1.21" -]) +check_dynamic_addresses p36 "0a:00:00:a8:01:21 192.168.1.21" # Clear the dynamic addresses assignment request. ovn-nbctl --wait=sb clear logical_switch_port p36 addresses -AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0], - [[[]] -]) +check_dynamic_addresses p36 # Set IPv6 prefix ovn-nbctl --wait=sb set Logical-switch sw0 other_config:ipv6_prefix="aef0::" -ovn-nbctl --wait=sb lsp-add sw0 p37 -- lsp-set-addresses p37 \ -"dynamic" +ovn-nbctl --wait=sb lsp-add sw0 p37 -- lsp-set-addresses p37 "dynamic" # With prefix aef0 and mac 0a:00:00:00:00:26, the dynamic IPv6 should be # - aef0::800:ff:fe00:26 (EUI64) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p37 dynamic_addresses], [0], - ["0a:00:00:a8:01:21 192.168.1.21 aef0::800:ff:fea8:121" -]) +check_dynamic_addresses p37 "0a:00:00:a8:01:21 192.168.1.21 aef0::800:ff:fea8:121" ovn-nbctl --wait=sb ls-add sw4 ovn-nbctl --wait=sb set Logical-switch sw4 other_config:ipv6_prefix="bef0::" \ -- set Logical-switch sw4 other_config:subnet=192.168.2.0/30 -ovn-nbctl --wait=sb lsp-add sw4 p38 -- lsp-set-addresses p38 \ -"dynamic" +ovn-nbctl --wait=sb lsp-add sw4 p38 -- lsp-set-addresses p38 "dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p38 dynamic_addresses], [0], - ["0a:00:00:a8:02:03 192.168.2.2 bef0::800:ff:fea8:203" -]) +check_dynamic_addresses p38 "0a:00:00:a8:02:03 192.168.2.2 bef0::800:ff:fea8:203" ovn-nbctl --wait=sb lsp-add sw4 p39 -- lsp-set-addresses p39 \ "f0:00:00:00:10:12 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p39 dynamic_addresses], [0], - ["f0:00:00:00:10:12 bef0::f200:ff:fe00:1012" -]) +check_dynamic_addresses p39 "f0:00:00:00:10:12 bef0::f200:ff:fe00:1012" # Test the case where IPv4 addresses are exhausted and IPv6 prefix is set # p40 should not have an IPv4 address since the pool is exhausted ovn-nbctl --wait=sb lsp-add sw4 p40 -- lsp-set-addresses p40 \ "dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p40 dynamic_addresses], [0], - ["0a:00:00:00:00:02 bef0::800:ff:fe00:2" -]) +check_dynamic_addresses p40 "0a:00:00:00:00:02 bef0::800:ff:fe00:2" # Test dynamic changes on switch ports. # @@ -7326,70 +7439,48 @@ ovn-nbctl --wait=sb lsp-add sw5 p41 -- lsp-set-addresses p41 \ "dynamic" # p41 will start with nothing -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) +check_dynamic_addresses p41 '' # Set a subnet. Now p41 should have an ipv4 address, too ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:a8:01:22 192.168.1.2" -]) +check_dynamic_addresses p41 "0a:00:00:a8:01:22 192.168.1.2" # Clear the other_config. The IPv4 address should be gone ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) +check_dynamic_addresses p41 '' # Set an IPv6 prefix. Now p41 should have an IPv6 address. ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="aef0::" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:00:00:03 aef0::800:ff:fe00:3" -]) +check_dynamic_addresses p41 "0a:00:00:00:00:03 aef0::800:ff:fe00:3" # Change the MAC address to a static one. The IPv6 address should update. ovn-nbctl --wait=sb lsp-set-addresses p41 "f0:00:00:00:10:2b dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b aef0::f200:ff:fe00:102b" -]) +check_dynamic_addresses p41 "f0:00:00:00:10:2b aef0::f200:ff:fe00:102b" # Change the IPv6 prefix. The IPv6 address should update. ovn-nbctl --wait=sb set Logical-Switch sw5 other_config:ipv6_prefix="bef0::" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b bef0::f200:ff:fe00:102b" -]) +check_dynamic_addresses p41 "f0:00:00:00:10:2b bef0::f200:ff:fe00:102b" # Clear the other_config. The IPv6 address should be gone ovn-nbctl --wait=sb clear Logical-Switch sw5 other_config -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - [[[]] -]) +check_dynamic_addresses p41 '' # Set the subnet again. Now p41 should get the IPv4 address again. ovn-nbctl --wait=sb add Logical-Switch sw5 other_config subnet=192.168.1.0/24 -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b 192.168.1.2" -]) +check_dynamic_addresses p41 "f0:00:00:00:10:2b 192.168.1.2" # Add an excluded IP address that conflicts with p41. p41 should update. ovn-nbctl --wait=sb add Logical-Switch sw5 other_config \ exclude_ips="192.168.1.2" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["f0:00:00:00:10:2b 192.168.1.3" -]) +check_dynamic_addresses p41 "f0:00:00:00:10:2b 192.168.1.3" # Add static ip address ovn-nbctl --wait=sb lsp-set-addresses p41 "dynamic 192.168.1.100" ovn-nbctl list Logical-Switch-Port p41 ovn-nbctl --wait=sb lsp-add sw5 p42 -- lsp-set-addresses p42 \ "dynamic 192.168.1.101" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0], - ["0a:00:00:a8:01:65 192.168.1.100" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p42 dynamic_addresses], [0], - ["0a:00:00:a8:01:66 192.168.1.101" -]) +check_dynamic_addresses p41 "0a:00:00:a8:01:65 192.168.1.100" +check_dynamic_addresses p42 "0a:00:00:a8:01:66 192.168.1.101" # define a mac address prefix ovn-nbctl ls-add sw6 @@ -7398,15 +7489,9 @@ for n in $(seq 1 3); do ovn-nbctl --wait=sb lsp-add sw6 "p5$n" -- lsp-set-addresses "p5$n" dynamic done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p51 dynamic_addresses], [0], - ["00:11:22:a8:64:03 192.168.100.2" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p52 dynamic_addresses], [0], - ["00:11:22:a8:64:04 192.168.100.3" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p53 dynamic_addresses], [0], - ["00:11:22:a8:64:05 192.168.100.4" -]) +check_dynamic_addresses p51 "00:11:22:a8:64:03 192.168.100.2" +check_dynamic_addresses p52 "00:11:22:a8:64:04 192.168.100.3" +check_dynamic_addresses p53 "00:11:22:a8:64:05 192.168.100.4" # verify configuration order does not break IPAM/MACAM ovn-nbctl ls-add sw7 @@ -7428,15 +7513,9 @@ for n in $(seq 1 3); do ovn-nbctl --wait=sb lsp-add sw8 "p8$n" -- lsp-set-addresses "p8$n" dynamic done -AT_CHECK([ovn-nbctl get Logical-Switch-Port p81 dynamic_addresses], [0], - ["00:11:22:00:00:06" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p82 dynamic_addresses], [0], - ["00:11:22:00:00:07" -]) -AT_CHECK([ovn-nbctl get Logical-Switch-Port p83 dynamic_addresses], [0], - ["00:11:22:00:00:08" -]) +check_dynamic_addresses p81 "00:11:22:00:00:06" +check_dynamic_addresses p82 "00:11:22:00:00:07" +check_dynamic_addresses p83 "00:11:22:00:00:08" # clear mac_prefix and check it is allocated in a random manner ovn-nbctl --wait=hv remove NB_Global . options mac_prefix @@ -7452,15 +7531,11 @@ ovn-nbctl ls-add sw10 ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:ipv6_prefix="ae01::" ovn-nbctl --wait=sb lsp-add sw10 p101 -- lsp-set-addresses p101 "dynamic ae01::1" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p101 dynamic_addresses], [0], - ["00:11:22:00:00:0a ae01::1" -]) +check_dynamic_addresses p101 "00:11:22:00:00:0a ae01::1" ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:subnet=192.168.110.0/24 ovn-nbctl --wait=sb lsp-add sw10 p102 -- lsp-set-addresses p102 "dynamic 192.168.110.10 ae01::2" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p102 dynamic_addresses], [0], - ["00:11:22:a8:6e:0b 192.168.110.10 ae01::2" -]) +check_dynamic_addresses p102 "00:11:22:a8:6e:0b 192.168.110.10 ae01::2" # Configure subnet using address from middle of the subnet and ensure # address is allocated from the beginning. @@ -7469,9 +7544,25 @@ ovn-nbctl --wait=sb set Logical-Switch sw11 other_config:subnet=172.16.1.254/29 ovn-nbctl --wait=sb lsp-add sw11 p103 -- lsp-set-addresses p103 "22:33:44:55:66:77 dynamic" -AT_CHECK([ovn-nbctl get Logical-Switch-Port p103 dynamic_addresses], [0], - ["22:33:44:55:66:77 172.16.1.250" -]) +check_dynamic_addresses p103 "22:33:44:55:66:77 172.16.1.250" + +ovn-nbctl ls-add sw12 +for i in $(seq 0 1); do + for j in $(seq 1 99); do + idx=$((i*100+j)) + ovn-nbctl lsp-add sw12 sw12-p${idx} -- \ + lsp-set-addresses sw12-p${idx} "00:00:00:00:$i:$j dynamic" + done +done +ovn-nbctl --wait=sb set Logical-Switch sw12 other_config:subnet=192.10.2.0/24 +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.127], [0], [ignore]) +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.128], [0], [ignore]) +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.180], [0], [ignore]) + +ovn-nbctl --wait=sb set Logical-Switch sw12 other_config:subnet=192.10.2.0/25 +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.127], [1]) +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.128], [1]) +AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep 192.10.2.180], [1]) as ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -7511,17 +7602,17 @@ # Create logical port foo1 in foo ovn-nbctl --wait=sb lsp-add foo foo1 \ -- lsp-set-addresses foo1 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo1 dynamic_addresses='"0a:00:00:a8:01:03 192.168.1.2"'], [0]) +check ovn-nbctl wait-until Logical-Switch-Port foo1 dynamic_addresses='"0a:00:00:a8:01:03 192.168.1.2"' # Create logical port alice1 in alice ovn-nbctl --wait=sb lsp-add alice alice1 \ -- lsp-set-addresses alice1 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port alice1 dynamic_addresses='"0a:00:00:a8:02:03 192.168.2.2"']) +check ovn-nbctl wait-until Logical-Switch-Port alice1 dynamic_addresses='"0a:00:00:a8:02:03 192.168.2.2"' # Create logical port foo2 in foo ovn-nbctl --wait=sb lsp-add foo foo2 \ -- lsp-set-addresses foo2 "dynamic" -AT_CHECK([ovn-nbctl --timeout=10 wait-until Logical-Switch-Port foo2 dynamic_addresses='"0a:00:00:a8:01:04 192.168.1.3"']) +check ovn-nbctl wait-until Logical-Switch-Port foo2 dynamic_addresses='"0a:00:00:a8:01:04 192.168.1.3"' # Create a hypervisor and create OVS ports corresponding to logical ports. net_add n1 @@ -7552,10 +7643,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets between foo1 and foo2 src_mac="0a0000a80103" dst_mac="0a0000a80104" @@ -7788,10 +7875,6 @@ ovs-ofctl dump-flows echo "---------------------" -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - src_mac="f00000000003" dst_mac="f00000000001" src_ip=`ip_to_hex 192 168 0 2` @@ -7847,23 +7930,25 @@ AT_CHECK([ovn-nbctl lsp-add ls0 ln_port]) AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) -AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) +AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1]) # Wait until the patch ports are created in hv1 to connect br-int to br-eth0 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-vsctl show | \ grep "Port patch-br-int-to-ln_port" | wc -l`]) +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + # Wait for packet to be received. -OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50]) +OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 140]) trim_zeros() { sed 's/\(00\)\{1,\}$//' } $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" -echo $expected > expout -expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002" -echo $expected >> expout -AT_CHECK([sort packets], [0], [expout]) +AT_CHECK([sort packets], [0], [dnl +fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001 +fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002 +]) OVN_CLEANUP([hv1]) @@ -7949,18 +8034,18 @@ ovn-nbctl lsp-add ls0 lp1 ovn-nbctl lsp-set-addresses lp0 "f0:00:00:00:00:01 192.168.0.1" ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:02 192.168.0.2" -dp_uuid=`ovn-sbctl find datapath | grep uuid | cut -f2 -d ":" | cut -f2 -d " "` +dp_uuid=$(fetch_column Datapath_Binding _uuid) ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp0 mac="mac1" ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp1 mac="mac2" ovn-sbctl find MAC_Binding # Delete port lp0 and check that its MAC_Binding is deleted. ovn-nbctl lsp-del lp0 ovn-sbctl find MAC_Binding -OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding logical_port=lp0 | wc -l` = 0]) +wait_row_count MAC_Binding 0 logical_port=lp0 # Delete logical switch ls0 and check that its MAC_Binding is deleted. ovn-nbctl ls-del ls0 ovn-sbctl find MAC_Binding -OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding | wc -l` = 0]) +wait_row_count MAC_Binding 0 OVN_CLEANUP([hv1]) @@ -8027,61 +8112,62 @@ AT_CHECK([ovn-nbctl lsp-add ls0 parent2]) AT_CHECK([ovn-nbctl ls-add ls1]) -dnl When a tag is provided, no allocation is done +AS_BOX([requested tag for parent1]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c0 parent1 3]) -AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3 -]) +c0_tag=$(ovn-nbctl lsp-get-tag c0) +echo c0_tag=$c0_tag +check test "$c0_tag" = 3 dnl The same 'tag' gets created in southbound database. -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c0"], [0], [3 -]) +check_row_count Port_Binding 1 logical_port=c0 tag=$c0_tag -dnl Allocate tags and see it getting created in both NB and SB +AS_BOX([tag allocation 1 for parent1]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c1 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c1], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c1"], [0], [1 -]) +c1_tag=$(ovn-nbctl lsp-get-tag c1) +echo c1_tag=$c1_tag +check test "$c1_tag" != "$c0_tag" +check_row_count Port_Binding 1 logical_port=c1 tag=$c1_tag +AS_BOX([tag allocation 2 for parent1]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c2 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c2"], [0], [2 -]) +c2_tag=$(ovn-nbctl lsp-get-tag c2) +echo c2_tag=$c2_tag +check test "$c2_tag" != "$c0_tag" +check test "$c2_tag" != "$c1_tag" +check_row_count Port_Binding 1 logical_port=c2 tag=$c2_tag + +AS_BOX([tag allocation 3 for parent1]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c3"], [0], [4 -]) +c3_tag=$(ovn-nbctl lsp-get-tag c3) +echo c3_tag=$c3_tag +check test "$c3_tag" != "$c0_tag" +check test "$c3_tag" != "$c1_tag" +check test "$c3_tag" != "$c2_tag" +check_row_count Port_Binding 1 logical_port=c3 tag=$c3_tag -dnl A different parent. +AS_BOX([tag allocation 1 for parent2]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c4 parent2 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c4"], [0], [1 -]) +c4_tag=$(ovn-nbctl lsp-get-tag c4) +echo c4_tag=$c4_tag +check_row_count Port_Binding 1 logical_port=c4 tag=$c4_tag +AS_BOX([tag allocation 2 for parent2]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c5 parent2 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c5"], [0], [2 -]) +c5_tag=$(ovn-nbctl lsp-get-tag c5) +echo c5_tag=$c5_tag +check test "$c5_tag" != "$c4_tag" +check_row_count Port_Binding 1 logical_port=c5 tag=$c5_tag -dnl Delete a logical port and create a new one. +AS_BOX([delete and add tag allocation for parent1]) AT_CHECK([ovn-nbctl --wait=sb lsp-del c1]) AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c6 parent1 0]) -AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1 -]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c6"], [0], [1 -]) +c6_tag=$(ovn-nbctl lsp-get-tag c6) +echo c6_tag=$c6_tag +check_row_count Port_Binding 1 logical_port=c6 tag=$c6_tag +check test "$c6_tag" != "$c0_tag" +check test "$c6_tag" != "$c2_tag" +check test "$c6_tag" != "$c3_tag" -dnl Restart northd to see that the same allocation remains. +AS_BOX([restart northd and make sure tag allocation is stable]) as northd OVS_APP_EXIT_AND_WAIT([ovn-northd]) start_daemon ovn-northd \ @@ -8090,30 +8176,30 @@ dnl Create a switch to make sure that ovn-northd has run through the main loop. AT_CHECK([ovn-nbctl --wait=sb ls-add ls-dummy]) -AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 + +AT_CHECK_UNQUOTED([ + for lsp in c0 c2 c3 c4 c5 c6; do + ovn-nbctl lsp-get-tag $lsp + done], [0], +[$c0_tag +$c2_tag +$c3_tag +$c4_tag +$c5_tag +$c6_tag ]) dnl Create a switch port with a tag that has already been allocated. dnl It should go through fine with a duplicate tag. -AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 2]) +AS_BOX([request duplicate tag]) +AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 $c5_tag]) AT_CHECK([ovn-nbctl lsp-get-tag c7], [0], [2 ]) -AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \ -logical_port="c7"], [0], [2 -]) -AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2 -]) +check_row_count Port_Binding 1 logical_port=c7 tag=$c5_tag +check_row_count Port_Binding 1 logical_port=c5 tag=$c5_tag +check_row_count Port_Binding 2 parent_port=parent2 tag=$c5_tag +AS_BOX([tag_request without parent_name]) AT_CHECK([ovn-nbctl ls-add ls2]) dnl When there is no parent_name provided (for say, 'localnet'), 'tag_request' dnl gets copied to 'tag' @@ -8246,8 +8332,8 @@ ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==86' reject ovn-nbctl --wait=hv --log --severity=alert --name=reject-flow acl-add lsw0 to-lport 1000 'tcp.dst==87' reject -ovn-sbctl dump-flows - +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) # Send packet that should be dropped without logging. packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac && @@ -8344,15 +8430,20 @@ # Add an ACL that rate-limits logs at 10 per second. ovn-nbctl meter-add http-rl1 drop 10 pktps ovn-nbctl --log --severity=alert --meter=http-rl1 --name=http-acl1 acl-add lsw0 to-lport 1000 'tcp.dst==80' drop +check ovn-nbctl --wait=hv sync # Add an ACL that rate-limits logs at 5 per second. ovn-nbctl meter-add http-rl2 drop 5 pktps ovn-nbctl --log --severity=alert --meter=http-rl2 --name=http-acl2 acl-add lsw0 to-lport 1000 'tcp.dst==81' allow +check ovn-nbctl --wait=hv sync # Add an ACL that doesn't rate-limit logs. ovn-nbctl --log --severity=alert --name=http-acl3 acl-add lsw0 to-lport 1000 'tcp.dst==82' drop ovn-nbctl --wait=hv sync +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + # For each ACL, send 100 packets. for i in `seq 1 100`; do ovs-appctl netdev-dummy/receive lp1 'in_port(1),eth(src=f0:00:00:00:00:01,dst=f0:00:00:00:00:02),eth_type(0x0800),ipv4(src=192.168.1.2,dst=192.168.1.3,proto=6,tos=0,ttl=64,frag=no),tcp(src=7777,dst=80)' @@ -8454,23 +8545,23 @@ AT_KEYWORDS([ovn]) ovn_start -ovn-nbctl ls-add lsw0 -ovn-nbctl --wait=sb lsp-add lsw0 lp1 -ovn-nbctl --wait=sb lsp-add lsw0 lp2 -ovn-nbctl --wait=sb lsp-add lsw0 lp3 -ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01 -ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02 -ovn-nbctl lsp-set-addresses lp3 f0:00:00:00:00:03 -ovn-nbctl lsp-set-port-security lp1 f0:00:00:00:00:01 -ovn-nbctl lsp-set-port-security lp2 f0:00:00:00:00:02 -ovn-nbctl --wait=sb sync +check ovn-nbctl ls-add lsw0 +check ovn-nbctl --wait=sb lsp-add lsw0 lp1 +check ovn-nbctl --wait=sb lsp-add lsw0 lp2 +check ovn-nbctl --wait=sb lsp-add lsw0 lp3 +check ovn-nbctl lsp-set-addresses lp1 f0:00:00:00:00:01 +check ovn-nbctl lsp-set-addresses lp2 f0:00:00:00:00:02 +check ovn-nbctl lsp-set-addresses lp3 f0:00:00:00:00:03 +check ovn-nbctl lsp-set-port-security lp1 f0:00:00:00:00:01 +check ovn-nbctl lsp-set-port-security lp2 f0:00:00:00:00:02 +check ovn-nbctl --wait=sb sync net_add n1 sim_add hv as hv -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1 -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2 +check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1 +check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2 AT_CAPTURE_FILE([trace]) ovn_trace () { @@ -8520,8 +8611,7 @@ # Mark DSCP with a valid value qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 match="inport\=\=\"lp1\"\ &&\ is_chassis_resident(\"lp1\")" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos) -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1 -]) +as hv check_row_count nb:QoS 1 check_tos 48 # check at hv without qos meter @@ -8529,7 +8619,7 @@ ]) # Update the meter rate -ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=100 +check ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=100 # check at hv with a qos meter table AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep rate=100 | wc -l], [0], [1 @@ -8538,11 +8628,11 @@ ]) # Update the DSCP marking -ovn-nbctl --wait=hv set QoS $qos_id action=dscp=63 +check ovn-nbctl --wait=hv set QoS $qos_id action=dscp=63 check_tos 63 # Update the meter rate -ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=4294967295,burst=4294967295 +check ovn-nbctl --wait=hv set QoS $qos_id bandwidth=rate=4294967295,burst=4294967295 # check at hv with a qos meter table AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep burst_size=4294967295 | wc -l], [0], [1 @@ -8550,13 +8640,12 @@ AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [1 ]) -ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lport" +check ovn-nbctl --wait=hv set QoS $qos_id match="outport\=\=\"lp2\"" direction="to-lport" check_tos 63 # Disable DSCP marking -ovn-nbctl --wait=hv qos-del lsw0 -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [0 -]) +check ovn-nbctl --wait=hv qos-del lsw0 +as hv check_row_count nb:QoS 0 check_tos 0 # check at hv without qos meter @@ -8564,9 +8653,8 @@ ]) # check meter with chassis not resident -ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp3" && is_chassis_resident("lp3")' rate=11123 burst=111230 -AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1 -]) +check ovn-nbctl --wait=hv qos-add lsw0 to-lport 1001 'inport=="lp3" && is_chassis_resident("lp3")' rate=11123 burst=111230 +as hv check_row_count nb:QoS 1 # check no meter table AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0 @@ -8575,11 +8663,11 @@ ]) # Check multiple qos meters -ovn-nbctl qos-del lsw0 -ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp1" && is_chassis_resident("lp1")' rate=100000 burst=100000 -ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp2" && is_chassis_resident("lp2")' rate=100000 burst=100000 -ovn-nbctl qos-add lsw0 to-lport 1002 'inport=="lp1" && is_chassis_resident("lp1")' rate=100001 burst=100001 -ovn-nbctl qos-add lsw0 to-lport 1002 'inport=="lp2" && is_chassis_resident("lp2")' rate=100001 burst=100001 +check ovn-nbctl qos-del lsw0 +check ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp1" && is_chassis_resident("lp1")' rate=100000 burst=100000 +check ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp2" && is_chassis_resident("lp2")' rate=100000 burst=100000 +check ovn-nbctl qos-add lsw0 to-lport 1002 'inport=="lp1" && is_chassis_resident("lp1")' rate=100001 burst=100001 +check ovn-nbctl --wait=hv qos-add lsw0 to-lport 1002 'inport=="lp2" && is_chassis_resident("lp2")' rate=100001 burst=100001 AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep meter | wc -l], [0], [4 ]) @@ -8875,10 +8963,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Test that ovn-controllers create ct-zone entry for container ports. foo1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-foo1) AT_CHECK([test ! -z $foo1_zoneid]) @@ -9186,9 +9270,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} trim_zeros() { sed 's/\(00\)\{1,\}$//' } @@ -9301,10 +9382,6 @@ ovn-sbctl list DNS echo "*************************" -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - reset_pcap_file() { local iface=$1 local pcap_file=$2 @@ -9700,9 +9777,9 @@ sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=foo1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ @@ -9710,19 +9787,19 @@ sim_add gw1 as gw1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 sim_add gw2 as gw2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.4 sim_add ext1 as ext1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int ext1-vif1 -- \ +check ovs-vsctl -- add-port br-int ext1-vif1 -- \ set interface ext1-vif1 external-ids:iface-id=outside1 \ options:tx_pcap=ext1/vif1-tx.pcap \ options:rxq_pcap=ext1/vif1-rx.pcap \ @@ -9733,76 +9810,76 @@ # for ARP resolution). OVN_POPULATE_ARP -ovn-nbctl create Logical_Router name=R1 +AT_CHECK([ovn-nbctl create Logical_Router name=R1 | uuidfilt], [0], [<0> +]) -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside +check ovn-nbctl ls-add foo +check ovn-nbctl ls-add alice +check ovn-nbctl ls-add outside # Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ +check ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 +check ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ type=router options:router-port=foo \ -- lsp-set-addresses rp-foo router # Connect alice to R1 as distributed router gateway port on gw1 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 +check ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 -ovn-nbctl \ - --id=@gc0 create Gateway_Chassis name=alice_gw1 \ - chassis_name=gw1 \ - priority=20 -- \ - --id=@gc1 create Gateway_Chassis name=alice_gw2 \ - chassis_name=gw2 \ - priority=10 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' +AT_CHECK([ovn-nbctl \ + --id=@gc0 create Gateway_Chassis name=alice_gw1 \ + chassis_name=gw1 \ + priority=20 -- \ + --id=@gc1 create Gateway_Chassis name=alice_gw2 \ + chassis_name=gw2 \ + priority=10 -- \ + set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' \ + | uuidfilt], [0], [<0> +<1> +]) -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ +check ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ type=router options:router-port=alice \ -- lsp-set-addresses rp-alice router # Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ +check ovn-nbctl lsp-add foo foo1 \ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" # Create logical port outside1 in outside -ovn-nbctl lsp-add outside outside1 \ +check ovn-nbctl lsp-add outside outside1 \ -- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" # Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys +check ovn-nbctl lsp-add alice ln-alice +check ovn-nbctl lsp-set-addresses ln-alice unknown +check ovn-nbctl lsp-set-type ln-alice localnet +check ovn-nbctl lsp-set-options ln-alice network_name=phys # Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys +check ovn-nbctl lsp-add outside ln-outside +check ovn-nbctl lsp-set-addresses ln-outside unknown +check ovn-nbctl lsp-set-type ln-outside localnet +check ovn-nbctl lsp-set-options ln-outside network_name=phys # Create bridge-mappings on gw1, gw2 and ext1, hv1 doesn't need # mapping to the external network, is the one generating packets -as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) +check as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +check as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +check as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -# Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv sync +AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore]) -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) reset_pcap_file() { local iface=$1 local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ options:rxq_pcap=dummy-rx.pcap rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ options:rxq_pcap=${pcap_file}-rx.pcap } @@ -9822,7 +9899,7 @@ # ARP request packet to expect at outside1 #arp_request=ffffffffffff${src_mac}08060001080006040001${src_mac}${src_ip}000000000000${dst_ip} - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet # Send ARP reply from outside1 back to the router # XXX: note, we could avoid this if we plug this port into a netns @@ -9833,11 +9910,12 @@ src_ip=`ip_to_hex 172 16 1 1` arp_reply=${src_mac}${reply_mac}08060001080006040002${reply_mac}${dst_ip}${src_mac}${src_ip} - as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply + check as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply + AT_CAPTURE_FILE([offlows]) OVS_WAIT_UNTIL([ - test `as $active_gw ovs-ofctl dump-flows br-int | grep table=66 | \ -grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1 + as $active_gw ovs-ofctl dump-flows br-int > offlows + test `grep table=66 offlows | grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1 ]) # Packet to Expect at ext1 chassis, outside1 port @@ -9860,16 +9938,35 @@ sleep 1 # Resend packet from foo1 to outside1 - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + + sleep 1 + + AT_CAPTURE_FILE([exp]) + AT_CAPTURE_FILE([rcv]) + check_packets() { + > exp + > rcv + + pcap=ext1/vif1-tx.pcap + type=ext1-vif1.expected + echo "--- $pcap" | tee -a exp >> rcv + sort -u "$type" >> exp + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort -u >> rcv + echo | tee -a exp >> rcv + + pcap=$active_gw/br-phys_n1-tx.pcap + echo "--- $pcap" | tee -a exp >> rcv + sort -u "$type" >> exp + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap > packets + (grep "$expected" packets; grep "$exp_gw_ip_garp" packets) | sort -u >> rcv + echo | tee -a exp >> rcv + + $at_diff exp rcv >/dev/null + } + + OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' exp rcv]) - OVN_CHECK_PACKETS([ext1/vif1-tx.pcap], [ext1-vif1.expected]) - $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $active_gw/br-phys_n1-tx.pcap > packets - cat packets | grep $expected > exp - # Its possible that $active_gw/br-phys_n1-tx.pcap may have received multiple - # garp packets. So consider only the first packet. - cat packets | grep $exp_gw_ip_garp | head -1 >> exp - AT_CHECK([cat exp], [0], [expout]) - rm -f expout if test $backup_vswitchd_dead != 1; then # Check for backup gw only if vswitchd is alive $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $backup_gw/br-phys_n1-tx.pcap > packets @@ -9879,14 +9976,18 @@ test_ip_packet gw1 gw2 0 -ovn-nbctl --timeout=3 --wait=hv \ +AT_CHECK( + [ovn-nbctl --wait=hv \ --id=@gc0 create Gateway_Chassis name=alice_gw1 \ chassis_name=gw1 \ priority=10 -- \ --id=@gc1 create Gateway_Chassis name=alice_gw2 \ chassis_name=gw2 \ priority=20 -- \ - set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' + set Logical_Router_Port alice 'gateway_chassis=[@gc0,@gc1]' | uuidfilt], 0, + [<0> +<1> +]) test_ip_packet gw2 gw1 0 @@ -10030,26 +10131,21 @@ as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) +check ovn-nbctl --wait=sb sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) # hv1 should be in 'ref_chassis' of the ha_chasssi_group as logical # switch 'foo' can reach the router 'R1' (which has gw router port) # via foo1 -> foo -> R0 -> join -> R1 -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$hv1_ch_uuid" = "$ref_ch_list"]) +hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1) +wait_column "$hv1_ch_uuid" HA_Chassis_Group ref_chassis # Allow some time for ovn-northd and ovn-controller to catch up. # XXX This should be more systematic. sleep 2 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - reset_pcap_file() { local iface=$1 local pcap_file=$2 @@ -10122,7 +10218,7 @@ test_ip_packet gw1 gw2 -ovn-nbctl --timeout=3 --wait=hv \ +ovn-nbctl --wait=hv \ --id=@gc0 create Gateway_Chassis name=alice_gw1 \ chassis_name=gw1 \ priority=10 -- \ @@ -10142,7 +10238,7 @@ # Logical network: # One LR R1 that has switches foo (192.168.1.0/24) and # alice (172.16.1.0/24) connected to it. The logical port -# between R1 and alice has a "redirect-chassis" specified, +# between R1 and alice has a gateway chassis specified, # i.e. it is the distributed router gateway port. # Switch alice also has a localnet port defined. # An additional switch outside has a localnet port and the @@ -10151,7 +10247,7 @@ # Physical network: # Three hypervisors hv[123]. # hv1 hosts vif foo1. -# hv2 is the "redirect-chassis" that hosts the distributed +# hv2 is the gateway chassis that hosts the distributed # router gateway port. # hv3 hosts vif outside1. # In order to show that connectivity works only through hv2, @@ -10167,9 +10263,9 @@ sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=foo1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ @@ -10177,14 +10273,14 @@ sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 sim_add hv3 as hv3 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl -- add-port br-int hv3-vif1 -- \ +check ovs-vsctl -- add-port br-int hv3-vif1 -- \ set interface hv3-vif1 external-ids:iface-id=outside1 \ options:tx_pcap=hv3/vif1-tx.pcap \ options:rxq_pcap=hv3/vif1-rx.pcap \ @@ -10195,110 +10291,119 @@ # for ARP resolution). OVN_POPULATE_ARP -ovn-nbctl create Logical_Router name=R1 +check ovn-nbctl lr-add R1 -- set Logical_Router R1 options:requested-tnl-key=1 -ovn-nbctl ls-add foo -ovn-nbctl ls-add alice -ovn-nbctl ls-add outside +check ovn-nbctl ls-add foo -- set Logical_Switch foo other_config:requested-tnl-key=2 +check ovn-nbctl ls-add alice -- set Logical_Switch alice other_config:requested-tnl-key=3 +check ovn-nbctl ls-add outside -- set Logical_Switch outside other_config:requested-tnl-key=4 # Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 -ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ - type=router options:router-port=foo \ +check ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 \ + -- set Logical_Router_Port foo options:requested-tnl-key=1 +check ovn-nbctl lsp-add foo rp-foo \ + -- set Logical_Switch_Port rp-foo type=router options:router-port=foo options:requested-tnl-key=1 \ -- lsp-set-addresses rp-foo router # Connect alice to R1 as distributed router gateway port on hv2 -ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis="hv2" -ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ - type=router options:router-port=alice \ +check ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ + -- set Logical_Router_Port alice options:requested-tnl-key=2 \ + -- lrp-set-gateway-chassis alice hv2 +check ovn-nbctl lsp-add alice rp-alice \ + -- set Logical_Switch_Port rp-alice type=router options:router-port=alice options:requested-tnl-key=1 \ -- lsp-set-addresses rp-alice router # Create logical port foo1 in foo -ovn-nbctl lsp-add foo foo1 \ --- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" +check ovn-nbctl lsp-add foo foo1 \ + -- set Logical_Switch_Port foo1 options:requested-tnl-key=2 \ + -- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" # Create logical port outside1 in outside -ovn-nbctl lsp-add outside outside1 \ --- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" +check ovn-nbctl lsp-add outside outside1 \ + -- set Logical_Switch_Port outside1 options:requested-tnl-key=1 \ + -- lsp-set-addresses outside1 "f0:00:00:01:02:04 172.16.1.3" # Create localnet port in alice -ovn-nbctl lsp-add alice ln-alice -ovn-nbctl lsp-set-addresses ln-alice unknown -ovn-nbctl lsp-set-type ln-alice localnet -ovn-nbctl lsp-set-options ln-alice network_name=phys +check ovn-nbctl --wait=sb lsp-add alice ln-alice \ + -- set Logical_Switch_Port ln-alice options:requested-tnl-key=2 +check ovn-nbctl lsp-set-addresses ln-alice unknown +check ovn-nbctl lsp-set-type ln-alice localnet +check ovn-nbctl lsp-set-options ln-alice network_name=phys # Create localnet port in outside -ovn-nbctl lsp-add outside ln-outside -ovn-nbctl lsp-set-addresses ln-outside unknown -ovn-nbctl lsp-set-type ln-outside localnet -ovn-nbctl lsp-set-options ln-outside network_name=phys +check ovn-nbctl --wait=sb lsp-add outside ln-outside \ + -- set Logical_Switch_Port ln-outside options:requested-tnl-key=2 +check ovn-nbctl lsp-set-addresses ln-outside unknown +check ovn-nbctl lsp-set-type ln-outside localnet +check ovn-nbctl lsp-set-options ln-outside network_name=phys # Create bridge-mappings on hv1 and hv3, leaving hv2 for later -as hv1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +check as hv1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +check as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys dnl Allow some time for ovn-northd and ovn-controller to catch up. ovn-nbctl --wait=hv sync -echo "---------NB dump-----" -ovn-nbctl show -echo "---------------------" -ovn-nbctl list logical_router -echo "---------------------" -ovn-nbctl list logical_router_port -echo "---------------------" - -echo "---------SB dump-----" -ovn-sbctl list datapath_binding -echo "---------------------" -ovn-sbctl list port_binding -echo "---------------------" -ovn-sbctl dump-flows -echo "---------------------" -ovn-sbctl list chassis -ovn-sbctl list encap -echo "------ Gateway_Chassis dump (SBDB) -------" -ovn-sbctl list Gateway_Chassis -echo "------ Port_Binding chassisredirect -------" -ovn-sbctl find Port_Binding type=chassisredirect -echo "-------------------------------------------" - -echo "------ hv1 dump ----------" -as hv1 ovs-ofctl show br-int -as hv1 ovs-ofctl dump-flows br-int -echo "------ hv2 dump ----------" -as hv2 ovs-ofctl show br-int -as hv2 ovs-ofctl dump-flows br-int -echo "------ hv3 dump ----------" -as hv3 ovs-ofctl show br-int -as hv3 ovs-ofctl dump-flows br-int -echo "--------------------------" - +(echo "---------NB dump-----" + ovn-nbctl show + echo "---------------------" + ovn-nbctl list logical_router + echo "---------------------" + ovn-nbctl list logical_router_port) > nbdump +AT_CAPTURE_FILE([nbdump]) + +(echo "---------SB dump-----" + ovn-sbctl list datapath_binding + echo "---------------------" + ovn-sbctl list port_binding + echo "---------------------" + ovn-sbctl list chassis + ovn-sbctl list encap + echo "------ Gateway_Chassis dump (SBDB) -------" + ovn-sbctl list Gateway_Chassis + echo "------ Port_Binding chassisredirect -------" + ovn-sbctl find Port_Binding type=chassisredirect) > sbdump +AT_CAPTURE_FILE([sbdump]) + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + +(echo "------ hv1 dump ----------" + as hv1 ovs-ofctl show br-int + as hv1 ovs-ofctl dump-flows br-int + echo "------ hv2 dump ----------" + as hv2 ovs-ofctl show br-int + as hv2 ovs-ofctl dump-flows br-int + echo "------ hv3 dump ----------" + as hv3 ovs-ofctl show br-int + as hv3 ovs-ofctl dump-flows br-int) > hvdump +AT_CAPTURE_FILE([hvdump]) + +as hv1 ovs-ofctl dump-flows br-int > hv1flows +as hv2 ovs-ofctl dump-flows br-int > hv2flows +AT_CAPTURE_FILE([hv1flows]) +AT_CAPTURE_FILE([hv2flows]) -# Check that redirect mapping is programmed only on hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=33 | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l], [0], [1 -]) -# Check that hv1 sends chassisredirect port traffic to hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | grep output | wc -l], [0], [1 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep =0x3,metadata=0x1 | wc -l], [0], [0 -]) -# Check that arp reply on distributed gateway port is only programmed on hv2 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [0 -]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep arp | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], [1 +AT_CHECK( + [# Check that redirect mapping is programmed only on hv2 + grep table=33 hv1flows | grep =0x3,metadata=0x1 | wc -l + grep table=33 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l + + # Check that hv1 sends chassisredirect port traffic to hv2 + grep table=32 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l + grep table=32 hv2flows | grep =0x3,metadata=0x1 | wc -l + + # Check that arp reply on distributed gateway port is only programmed on hv2 + grep arp hv1flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l + grep arp hv2flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l], [0], + [0 +1 +1 +0 +0 +1 ]) - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - - : > hv2-vif1.expected : > hv3-vif1.expected @@ -10453,13 +10558,11 @@ echo $expected >> hv2-vif1.expected OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [hv2-vif1.expected]) -AT_CHECK([ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=cr-alice | wc -l], [0], [1 -]) +check_row_count Port_Binding 1 logical_port=cr-alice -ovn-nbctl --timeout=3 --wait=sb remove Logical_Router_Port alice options redirect-chassis +check ovn-nbctl --wait=sb lrp-del-gateway-chassis alice hv2 -AT_CHECK([ovn-sbctl find Port_Binding logical_port=cr-alice | wc -l], [0], [0 -]) +check_row_count Port_Binding 0 logical_port=cr-alice OVN_CLEANUP([hv1],[hv2],[hv3]) @@ -10474,7 +10577,7 @@ ovn-nbctl create Logical_Router name=lr0 # Add distributed gateway port to distributed router ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24 \ - -- set Logical_Router_Port lrp0 options:redirect-chassis="hv2" + -- lrp-set-gateway-chassis lrp0 hv2 ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \ type=router options:router-port=lrp0 addresses="router" # Add router port to ls1 @@ -10612,7 +10715,7 @@ # Logical network: # # One LR R1 that has switches foo (192.168.1.0/24) and # # alice (172.16.1.0/24) connected to it. The logical port -# # between R1 and alice has a "redirect-chassis" specified, +# # between R1 and alice has a gateway chassis specified, # # i.e. it is the distributed router gateway port(172.16.1.6). # # Switch alice also has a localnet port defined. # # An additional switch outside has the same subnet as alice @@ -10623,7 +10726,7 @@ # Physical network: # # Four hypervisors hv[1234]. # # hv1 hosts vif foo1. -# # hv2 is the "redirect-chassis" that hosts the distributed router gateway port. +# # hv2 is the gateway chassis that hosts the distributed router gateway port. # # Later to test GARPs for the router port - foo, hv2 and hv4 are added to the ha_chassis_group # # hv3 hosts nexthop port vif outside1. # # All other tests connect hypervisors to network n1 through br-phys for tunneling. @@ -10707,7 +10810,7 @@ # Connect alice to R1 as distributed router gateway port (172.16.1.6) on hv2 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.6/24 \ - -- set Logical_Router_Port alice options:redirect-chassis="hv2" + -- lrp-set-gateway-chassis alice hv2 ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ type=router options:router-port=alice \ -- lsp-set-addresses rp-alice router \ @@ -10745,8 +10848,7 @@ ovn-nbctl lsp-set-options ln-outside network_name=phys # Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # Check that there is a logical flow in logical switch foo's pipeline # to set the outport to rp-foo (which is expected). @@ -10754,7 +10856,7 @@ grep rp-foo | grep -v is_chassis_resident | grep priority=50 -c`]) # Set the option 'reside-on-redirect-chassis' for foo -ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true +check ovn-nbctl --wait=hv set logical_router_port foo options:reside-on-redirect-chassis=true # Check that there is a logical flow in logical switch foo's pipeline # to set the outport to rp-foo with the condition is_chassis_redirect. ovn-sbctl dump-flows foo @@ -10790,10 +10892,6 @@ echo "--------------------------" done -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - foo1_ip=$(ip_to_hex 192 168 1 2) gw_ip=$(ip_to_hex 172 16 1 6) dst_ip=$(ip_to_hex 8 8 8 8) @@ -10873,7 +10971,7 @@ ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 20 hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1` -ovn-nbctl remove logical_router_port alice options redirect-chassis +ovn-nbctl lrp-del-gateway-chassis alice hv2 ovn-nbctl --wait=sb set logical_router_port alice ha_chassis_group=$hagrp1_uuid # When hv2 claims the gw router port cr-alice, it should send out @@ -10901,11 +10999,9 @@ ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 40 # Wait till cr-alice is claimed by hv4 -hv4_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=hv4) +hv4_chassis=$(fetch_column Chassis _uuid name=hv4) # check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-alice | grep $hv4_chassis | wc -l], [0],[[1 -]]) +wait_row_count Port_Binding 1 logical_port=cr-alice chassis=$hv4_chassis # Reset the pcap file for hv2/br-ex_n2. From now on ovn-controller in hv2 # should not send GARPs for the router ports. @@ -11244,10 +11340,6 @@ # XXX This should be more systematic. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets between foo1 and alice1 src_mac="f00000010203" dst_mac="000000010203" @@ -11258,7 +11350,7 @@ # Send the first packet to trigger a ARP response and population of # mac_bindings table. as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="10.0.0.2" | wc -l` -gt 0]) +wait_row_count MAC_Binding 1 ip="10.0.0.2" ovn-nbctl --wait=hv sync # Packet to Expect at 'alice1' @@ -11461,8 +11553,7 @@ ovn-nbctl lsp-set-options ln-outside network_name=phys # Allow some time for ovn-northd and ovn-controller to catch up. -# XXX This should be more systematic. -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync echo "---------NB dump-----" ovn-nbctl show @@ -11482,38 +11573,22 @@ ovn-sbctl list chassis ovn-sbctl list encap echo "---------------------" -echo "------ Gateway_Chassis dump (SBDB) -------" -ovn-sbctl list Gateway_Chassis +echo "------ ha_Chassis_Group dump (SBDB) -------" +ovn-sbctl list HA_Chassis_Group +echo "------ ha_Chassis dump (SBDB) -------" +ovn-sbctl list HA_Chassis echo "------ Port_Binding chassisredirect -------" ovn-sbctl find Port_Binding type=chassisredirect echo "-------------------------------------------" # There should be one ha_chassis_group with the name "outside" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="outside"` - -AT_CHECK([test $ha_chassi_grp_name = outside]) +check_row_count HA_Chassis_Group 1 name=outside # There should be 2 ha_chassis rows in SB DB. -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | awk '{print $3}' \ -| grep '-' | wc -l ], [0], [2 -]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -# Trim the spaces. -ha_ch=`echo $ha_ch | sed 's/ //g'` - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done +check_row_count HA_Chassis 2 'chassis!=[[]]' -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) +ha_ch=$(fetch_column HA_Chassis_Group ha_chassis) +check_column "$ha_ch" HA_Chassis _uuid for chassis in gw1 gw2 hv1 hv2; do as $chassis @@ -11556,8 +11631,8 @@ echo "--- hv2 ---" as hv2 ovs-ofctl dump-flows br-int table=32 -gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1) -gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2) +gw1_chassis=$(fetch_column Chassis _uuid name=gw1) +gw2_chassis=$(fetch_column Chassis _uuid name=gw2) OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ @@ -11585,29 +11660,12 @@ ]]) # check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) - -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` - -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $hv1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $hv2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis +hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1) +hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2) +exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid" +wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # at this point, we invert the priority of the gw chassis between gw1 and gw2 @@ -11618,8 +11676,7 @@ set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' -# XXX: Let the change propagate down to the ovn-controllers -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # we make sure that the hypervisors noticed, and inverted the slave ports OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ @@ -11633,9 +11690,7 @@ ]) # check that the chassis redirect port has been reclaimed by the gw2 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1 -]]) +wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis # check BFD enablement on tunnel ports from gw1 ######### as gw1 @@ -11707,9 +11762,7 @@ ]]) # check that the chassis redirect port has been reclaimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) +wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-rx"=2000 as gw2 @@ -11742,11 +11795,7 @@ # reference to hv1. as hv1 ovs-vsctl del-port hv1-vif1 -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$hv2_ch_uuid" = "$ref_ch_list"]) +wait_column "$hv2_ch_uuid" HA_Chassis_Group ref_chassis # Delete the inside2 vif. ovn-sbctl show @@ -11755,19 +11804,14 @@ as hv2 ovs-vsctl del-port hv2-vif1 # ref_chassis of ha_chassis_group should be empty -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - exp_ref_ch_list="" - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_column '' HA_Chassis_Group ref_chassis # Delete the Gateway_Chassis for lrp - outside ovn-nbctl clear Logical_Router_Port outside gateway_chassis # There shoud be no ha_chassis_group rows in SB DB. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 +wait_row_count HA_Chassis 0 ovn-nbctl remove NB_Global . options "bfd-min-rx" ovn-nbctl remove NB_Global . options "bfd-min-tx" @@ -11785,16 +11829,13 @@ # ovn-northd should not create HA chassis group and HA chassis rows # unless the HA chassis group in OVN NB DB is associated to # a logical router port. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) +wait_row_count HA_Chassis 0 # Associate hagrp1 to outside logical router port ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \ -find ha_chassis_group | wc -l`]) - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 +wait_row_count HA_Chassis 2 OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ @@ -11822,9 +11863,18 @@ ]]) # check that the chassis redirect port has been claimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) +# +# XXX actually it doesn't happen, the test has always been wrong here +# because the following just checks that "wc -l" succeeds (and it always +# does): +# +# OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ +# logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 +# ]]) +# +# If it were correct, then the following would be a good substitute: +# +# wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis # Re add the ovs ports. for i in 1 2; do @@ -11836,24 +11886,10 @@ ofport-request=1 done -hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` -hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` - -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $hv1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $hv2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done - -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1) +hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2) +exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid" +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 @@ -11869,12 +11905,21 @@ ]) # check that the chassis redirect port has been reclaimed by the gw2 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1 -]]) - -# check BFD enablement on tunnel ports from gw1 ######### -as gw1 +# +# XXX actually it doesn't happen, the test has always been wrong here +# because the following just checks that "wc -l" succeeds (and it always +# does): +# +# OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ +# logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1 +# ]]) +# +# If it were correct, then the following would be a good substitute: +# +# wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis + +# check BFD enablement on tunnel ports from gw1 ######### +as gw1 for chassis in gw2 hv1 hv2; do echo "checking gw1 -> $chassis" AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0], @@ -11941,9 +11986,7 @@ ]]) # check that the chassis redirect port has been reclaimed by the gw1 chassis -OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \ -logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1 -]]) +wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis OVN_CLEANUP([gw1],[gw2],[hv1],[hv2]) @@ -11998,7 +12041,7 @@ AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) # wait for earlier changes to take effect -AT_CHECK([ovn-nbctl --timeout=3 --wait=hv sync], [0], [ignore]) +check ovn-nbctl --wait=hv sync reset_pcap_file() { local iface=$1 @@ -12198,7 +12241,7 @@ ovn-nbctl lsp-set-options ln-outside network_name=phys # Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # currently when ovn-controller is restarted, the old entry is deleted # and a new one is created, which leaves the Gateway_Chassis with @@ -12210,22 +12253,14 @@ ovn-sbctl destroy Chassis $gw2_chassis # Wait for the gw2_chassis row is recreated. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns=_uuid find Chassis name=gw2 | wc -l`]) - -gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1) -gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2) +wait_row_count Chassis 1 name=gw2 # When gw2 chassis row is destroyed, it gets recreated. There # is a small window in which gw2 may claim the cr-outside port if # it has not established bfd tunnel with gw1. # So make sure that, cr-outside is claimed by gw1 finally. -OVS_WAIT_WHILE( - [cr_outside_ch=`ovn-sbctl --bare --columns=chassis find Port_binding logical_port=cr-outside` - test $cr_outside_ch = $gw2_chassis]) - -OVS_WAIT_UNTIL( - [cr_outside_ch=`ovn-sbctl --bare --columns=chassis find Port_binding logical_port=cr-outside` - test $cr_outside_ch = $gw1_chassis]) +gw1_chassis=$(fetch_column Chassis _uuid name=gw1) +wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis OVN_CLEANUP([gw1],[gw2],[hv1]) @@ -12259,7 +12294,7 @@ ovn-nbctl lrp-add lr0_ip6 ip6_public 00:00:02:01:02:04 \ 2001:db8:1:0:200:02ff:fe01:0204/64 \ --- set Logical_Router_port ip6_public options:redirect-chassis="hv1" +-- lrp-set-gateway-chassis ip6_public hv1 # Install default static route. ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ @@ -12287,7 +12322,7 @@ ovn-nbctl lrp-add lr1_ip6 ip6_public_1 00:01:02:01:02:04 \ 2002:db8:1:0:200:02ff:fe01:0204/64 \ --- set Logical_Router_port ip6_public_1 options:redirect-chassis="hv2" +-- lrp-set-gateway-chassis ip6_public_1 hv2 # Install default static route. ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ @@ -12338,11 +12373,9 @@ wc -l], [0], [4 ]) -cr_uuid=`ovn-sbctl find port_binding logical_port=cr-ip6_public | grep _uuid | cut -f2 -d ":"` - # Get the redirect chassis uuid. -chassis_uuid=`ovn-sbctl list chassis hv1 | grep _uuid | cut -f2 -d ":"` -OVS_WAIT_UNTIL([test $chassis_uuid = `ovn-sbctl get port_binding $cr_uuid chassis`]) +chassis_uuid=$(fetch_column Chassis _uuid name=hv1) +wait_row_count Port_Binding 1 logical_port=cr-ip6_public chassis=$chassis_uuid trim_zeros() { sed 's/\(00\)\{1,\}$//' @@ -12487,13 +12520,13 @@ ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1 # Allow some time for ovn-northd and ovn-controller to catch up. -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # Retrieve hv1 and hv2 chassis UUIDs from southbound database -ovn-sbctl wait-until chassis hv1 -ovn-sbctl wait-until chassis hv2 -hv1_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv1) -hv2_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv2) +wait_row_count Chassis 1 name=hv1 +wait_row_count Chassis 1 name=hv2 +hv1_uuid=$(fetch_column Chassis _uuid name=hv1) +hv2_uuid=$(fetch_column Chassis _uuid name=hv2) # (1) Chassis hv2 should not bind lsp0 when requested-chassis is hv1. echo "verifying that hv2 does not bind lsp0 when hv2 physical/logical mapping is added" @@ -12501,7 +12534,7 @@ ovs-vsctl set interface hv2-vif0 external-ids:iface-id=lsp0 OVS_WAIT_UNTIL([test 1 = $(grep -c "Not claiming lport lsp0" hv2/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], []) +wait_row_count Port_Binding 1 logical_port=lsp0 'chassis=[[]]' # (2) Chassis hv2 should not add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables. AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) @@ -12513,7 +12546,7 @@ ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0 OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], []) +check_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0 # (4) Chassis hv1 should add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables. as hv1 ovs-ofctl dump-flows br-int @@ -12526,7 +12559,7 @@ ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2 OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv2_uuid"]) +wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0 # (6) Chassis hv2 should add flows and hv1 should not. AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore]) @@ -12553,23 +12586,23 @@ ovn_attach n1 br-phys 192.168.0.11 ovs-vsctl -- add-port br-int hv1-vif0 -- set Interface hv1-vif0 ofport-request=1 -ovn-sbctl wait-until chassis hv1 -hv1_hostname=$(ovn-sbctl --bare --columns hostname find Chassis name=hv1) +wait_row_count Chassis 1 name=hv1 +hv1_hostname=$(fetch Chassis hostname name=hv1) echo "hv1_hostname=${hv1_hostname}" -ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=${hv1_hostname} +check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=${hv1_hostname} as hv1 ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0 -hv1_uuid=$(ovn-sbctl --bare --columns _uuid find Chassis name=hv1) +hv1_uuid=$(fetch_column Chassis _uuid name=hv1) echo "hv1_uuid=${hv1_uuid}" OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)]) -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], []) +wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore]) AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore]) -ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=non-existant-chassis +check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=non-existant-chassis OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)]) -ovn-nbctl --wait=hv --timeout=3 sync -AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], []) +check ovn-nbctl --wait=hv sync +wait_column '' Port_Binding chasssi logical_port=lsp0 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], []) AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], []) @@ -12723,42 +12756,42 @@ rm -f expected } -# Baseline test with no MTU +echo "Baseline test with no MTU" ra_test 0 00 0 0 0 c0 40 aef00000000000000000000000000000 -# Now make sure an MTU option makes it +echo "Now make sure an MTU option makes it" ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:mtu=1500 ra_test 000005dc 00 0 0 0 c0 40 aef00000000000000000000000000000 -# Now test for multiple network prefixes +echo "Now test for multiple network prefixes" ovn-nbctl --wait=hv set Logical_Router_port ro-sw networks='aef0\:\:1/64 fd0f\:\:1/48' ra_test 000005dc 00 0 0 0 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 -# Test PRF for default gw +echo "Test PRF for default gw" ovn-nbctl --wait=hv set Logical_Router_port ro-sw ipv6_ra_configs:router_preference="LOW" ra_test 000005dc 18 0 0 0 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 -# Now test for RDNSS +echo "Now test for RDNSS" ovn-nbctl --wait=hv set Logical_Router_port ro-sw ipv6_ra_configs:rdnss='aef0::11' dns_addr=aef00000000000000000000000000011 ra_test 000005dc 18 $dns_addr 0 0 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 -# Now test for DNSSL +echo "Now test for DNSSL" ovn-nbctl --wait=hv set Logical_Router_port ro-sw ipv6_ra_configs:dnssl="aa.bb.cc" ovn-nbctl --wait=hv set Logical_Router_port ro-sw ipv6_ra_configs:router_preference="HIGH" dnssl=02616102626202636300000000000000 ra_test 000005dc 08 $dns_addr $dnssl 0 c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 -# Now test Route Info option +echo "Now test Route Info option" ovn-nbctl --wait=hv set Logical_Router_port ro-sw ipv6_ra_configs:route_info="HIGH-aef1::11/48,LOW-aef2::11/96" route_info=18023008ffffffffaef100000000000018036018ffffffffaef20000000000000000000000000000 ra_test 000005dc 08 $dns_addr $dnssl $route_info c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 - -## Test a different address mode now +exit 0 +echo "Test a different address mode now" ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateful ra_test 000005dc 88 $dns_addr $dnssl $route_info 80 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 -# And the other address mode +echo "And the other address mode" ovn-nbctl --wait=hv set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=dhcpv6_stateless ra_test 000005dc 48 $dns_addr $dnssl $route_info c0 40 aef00000000000000000000000000000 30 fd0f0000000000000000000000000000 @@ -12796,7 +12829,7 @@ local reply=${eth_src}${eth_dst}08004500003000004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload} echo $reply$orig_pkt_in_reply >> vif$inport.expected - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet + check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } # test_ipv6_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST EXP_ICMP_CHKSUM @@ -12814,7 +12847,7 @@ local reply=${eth_src}${eth_dst}86dd6000000000383aff${ipv6_dst}${ipv6_src}0101${exp_icmp_chksum}00000000${ip6_hdr}0000000000000000 echo $reply >> vif$inport.expected - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet + check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } # test_tcp_syn_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_IP_CHKSUM EXP_TCP_RST_CHKSUM @@ -12842,7 +12875,7 @@ local reply=${eth_src}${eth_dst}08004500002800004000${tcp_rst_ttl}06${exp_ip_chksum}${ipv4_dst}${ipv4_src}${tcp_dport}${tcp_sport}000000000000000250140000${exp_tcp_rst_chksum}0000 echo $reply >> vif$inport.expected - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet + check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } # Create hypervisors hv[123]. @@ -12850,18 +12883,18 @@ # Add all of the vifs to a single logical switch sw0. net_add n1 -ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add sw0 for i in 1 2 3; do sim_add hv$i as hv$i - ovs-vsctl add-br br-phys + check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.$i for j in 1 2 3; do ovn-nbctl lsp-add sw0 sw0-p$i$j -- \ lsp-set-addresses sw0-p$i$j "00:00:00:00:00:$i$j 192.168.1.$i$j" - ovs-vsctl -- add-port br-int vif$i$j -- \ + check ovs-vsctl -- add-port br-int vif$i$j -- \ set interface vif$i$j \ external-ids:iface-id=sw0-p$i$j \ options:tx_pcap=hv$i/vif$i$j-tx.pcap \ @@ -12874,20 +12907,19 @@ # allow some time for ovn-northd and ovn-controller to catch up. sleep 1 -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - for i in 1 2 3; do : > vif${i}1.expected done -ovn-nbctl --log acl-add sw0 to-lport 1000 "outport == \"sw0-p12\"" reject -ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject -ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject +check ovn-nbctl --log acl-add sw0 to-lport 1000 "outport == \"sw0-p12\"" reject +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. -ovn-nbctl --timeout=3 --wait=hv sync +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) test_ip_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 f85b f576 test_ip_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 f85b f576 @@ -12942,11 +12974,11 @@ -- lsp-set-addresses lp$i$j$k \ "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" # logical ports lp[12]?1 belongs to port group pg1 - if test $i != 3 && test $k == 1; then + if test $i != 3 && test $k = 1; then pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`" fi # logical ports lp[23]?2 belongs to port group pg2 - if test $i != 1 && test $k == 2; then + if test $i != 1 && test $k = 2; then pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`" fi done @@ -13081,9 +13113,6 @@ # Send IP packets between all pairs of source and destination ports, # packets matches ACL (pg2 to pg1) should be dropped -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} for is in 1 2 3; do for js in 1 2 3; do for ks in 1 2 3; do @@ -13100,8 +13129,8 @@ if test $d != $s; then unicast=$d; else unicast=; fi # packets matches ACL should be dropped - if test $id != 3 && test $kd == 1; then - if test $is != 1 && test $ks == 2; then + if test $id != 3 && test $kd = 1; then + if test $is != 1 && test $ks = 2; then unicast= fi fi @@ -13167,11 +13196,11 @@ -- lsp-set-addresses lp$i$j$k \ "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k" # logical ports lp[12]?1 belongs to port group pg1 - if test $i != 3 && test $k == 1; then + if test $i != 3 && test $k = 1; then pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`" fi # logical ports lp[23]?2 belongs to port group pg2 - if test $i != 1 && test $k == 2; then + if test $i != 1 && test $k = 2; then pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`" fi done @@ -13321,9 +13350,6 @@ # Send IP packets between all pairs of source and destination ports, # packets matches ACL1 but not ACL2 should be dropped -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} for is in 1 2 3; do for js in 1 2 3; do for ks in 1 2 3; do @@ -13343,8 +13369,8 @@ if test $d != $s; then unicast=$d; else unicast=; fi # packets matches ACL1 but not ACL2 should be dropped - if test $id != 3 && test $kd == 1; then - if test $is == 1 || test $ks != 2; then + if test $id != 3 && test $kd = 1; then + if test $is = 1 || test $ks != 2; then unicast= fi fi @@ -13407,29 +13433,17 @@ ovn-nbctl --wait=sb sync dnl Check if port group address sets were populated with ports' addresses -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.0.0.1", "10.0.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses], - [0], [[["10.0.0.2", "10.0.0.3"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8::1", "2001:db8::2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses], - [0], [[["2001:db8::2", "2001:db8::3"]] -]) +check_column '10.0.0.1 10.0.0.2' Address_Set addresses name=pg1_ip4 +check_column '10.0.0.2 10.0.0.3' Address_Set addresses name=pg2_ip4 +check_column '2001:db8::1 2001:db8::2' Address_Set addresses name=pg1_ip6 +check_column '2001:db8::2 2001:db8::3' Address_Set addresses name=pg2_ip6 ovn-nbctl --wait=sb lsp-set-addresses lp1 \ "02:00:00:00:00:01 10.0.0.11 2001:db8::11" dnl Check if updated address got propagated to the port group address sets -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.0.0.11", "10.0.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8::11", "2001:db8::2"]] -]) +check_column '10.0.0.11 10.0.0.2' Address_Set addresses name=pg1_ip4 +check_column '2001:db8::11 2001:db8::2' Address_Set addresses name=pg1_ip6 AT_CLEANUP @@ -13468,28 +13482,31 @@ ovn-nbctl --wait=sb sync dnl Check if port group address sets were populated with ports' addresses -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.1.0.2", "10.2.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses], - [0], [[["10.2.0.2", "10.3.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8:1::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses], - [0], [[["2001:db8:2::ff:fe00:2", "2001:db8:3::ff:fe00:3"]] +AT_CHECK( + [ovn-sbctl get Address_Set pg1_ip4 addresses \ + -- get Address_Set pg2_ip4 addresses \ + -- get Address_Set pg1_ip6 addresses \ + -- get Address_Set pg2_ip6 addresses], + [0], + [[["10.1.0.2", "10.2.0.2"]] +[["10.2.0.2", "10.3.0.2"]] +[["2001:db8:1::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] +[["2001:db8:2::ff:fe00:2", "2001:db8:3::ff:fe00:3"]] ]) +dnl Check if updated address got propagated to the port group address sets ovn-nbctl --wait=sb set Logical_Switch ls1 \ other_config:subnet=10.11.0.0/24 other_config:ipv6_prefix="2001:db8:11::" - -dnl Check if updated address got propagated to the port group address sets -AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses], - [0], [[["10.11.0.2", "10.2.0.2"]] -]) -AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses], - [0], [[["2001:db8:11::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] +AT_CHECK( + [ovn-sbctl get Address_Set pg1_ip4 addresses \ + -- get Address_Set pg2_ip4 addresses \ + -- get Address_Set pg1_ip6 addresses \ + -- get Address_Set pg2_ip6 addresses], + [0], + [[["10.11.0.2", "10.2.0.2"]] +[["10.2.0.2", "10.3.0.2"]] +[["2001:db8:11::ff:fe00:1", "2001:db8:2::ff:fe00:2"]] +[["2001:db8:2::ff:fe00:2", "2001:db8:3::ff:fe00:3"]] ]) AT_CLEANUP @@ -13533,7 +13550,7 @@ addresses=\"10.0.0.7\",\"10.0.0.8\",\"10.0.0.9\" ovn-nbctl acl-add ls1 to-lport 1001 \ 'ip4 && ip4.src == $set1 && ip4.dst == $set1' allow -ovn-nbctl acl-add ls1 to-lport 1001 \ +check ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \ 'ip4 && ip4.src == $set1 && ip4.dst == $set2' drop # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... @@ -13556,10 +13573,6 @@ done } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - reset_pcap_file() { local iface=$1 local pcap_file=$2 @@ -13709,6 +13722,220 @@ OVN_CLEANUP([hv1]) AT_CLEANUP +AT_SETUP([ovn -- Superseding ACLs with conjunction]) +ovn_start + +ovn-nbctl ls-add ls1 + +ovn-nbctl lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01" + +ovn-nbctl lsp-add ls1 ls1-lp2 \ +-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02" + +net_add n1 +sim_add hv1 + +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... +# +# This shell function causes an ip packet to be received on INPORT. +# The packet's content has Ethernet destination DST and source SRC +# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits). +# The OUTPORTs (zero or more) list the VIFs on which the packet should +# be received. INPORT and the OUTPORTs are specified as logical switch +# 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 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 + for outport; do + echo $packet >> $outport.expected + done +} + +reset_pcap_file() { + local iface=$1 + local pcap_file=$2 + ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ +options:rxq_pcap=dummy-rx.pcap + rm -f ${pcap_file}*.pcap + ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ +options:rxq_pcap=${pcap_file}-rx.pcap +} + +# Add a default deny ACL and an allow ACL for specific IP traffic. +ovn-nbctl acl-add ls1 to-lport 2 'arp' allow +ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop +ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow +ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow +ovn-nbctl --wait=hv sync + +# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. +for src in `seq 1 2`; do + for dst in `seq 3 4`; do + sip=`ip_to_hex 10 0 0 $src` + dip=`ip_to_hex 10 0 0 $dst` + + test_ip 1 f00000000001 f00000000002 $sip $dip 2 + done +done + +# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.5 should be dropped. +dip=`ip_to_hex 10 0 0 5` +for src in `seq 1 2`; do + sip=`ip_to_hex 10 0 0 $src` + + test_ip 1 f00000000001 f00000000002 $sip $dip +done + +cat 2.expected > expout +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], [expout]) +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 2.packets +> 2.expected + +# Add two less restrictive allow ACLs for src IP 10.0.0.1. +ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' allow +ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow +ovn-nbctl --wait=hv sync + +# Check OVS flows, the less restrictive flows should have been installed. +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ + grep "priority=1003" | \ + sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl + table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() +]) + +# 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 + for dst in `seq 3 4`; do + sip=`ip_to_hex 10 0 0 $src` + dip=`ip_to_hex 10 0 0 $dst` + + test_ip 1 f00000000001 f00000000002 $sip $dip 2 + done +done + +# Traffic 10.0.0.2 -> 10.0.0.5 should be dropped. +sip=`ip_to_hex 10 0 0 2` +dip=`ip_to_hex 10 0 0 5` +test_ip 1 f00000000001 f00000000002 $sip $dip + +# Traffic 10.0.0.1 -> 10.0.0.5 should be allowed. +sip=`ip_to_hex 10 0 0 1` +dip=`ip_to_hex 10 0 0 5` +test_ip 1 f00000000001 f00000000002 $sip $dip 2 + +cat 2.expected > expout +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], [expout]) +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 2.packets +> 2.expected + +#sleep infinity + +# Remove the first less restrictive allow ACL. +ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' +ovn-nbctl --wait=hv sync + +# Check OVS flows, the second less restrictive allow ACL should have been installed. +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ + grep "priority=1003" | \ + sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl + table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() +]) + +# Remove the less restrictive allow ACL. +ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1' +ovn-nbctl --wait=hv sync + +# Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled. +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ + grep "priority=1003" | \ + sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl + table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() +]) + +# 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 + for dst in `seq 3 4`; do + sip=`ip_to_hex 10 0 0 $src` + dip=`ip_to_hex 10 0 0 $dst` + + test_ip 1 f00000000001 f00000000002 $sip $dip 2 + done +done + +# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.5 should be dropped. +dip=`ip_to_hex 10 0 0 5` +for src in `seq 1 2`; do + sip=`ip_to_hex 10 0 0 $src` + + test_ip 1 f00000000001 f00000000002 $sip $dip +done + +cat 2.expected > expout +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], [expout]) + +# Re-add the less restrictive allow ACL for src IP 10.0.0.1 +ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow +ovn-nbctl --wait=hv sync + +# Check OVS flows, the less restrictive flows should have been installed. +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ + grep "priority=1003" | \ + sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl + table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() + table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() +]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP + # 3 hypervisors, one logical switch, 3 logical ports per hypervisor AT_SETUP([ovn -- L2 Drop and Allow ACL w/ Stateful ACL]) ovn_start @@ -13793,12 +14020,12 @@ # Test drop rule # -------------- ovn-nbctl acl-del lsw0 -ovn-nbctl --log --severity=info --name=drop-acl acl-add lsw0 to-lport 5000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' drop +check ovn-nbctl --wait=hv --log --severity=info --name=drop-acl acl-add lsw0 to-lport 5000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' drop for sf in 0 1; do if test ${sf} = 1; then # Add a stateful rule and re-run the check to make sure the # drop rule is still effective.. - ovn-nbctl acl-add lsw0 from-lport 2000 "inport == lp31 && ip" allow-related + ovn-nbctl --wait=hv acl-add lsw0 from-lport 2000 "inport == lp31 && ip" allow-related fi for is in 1 2 3; do s=${is}1 @@ -13832,13 +14059,17 @@ # drop all packets to 11 and 21. ovn-nbctl acl-add lsw0 to-lport 5000 'outport == @pg1 && eth.src == $set1' drop # allow 0x1234 between 11 and 21 -ovn-nbctl --log --severity=info --name=allow-acl acl-add lsw0 to-lport 6000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' allow +check ovn-nbctl --wait=hv --log --severity=info --name=allow-acl acl-add lsw0 to-lport 6000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' allow for sf in 0 1; do if test ${sf} = 1; then # Add a stateful rule and re-run the check to make sure the # allow rule is still effective.. - ovn-nbctl acl-add lsw0 from-lport 2000 "inport == lp31 && ip" allow-related + check ovn-nbctl --wait=hv acl-add lsw0 from-lport 2000 "inport == lp31 && ip" allow-related fi + # dump information and flows with counters + ovn-sbctl dump-flows -- list multicast_group > sbflows$sf + AT_CAPTURE_FILE([sbflows0]) + AT_CAPTURE_FILE([sbflows1]) for is in 1 2 3; do s=${is}1 for id in 1 2 3; do @@ -13863,52 +14094,48 @@ done done -# Clean up the ACL -ovn-nbctl acl-del lsw0 - -# dump information and flows with counters -ovn-sbctl dump-flows -- list multicast_group - -echo "------ hv1 dump ------" -as hv1 ovs-vsctl show -as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv2 dump ------" -as hv2 ovs-vsctl show -as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int - -echo "------ hv3 dump ------" -as hv3 ovs-vsctl show -as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows1 +as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows2 +as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows3 # Now check the packets actually received against the ones expected. -for i in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [${i}1.expected]) -done - -# need to verify the log for ACL hit as well, since in the allow case -# (unlike the drop case) it is tricky to pass just with the expected; -# since with the stateful rule the packet will still get by (default -# rule) even if it doesn't hit the allow rule. -# The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2 -# (with/without stateful rule) for hv1 and hv2, each. - -hv1_drop_acl_hit=`grep acl_log hv1/ovn-controller.log | grep "|INFO|name=\"drop-acl\"" | wc -l` -hv2_drop_acl_hit=`grep acl_log hv2/ovn-controller.log | grep "|INFO|name=\"drop-acl\"" | wc -l` - -hv1_allow_acl_hit=`grep acl_log hv1/ovn-controller.log | grep "|INFO|name=\"allow-acl\"" | wc -l` -hv2_allow_acl_hit=`grep acl_log hv2/ovn-controller.log | grep "|INFO|name=\"allow-acl\"" | wc -l` +AT_CAPTURE_FILE([expected]) +AT_CAPTURE_FILE([received]) +check_packets() { + > expected + > received + for i in 1 2 3; do + echo "--- hv$i vif${i}1" | tee -a expected >> received + sort ${i}1.expected >> expected + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv$i/vif${i}1-tx.pcap | sort >> received + echo | tee -a expected >> received + done + + # need to verify the log for ACL hit as well, since in the allow case + # (unlike the drop case) it is tricky to pass just with the expected; + # since with the stateful rule the packet will still get by (default + # rule) even if it doesn't hit the allow rule. + # The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2 + # (with/without stateful rule) for hv1 and hv2, each. + cat >>expected <>received </dev/null +} +OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received]) OVN_CLEANUP([hv1],[hv2],[hv3]) @@ -13967,10 +14194,6 @@ as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - for i in 1 2; do net_add n$i ovn-nbctl ls-add sw$i @@ -14112,10 +14335,6 @@ as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - for i in 1 2; do net_add n$i ovn-nbctl ls-add sw$i @@ -14301,14 +14520,10 @@ # Create HA chassis group ovn-nbctl ha-chassis-group-add hagrp1 ovn-nbctl ha-chassis-group-add-chassis hagrp1 hv1 30 +ovn-nbctl --wait=sb sync hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name="hagrp1"` -# There should be 1 HA_Chassis rows with chassis sets -OVS_WAIT_UNTIL([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep '-' | wc -l ], [0], [1 -]) - as hv1 ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 @@ -14364,7 +14579,7 @@ chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` -AT_CHECK([test x$chassis == x], [0], []) +AT_CHECK([test x$chassis = x], [0], []) # Associate hagrp1 ha-chassis-group to ls1-lp_ext1 ovn-nbctl --wait=hv set Logical_Switch_Port ls1-lp_ext1 \ @@ -14382,17 +14597,21 @@ # No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2 # as no localnet port added to ls1 yet. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +# +# This used to be specific to a particular OpenFlow table, but that can +# easily shift around as OVN evolves, so it's been removed to avoid +# gratuitous breakage. +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | wc -l], [0], [0 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | wc -l], [0], [0 ]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 ]) @@ -14414,38 +14633,40 @@ test "$chassis" = "$hv1_uuid"]) # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +(ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows +as hv1 ovs-ofctl dump-flows br-int > brintflows +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \ wc -l], [0], [3 ]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ grep reg14=0x$ln_public_key | wc -l], [0], [1 ]) # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2 -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | wc -l], [0], [0 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0 ]) # No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and # hv2 as requested-chassis option is not set. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.07" | wc -l], [0], [0 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.07" | wc -l], [0], [0 ]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0 ]) @@ -14601,10 +14822,6 @@ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - AT_CAPTURE_FILE([ofctl_monitor0_hv1.log]) as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ --pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log @@ -14697,21 +14914,21 @@ test "$chassis" = "$hv2_uuid"]) # 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 | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \ wc -l], [0], [3 ]) -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ grep reg14=0x$ln_public_key | wc -l], [0], [1 ]) # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep "0a.00.00.06" | wc -l], [0], [0 ]) -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \ +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ grep controller | grep tp_src=546 | grep \ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \ grep reg14=0x$ln_public_key | wc -l], [0], [0 @@ -14977,7 +15194,7 @@ # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined # to router mac. AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \ -table=27,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ +table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ grep -c "actions=drop"], [0], [1 ]) @@ -15186,17 +15403,17 @@ ovn-nbctl lsp-add sw1 sw1-p2 ovn-nbctl lsp-add sw1 sw1-p3 ovn-nbctl lsp-add sw1 sw1-p4 +ovn-nbctl --wait=sb lsp-add sw1 sw1-p5 -ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1" "00:00:00:00:00:02 10.0.0.2 aef0::2" -ovn-nbctl lsp-set-addresses sw1-p2 "00:00:00:00:00:03 dynamic" -ovn-nbctl lsp-set-addresses sw1-p3 "dynamic" -ovn-nbctl lsp-set-addresses sw1-p4 "router" -ovn-nbctl lsp-set-addresses sw1-p5 "unknown" +check ovn-nbctl --wait=sb lsp-set-addresses sw1-p1 "00:00:00:00:00:01 10.0.0.1 aef0::1" "00:00:00:00:00:02 10.0.0.2 aef0::2" +check ovn-nbctl --wait=sb lsp-set-addresses sw1-p2 "00:00:00:00:00:03 dynamic" +check ovn-nbctl --wait=sb lsp-set-addresses sw1-p3 "dynamic" +check ovn-nbctl --wait=sb lsp-set-addresses sw1-p4 "router" +check ovn-nbctl --wait=sb lsp-set-addresses sw1-p5 "unknown" ovn-nbctl list logical_switch_port # Now try to add duplicate addresses on a new port. These should all fail -ovn-nbctl --wait=sb lsp-add sw1 sw1-p5 AT_CHECK([ovn-nbctl lsp-set-addresses sw1-p5 "00:00:00:00:00:04 10.0.0.1"], [1], [], [ovn-nbctl: Error on switch sw1: duplicate IPv4 address '10.0.0.1' found on logical switch port 'sw1-p1' ]) @@ -15286,12 +15503,8 @@ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - test_ip_packet_larger() { - local icmp_pmtu_reply_expected=$1 + local mtu=$1 # Send ip packet from sw0-port1 to outside src_mac="505400000001" # sw-port1 mac @@ -15312,13 +15525,16 @@ gw_ip_garp=ffffffffffff00002020121308060001080006040001000020201213aca80064000000000000aca80064 + packet_bytes=$(expr ${#packet} / 2) + mtu_needed=$(expr ${packet_bytes} - 18) + # If icmp_pmtu_reply_expected is 0, it means the packet is lesser than # the gateway mtu and should be delivered to the provider bridge via the # localnet port. # If icmp_pmtu_reply_expected is 1, it means the packet is larger than # the gateway mtu and ovn-controller should drop the packet and instead # generate ICMPv4 Destination Unreachable message with pmtu set to 100. - if test $icmp_pmtu_reply_expected = 0; then + if test $mtu -ge $mtu_needed; then # Packet to expect at br-phys. src_mac="000020201213" dst_mac="00000012af11" @@ -15334,15 +15550,13 @@ echo $expected > br_phys_n1.expected echo $gw_ip_garp >> br_phys_n1.expected else - # MTU would be 118 - 18 = 100 (hex 0064) - mtu=0064 src_ip=`ip_to_hex 10 0 0 1` dst_ip=`ip_to_hex 10 0 0 3` # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload)) reply_pkt_len=0092 ip_csum=f993 icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe016867 - icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000${mtu} + icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu) icmp_reply=${icmp_reply}4500${pkt_len}000000003f01c4d9 icmp_reply=${icmp_reply}${orig_packet_l3} echo $icmp_reply > hv1-vif1.expected @@ -15352,9 +15566,9 @@ as hv1 reset_pcap_file hv1-vif1 hv1/vif1 # Send packet from sw0-port1 to outside - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - if test $icmp_pmtu_reply_expected = 0; then + if test $mtu -ge $mtu_needed; then OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [br_phys_n1.expected]) $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > pkts # hv1/vif1-tx.pcap can receive the GARP packet generated by ovn-controller @@ -15373,7 +15587,7 @@ } test_ip6_packet_larger() { - local icmp_pmtu_reply_expected=$1 + local mtu=$1 local eth_src=505400000001 local eth_dst=00000000ff01 @@ -15394,21 +15608,48 @@ as hv1 reset_pcap_file hv1-vif1 hv1/vif1 # Send packet from sw0-port1 to outside - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + tcpdump_hex ">> sending packet:" $packet + check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + AT_CHECK([as hv1 ovs-appctl ofproto/trace br-int in_port=hv1-vif1 $packet > trace-$mtu], + [0], [ignore]) + AT_CAPTURE_FILE([trace-$mtu]) + + packet_bytes=$(expr ${#packet} / 2) + mtu_needed=$(expr ${packet_bytes} - 18) + if test $mtu -lt $mtu_needed; then + # First construct the inner IPv6 packet. + inner_ip6=6000000000583afe${ipv6_src}${ipv6_dst} + inner_icmp6=8000000062f00001 + inner_icmp6_and_payload=$(icmp6_csum_inplace ${inner_icmp6}${payload} ${inner_ip6}) + inner_packet=${inner_ip6}${inner_icmp6_and_payload} + + # Then the outer. + outer_ip6=6000000000883afe${ipv6_rt}${ipv6_src} + outer_icmp6_and_payload=$(icmp6_csum_inplace 020000000000$(printf "%04x" $mtu)${inner_packet} $outer_ip6) + outer_packet=${outer_ip6}${outer_icmp6_and_payload} + + icmp6_reply=${eth_src}${eth_dst}86dd${outer_packet} + + echo + tcpdump_hex ">> expecting reply packet" $icmp6_reply - if test $icmp_pmtu_reply_expected = 1; then - icmp6_reply=${eth_src}${eth_dst}86dd6000000000883afe - icmp6_reply=${icmp6_reply}${ipv6_rt}${ipv6_src}020041ff00000076 - icmp6_reply=${icmp6_reply}6000000000583afe${ipv6_src}${ipv6_dst} - icmp6_reply=${icmp6_reply}8000ec7662f00001${payload} - echo $icmp6_reply > hv1-vif1.expected + # The "trace" above sends a second packets as a side effect. + (echo $icmp6_reply; echo $icmp6_reply) > hv1-vif1.expected OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [hv1-vif1.expected]) fi } -ovn-nbctl show -ovn-sbctl show +ovn-nbctl --wait=hv sync + +ovn-nbctl show > nbdump +AT_CAPTURE_FILE([nbdump]) + +ovn-sbctl show > sbdump +AT_CAPTURE_FILE([sbdump]) + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) AT_CHECK([as hv1 ovs-ofctl dump-flows br-int \ | grep "check_pkt_larger" | wc -l], [0], [[0 @@ -15418,34 +15659,27 @@ ovn-sbctl create MAC_Binding ip=172.168.0.3 datapath=$dp_uuid \ logical_port=lr0-public mac="00\:00\:00\:12\:af\:11" -# Set the gateway mtu to 100. If the packet length is > 118, ovn-controller -# should send icmp host not reachable with pmtu set to 100. -ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=100 -as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply -OVS_WAIT_UNTIL([ - test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(118)" | \ - wc -l` -eq 1 +# Try different gateway mtus and send a 142-byte packet (corresponding +# to a 124-byte MTU). If the MTU is less than 124, ovn-controller +# should send icmp host not reachable with pmtu set to $mtu. +for mtu in 100 500 118; do + AS_BOX([testing mtu $mtu]) + check ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=$mtu + ovn-sbctl dump-flows > sbflows-$mtu + AT_CAPTURE_FILE([sbflows-$mtu]) + + OVS_WAIT_FOR_OUTPUT([ + as hv1 ovs-ofctl dump-flows br-int > br-int-flows-$mtu + AT_CAPTURE_FILE([br-int-flows-$mtu]) + grep "check_pkt_larger($(expr $mtu + 18))" br-int-flows-$mtu | wc -l], [0], [1 ]) -icmp_reply_expected=1 -test_ip_packet_larger $icmp_reply_expected + AS_BOX([testing mtu $mtu - IPv4]) + test_ip_packet_larger $mtu -# Set the gateway mtu to 500. -ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=500 -as hv3 ovs-appctl netdev-dummy/receive hv3-vif1 $arp_reply -OVS_WAIT_UNTIL([ - test `as hv1 ovs-ofctl dump-flows br-int | grep "check_pkt_larger(518)" | \ - wc -l` -eq 1 -]) - -# Now the packet should be sent via the localnet port to br-phys. -icmp_reply_expected=0 -test_ip_packet_larger $icmp_reply_expected - -# Set the gateway mtu to 118 -ovn-nbctl --wait=hv set logical_router_port lr0-public options:gateway_mtu=118 -icmp_reply_expected=1 -test_ip6_packet_larger $icmp_reply_expected + AS_BOX([testing mtu $mtu - IPv6]) + test_ip6_packet_larger $mtu +done OVN_CLEANUP([hv1]) AT_CLEANUP @@ -15548,7 +15782,7 @@ -- lsp-set-addresses rp-sw0 router ovn-nbctl lrp-add lr0 sw1 00:00:02:01:02:03 172.16.1.1/24 2002:0:0:0:0:0:0:1/64 \ - -- set Logical_Router_Port sw1 options:redirect-chassis="hv2" + -- lrp-set-gateway-chassis sw1 hv2 ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \ type=router options:router-port=sw1 \ -- lsp-set-addresses rp-sw1 router @@ -15567,9 +15801,8 @@ OVN_POPULATE_ARP ovn-nbctl --wait=hv sync -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) src_mac=f00000010203 src_ip=$(ip_to_hex 192 168 1 2) @@ -15610,6 +15843,9 @@ ovn-nbctl lr-nat-del lr0 snat ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.16.1.2 192.168.1.3 sw0-p1 f0:00:00:01:02:04 ovn-nbctl lr-route-add lr0 172.16.2.0/24 172.16.1.11 +ovn-nbctl --wait=hv sync +ovn-sbctl dump-flows > sbflows2 +AT_CAPTURE_FILE([sbflows2]) dst_ip=$(ip_to_hex 172 16 2 10) fip_ip=$(ip_to_hex 172 16 1 2) @@ -15642,85 +15878,116 @@ # GARPs have been sent out to the external network as well. # Create logical switches -ovn-nbctl ls-add sw0 -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add pub +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add sw1 +check ovn-nbctl ls-add pub # Created localnet port on public switch -ovn-nbctl lsp-add pub ln-pub -ovn-nbctl lsp-set-type ln-pub localnet -ovn-nbctl lsp-set-addresses ln-pub unknown -ovn-nbctl lsp-set-options ln-pub network_name=phys +check ovn-nbctl lsp-add pub ln-pub +check ovn-nbctl lsp-set-type ln-pub localnet +check ovn-nbctl lsp-set-addresses ln-pub unknown +check ovn-nbctl lsp-set-options ln-pub network_name=phys # Create logical routers and connect them to public switch -ovn-nbctl create Logical_Router name=lr0 -ovn-nbctl create Logical_Router name=lr1 +AT_CHECK([(ovn-nbctl create Logical_Router name=lr0; + ovn-nbctl create Logical_Router name=lr1) | uuidfilt], [0], [<0> +<1> +]) -ovn-nbctl lrp-add lr0 lr0-pub f0:00:00:00:00:01 172.24.4.220/24 -ovn-nbctl lsp-add pub pub-lr0 -- set Logical_Switch_Port pub-lr0 \ +check ovn-nbctl lrp-add lr0 lr0-pub f0:00:00:00:00:01 172.24.4.220/24 +check ovn-nbctl lsp-add pub pub-lr0 -- set Logical_Switch_Port pub-lr0 \ type=router options:router-port=lr0-pub options:nat-addresses="router" addresses="router" -ovn-nbctl lrp-add lr1 lr1-pub f0:00:00:00:01:01 172.24.4.221/24 -ovn-nbctl lsp-add pub pub-lr1 -- set Logical_Switch_Port pub-lr1 \ +check ovn-nbctl lrp-add lr1 lr1-pub f0:00:00:00:01:01 172.24.4.221/24 +check ovn-nbctl lsp-add pub pub-lr1 -- set Logical_Switch_Port pub-lr1 \ type=router options:router-port=lr1-pub options:nat-addresses="router" addresses="router" -ovn-nbctl lrp-set-gateway-chassis lr0-pub hv1 10 -ovn-nbctl lrp-set-gateway-chassis lr1-pub hv1 10 +check ovn-nbctl lrp-set-gateway-chassis lr0-pub hv1 10 +check ovn-nbctl lrp-set-gateway-chassis lr1-pub hv1 10 # Connect sw0 and sw1 to lr0 and lr1 -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.254/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -- set Logical_Switch_Port sw0-lr0 type=router \ +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.254/24 +check ovn-nbctl lsp-add sw0 sw0-lr0 -- set Logical_Switch_Port sw0-lr0 type=router \ options:router-port=lr0-sw0 addresses="router" -ovn-nbctl lrp-add lr1 lr1-sw1 00:00:00:00:ff:02 20.0.0.254/24 -ovn-nbctl lsp-add sw1 sw1-lr1 -- set Logical_Switch_Port sw1-lr1 type=router \ +check ovn-nbctl lrp-add lr1 lr1-sw1 00:00:00:00:ff:02 20.0.0.254/24 +check ovn-nbctl lsp-add sw1 sw1-lr1 -- set Logical_Switch_Port sw1-lr1 type=router \ options:router-port=lr1-sw1 addresses="router" # Add SNAT rules -ovn-nbctl lr-nat-add lr0 snat 172.24.4.220 10.0.0.0/24 -ovn-nbctl lr-nat-add lr1 snat 172.24.4.221 20.0.0.0/24 +check ovn-nbctl lr-nat-add lr0 snat 172.24.4.220 10.0.0.0/24 +check ovn-nbctl lr-nat-add lr1 snat 172.24.4.221 20.0.0.0/24 net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 172.24.4.1 -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -ovs-vsctl add-port br-int vif0 -- set Interface vif0 external-ids:iface-id=lp0 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 +check ovs-vsctl add-port br-int vif0 -- set Interface vif0 external-ids:iface-id=lp0 +check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 -ovn-nbctl lsp-add sw0 lp0 -ovn-nbctl lsp-add sw1 lp1 -ovn-nbctl lsp-set-addresses lp0 "50:54:00:00:00:01 10.0.0.10" -ovn-nbctl lsp-set-addresses lp1 "50:54:00:00:00:02 20.0.0.10" +check ovn-nbctl lsp-add sw0 lp0 +check ovn-nbctl lsp-add sw1 lp1 +check ovn-nbctl lsp-set-addresses lp0 "50:54:00:00:00:01 10.0.0.10" +check ovn-nbctl lsp-set-addresses lp1 "50:54:00:00:00:02 20.0.0.10" OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup]) OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp1` = xup]) # Create two floating IPs, one for each VIF -ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10 -ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10 +check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10 +check ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10 # Check that the MAC_Binding entries have been properly created -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr0-pub" ip="172.24.4.200" | wc -l` -gt 0]) -OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr1-pub" ip="172.24.4.100" | wc -l` -gt 0]) +wait_row_count MAC_Binding 1 logical_port=lr0-pub ip=172.24.4.200 +wait_row_count MAC_Binding 1 logical_port=lr1-pub ip=172.24.4.100 # Check that the GARPs went also to the external physical network # Wait until at least 4 packets have arrived and copy them to a separate file as # more GARPs are expected in the capture in order to avoid race conditions. -OVS_WAIT_UNTIL([test `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | wc -l` -gt 4]) -$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | head -n4 > hv1/br-phys-tx4.pcap - +# +# The expected output is: # GARP for lp0 172.24.4.100 on lr0-pub MAC (f0:00:00:00:00:01) -echo "fffffffffffff0000000000108060001080006040001f00000000001ac180464000000000000ac180464" > expout # GARP for 172.24.4.220 on lr0-pub (f0:00:00:00:00:01) -echo "fffffffffffff0000000000108060001080006040001f00000000001ac1804dc000000000000ac1804dc" >> expout # GARP for lp1 172.24.4.200 on lr1-pub MAC (f0:00:00:00:01:01) -echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804c8000000000000ac1804c8" >> expout # GARP for 172.24.4.221 on lr1-pub (f0:00:00:00:01:01) -echo "fffffffffffff0000000010108060001080006040001f00000000101ac1804dd000000000000ac1804dd" >> expout -AT_CHECK([sort hv1/br-phys-tx4.pcap], [0], [expout]) -#OVN_CHECK_PACKETS([hv1/br-phys-tx4.pcap], [br-phys.expected]) + +OVS_WAIT_FOR_OUTPUT( + [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | sort | uniq], + [0], + [fffffffffffff0000000000108060001080006040001f00000000001ac180464000000000000ac180464 +fffffffffffff0000000000108060001080006040001f00000000001ac1804dc000000000000ac1804dc +fffffffffffff0000000010108060001080006040001f00000000101ac1804c8000000000000ac1804c8 +fffffffffffff0000000010108060001080006040001f00000000101ac1804dd000000000000ac1804dd +]) + +# Now make sure that always_learn_from_arp_request=false is also honored +# for locally injected mac bindings. +check ovn-nbctl set logical_router lr0 options:always_learn_from_arp_request=false +check ovn-nbctl set logical_router lr1 options:always_learn_from_arp_request=false +check ovn-nbctl --wait=hv sync + +# Recreate two floating IPs, one for each VIF. +check ovn-nbctl lr-nat-del lr0 dnat_and_snat 172.24.4.100 +check ovn-nbctl lr-nat-del lr1 dnat_and_snat 172.24.4.200 + +check ovn-sbctl --all destroy mac_binding + +# Create a mac_binding entry on lr0-pub for 172.24.4.200 with a +# wrong mac, expecting it to be updated with the real mac. +lr0_dp=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) +ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-pub \ + ip=172.24.4.200 mac=\"aa:aa:aa:aa:aa:aa\" + +check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10 +check ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10 +check ovn-nbctl --wait=hv sync + +check_row_count MAC_Binding 1 logical_port=lr0-pub ip=172.24.4.200 +check_row_count MAC_Binding 0 logical_port=lr1-pub ip=172.24.4.100 +check_column "f0:00:00:00:01:01" MAC_Binding mac logical_port=lr0-pub \ + ip=172.24.4.200 OVN_CLEANUP([hv1]) AT_CLEANUP @@ -15778,8 +16045,7 @@ done dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync dnl Assert that each Chassis has a tunnel formed to every other Chassis as hv1 @@ -15846,8 +16112,7 @@ ovs-vsctl set open . external-ids:ovn-transport-zones=tz2 dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync as hv1 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], @@ -15883,8 +16148,7 @@ done dnl Wait for the changes to be propagated -ovn-nbctl --wait=sb --timeout=3 sync -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync as hv1 AT_CHECK([ovs-vsctl --bare --columns=name find interface type="geneve" | awk NF | sort], [0], @@ -16027,10 +16291,6 @@ esac } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - net_add n1 for i in 1 2; do sim_add hv$i @@ -16245,27 +16505,24 @@ # Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline # with bind_vport action. -ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt +ovn-sbctl dump-flows sw0 > sw0-flows +AT_CAPTURE_FILE([sw0-flows]) -AT_CHECK([cat lflows.txt], [0], [dnl - table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) - table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) - table=14(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;) +AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) ]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt +ovn-sbctl dump-flows lr0 > lr0-flows +AT_CAPTURE_FILE([lr0-flows]) # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to # zero if the ip4.dst is the virtual ip in the router pipeline. -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(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;) +AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) ]) -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"` hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"` @@ -16304,13 +16561,13 @@ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p1]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt # There should be an arp resolve flow to resolve the virtual_ip with the # sw0-p1's MAC. -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(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;) +ovn-sbctl dump-flows lr0 > lr0-flows2 +AT_CAPTURE_FILE([lr0-flows2]) +AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) ]) # Forcibly clear virtual_parent. ovn-controller should release the binding @@ -16345,13 +16602,15 @@ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p3]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt # There should be an arp resolve flow to resolve the virtual_ip with the # sw0-p2's MAC. -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(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;) +sleep 1 +ovn-sbctl dump-flows lr0 > lr0-flows3 +AT_CAPTURE_FILE([lr0-flows3]) +cp ovn-sb/ovn-sb.db lr0-flows3.db +AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;) ]) # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir @@ -16368,13 +16627,14 @@ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p2]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt # There should be an arp resolve flow to resolve the virtual_ip with the # sw0-p3's MAC. -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) +sleep 1 +ovn-sbctl dump-flows lr0 > lr0-flows4 +AT_CAPTURE_FILE([lr0-flows4]) +AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) ]) # Now send arp reply from sw0-p1. hv1 should claim sw0-vir @@ -16387,15 +16647,15 @@ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) +sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p1]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(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;) +ovn-sbctl dump-flows lr0 > lr0-flows5 +AT_CAPTURE_FILE([lr0-flows5]) +AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) ]) # Delete hv1-vif1 port. hv1 should release sw0-vir @@ -16403,17 +16663,17 @@ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ logical_port=sw0-vir) = x], [0], []) +sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = x]) # 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 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) +ovn-sbctl dump-flows lr0 > lr0-flows6 +AT_CAPTURE_FILE([lr0-flows6]) +AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) ]) # Now send arp reply from sw0-p2. hv2 should claim sw0-vir @@ -16423,18 +16683,18 @@ spa=$(ip_to_hex 10 0 0 10) tpa=$(ip_to_hex 10 0 0 3) send_arp_reply 2 1 $eth_src $eth_dst $spa $tpa +sleep 1 OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) - +sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p2]) -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl - table=13(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) +ovn-sbctl dump-flows lr0 > lr0-flows7 +AT_CAPTURE_FILE([lr0-flows7]) +AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) ]) # Delete sw0-p2 logical port @@ -16442,39 +16702,34 @@ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ logical_port=sw0-vir) = x], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = x]) # 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 -ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl -]) +ovn-sbctl dump-flows sw0 > sw0-flows2 +AT_CAPTURE_FILE([sw0-flows2]) +AT_CHECK([grep ls_in_arp_rsp sw0-flows2 | grep bind_vport], [1]) # Add back virtual_ip and clear virtual_parents. ovn-nbctl --wait=hv set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 -ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl - table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) - table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) +ovn-sbctl dump-flows sw0 > sw0-flows3 +AT_CAPTURE_FILE([sw0-flows3]) +AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../table=??/'], [0], [dnl + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) + table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) ]) ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents -ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl -]) - -ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \ -> lflows.txt - -AT_CHECK([cat lflows.txt], [0], [dnl -]) +ovn-sbctl dump-flows sw0 > sw0-flows4 +AT_CAPTURE_FILE([sw0-flows4]) +AT_CHECK([grep ls_in_arp_rsp sw0-flows4 | grep bind_vport], [1]) + +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]) OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP @@ -16568,17 +16823,22 @@ ovn-nbctl --wait=hv meter-add event-elb drop 100 pktps 10 OVN_POPULATE_ARP -ovn-nbctl --timeout=3 --wait=hv sync -ovn-sbctl lflow-list -as hv1 ovs-ofctl dump-flows br-int +check ovn-nbctl --wait=hv sync +ovn-sbctl lflow-list > sbflows +AT_CAPTURE_FILE([sbflows]) +as hv1 ovs-ofctl dump-flows br-int > offlows +AT_CAPTURE_FILE([offlows]) packet0="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:00:00:21 && ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.1.100 && tcp && tcp.src==10000 && tcp.dst==80" as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet0" -ovn-sbctl list controller_event -uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}') +ovn-sbctl list controller_event > events +AT_CAPTURE_FILE([events]) +uuid=$(awk '/_uuid/{print $3}' < events) +echo uuid=$uuid +AT_CHECK([test -n "$uuid"]) AT_CHECK([ovn-sbctl get controller_event $uuid event_type], [0], [dnl empty_lb_backends ]) @@ -16650,21 +16910,18 @@ # - subnet 30.0.0.0/8 # - 1 port bound on hv1 (sw3-p1) # - 1 port bound on hv2 (sw3-p2) +# - 1 localnet port (sw3-ln) reset_pcap_file() { local iface=$1 local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ options:rxq_pcap=dummy-rx.pcap rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # # send_igmp_v3_report INPORT HV ETH_SRC IP_SRC IP_CSUM GROUP REC_TYPE # IGMP_CSUM OUTFILE @@ -16697,7 +16954,7 @@ local packet=${eth}${ip}${igmp} echo ${packet} >> ${outfile} - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} } # @@ -16742,7 +16999,7 @@ local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst} local packet=${eth}${ip}${data} - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} } # @@ -16776,6 +17033,9 @@ ovn-nbctl lsp-add sw2 sw2-p2 ovn-nbctl lsp-add sw3 sw3-p1 ovn-nbctl lsp-add sw3 sw3-p2 +ovn-nbctl lsp-add sw3 sw3-ln \ + -- lsp-set-type sw3-ln localnet \ + -- lsp-set-options sw3-ln network_name=phys ovn-nbctl lr-add rtr ovn-nbctl lrp-add rtr rtr-sw1 00:00:00:00:01:00 10.0.0.254/24 @@ -16786,11 +17046,11 @@ -- lsp-set-type sw1-rtr router \ -- lsp-set-addresses sw1-rtr 00:00:00:00:01:00 \ -- lsp-set-options sw1-rtr router-port=rtr-sw1 -ovn-nbctl lsp-add sw2 sw2-rtr \ +check ovn-nbctl lsp-add sw2 sw2-rtr \ -- lsp-set-type sw2-rtr router \ -- lsp-set-addresses sw2-rtr 00:00:00:00:02:00 \ -- lsp-set-options sw2-rtr router-port=rtr-sw2 -ovn-nbctl lsp-add sw3 sw3-rtr \ +check ovn-nbctl lsp-add sw3 sw3-rtr \ -- lsp-set-type sw3-rtr router \ -- lsp-set-addresses sw3-rtr 00:00:00:00:03:00 \ -- lsp-set-options sw3-rtr router-port=rtr-sw3 @@ -16798,69 +17058,88 @@ net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw1-p11 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw1-p12 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif3 -- \ +check ovs-vsctl -- add-port br-int hv1-vif3 -- \ set interface hv1-vif3 external-ids:iface-id=sw2-p1 \ options:tx_pcap=hv1/vif3-tx.pcap \ options:rxq_pcap=hv1/vif3-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif4 -- \ +check ovs-vsctl -- add-port br-int hv1-vif4 -- \ set interface hv1-vif4 external-ids:iface-id=sw3-p1 \ options:tx_pcap=hv1/vif4-tx.pcap \ options:rxq_pcap=hv1/vif4-rx.pcap \ ofport-request=1 +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ +check ovs-vsctl -- add-port br-int hv2-vif1 -- \ set interface hv2-vif1 external-ids:iface-id=sw1-p21 \ options:tx_pcap=hv2/vif1-tx.pcap \ options:rxq_pcap=hv2/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif2 -- \ +check ovs-vsctl -- add-port br-int hv2-vif2 -- \ set interface hv2-vif2 external-ids:iface-id=sw1-p22 \ options:tx_pcap=hv2/vif2-tx.pcap \ options:rxq_pcap=hv2/vif2-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif3 -- \ +check ovs-vsctl -- add-port br-int hv2-vif3 -- \ set interface hv2-vif3 external-ids:iface-id=sw2-p2 \ options:tx_pcap=hv2/vif3-tx.pcap \ options:rxq_pcap=hv2/vif3-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif4 -- \ +check ovs-vsctl -- add-port br-int hv2-vif4 -- \ set interface hv2-vif4 external-ids:iface-id=sw3-p2 \ options:tx_pcap=hv2/vif4-tx.pcap \ options:rxq_pcap=hv2/vif4-rx.pcap \ ofport-request=1 +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys OVN_POPULATE_ARP # Enable IGMP snooping on sw1. -ovn-nbctl set Logical_Switch sw1 \ +check ovn-nbctl --wait=hv set Logical_Switch sw1 \ other_config:mcast_querier="false" \ other_config:mcast_snoop="true" +AT_CAPTURE_FILE([sbflows]) +cp ovn-sb/ovn-sb.db ovn-sb.db +ovn-sbctl dump-flows > sbflows + # No IGMP query should be generated by sw1 (mcast_querier="false"). +# (Therefore everything is expected to be empty.) +AT_CAPTURE_FILE([expected]) +AT_CAPTURE_FILE([received]) > expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) +> received +for i in 1 2; do + for j in 1 2; do + pcap=hv$i/vif$j-tx.pcap + echo "--- $pcap" | tee -a expected >> received + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received + echo | tee -a expected >> received + done +done +check $at_diff -F'^---' expected received -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync + +AT_CAPTURE_FILE([sbflows2]) +cp ovn-sb/ovn-sb.db ovn-sb2.db +ovn-sbctl dump-flows > sbflows2 # Inject IGMP Join for 239.0.1.68 on sw1-p11. send_igmp_v3_report hv1-vif1 hv1 \ @@ -16873,10 +17152,12 @@ /dev/null # Check that the IGMP Group is learned on both hv. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "2" -]) +wait_row_count IGMP_Group 2 address=239.0.1.68 +check ovn-nbctl --wait=hv sync + +AT_CAPTURE_FILE([sbflows3]) +cp ovn-sb/ovn-sb.db ovn-sb3.db +ovn-sbctl dump-flows > sbflows3 # Send traffic and make sure it gets forwarded only on the two ports that # joined. @@ -16891,12 +17172,30 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ e518e518000a3b3a0000 expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +AT_CAPTURE_FILE([exp]) +AT_CAPTURE_FILE([rcv]) +check_packets() { + > exp + > rcv + for tuple in "$@"; do + set $tuple; pcap=$1 type=$2 + echo "--- $pcap" | tee -a exp >> rcv + sort "$type" >> exp + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> rcv + echo | tee -a exp >> rcv + done + + $at_diff exp rcv >/dev/null +} + +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Inject IGMP Leave for 239.0.1.68 on sw1-p11. send_igmp_v3_report hv1-vif1 hv1 \ @@ -16905,10 +17204,8 @@ /dev/null # Check IGMP_Group table on both HV. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address=239.0.1.68 +check ovn-nbctl --wait=hv sync # Send traffic and make sure it gets forwarded only on the port that joined. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 @@ -16924,19 +17221,19 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ e518e518000a3b3a0000 expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Flush IGMP groups. -ovn-sbctl ip-multicast-flush sw1 -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "0" -]) +check ovn-sbctl ip-multicast-flush sw1 +wait_row_count IGMP_Group 0 address=239.0.1.68 +check ovn-nbctl --wait=hv sync # Check that traffic for 224.0.0.X is flooded even if some hosts register for # it. @@ -16947,10 +17244,7 @@ /dev/null # Check that the IGMP Group is learned. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "224.0.0.42" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address=224.0.0.42 # Send traffic and make sure it gets flooded to all ports. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 @@ -16967,12 +17261,14 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 224 0 0 42) 1e 01 f989 11 \ e518e518000a4b540000 expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv2/vif2-tx.pcap expected'], + [$at_diff -F'^---' exp rcv]) # Enable IGMP snooping and querier on sw2 and set query interval to minimum. -ovn-nbctl set Logical_Switch sw2 \ +check ovn-nbctl set Logical_Switch sw2 \ other_config:mcast_snoop="true" \ other_config:mcast_querier="true" \ other_config:mcast_query_interval=1 \ @@ -16984,19 +17280,22 @@ store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected -sleep 1 -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif3-tx.pcap expected' \ + 'hv2/vif3-tx.pcap expected'], + [$at_diff -F'^---' exp rcv]) # Disable IGMP querier on sw2. -ovn-nbctl set Logical_Switch sw2 \ +check ovn-nbctl set Logical_Switch sw2 \ other_config:mcast_querier="false" # Enable IGMP snooping on sw3. -ovn-nbctl set Logical_Switch sw3 \ +check ovn-nbctl set Logical_Switch sw3 \ other_config:mcast_querier="false" \ other_config:mcast_snoop="true" +check ovn-nbctl --wait=hv sync + # Send traffic from sw3 and make sure rtr doesn't relay it. > expected_empty @@ -17016,17 +17315,19 @@ # Sleep a bit to make sure no traffic is received and then check. sleep 1 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Enable IGMP relay on rtr -ovn-nbctl set logical_router rtr \ +check ovn-nbctl set logical_router rtr \ options:mcast_relay="true" # Inject IGMP Join for 239.0.1.68 on sw1-p11. @@ -17041,10 +17342,8 @@ /dev/null # Check that the IGMP Group is learned by all switches. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "2" -]) +wait_row_count IGMP_Group 2 address=239.0.1.68 +check ovn-nbctl --wait=hv sync # Send traffic from sw3 and make sure it is relayed by rtr. # to ports that joined. @@ -17074,14 +17373,16 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \ e518e518000a3b3a0000 expected_routed_sw2 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_routed_sw1' \ + 'hv2/vif3-tx.pcap expected_routed_sw2' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Inject IGMP Join for 239.0.1.68 on sw3-p1. send_igmp_v3_report hv1-vif4 hv1 \ @@ -17090,10 +17391,8 @@ /dev/null # Check that the IGMP Group is learned by all switches. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "3" -]) +wait_row_count IGMP_Group 3 address=239.0.1.68 +check ovn-nbctl --wait=hv sync # Send traffic from sw3 and make sure it is relayed by rtr # to ports that joined. @@ -17128,23 +17427,35 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ e518e518000a3b3a0000 expected_switched -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +# TODO: IGMP Relay duplicates IP multicast packets in some conditions, for +# more details see TODO.rst, section "IP Multicast Relay". Once that issue is +# fixed the duplicated packets should not appear anymore. +store_ip_multicast_pkt \ + 000000000100 01005e000144 \ + $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \ + e518e518000a3b3a0000 expected_routed_sw1 +store_ip_multicast_pkt \ + 000000000200 01005e000144 \ + $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \ + e518e518000a3b3a0000 expected_routed_sw2 + +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_routed_sw1' \ + 'hv2/vif3-tx.pcap expected_routed_sw2' \ + 'hv1/vif4-tx.pcap expected_switched' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Flush IGMP groups. ovn-sbctl ip-multicast-flush sw1 ovn-sbctl ip-multicast-flush sw2 ovn-sbctl ip-multicast-flush sw3 -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "0" -]) +wait_row_count IGMP_Group 0 address=239.0.1.68 +check ovn-nbctl --wait=hv sync as hv1 reset_pcap_file hv1-vif1 hv1/vif1 as hv1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -17179,10 +17490,8 @@ expected_reports # Check that the IGMP Group is learned. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address=239.0.1.68 +check ovn-nbctl --wait=hv sync # Send traffic from sw1-p21 send_ip_multicast_pkt hv2-vif1 hv2 \ @@ -17198,20 +17507,22 @@ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \ e518e518000a3b3a0000 expected_routed -# Sleep a bit to make sure no duplicate traffic is received -sleep 1 # Check that traffic is switched to sw1-p11 and sw1-p12 # Check that IGMP join is flooded on sw1-p21 # Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +AT_CAPTURE_FILE([expected]) +AT_CAPTURE_FILE([received]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_switched' \ + 'hv1/vif2-tx.pcap expected_switched' \ + 'hv1/vif3-tx.pcap expected_routed' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_reports' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # With ovn-monitor-all=true, make sure ovn-controllers don't delete each # other's IGMP_Group records. @@ -17229,30 +17540,18 @@ as hv3 ovs-vsctl set open . external_ids:ovn-monitor-all=true # Wait until ovn-monitor-all is processed by ovn-controller. -OVS_WAIT_UNTIL([ - test $(ovn-sbctl get chassis hv1 other_config:ovn-monitor-all) = '"true"' -]) -OVS_WAIT_UNTIL([ - test $(ovn-sbctl get chassis hv2 other_config:ovn-monitor-all) = '"true"' -]) -OVS_WAIT_UNTIL([ - test $(ovn-sbctl get chassis hv3 other_config:ovn-monitor-all) = '"true"' -]) +wait_row_count Chassis 1 name=hv1 other_config:ovn-monitor-all='"true"' +wait_row_count Chassis 1 name=hv2 other_config:ovn-monitor-all='"true"' +wait_row_count Chassis 1 name=hv3 other_config:ovn-monitor-all='"true"' # Inject a fake IGMP_Group entry. -dp=$(ovn-sbctl --bare --columns _uuid list Datapath_Binding sw3) -ch=$(ovn-sbctl --bare --columns _uuid list Chassis hv3) -ovn-sbctl create IGMP_Group address="239.0.1.42" datapath=$dp chassis=$ch +dp=$(fetch_column Datapath_Binding _uuid external_ids:name=sw2) +ch=$(fetch_column Chassis _uuid name=hv3) +ovn-sbctl create IGMP_Group address=239.0.1.42 datapath=$dp chassis=$ch ovn-nbctl --wait=hv sync -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c` - test "${total_entries}" = "1" -]) -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.42" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address=239.0.1.68 +wait_row_count IGMP_Group 1 address=239.0.1.42 OVN_CLEANUP([hv1], [hv2], [hv3]) AT_CLEANUP @@ -17275,14 +17574,15 @@ # - subnet 30::/64 # - 1 port bound on hv1 (sw3-p1) # - 1 port bound on hv2 (sw3-p2) +# - 1 localnet port (sw3-ln) reset_pcap_file() { local iface=$1 local pcap_file=$2 - ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ options:rxq_pcap=dummy-rx.pcap rm -f ${pcap_file}*.pcap - ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ + check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ options:rxq_pcap=${pcap_file}-rx.pcap } @@ -17319,7 +17619,7 @@ local packet=${eth}${ip}${mld} echo ${packet} >> ${outfile} - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} } # @@ -17366,7 +17666,7 @@ local ip=60000000${ip_len}${proto}${ip_ttl}${ip_src}${ip_dst} local packet=${eth}${ip}${data} - as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} } # @@ -17388,33 +17688,36 @@ echo ${packet} >> ${outfile} } -ovn-nbctl ls-add sw1 -ovn-nbctl ls-add sw2 -ovn-nbctl ls-add sw3 - -ovn-nbctl lsp-add sw1 sw1-p11 -ovn-nbctl lsp-add sw1 sw1-p12 -ovn-nbctl lsp-add sw1 sw1-p21 -ovn-nbctl lsp-add sw1 sw1-p22 -ovn-nbctl lsp-add sw2 sw2-p1 -ovn-nbctl lsp-add sw2 sw2-p2 -ovn-nbctl lsp-add sw3 sw3-p1 -ovn-nbctl lsp-add sw3 sw3-p2 - -ovn-nbctl lr-add rtr -ovn-nbctl lrp-add rtr rtr-sw1 00:00:00:00:01:00 10::fe/64 -ovn-nbctl lrp-add rtr rtr-sw2 00:00:00:00:02:00 20::fe/64 -ovn-nbctl lrp-add rtr rtr-sw3 00:00:00:00:03:00 30::fe/64 +check ovn-nbctl ls-add sw1 +check ovn-nbctl ls-add sw2 +check ovn-nbctl ls-add sw3 + +check ovn-nbctl lsp-add sw1 sw1-p11 +check ovn-nbctl lsp-add sw1 sw1-p12 +check ovn-nbctl lsp-add sw1 sw1-p21 +check ovn-nbctl lsp-add sw1 sw1-p22 +check ovn-nbctl lsp-add sw2 sw2-p1 +check ovn-nbctl lsp-add sw2 sw2-p2 +check ovn-nbctl lsp-add sw3 sw3-p1 +check ovn-nbctl lsp-add sw3 sw3-p2 +check ovn-nbctl lsp-add sw3 sw3-ln \ + -- lsp-set-type sw3-ln localnet \ + -- lsp-set-options sw3-ln network_name=phys + +check ovn-nbctl lr-add rtr +check ovn-nbctl lrp-add rtr rtr-sw1 00:00:00:00:01:00 10::fe/64 +check ovn-nbctl lrp-add rtr rtr-sw2 00:00:00:00:02:00 20::fe/64 +check ovn-nbctl lrp-add rtr rtr-sw3 00:00:00:00:03:00 30::fe/64 -ovn-nbctl lsp-add sw1 sw1-rtr \ +check ovn-nbctl lsp-add sw1 sw1-rtr \ -- lsp-set-type sw1-rtr router \ -- lsp-set-addresses sw1-rtr 00:00:00:00:01:00 \ -- lsp-set-options sw1-rtr router-port=rtr-sw1 -ovn-nbctl lsp-add sw2 sw2-rtr \ +check ovn-nbctl lsp-add sw2 sw2-rtr \ -- lsp-set-type sw2-rtr router \ -- lsp-set-addresses sw2-rtr 00:00:00:00:02:00 \ -- lsp-set-options sw2-rtr router-port=rtr-sw2 -ovn-nbctl lsp-add sw3 sw3-rtr \ +check ovn-nbctl lsp-add sw3 sw3-rtr \ -- lsp-set-type sw3-rtr router \ -- lsp-set-addresses sw3-rtr 00:00:00:00:03:00 \ -- lsp-set-options sw3-rtr router-port=rtr-sw3 @@ -17422,84 +17725,117 @@ # Conntrack marks all IPv6 Neighbor Discovery and MLD packets as invalid, # make sure to test that conntrack is bypassed for MLD by adding an empty # allow-related ACL and an empty load balancer. -ovn-nbctl acl-add sw1 from-lport 1 "1" allow-related -ovn-nbctl acl-add sw2 from-lport 1 "1" allow-related -ovn-nbctl acl-add sw3 from-lport 1 "1" allow-related -ovn-nbctl acl-add sw1 to-lport 1 "1" allow-related -ovn-nbctl acl-add sw2 to-lport 1 "1" allow-related -ovn-nbctl acl-add sw3 to-lport 1 "1" allow-related - -ovn-nbctl lb-add lb0 [[4242::1]]:80 "" -ovn-nbctl ls-lb-add sw1 lb0 -ovn-nbctl ls-lb-add sw2 lb0 -ovn-nbctl ls-lb-add sw3 lb0 +check ovn-nbctl acl-add sw1 from-lport 1 "1" allow-related +check ovn-nbctl acl-add sw2 from-lport 1 "1" allow-related +check ovn-nbctl acl-add sw3 from-lport 1 "1" allow-related +check ovn-nbctl acl-add sw1 to-lport 1 "1" allow-related +check ovn-nbctl acl-add sw2 to-lport 1 "1" allow-related +check ovn-nbctl acl-add sw3 to-lport 1 "1" allow-related + +check ovn-nbctl lb-add lb0 [[4242::1]]:80 "[[4242::2]]:80" +check ovn-nbctl ls-lb-add sw1 lb0 +check ovn-nbctl ls-lb-add sw2 lb0 +check ovn-nbctl ls-lb-add sw3 lb0 net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw1-p11 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw1-p12 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif3 -- \ +check ovs-vsctl -- add-port br-int hv1-vif3 -- \ set interface hv1-vif3 external-ids:iface-id=sw2-p1 \ options:tx_pcap=hv1/vif3-tx.pcap \ options:rxq_pcap=hv1/vif3-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif4 -- \ +check ovs-vsctl -- add-port br-int hv1-vif4 -- \ set interface hv1-vif4 external-ids:iface-id=sw3-p1 \ options:tx_pcap=hv1/vif4-tx.pcap \ options:rxq_pcap=hv1/vif4-rx.pcap \ ofport-request=1 +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ +check ovs-vsctl -- add-port br-int hv2-vif1 -- \ set interface hv2-vif1 external-ids:iface-id=sw1-p21 \ options:tx_pcap=hv2/vif1-tx.pcap \ options:rxq_pcap=hv2/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif2 -- \ +check ovs-vsctl -- add-port br-int hv2-vif2 -- \ set interface hv2-vif2 external-ids:iface-id=sw1-p22 \ options:tx_pcap=hv2/vif2-tx.pcap \ options:rxq_pcap=hv2/vif2-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif3 -- \ +check ovs-vsctl -- add-port br-int hv2-vif3 -- \ set interface hv2-vif3 external-ids:iface-id=sw2-p2 \ options:tx_pcap=hv2/vif3-tx.pcap \ options:rxq_pcap=hv2/vif3-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv2-vif4 -- \ +check ovs-vsctl -- add-port br-int hv2-vif4 -- \ set interface hv2-vif4 external-ids:iface-id=sw3-p2 \ options:tx_pcap=hv2/vif4-tx.pcap \ options:rxq_pcap=hv2/vif4-rx.pcap \ ofport-request=1 +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys + +check ovn-nbctl --wait=hv sync + +AT_CAPTURE_FILE([sbflows]) +ovn-sbctl dump-flows > sbflows + +AT_CAPTURE_FILE([exp]) +AT_CAPTURE_FILE([rcv]) +check_packets() { + > exp + > rcv + if test "$1" = --uniq; then + sort="sort -u"; shift + else + sort=sort + fi + for tuple in "$@"; do + set $tuple; pcap=$1 type=$2 + echo "--- $pcap" | tee -a exp >> rcv + $sort "$type" >> exp + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv + echo | tee -a exp >> rcv + done + + $at_diff exp rcv >/dev/null +} OVN_POPULATE_ARP # Enable multicast snooping on sw1. -ovn-nbctl set Logical_Switch sw1 \ +check ovn-nbctl --wait=hv set Logical_Switch sw1 \ other_config:mcast_querier="false" \ other_config:mcast_snoop="true" # No IGMP/MLD query should be generated by sw1 (mcast_querier="false"). > expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected' \ + 'hv1/vif2-tx.pcap expected' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv2/vif2-tx.pcap expected'], + [$at_diff -F'^---' exp rcv]) -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync + +AT_CAPTURE_FILE([sbflows2]) +ovn-sbctl dump-flows > sbflows2 # Inject MLD Join for ff0a:dead:beef::1 on sw1-p11. send_mld_v2_report hv1-vif1 hv1 \ @@ -17513,10 +17849,13 @@ /dev/null # Check that the IP multicast group is learned on both hv. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "2" -]) +wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"' + +# This gives the ovn-controller nodes a chance to see the new IGMP_Group. +check ovn-nbctl --wait=hv sync + +AT_CAPTURE_FILE([sbflows3]) +ovn-sbctl dump-flows > sbflows3 # Send traffic and make sure it gets forwarded only on the two ports that # joined. @@ -17535,12 +17874,14 @@ 93407a69000e1b5e61736461640a \ expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Inject MLD Leave for ff0a:dead:beef::1 on sw1-p11. send_mld_v2_report hv1-vif1 hv1 \ @@ -17549,10 +17890,7 @@ /dev/null # Check IGMP_Group table on both HV. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"' # Send traffic and make sure it gets forwarded only on the port that joined. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 @@ -17572,19 +17910,18 @@ 93407a69000e1b5e61736461640a \ expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Flush IP multicast groups. -ovn-sbctl ip-multicast-flush sw1 -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep " ff0a:dead:beef::1" -c` - test "${total_entries}" = "0" -]) +check ovn-sbctl ip-multicast-flush sw1 +wait_row_count IGMP_Group 0 address='"ff0a:dead:beef::1"' # Check that traffic for "all-hosts" is flooded even if some hosts register # for it. @@ -17595,10 +17932,7 @@ /dev/null # Check that the Multicast Group is learned. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff02::1" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address='"ff02::1"' # Send traffic and make sure it gets flooded to all ports. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 @@ -17618,37 +17952,56 @@ 93407a69000eb90361736461640a \ expected -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected' \ + 'hv2/vif1-tx.pcap expected' \ + 'hv2/vif2-tx.pcap expected'], + [$at_diff -F'^---' exp rcv]) # Enable multicast snooping and querier on sw2 and set query interval to # minimum. -ovn-nbctl set Logical_Switch sw2 \ +check ovn-nbctl --wait=hv set Logical_Switch sw2 \ other_config:mcast_snoop="true" \ other_config:mcast_querier="true" \ other_config:mcast_query_interval=1 \ other_config:mcast_eth_src="00:00:00:00:02:fe" \ other_config:mcast_ip6_src="fe80::fe" -# Wait for 1 query interval (1 sec) and check that two queries are generated. +AT_CAPTURE_FILE([sbflows4]) +ovn-sbctl dump-flows > sbflows4 + +# Check that multiple queries are generated over time. > expected store_mld_query 0000000002fe fe8000000000000000000000000000fe expected store_mld_query 0000000002fe fe8000000000000000000000000000fe expected -sleep 1 - -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected]) +for count in 1 2 3; do + as hv1 reset_pcap_file hv1-vif1 hv1/vif1 + as hv1 reset_pcap_file hv1-vif2 hv1/vif2 + as hv1 reset_pcap_file hv1-vif3 hv1/vif3 + as hv1 reset_pcap_file hv1-vif4 hv1/vif4 + as hv2 reset_pcap_file hv2-vif1 hv2/vif1 + as hv2 reset_pcap_file hv2-vif2 hv2/vif2 + as hv2 reset_pcap_file hv2-vif3 hv2/vif3 + as hv2 reset_pcap_file hv2-vif4 hv2/vif4 + OVS_WAIT_UNTIL( + [check_packets --uniq \ + 'hv1/vif3-tx.pcap expected' \ + 'hv2/vif3-tx.pcap expected'], + [$at_diff -F'^---' exp rcv]) +done # Disable multicast querier on sw2. -ovn-nbctl set Logical_Switch sw2 \ +check ovn-nbctl set Logical_Switch sw2 \ other_config:mcast_querier="false" # Enable multicast snooping on sw3. -ovn-nbctl set Logical_Switch sw3 \ +check ovn-nbctl --wait=sb set Logical_Switch sw3 \ other_config:mcast_querier="false" \ other_config:mcast_snoop="true" +AT_CAPTURE_FILE([sbflows5]) +ovn-sbctl dump-flows > sbflows5 + # Send traffic from sw3 and make sure rtr doesn't relay it. > expected_empty @@ -17669,18 +18022,22 @@ # Sleep a bit to make sure no traffic is received and then check. sleep 1 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Enable multicast relay on rtr -ovn-nbctl set logical_router rtr \ - options:mcast_relay="true" +check ovn-nbctl --wait=sb set logical_router rtr options:mcast_relay="true" + +AT_CAPTURE_FILE([sbflows6]) +ovn-sbctl dump-flows > sbflows6 # Inject MLD Join for ff0a:dead:beef::1 on sw1-p11. send_mld_v2_report hv1-vif1 hv1 \ @@ -17695,10 +18052,7 @@ /dev/null # Check that the IGMP Group is learned by all switches. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "2" -]) +wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"' # Send traffic from sw3 and make sure it is relayed by rtr. # to ports that joined. @@ -17733,14 +18087,16 @@ 93407a69000e1b5e61736461640a \ expected_routed_sw2 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_routed_sw1' \ + 'hv2/vif3-tx.pcap expected_routed_sw2' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Inject MLD Join for 239.0.1.68 on sw3-p1. send_mld_v2_report hv1-vif4 hv1 \ @@ -17749,10 +18105,8 @@ /dev/null # Check that the Multicast Group is learned by all switches. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "3" -]) +wait_row_count IGMP_Group 3 address='"ff0a:dead:beef::1"' +check ovn-nbctl --wait=hv sync # Send traffic from sw3 and make sure it is relayed by rtr # to ports that joined. @@ -17794,23 +18148,38 @@ 93407a69000e1b5e61736461640a \ expected_switched -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_routed_sw1]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_routed_sw2]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +# TODO: MLD Relay duplicates IP multicast packets in some conditions, for +# more details see TODO.rst, section "IP Multicast Relay". Once that issue is +# fixed the duplicated packets should not appear anymore. +store_ip_multicast_pkt \ + 000000000100 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw1 +store_ip_multicast_pkt \ + 000000000200 333300000001 \ + 10000000000000000000000000000042 ff0adeadbeef00000000000000000001 \ + 000e 01 11 \ + 93407a69000e1b5e61736461640a \ + expected_routed_sw2 + +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_routed_sw1' \ + 'hv2/vif3-tx.pcap expected_routed_sw2' \ + 'hv1/vif4-tx.pcap expected_switched' \ + 'hv1/vif2-tx.pcap expected_empty' \ + 'hv1/vif3-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_empty' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) # Flush multicast groups. -ovn-sbctl ip-multicast-flush sw1 -ovn-sbctl ip-multicast-flush sw2 -ovn-sbctl ip-multicast-flush sw3 -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "0" -]) +check ovn-sbctl ip-multicast-flush sw1 +check ovn-sbctl ip-multicast-flush sw2 +check ovn-sbctl ip-multicast-flush sw3 +wait_row_count IGMP_Group 0 address='"ff0a:dead:beef::1"' as hv1 reset_pcap_file hv1-vif1 hv1/vif1 as hv1 reset_pcap_file hv1-vif2 hv1/vif2 @@ -17827,16 +18196,16 @@ > expected_reports # Enable mcast_flood on sw1-p11 -ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true' +check ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true' # Enable mcast_flood_reports on sw1-p21 -ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true' +check ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true' # Enable mcast_flood on rtr-sw2 -ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true' +check ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true' # Enable mcast_flood on sw2-p1 -ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true' +check ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true' -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync # Inject MLD Join for ff0a:dead:beef::1 on sw1-p12. send_mld_v2_report hv1-vif2 hv1 \ @@ -17845,10 +18214,8 @@ expected_reports # Check that the IP multicast group is learned. -OVS_WAIT_UNTIL([ - total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c` - test "${total_entries}" = "1" -]) +wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"' +check ovn-nbctl --wait=hv sync # Send traffic from sw1-p21 send_ip_multicast_pkt hv2-vif1 hv2 \ @@ -17872,17 +18239,22 @@ # Sleep a bit to make sure no duplicate traffic is received sleep 1 +AT_CAPTURE_FILE([sbflows7]) +ovn-sbctl dump-flows > sbflows7 + # Check that traffic is switched to sw1-p11 and sw1-p12 # Check that MLD join is flooded on sw1-p21 # Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1 -OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched]) -OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed]) -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports]) -OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) -OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty]) +OVS_WAIT_UNTIL( + [check_packets 'hv1/vif1-tx.pcap expected_switched' \ + 'hv1/vif2-tx.pcap expected_switched' \ + 'hv1/vif3-tx.pcap expected_routed' \ + 'hv1/vif4-tx.pcap expected_empty' \ + 'hv2/vif1-tx.pcap expected_reports' \ + 'hv2/vif2-tx.pcap expected_empty' \ + 'hv2/vif3-tx.pcap expected_empty' \ + 'hv2/vif4-tx.pcap expected_empty'], + [$at_diff -F'^---' exp rcv]) OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP @@ -17908,7 +18280,8 @@ m4_define([DVR_N_S_ARP_HANDLING], - [AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S ARP handling, encap $1]) + [OVN_FOR_EACH_NORTHD([ + AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S ARP handling, encap $1]) set -x encap=$1 ovn_start @@ -18099,31 +18472,30 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 ovn-nbctl --wait=sb sync + wait_row_count Port_Binding 1 logical_port=cr-router-to-underlay + # Dump a bunch of info helpful for debugging if there's a failure. - echo "------ OVN dump ------" - ovn-nbctl show - ovn-sbctl show - ovn-sbctl list port_binding - ovn-sbctl list mac_binding + ovn-sbctl dump-flows > sbflows + AT_CAPTURE_FILE([sbflows]) - echo "------ hv1 dump ------" - as hv1 ovs-vsctl show - as hv1 ovs-vsctl list Open_Vswitch + (ovn-nbctl show + ovn-sbctl show + ovn-sbctl list port_binding + ovn-sbctl list mac_binding) > ovndump + AT_CAPTURE_FILE([ovndump]) - echo "------ hv2 dump ------" - as hv2 ovs-vsctl show - as hv2 ovs-vsctl list Open_Vswitch + (as hv1 ovs-vsctl show; as hv1 ovs-vsctl list Open_Vswitch) > hv1dump + AT_CAPTURE_FILE([hv1dump]) - echo "------ hv3 dump ------" - as hv3 ovs-vsctl show - as hv3 ovs-vsctl list Open_Vswitch + (as hv2 ovs-vsctl show; as hv2 ovs-vsctl list Open_Vswitch) > hv2dump + AT_CAPTURE_FILE([hv2dump]) - echo "------ hv4 dump ------" - as hv4 ovs-vsctl show - as hv4 ovs-vsctl list Open_Vswitch + (as hv3 ovs-vsctl show; as hv3 ovs-vsctl list Open_Vswitch) > hv3dump + AT_CAPTURE_FILE([hv3dump]) - OVS_WAIT_UNTIL([test x`ovn-sbctl --bare --columns chassis find port_binding logical_port=cr-router-to-underlay | wc -l` = x1]) + (as hv4 ovs-vsctl show; as hv4 ovs-vsctl list Open_Vswitch) > hv4dump + AT_CAPTURE_FILE([hv4dump]) test_arp vif-north f0f000000011 $sip $tip 000001010207 @@ -18144,13 +18516,14 @@ # validate max_tunid reflects the type of encapsulation used max_tunid=`ovn-nbctl get NB_Global . options:max_tunid | sed s/":"//g | sed s/\"//g` - echo $max_tunid if [[ $encap = vxlan ]]; then max_tunid_expected=4095 else max_tunid_expected=16711680 fi - AT_CHECK([test $max_tunid -eq $max_tunid_expected]) + echo max_tunid=$max_tunid max_tunid_expected=$max_tunid_expected + AT_CHECK([test -n "$max_tunid"]) + AT_CHECK([test "$max_tunid" -eq "$max_tunid_expected"]) echo "----------- Post Traffic hv1 dump -----------" as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int @@ -18170,13 +18543,14 @@ OVN_CLEANUP([hv1],[hv2],[hv3],[hv4]) - AT_CLEANUP]) + AT_CLEANUP])]) DVR_N_S_ARP_HANDLING([geneve]) DVR_N_S_ARP_HANDLING([vxlan]) m4_define([DVR_N_S_PING], - [AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S Ping, encap $1]) + [OVN_FOR_EACH_NORTHD([ + AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S Ping, encap $1]) AT_KEYWORDS([$1]) encap=$1 ovn_start @@ -18235,10 +18609,6 @@ esac } - ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" - } - net_add n1 for i in 1 2; do sim_add hv$i @@ -18376,11 +18746,6 @@ echo router-to-`vif_to_ls ${1}` } - ip_to_hex() { - printf "%02x%02x%02x%02x" "${@}" - } - - 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} outport=${6} @@ -18480,7 +18845,7 @@ OVN_CLEANUP([hv1],[hv2],[hv3],[hv4]) - AT_CLEANUP]) + AT_CLEANUP])]) DVR_N_S_PING([geneve]) DVR_N_S_PING([vxlan]) @@ -18558,10 +18923,6 @@ as hv1 ovs-appctl -t ovn-controller vlog/set dbg -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # From sw0-p1 send GARP for 10.0.0.30. # ovn-controller should learn the # mac_binding entry @@ -18569,14 +18930,14 @@ # ip - 10.0.0.30 # mac - 50:54:00:00:00:03 -AT_CHECK([test 0 = `ovn-sbctl list mac_binding | wc -l`]) +check_row_count MAC_Binding 0 eth_src=505400000003 eth_dst=ffffffffffff spa=$(ip_to_hex 10 0 0 30) tpa=$(ip_to_hex 10 0 0 30) send_garp 1 1 $eth_src $eth_dst $spa $tpa -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`]) +wait_row_count MAC_Binding 1 AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \ list mac_binding], [0], [lr0-sw0 @@ -18600,7 +18961,7 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_packets=1 | wc -l`]) # The packet should not be sent to ovn-controller. The packet -count should be 1 only. +# count should be 1 only. AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \ grep controller | grep -v n_packets=0 | wc -l`]) @@ -18616,7 +18977,7 @@ # should be updated. OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -AT_CHECK([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`]) +check_row_count MAC_Binding 1 AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \ list mac_binding], [0], [lr0-sw0 @@ -18719,10 +19080,6 @@ ovn-nbctl lsp-set-type ln1 localnet ovn-nbctl lsp-set-options ln1 network_name=phys -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - net_add n1 sim_add hv1 @@ -18832,10 +19189,6 @@ esac } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - net_add n1 for i in 1 2; do sim_add hv$i @@ -18974,11 +19327,6 @@ echo router-to-`vif_to_ls $1` } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - - 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 outport=$6 @@ -19042,17 +19390,14 @@ OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected]) # Confirm that NATing happened without connection tracker - -AT_CHECK([ovn-sbctl dump-flows router | grep ct_snat | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows router | grep ct_dnat | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows router | grep ip4.dst=| wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows router | grep ip4.src=| wc -l], [0], [2 +ovn-sbctl dump-flows router > sbflows +AT_CAPTURE_FILE([sbflows]) +AT_CHECK([for regex in ct_snat ct_dnat ip4.dst= ip4.src=; do + grep -c "$regex" sbflows; +done], [0], [0 +0 +2 +2 ]) echo "----------- Post Traffic hv1 dump -----------" @@ -19092,12 +19437,12 @@ as hv1 ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ @@ -19105,108 +19450,106 @@ sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl -- add-port br-int hv2-vif1 -- \ +check ovs-vsctl -- add-port br-int hv2-vif1 -- \ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \ options:tx_pcap=hv2/vif1-tx.pcap \ options:rxq_pcap=hv2/vif1-rx.pcap \ ofport-request=1 -ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-p1 -ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" -ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3" - -ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" -ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4" +check ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" +check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3" # Create port group and ACLs for sw0 ports. -ovn-nbctl pg-add pg0_drop sw0-p1 sw0-p2 -ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop -ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop - -ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 -ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip4" allow-related -ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src == 0.0.0.0/0 && icmp4" allow-related -ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src == 0.0.0.0/0 && tcp && tcp.dst == 80" allow-related -ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src == 0.0.0.0/0 && udp && udp.dst == 80" allow-related +check ovn-nbctl pg-add pg0_drop sw0-p1 +check ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop +check ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop # Create the second logical switch with one port -ovn-nbctl ls-add sw1 -ovn-nbctl lsp-add sw1 sw1-p1 -ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" -ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw1 sw1-p1 +check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" +check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" # Create port group and ACLs for sw1 ports. -ovn-nbctl pg-add pg1_drop sw1-p1 -ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop -ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop - -ovn-nbctl pg-add pg1 sw1-p1 -ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip4" allow-related -ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && icmp4" allow-related -ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && tcp && tcp.dst == 80" allow-related -ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && udp && udp.dst == 80" allow-related +check ovn-nbctl pg-add pg1_drop sw1-p1 +check ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop +check ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop + +check ovn-nbctl pg-add pg1 sw1-p1 +check ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip4" allow-related +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && icmp4" allow-related +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && tcp && tcp.dst == 80" allow-related +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == 0.0.0.0/0 && udp && udp.dst == 80" allow-related # Create a logical router and attach both logical switches -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -ovn-nbctl lsp-set-type sw0-lr0 router -ovn-nbctl lsp-set-addresses sw0-lr0 router -ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 - -ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 -ovn-nbctl lsp-add sw1 sw1-lr0 -ovn-nbctl lsp-set-type sw1-lr0 router -ovn-nbctl lsp-set-addresses sw1-lr0 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 --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 - -ovn-nbctl --wait=sb -- --id=@hc create \ -Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \ -health_check @hc - -ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -ovn-nbctl --wait=sb ls-lb-add sw1 lb1 -ovn-nbctl --wait=sb lr-lb-add lr0 lb1 - -ovn-nbctl ls-add public -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 -ovn-nbctl lsp-add public public-lr0 -ovn-nbctl lsp-set-type public-lr0 router -ovn-nbctl lsp-set-addresses public-lr0 router -ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public +check ovn-nbctl lr-add lr0 +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 +check ovn-nbctl lsp-add sw0 sw0-lr0 +check ovn-nbctl lsp-set-type sw0-lr0 router +check ovn-nbctl lsp-set-addresses sw0-lr0 router +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 + +check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 +check ovn-nbctl lsp-add sw1 sw1-lr0 +check ovn-nbctl lsp-set-type sw1-lr0 router +check ovn-nbctl lsp-set-addresses sw1-lr0 router +check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 + +check ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 +OVN_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1) +check ovn-nbctl set load_balancer ${OVN_LB_ID} selection_fields="ip_dst,ip_src,tp_dst,tp_src" + +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 + +AT_CHECK([ovn-nbctl --wait=sb \ + -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10\:80" \ + options:failure_count=100 \ + -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0> +]) + +check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 +check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 +check ovn-nbctl --wait=sb lr-lb-add lr0 lb1 + +check ovn-nbctl ls-add public +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 +check ovn-nbctl lsp-add public public-lr0 +check ovn-nbctl lsp-set-type public-lr0 router +check ovn-nbctl lsp-set-addresses public-lr0 router +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public # localnet port -ovn-nbctl lsp-add public ln-public -ovn-nbctl lsp-set-type ln-public localnet -ovn-nbctl lsp-set-addresses ln-public unknown -ovn-nbctl lsp-set-options ln-public network_name=public +check ovn-nbctl lsp-add public ln-public +check ovn-nbctl lsp-set-type ln-public localnet +check ovn-nbctl lsp-set-addresses ln-public unknown +check ovn-nbctl lsp-set-options ln-public network_name=public # schedule the gw router port to a chassis. Change the name of the chassis -ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 +check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 OVN_POPULATE_ARP -ovn-nbctl --wait=hv sync - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) +check ovn-nbctl --wait=hv sync -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) -]) +wait_row_count Service_Monitor 2 -ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +AT_CAPTURE_FILE([sbflows]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows > sbflows + ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, + [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) +]) + +AT_CAPTURE_FILE([sbflows2]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows > sbflows2 + ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, + [ (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) ]) # get the svc monitor mac. @@ -19223,8 +19566,8 @@ grep "405400000003${svc_mon_src_mac}" | wc -l`] ) -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ -service_monitor | grep offline | wc -l`]) +check ovn-nbctl set load_balancer_health_check 10.0.0.10:80 options:failure_count=1 +wait_row_count Service_Monitor 2 status=offline OVS_WAIT_UNTIL( [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \ @@ -19236,48 +19579,43 @@ grep "405400000003${svc_mon_src_mac}" | wc -l`] ) -ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \ -| grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;) +AT_CAPTURE_FILE([sbflows3]) +ovn-sbctl dump-flows sw0 > sbflows3 +AT_CHECK( + [grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" sbflows3 | grep priority=120 |\ + sed 's/table=../table=??/'], [0], + [ table=??(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;) ]) -ovn-sbctl dump-flows lr0 | grep lr_in_dnat | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;) +AT_CAPTURE_FILE([sbflows4]) +ovn-sbctl dump-flows lr0 > sbflows4 +AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl + (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;) + (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;) ]) # Delete sw0-p1 -ovn-nbctl lsp-del sw0-p1 +check ovn-nbctl lsp-del sw0-p1 -OVS_WAIT_UNTIL([test 1 = $(ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l)]) +wait_row_count Service_Monitor 1 # Add back sw0-p1 but without any IP address. -ovn-nbctl lsp-add sw0 sw0-p1 -ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \ -lsp-set-port-security sw0-p1 "50:54:00:00:00:03" - -OVS_WAIT_UNTIL([test 2 = $(ovn-sbctl --bare --columns status find \ -service_monitor | grep offline | wc -l)]) - -ovn-nbctl lsp-del sw0-p1 -ovn-nbctl lsp-del sw1-p1 -OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l)]) +check ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \ + lsp-set-port-security sw0-p1 "50:54:00:00:00:03" + +wait_row_count Service_Monitor 2 status=offline + +check ovn-nbctl lsp-del sw0-p1 +check ovn-nbctl lsp-del sw1-p1 +wait_row_count Service_Monitor 0 # Add back sw0-p1 but without any address set. -ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl lsp-add sw0 sw0-p1 -OVS_WAIT_UNTIL([test 1 = $(ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l)]) - -OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns status find \ -service_monitor | grep offline | wc -l)]) - -OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns status find \ -service_monitor | grep online | wc -l)]) +wait_row_count Service_Monitor 1 +wait_row_count Service_Monitor 0 status=offline +wait_row_count Service_Monitor 0 status=online OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP @@ -19388,9 +19726,7 @@ # And now for the anticlimax. We need to ensure that there is no # service monitor in the southbound db. - -AT_CHECK([test 0 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) +check_row_count Service_Monitor 0 # Let's also be sure the warning message about SCTP load balancers is # is in the ovn-northd log @@ -19402,10 +19738,6 @@ AT_SETUP([ovn -- ARP/ND request broadcast limiting]) ovn_start -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - send_arp_request() { local hv=$1 inport=$2 eth_src=$3 spa=$4 tpa=$5 local eth_dst=ffffffffffff @@ -19415,7 +19747,7 @@ local arp=0001080006040001${eth_src}${spa}${eth_dst}${tpa} local request=${eth}${arp} - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request + check as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request } send_nd_ns() { @@ -19442,7 +19774,7 @@ local request=${eth}${ip}${icmp6} - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request + check as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request } src_mac=000000000001 @@ -19450,63 +19782,63 @@ net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +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 sim_add hv2 as hv2 -ovs-vsctl add-br br-phys -ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys +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.2 # One Aggregation Switch connected to two Logical networks (routers). -ovn-nbctl ls-add sw-agg -ovn-nbctl lsp-add sw-agg sw-agg-ext \ +check ovn-nbctl ls-add sw-agg +check ovn-nbctl lsp-add sw-agg sw-agg-ext \ -- lsp-set-addresses sw-agg-ext 00:00:00:00:00:01 -ovn-nbctl lsp-add sw-agg sw-rtr1 \ +check ovn-nbctl lsp-add sw-agg sw-rtr1 \ -- lsp-set-type sw-rtr1 router \ -- lsp-set-addresses sw-rtr1 00:00:00:00:01:00 \ -- lsp-set-options sw-rtr1 router-port=rtr1-sw -ovn-nbctl lsp-add sw-agg sw-rtr2 \ +check ovn-nbctl lsp-add sw-agg sw-rtr2 \ -- lsp-set-type sw-rtr2 router \ -- lsp-set-addresses sw-rtr2 00:00:00:00:02:00 \ -- lsp-set-options sw-rtr2 router-port=rtr2-sw # Localnet port on the Aggregation Switch. -ovn-nbctl lsp-add sw-agg sw-agg-ln -ovn-nbctl lsp-set-addresses sw-agg-ln unknown -ovn-nbctl lsp-set-type sw-agg-ln localnet -ovn-nbctl lsp-set-options sw-agg-ln network_name=phys +check ovn-nbctl lsp-add sw-agg sw-agg-ln +check ovn-nbctl lsp-set-addresses sw-agg-ln unknown +check ovn-nbctl lsp-set-type sw-agg-ln localnet +check ovn-nbctl lsp-set-options sw-agg-ln network_name=phys # Configure L3 interface IPv4 & IPv6 on both routers. -ovn-nbctl lr-add rtr1 -ovn-nbctl lrp-add rtr1 rtr1-sw 00:00:00:00:01:00 10.0.0.1/24 10::1/64 +check ovn-nbctl lr-add rtr1 +check ovn-nbctl lrp-add rtr1 rtr1-sw 00:00:00:00:01:00 10.0.0.1/24 10::1/64 -ovn-nbctl lrp-add rtr1 rtr1-sw1 00:00:01:00:00:00 20.0.0.1/24 20::1/64 +check ovn-nbctl lrp-add rtr1 rtr1-sw1 00:00:01:00:00:00 20.0.0.1/24 20::1/64 -ovn-nbctl lr-add rtr2 -ovn-nbctl lrp-add rtr2 rtr2-sw 00:00:00:00:02:00 10.0.0.2/24 10::2/64 +check ovn-nbctl lr-add rtr2 +check ovn-nbctl lrp-add rtr2 rtr2-sw 00:00:00:00:02:00 10.0.0.2/24 10::2/64 # Configure router gateway ports. -ovn-nbctl lrp-set-gateway-chassis rtr1-sw hv1 20 -ovn-nbctl lrp-set-gateway-chassis rtr2-sw hv1 20 +check ovn-nbctl lrp-set-gateway-chassis rtr1-sw hv1 20 +check ovn-nbctl lrp-set-gateway-chassis rtr2-sw hv1 20 # One private network behind rtr1 with two VMs. -ovn-nbctl ls-add sw1 -ovn-nbctl lsp-add sw1 sw1-p1 \ +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw1 sw1-p1 \ -- lsp-set-addresses sw1-p1 00:00:00:01:00:00 -ovn-nbctl lsp-add sw1 sw1-p2 \ +check ovn-nbctl lsp-add sw1 sw1-p2 \ -- lsp-set-addresses sw1-p2 00:00:00:02:00:00 -ovn-nbctl lsp-add sw1 sw1-rtr1 \ +check ovn-nbctl lsp-add sw1 sw1-rtr1 \ -- lsp-set-type sw1-rtr1 router \ -- lsp-set-addresses sw1-rtr1 00:00:01:00:00:00 \ -- lsp-set-options sw1-rtr1 router-port=rtr1-sw1 # Bind a "VM" connected to sw-agg on hv1. as hv1 -ovs-vsctl -- add-port br-int hv1-vif0 -- \ +check ovs-vsctl -- add-port br-int hv1-vif0 -- \ set interface hv1-vif0 external-ids:iface-id=sw-agg-ext \ options:tx_pcap=hv1/vif0-tx.pcap \ options:rxq_pcap=hv1/vif0-rx.pcap \ @@ -19514,7 +19846,7 @@ # Bind a "VM" connected to sw1 on hv1. as hv1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw1-p1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ @@ -19522,14 +19854,17 @@ # Bind a "VM" connected to sw1 on hv2. as hv2 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=3 OVN_POPULATE_ARP -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) sw_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding sw-agg) sw_dp_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding sw-agg) @@ -19547,22 +19882,26 @@ send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 1) # Verify that the ARP request is sent only to rtr1. -match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.1,arp_op=1" +match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.1,arp_op=1" match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15" match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15" +for var in sw_dp_uuid sw_dp_key sw1_dp_key r1_dp_key r1_tnl_key r2_tnl_key \ + match_arp_req match_send_rtr1 match_send_rtr2; do + AS_VAR_COPY([value], [$var]) + echo "$var=$value" +done + as hv1 -OVS_WAIT_UNTIL([ - pkts_to_rtr1=$(ovs-ofctl dump-flows br-int | \ - grep -E "${match_arp_req}" | grep "${match_send_rtr1}" | \ - grep n_packets=1 -c) - test "1" = "${pkts_to_rtr1}" -]) -OVS_WAIT_UNTIL([ - pkts_to_rtr2=$(ovs-ofctl dump-flows br-int | \ - grep -E "${match_arp_req}" | grep "${match_send_rtr2}" | \ - grep n_packets=1 -c) - test "0" = "${pkts_to_rtr2}" +AT_CAPTURE_FILE([offlows]) +OVS_WAIT_FOR_OUTPUT([ + ovs-ofctl dump-flows br-int > offlows + for match in "$match_send_rtr1" "$match_send_rtr2"; do + grep -E "$match_arp_req.*$match" offlows | grep -c 'n_packets=[[1-9]]' + done + : +], [0], [1 +0 ]) # Inject ND_NS for ofirst router owned IP address. @@ -19571,39 +19910,36 @@ send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d # Verify that the ND_NS is sent only to rtr1. -match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::1" +match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::1" as hv1 -OVS_WAIT_UNTIL([ - pkts_to_rtr1=$(ovs-ofctl dump-flows br-int | \ - grep -E "${match_nd_ns}" | grep "${match_send_rtr1}" | \ - grep n_packets=1 -c) - test "1" = "${pkts_to_rtr1}" -]) -OVS_WAIT_UNTIL([ - pkts_to_rtr2=$(ovs-ofctl dump-flows br-int | \ - grep -E "${match_nd_ns}" | grep "${match_send_rtr2}" | \ - grep n_packets=1 -c) - test "0" = "${pkts_to_rtr2}" +OVS_WAIT_FOR_OUTPUT([ + ovs-ofctl dump-flows br-int > offlows + for match in "$match_send_rtr1" "$match_send_rtr2"; do + grep -E "$match_nd_ns.*$match" offlows | grep -c 'n_packets=[[1-9]]' + done + : +], [0], [1 +0 ]) # Configure load balancing on both routers. -ovn-nbctl lb-add lb1-v4 10.0.0.11 42.42.42.1 -ovn-nbctl lb-add lb1-v6 10::11 42::1 -ovn-nbctl lr-lb-add rtr1 lb1-v4 -ovn-nbctl lr-lb-add rtr1 lb1-v6 - -ovn-nbctl lb-add lb2-v4 10.0.0.22 42.42.42.2 -ovn-nbctl lb-add lb2-v6 10::22 42::2 -ovn-nbctl lr-lb-add rtr2 lb2-v4 -ovn-nbctl lr-lb-add rtr2 lb2-v6 -ovn-nbctl --wait=hv sync +check ovn-nbctl lb-add lb1-v4 10.0.0.11 42.42.42.1 +check ovn-nbctl lb-add lb1-v6 10::11 42::1 +check ovn-nbctl lr-lb-add rtr1 lb1-v4 +check ovn-nbctl lr-lb-add rtr1 lb1-v6 + +check ovn-nbctl lb-add lb2-v4 10.0.0.22 42.42.42.2 +check ovn-nbctl lb-add lb2-v6 10::22 42::2 +check ovn-nbctl lr-lb-add rtr2 lb2-v4 +check ovn-nbctl lr-lb-add rtr2 lb2-v6 +check ovn-nbctl --wait=hv sync # Inject ARP request for first router owned VIP address. send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 11) # Verify that the ARP request is sent only to rtr1. -match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.11,arp_op=1" +match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.11,arp_op=1" match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15" match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15" @@ -19627,7 +19963,7 @@ send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d # Verify that the ND_NS is sent only to rtr1. -match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::11" +match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::11" as hv1 OVS_WAIT_UNTIL([ @@ -19644,17 +19980,21 @@ ]) # Configure NAT on both routers. -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.111 42.42.42.1 -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::111 42::1 -ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10.0.0.222 42.42.42.2 -ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10::222 42::2 +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.111 42.42.42.1 +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::111 42::1 +check ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10.0.0.222 42.42.42.2 +check ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10::222 42::2 +check ovn-nbctl --wait=hv sync # Configure FIP1 and FIP2 on rtr1 for sw1-p1 and sw1-p2. -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.121 20.0.0.11 sw1-p1 00:00:00:01:00:00 -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::121 20::11 sw1-p1 00:00:00:01:00:00 -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.122 20.0.0.12 sw1-p2 00:00:00:02:00:00 -ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::122 20::12 sw1-p2 00:00:00:02:00:00 -ovn-nbctl --wait=hv sync +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.121 20.0.0.11 sw1-p1 00:00:00:01:00:00 +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::121 20::11 sw1-p1 00:00:00:01:00:00 +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.122 20.0.0.12 sw1-p2 00:00:00:02:00:00 +check ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::122 20::12 sw1-p2 00:00:00:02:00:00 +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows2 +AT_CAPTURE_FILE([sbflows2]) # Check that broadcast domain limiting flows match only on IPs that are # directly reachable via the router port. @@ -19667,7 +20007,7 @@ # - 10.0.0.22, 10::22 - LB VIPs. # - 10.0.0.222, 10::222 - DNAT IPs. as hv1 -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl arp_tpa=10.0.0.1 arp_tpa=10.0.0.11 arp_tpa=10.0.0.111 @@ -19677,7 +20017,7 @@ arp_tpa=10.0.0.22 arp_tpa=10.0.0.222 ]) -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl nd_target=10::1 nd_target=10::11 nd_target=10::111 @@ -19693,10 +20033,10 @@ # For sw1-rtr1: # - 20.0.0.1, 20::1, fe80::200:1ff:fe00:0 - interface IPs. as hv1 -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl arp_tpa=20.0.0.1 ]) -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl nd_target=20::1 nd_target=fe80::200:1ff:fe00:0 ]) @@ -19708,13 +20048,13 @@ # - 00:00:00:01:00:00 - dnat_and_snat external MAC. # - 00:00:00:02:00:00 - dnat_and_snat external MAC. as hv1 -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl dl_src=00:00:00:00:01:00 dl_src=00:00:00:00:02:00 dl_src=00:00:00:01:00:00 dl_src=00:00:00:02:00:00 ]) -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}.*icmp_type=135" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}.*icmp_type=135" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl dl_src=00:00:00:00:01:00 dl_src=00:00:00:00:02:00 dl_src=00:00:00:01:00:00 @@ -19724,7 +20064,7 @@ # Self originated ARP/NS with SMACs owned by rtr1-sw1 should be flooded: # - 00:00:01:00:00:00 - interface MAC. as hv1 -AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl +AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl dl_src=00:00:01:00:00:00 ]) @@ -19732,7 +20072,7 @@ send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 111) # Verify that the ARP request is sent only to rtr1. -match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.111,arp_op=1" +match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.111,arp_op=1" match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15" match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15" @@ -19796,7 +20136,7 @@ send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d # Verify that the ND_NS is sent only to rtr1. -match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::111" +match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::111" as hv1 OVS_WAIT_UNTIL([ @@ -19876,7 +20216,7 @@ 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 -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # Trace with --ovs should see ovs flow related to the ACL AT_CHECK([ovn-trace --ovs lsw0 'inport == "lp1" && eth.type == 0x1234' | grep "dl_type=0x1234" | grep "cookie"], [0], [ignore]) @@ -19885,7 +20225,7 @@ ovn-nbctl acl-del lsw0 -- \ acl-add lsw0 from-lport 1000 'eth.type == 0x1234' allow -ovn-nbctl --wait=hv --timeout=3 sync +check ovn-nbctl --wait=hv sync # Trace with --ovs should still see the ovs flow related to the ACL, which # means the OVS flow is updated with new cookie corresponding to the new lflow. @@ -19908,10 +20248,10 @@ for az in `seq 1 $n_az`; do sim_add hv$az as hv$az - ovs-vsctl add-br br-phys + check ovs-vsctl add-br br-phys ovn_az_attach az$az n1 br-phys 192.168.$az.1 16 for p in `seq 1 $n_ts`; do - ovs-vsctl -- add-port br-int vif$p -- \ + check ovs-vsctl -- add-port br-int vif$p -- \ set interface vif$p external-ids:iface-id=lsp$az-$p \ options:tx_pcap=hv$az/vif$p-tx.pcap \ options:rxq_pcap=hv$az/vif$p-rx.pcap \ @@ -19920,21 +20260,27 @@ sim_add gw$az as gw$az - ovs-vsctl add-br br-phys + check ovs-vsctl add-br br-phys ovn_az_attach az$az n1 br-phys 192.168.$az.2 16 - ovs-vsctl set open . external-ids:ovn-is-interconn=true + check ovs-vsctl set open . external-ids:ovn-is-interconn=true done for ts in `seq 1 $n_ts`; do - ovn-ic-nbctl create Transit_Switch name=ts$ts + AT_CHECK([ovn-ic-nbctl create Transit_Switch name=ts$ts], [0], [ignore]) + for az in `seq 1 $n_az`; do + echo "az$az: wait for ts$ts..." + check ovn_as az$az ovn-nbctl wait-until logical_switch ts$ts + done done for az in `seq 1 $n_az`; do ovn_as az$az - ovn-nbctl set nb_global . options:ic-route-learn=true - ovn-nbctl set nb_global . options:ic-route-adv=true + check ovn-nbctl set nb_global . options:ic-route-learn=true + check ovn-nbctl set nb_global . options:ic-route-adv=true # Each AZ has n_ts LSPi->LSi->LRi connecting to each TSi + echo + echo "az$az" for i in `seq 1 $n_ts`; do lsp_mac=00:00:00:0$az:0$i:00 lrp_ls_mac=00:00:00:0$az:0$i:01 @@ -19943,25 +20289,27 @@ lrp_ls_ip=10.$az.$i.1 lrp_ts_ip=169.254.$i.$az - ovn-nbctl ls-add ls$az-$i - ovn-nbctl lsp-add ls$az-$i lsp$az-$i - ovn-nbctl lsp-set-addresses lsp$az-$i "$lsp_mac $lsp_ip" - - ovn-nbctl lr-add lr$az-$i - - ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ls$az-$i $lrp_ls_mac $lrp_ls_ip/24 - ovn-nbctl lsp-add ls$az-$i lsp-ls$az-$i-lr$az-$i - ovn-nbctl lsp-set-addresses lsp-ls$az-$i-lr$az-$i router - ovn-nbctl lsp-set-type lsp-ls$az-$i-lr$az-$i router - ovn-nbctl lsp-set-options lsp-ls$az-$i-lr$az-$i router-port=lrp-lr$az-$i-ls$az-$i - - ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ts$i $lrp_ts_mac $lrp_ts_ip/24 - ovn-nbctl lsp-add ts$i lsp-ts$i-lr$az-$i - ovn-nbctl lsp-set-addresses lsp-ts$i-lr$az-$i router - ovn-nbctl lsp-set-type lsp-ts$i-lr$az-$i router - ovn-nbctl lsp-set-options lsp-ts$i-lr$az-$i router-port=lrp-lr$az-$i-ts$i - ovn-nbctl lrp-set-gateway-chassis lrp-lr$az-$i-ts$i gw$az + check ovn-nbctl ls-add ls$az-$i + check ovn-nbctl lsp-add ls$az-$i lsp$az-$i + check ovn-nbctl lsp-set-addresses lsp$az-$i "$lsp_mac $lsp_ip" + + check ovn-nbctl lr-add lr$az-$i + + check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ls$az-$i $lrp_ls_mac $lrp_ls_ip/24 + check ovn-nbctl lsp-add ls$az-$i lsp-ls$az-$i-lr$az-$i + check ovn-nbctl lsp-set-addresses lsp-ls$az-$i-lr$az-$i router + check ovn-nbctl lsp-set-type lsp-ls$az-$i-lr$az-$i router + check ovn-nbctl lsp-set-options lsp-ls$az-$i-lr$az-$i router-port=lrp-lr$az-$i-ls$az-$i + + check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ts$i $lrp_ts_mac $lrp_ts_ip/24 + check ovn-nbctl lsp-add ts$i lsp-ts$i-lr$az-$i + check ovn-nbctl lsp-set-addresses lsp-ts$i-lr$az-$i router + check ovn-nbctl lsp-set-type lsp-ts$i-lr$az-$i router + check ovn-nbctl lsp-set-options lsp-ts$i-lr$az-$i router-port=lrp-lr$az-$i-ts$i + check ovn-nbctl lrp-set-gateway-chassis lrp-lr$az-$i-ts$i gw$az done + check ovn-nbctl --wait=hv sync + ovn-sbctl list Port_Binding > az$az.ports done # Pre-populate the hypervisors' ARP tables so that we don't lose any @@ -19970,21 +20318,64 @@ OVN_POPULATE_ARP for i in `seq 1 $n_az`; do - AT_CHECK([ovn_as az$i ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) + check ovn_as az$i ovn-nbctl --wait=hv sync + ovn_as az$i ovn-sbctl dump-flows > az$i/sbflows done # Allow some time for ovn-northd and ovn-controller to catch up. # XXX This should be more systematic. sleep 2 +ovn-ic-nbctl show > ic-nbctl.dump +AT_CAPTURE_FILE([ic-nbctl.dump]) + +(echo "---------ISB dump-----" + ovn-ic-sbctl show + echo "---------------------" + ovn-ic-sbctl list gateway + echo "---------------------" + ovn-ic-sbctl list datapath_binding + echo "---------------------" + ovn-ic-sbctl list port_binding + echo "---------------------" + ovn-ic-sbctl list route + echo "---------------------") > ic-sbctl.dump +AT_CAPTURE_FILE([ic-sbctl.dump]) + +AT_CAPTURE_FILE([expected]) +AT_CAPTURE_FILE([received]) +check_packets() { + > expected + > received + for az in `seq 1 $n_az`; do + for i in `seq 1 $n_ts`; do + pcap=hv$az/vif$i-tx.pcap + echo "--- $pcap" | tee -a expected >> received + if test -e $az-$i.expected; then + sort $az-$i.expected >> expected + fi + if test -e $pcap; then + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received + fi + echo | tee -a expected >> received + done + done + + $at_diff expected received >/dev/null +} + # Send packets between AZs on each TS -for s_az in `seq 1 $n_az`; do - for d_az in `seq 1 $n_az`; do +for s_az in $(seq 1 $n_az); do + ovn_as az${s_az} + as hv${s_az} + for d_az in $(seq 1 $n_az); do if test $s_az = $d_az; then continue fi - for i in `seq 1 $n_ts`; do + for i in $(seq 1 $n_ts); do + echo + AS_BOX([packet from az$s_az to az$d_az via ts$i]) lsp_smac=00:00:00:0${s_az}:0$i:00 lsp_dmac=00:00:00:0${d_az}:0$i:00 lrp_ls_smac=00:00:00:0${s_az}:0$i:01 @@ -19992,10 +20383,18 @@ lsp_sip=10.${s_az}.$i.123 lsp_dip=10.${d_az}.$i.123 - packet="inport==\"lsp${s_az}-$i\" && eth.src==$lsp_smac && eth.dst==$lrp_ls_smac && + ovn_inport=lsp${s_az}-$i + packet="inport==\"$ovn_inport\" && eth.src==$lsp_smac && eth.dst==$lrp_ls_smac && ip4 && ip.ttl==64 && ip4.src==$lsp_sip && ip4.dst==$lsp_dip && udp && udp.src==53 && udp.dst==4369" - AT_CHECK([as hv${s_az} ovs-appctl -t ovn-controller inject-pkt "$packet"]) + echo "sending: $packet" + AT_CHECK([ovn-trace --ovs "$packet" > ${s_az}-${d_az}-$i.ovn-trace]) + AT_CHECK([ovs-appctl -t ovn-controller inject-pkt "$packet"]) + ovs_inport=$(ovs-vsctl --bare --columns=ofport find Interface external-ids:iface-id="$ovn_inport") + + ovs_packet=$(echo $packet | ovstest test-ovn expr-to-packets) + echo ovs_inport=$ovs_inport ovs_packet=$ovs_packet + AT_CHECK([ovs-appctl ofproto/trace br-int in_port="$ovs_inport" "$ovs_packet" > ${s_az}-${d_az}-$i.ovs-trace]) # Packet to Expect # The TTL should be decremented by 2. @@ -20006,28 +20405,7 @@ done done done - -echo "---------INB dump-----" -ovn-ic-nbctl show -echo "---------------------" - -echo "---------ISB dump-----" -ovn-ic-sbctl show -echo "---------------------" -ovn-ic-sbctl list gateway -echo "---------------------" -ovn-ic-sbctl list datapath_binding -echo "---------------------" -ovn-ic-sbctl list port_binding -echo "---------------------" -ovn-ic-sbctl list route -echo "---------------------" - -for az in `seq 1 $n_az`; do - for i in `seq 1 $n_ts`; do - OVN_CHECK_PACKETS([hv$az/vif$i-tx.pcap], [$az-$i.expected]) - done -done +OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received]) for az in `seq 1 $n_az`; do OVN_CLEANUP_SBOX([hv$az]) @@ -20106,7 +20484,10 @@ ofport-request=3 # wait for earlier changes to take effect -AT_CHECK([ovn-nbctl --timeout=3 --wait=hv sync], [0], [ignore]) +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) for i in $(seq 5001 5010); do packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 && @@ -20148,24 +20529,24 @@ # One LR - R1 has a logical switch ls1 and ls2 connected to it. # Logical switch ls1 has one port while ls2 has two logical switch ports as # child ports. -ovn-nbctl lr-add R1 -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 +check ovn-nbctl lr-add R1 +check ovn-nbctl ls-add ls1 +check ovn-nbctl ls-add ls2 # Logical switch ls1 to R1 connectivity -ovn-nbctl lrp-add R1 R1-ls1 00:00:00:01:02:f1 192.168.1.1/24 -ovn-nbctl lsp-add ls1 ls1-R1 -- set Logical_Switch_Port ls1-R1 \ +check ovn-nbctl lrp-add R1 R1-ls1 00:00:00:01:02:f1 192.168.1.1/24 +check ovn-nbctl lsp-add ls1 ls1-R1 -- set Logical_Switch_Port ls1-R1 \ type=router options:router-port=R1-ls1 -- lsp-set-addresses ls1-R1 router -ovn-nbctl lsp-add ls1 lsp11 \ +check ovn-nbctl lsp-add ls1 lsp11 \ -- lsp-set-addresses lsp11 "00:00:00:01:02:01 192.168.1.2" # Logical switch ls2 to R1 connectivity -ovn-nbctl lrp-add R1 R1-ls2 00:00:00:01:02:f2 172.16.1.1/24 -ovn-nbctl lsp-add ls2 ls2-R1 -- set Logical_Switch_Port ls2-R1 \ +check ovn-nbctl lrp-add R1 R1-ls2 00:00:00:01:02:f2 172.16.1.1/24 +check ovn-nbctl lsp-add ls2 ls2-R1 -- set Logical_Switch_Port ls2-R1 \ type=router options:router-port=R1-ls2 -- lsp-set-addresses ls2-R1 router -ovn-nbctl lsp-add ls2 lsp21 \ +check ovn-nbctl lsp-add ls2 lsp21 \ -- lsp-set-addresses lsp21 "00:00:00:01:02:01 172.16.1.2" -ovn-nbctl lsp-add ls2 lsp22 \ +check ovn-nbctl lsp-add ls2 lsp22 \ -- lsp-set-addresses lsp22 "00:00:00:01:02:02 172.16.1.3" # Create a network @@ -20174,48 +20555,47 @@ # Create hypervisor hv1 connected to n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lsp11 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 +check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lsp11 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 # Create hypervisor hv2 connected to n1 sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 -ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lsp21 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1 +check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lsp21 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1 # Create hypervisor hv3 connected to n1 sim_add hv3 as hv3 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.3 -ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lsp22 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1 +check ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lsp22 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1 # Add a forwarding group on ls2 with lsp21 and lsp22 as child ports # virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef -ovn-nbctl fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22 - -# Allow some time for ovn-northd and ovn-controller to catch up. -sleep 1 +check ovn-nbctl --wait=hv fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22 # Check logical flow -AT_CHECK([ovn-sbctl dump-flows | grep ls_in_l2_lkup | grep fwd_group | wc -l], [0], [dnl -1 +AT_CAPTURE_FILE([sbflows]) +ovn-sbctl dump-flows > sbflows +AT_CHECK([grep ls_in_l2_lkup sbflows | grep fwd_group | wc -l], [0], [1 ]) # Check openflow rule with "group" on hypervisor -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ - grep "dl_dst=00:11:de:ad:be:ef actions=group" | wc -l], [0], [dnl -1 +AT_CAPTURE_FILE([offlows]) +as hv1 ovs-ofctl dump-flows br-int > offlows +AT_CHECK([grep -c "dl_dst=00:11:de:ad:be:ef actions=group" offlows], [0], [1 ]) # Verify openflow group members + # Verify openflow group members +AT_CAPTURE_FILE([ofgroups]) +as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int > ofgroups for child_port in lsp21 lsp22; do - tunnel_key=`ovn-sbctl --bare --column tunnel_key find port_binding logical_port=$child_port` - AT_CHECK([as hv1 ovs-ofctl -O OpenFlow13 dump-groups br-int | \ - grep "bucket=actions=load:0x"$tunnel_key | wc -l], [0], [dnl -1 + tunnel_key=`ovn-sbctl get Port_Binding $child_port tunnel_key` + AT_CHECK([grep -c "bucket=actions=load:0x"$tunnel_key ofgroups], [0], [1 ]) done @@ -20227,24 +20607,21 @@ packet="inport==\"lsp11\" && eth.src==$src_mac && eth.dst==$dst_mac && ip4 && ip.ttl==64 && ip4.src==$src_ip && ip4.dst==$dst_ip && udp && udp.src==53 && udp.dst==4369" -as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" +check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet" # Check if the packet hit the forwarding group policy -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \ - grep "dl_dst=00:11:de:ad:be:ef actions=group" | \ - grep "n_packets=1" | wc -l], [0], [dnl -1 +AT_CAPTURE_FILE([offlows2]) +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int > offlows2 + grep "dl_dst=00:11:de:ad:be:ef actions=group" offlows2 | \ + grep "n_packets=1" | wc -l], [0], [1 ]) # Delete the forwarding group -ovn-nbctl fwd-group-del fwd_grp1 +check ovn-nbctl fwd-group-del fwd_grp1 # Add a forwarding group with liveness on ls2 with lsp21 and lsp22 as child # ports virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef -ovn-nbctl --liveness fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22 - -# Allow some time for ovn-northd and ovn-controller to catch up. -sleep 1 +check ovn-nbctl --wait=hv --liveness fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22 # Verify openflow group members ofport_lsp21=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-hv2-0) @@ -20277,10 +20654,6 @@ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - build_udp() { local sport=$1 dport=$2 chksum=$3 local len=000a @@ -20381,6 +20754,9 @@ ${tcp_payload} ${hp_tcp_payload} \ expected +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + # Check that traffic is hairpinned. OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) @@ -20514,107 +20890,109 @@ net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw0-port2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=2 -as hv1 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-phys - -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-port1 -ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 10.0.0.5" -ovn-nbctl lsp-set-port-security sw0-port1 "50:54:00:00:00:03 10.0.0.3 10.0.0.5" - -ovn-nbctl lsp-add sw0 sw0-port2 -ovn-nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 aef0::4" - -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 aef0::1/64 -ovn-nbctl lsp-add sw0 sw0-lr0 -ovn-nbctl lsp-set-type sw0-lr0 router -ovn-nbctl lsp-set-addresses sw0-lr0 router -ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 +as hv1 check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-phys -ovn-nbctl ls-add public -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 bef0::1/64 -ovn-nbctl lsp-add public public-lr0 -ovn-nbctl lsp-set-type public-lr0 router -ovn-nbctl lsp-set-addresses public-lr0 router -ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-port1 +check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 10.0.0.5" +check ovn-nbctl lsp-set-port-security sw0-port1 "50:54:00:00:00:03 10.0.0.3 10.0.0.5" + +check ovn-nbctl lsp-add sw0 sw0-port2 +check ovn-nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 aef0::4" + +check ovn-nbctl lr-add lr0 +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 aef0::1/64 +check ovn-nbctl lsp-add sw0 sw0-lr0 +check ovn-nbctl lsp-set-type sw0-lr0 router +check ovn-nbctl lsp-set-addresses sw0-lr0 router +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 + +check ovn-nbctl ls-add public +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 bef0::1/64 +check ovn-nbctl lsp-add public public-lr0 +check ovn-nbctl lsp-set-type public-lr0 router +check ovn-nbctl lsp-set-addresses public-lr0 router +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public # localnet port -ovn-nbctl lsp-add public ln-public -ovn-nbctl lsp-set-type ln-public localnet -ovn-nbctl lsp-set-addresses ln-public unknown -ovn-nbctl lsp-set-options ln-public network_name=public +check ovn-nbctl lsp-add public ln-public +check ovn-nbctl lsp-set-type ln-public localnet +check ovn-nbctl lsp-set-addresses ln-public unknown +check ovn-nbctl lsp-set-options ln-public network_name=public + +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 -ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20 -ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24 +wait_row_count datapath_binding 1 external-ids:name=lr0 lr0_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding lr0) -ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip=172.168.0.120 \ -logical_port=lr0-public mac="10\:54\:00\:00\:00\:03" -ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip=172.168.0.200 \ -logical_port=lr0-public mac="10\:54\:00\:00\:00\:04" -ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:4" \ -logical_port=lr0-public mac="10\:54\:00\:00\:00\:05" -ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:5" \ -logical_port=lr0-public mac="10\:54\:00\:00\:00\:06" -ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:6" \ -logical_port=lr0-public mac="10\:54\:00\:00\:00\:07" -ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ -ip_prefix="\:\:/64" nexthop="bef0\:\:4" -- add Logical_Router lr0 \ -static_routes @lrt +AT_CHECK( + [ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip=172.168.0.120 \ + logical_port=lr0-public mac="10\:54\:00\:00\:00\:03" + ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip=172.168.0.200 \ + logical_port=lr0-public mac="10\:54\:00\:00\:00\:04" + ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:4" \ + logical_port=lr0-public mac="10\:54\:00\:00\:00\:05" + ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:5" \ + logical_port=lr0-public mac="10\:54\:00\:00\:00\:06" + ovn-sbctl create mac_binding datapath=$lr0_dp_uuid ip="bef0\:\:6" \ + logical_port=lr0-public mac="10\:54\:00\:00\:00\:07" + ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ + ip_prefix="\:\:/64" nexthop="bef0\:\:4" -- add Logical_Router lr0 \ + static_routes @lrt], + [0], [stdout]) +AT_CHECK([uuidfilt stdout], [0], + [<0> +<1> +<2> +<3> +<4> +<5> +]) -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync # Add logical router policy and set pkt_mark on it. -ovn-nbctl lr-policy-add lr0 2000 "ip4.src == 10.0.0.3" allow pkt_mark=100 -ovn-nbctl lr-policy-add lr0 1000 "ip4.src == 10.0.0.4" allow -ovn-nbctl lr-policy-add lr0 900 "ip4.src == 10.0.0.5" reroute 172.168.0.200 pkt_mark=3 -ovn-nbctl lr-policy-add lr0 2001 "ip6.dst == bef0::5" reroute bef0::6 -ovn-nbctl lr-policy-add lr0 1001 "ip6" allow +check ovn-nbctl lr-policy-add lr0 2000 "ip4.src == 10.0.0.3" allow pkt_mark=100 +check ovn-nbctl lr-policy-add lr0 1000 "ip4.src == 10.0.0.4" allow +check ovn-nbctl lr-policy-add lr0 900 "ip4.src == 10.0.0.5" reroute 172.168.0.200 pkt_mark=3 +check ovn-nbctl lr-policy-add lr0 2001 "ip6.dst == bef0::5" reroute bef0::6 +check ovn-nbctl lr-policy-add lr0 1001 "ip6" allow pol1=$(ovn-nbctl --bare --columns _uuid find logical_router_policy priority=2000) pol4=$(ovn-nbctl --bare --columns _uuid find logical_router_policy priority=2001) pol5=$(ovn-nbctl --bare --columns _uuid find logical_router_policy priority=1001) -ovn-nbctl set logical_router_policy $pol4 options:pkt_mark=4 -ovn-nbctl set logical_router_policy $pol5 options:pkt_mark=4294967295 -ovn-nbctl --wait=hv sync - -OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ - grep "load:0x64->NXM_NX_PKT_MARK" -c) -]) - -OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ - grep "load:0x3->NXM_NX_PKT_MARK" -c) -]) - -OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ - grep "load:0x4->NXM_NX_PKT_MARK" -c) -]) - -as hv1 ovs-ofctl dump-flows br-int table=20 | grep NXM_NX_PKT_MARK - +check ovn-nbctl set logical_router_policy $pol4 options:pkt_mark=4 +check ovn-nbctl set logical_router_policy $pol5 options:pkt_mark=4294967295 +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) +AT_CAPTURE_FILE([offlows]) OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ - grep "load:0xffffffff->NXM_NX_PKT_MARK" -c) + as hv1 ovs-ofctl dump-flows br-int table=20 > offlows + test $(grep -c "load:0x64->NXM_NX_PKT_MARK" offlows) = 1 && \ + test $(grep -c "load:0x3->NXM_NX_PKT_MARK" offlows) = 1 && \ + test $(grep -c "load:0x4->NXM_NX_PKT_MARK" offlows) = 1 && \ + test $(grep -c "load:0xffffffff->NXM_NX_PKT_MARK" offlows) = 1 ]) -AT_CHECK([as hv1 ovs-ofctl del-flows br-phys]) +as hv1 check ovs-ofctl del-flows br-phys AT_DATA([flows.txt], [dnl table=0, priority=0 actions=NORMAL table=0, priority=200 arp,actions=drop @@ -20625,16 +21003,14 @@ table=0, priority=100, pkt_mark=0xffffffff actions=drop ]) -AT_CHECK([as hv1 ovs-ofctl --protocols=OpenFlow13 add-flows br-phys flows.txt]) - -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} +as hv1 check ovs-ofctl --protocols=OpenFlow13 add-flows br-phys flows.txt +sleep 5 send_ipv4_pkt() { local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 local ip_src=$5 ip_dst=$6 packet=${eth_dst}${eth_src}08004500001c0000000040110000${ip_src}${ip_dst}0035111100080000 + tcpdump_hex $packet as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} } @@ -20650,10 +21026,11 @@ send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \ $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120) +AT_CAPTURE_FILE([offlows2]) OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \ - grep "priority=100,pkt_mark=0x64" | \ - grep "n_packets=1" -c) + as hv1 ovs-ofctl dump-flows br-phys table=0 > offlows2 + test 1 -eq $(grep "priority=100,pkt_mark=0x64" offlows2 | \ + grep -c "n_packets=1") ]) AT_CHECK([ @@ -20848,14 +21225,14 @@ sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ @@ -21165,10 +21542,6 @@ fi } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - sw0p1_ip=$(ip_to_hex 192 168 1 2) rtr_ip=$(ip_to_hex 192 168 1 1) test_arp 1 1 000000010102 $sw0p1_ip $rtr_ip 000000000001 @@ -21288,12 +21661,12 @@ net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 # Logical network @@ -21313,44 +21686,44 @@ ovn-nbctl create Logical_Router name=DR gw_uuid=$(ovn-nbctl create Logical_Router name=GW) -ovn-nbctl ls-add ls1 -ovn-nbctl ls-add ls2 -ovn-nbctl ls-add join -ovn-nbctl ls-add ext +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 -ovn-nbctl lrp-add DR dr-ls1 00:00:01:01:02:03 10.0.0.1/24 -ovn-nbctl lsp-add ls1 ls1-dr -- set Logical_Switch_Port ls1-dr \ +check ovn-nbctl lrp-add DR dr-ls1 00:00:01:01:02:03 10.0.0.1/24 +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 -ovn-nbctl lrp-add DR dr-ls2 00:00:01:01:02:04 10.0.0.2/24 -ovn-nbctl lsp-add ls2 ls2-dr -- set Logical_Switch_Port ls2-dr \ +check ovn-nbctl lrp-add DR dr-ls2 00:00:01:01:02:04 10.0.0.2/24 +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 -ovn-nbctl lrp-add DR dr-join 00:00:02:01:02:03 20.0.0.1/24 -ovn-nbctl lsp-add join join-dr -- set Logical_Switch_Port join-dr \ +check ovn-nbctl lrp-add DR dr-join 00:00:02:01:02:03 20.0.0.1/24 +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 -ovn-nbctl lrp-add GW gw-join 00:00:02:01:02:04 20.0.0.2/24 -ovn-nbctl lsp-add join join-gw -- set Logical_Switch_Port join-gw \ +check ovn-nbctl lrp-add GW gw-join 00:00:02:01:02:04 20.0.0.2/24 +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 -ovn-nbctl lrp-add GW gw-ext 00:00:03:01:02:03 172.16.0.1/16 -ovn-nbctl lsp-add ext ext-gw -- set Logical_Switch_Port ext-gw \ +check ovn-nbctl lrp-add GW gw-ext 00:00:03:01:02:03 172.16.0.1/16 +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"' -ovn-nbctl lr-route-add GW 10.0.0.0/24 20.0.0.1 -ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2 +check ovn-nbctl lr-route-add GW 10.0.0.0/24 20.0.0.1 +check ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2 # Now add some ECMP routes to the GW router. -ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.2 -ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.3 +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 -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync # Ensure ECMP symmetric reply flows are not present on any hypervisor. AT_CHECK([ @@ -21488,10 +21861,6 @@ AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.1], [1], []) AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.2], [1], []) -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets from p1 to p2 src_mac="f00000000102" dst_mac="000000000101" @@ -21669,9 +22038,9 @@ -- set Interface vif1 external_ids:iface-id=lsp # Wait for port to be bound. -OVS_WAIT_UNTIL([test $(ovn-sbctl --columns _uuid --bare list chassis hv1 | wc -l) -eq 1]) -ch=$(ovn-sbctl --columns _uuid --bare list chassis hv1) -OVS_WAIT_UNTIL([test $(ovn-sbctl --columns chassis --bare list port_binding lsp | grep $ch -c) -eq 1]) +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 @@ -21690,33 +22059,105 @@ OVN_CLEANUP([hv1]) AT_CLEANUP -# Test dropping traffic destined to router owned IPs. -AT_SETUP([ovn -- gateway router drop traffic for own IPs]) +AT_SETUP([ovn -- Multiple OVS port changes Incremental Processing]) ovn_start -ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1 -ovn-nbctl ls-add s1 - -# Connnect r1 to s1. -ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24 -ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router \ - options:router-port=lrp-r1-s1 addresses=router - -# Create logical port p1 in s1 -ovn-nbctl lsp-add s1 p1 \ --- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" - -# Create two hypervisor and create OVS ports corresponding to logical ports. net_add n1 - sim_add hv1 as hv1 ovs-vsctl add-br br-phys -ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ - set interface hv1-vif1 external-ids:iface-id=p1 \ - options:tx_pcap=hv1/vif1-tx.pcap \ - options:rxq_pcap=hv1/vif1-rx.pcap \ +ovn_attach n1 br-phys 192.168.0.10 + +check ovn-nbctl ls-add sw +check ovn-nbctl lsp-add sw lsp1 +check ovn-nbctl lsp-add sw lsp2 + +as hv1 +ovs-vsctl \ + -- add-port br-int vif1 \ + -- set Interface vif1 external_ids:iface-id=lsp1 \ + ofport-request=1 +ovs-vsctl \ + -- add-port br-int vif2 \ + -- set Interface vif2 external_ids:iface-id=lsp2 \ + ofport-request=2 + +# Wait for ports 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=lsp1 chassis=$ch +wait_row_count Port_Binding 1 logical_port=lsp2 chassis=$ch + +AS_BOX([check output flows for initial interfaces]) +as hv1 ovs-ofctl dump-flows br-int table=65 > offlows_table65.txt +AT_CAPTURE_FILE([offlows_table65.txt]) +AT_CHECK_UNQUOTED([grep -c "output:1" offlows_table65.txt], [0], [dnl +1 +]) +AT_CHECK_UNQUOTED([grep -c "output:2" offlows_table65.txt], [0], [dnl +1 +]) + +AS_BOX([delete and add OVS interfaces and force batch of updates]) +as hv1 ovn-appctl -t ovn-controller debug/pause + +as hv1 +ovs-vsctl \ + -- del-port vif1 \ + -- del-port vif2 + +as hv1 +ovs-vsctl \ + -- add-port br-int vif1 \ + -- set Interface vif1 external_ids:iface-id=lsp1 \ + ofport-request=3 \ + -- add-port br-int vif2 \ + -- set Interface vif2 external_ids:iface-id=lsp2 \ + ofport-request=4 + +as hv1 ovn-appctl -t ovn-controller debug/resume +check ovn-nbctl --wait=hv sync + +AS_BOX([check output flows for new interfaces]) +as hv1 ovs-ofctl dump-flows br-int table=65 > offlows_table65_2.txt +AT_CAPTURE_FILE([offlows_table65_2.txt]) +AT_CHECK_UNQUOTED([grep -c "output:3" offlows_table65_2.txt], [0], [dnl +1 +]) +AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl +1 +]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP + +# Test dropping traffic destined to router owned IPs. +AT_SETUP([ovn -- gateway router drop traffic for own IPs]) +ovn_start + +ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1 +ovn-nbctl ls-add s1 + +# Connnect r1 to s1. +ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24 +ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router \ + options:router-port=lrp-r1-s1 addresses=router + +# Create logical port p1 in s1 +ovn-nbctl lsp-add s1 p1 \ +-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" + +# Create two hypervisor and create OVS ports corresponding to logical ports. +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=p1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 # Pre-populate the hypervisors' ARP tables so that we don't lose any @@ -21730,10 +22171,6 @@ AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.1.1], [1], []) -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - # Send ip packets from p1 to lrp-r1-s1 src_mac="f00000000102" dst_mac="000000000101" @@ -21781,8 +22218,8 @@ AT_SETUP([ovn -- nb_cfg timestamp]) ovn_start -ovn-nbctl ls-add s2 -ovn-nbctl ls-add join +check ovn-nbctl ls-add s2 +check ovn-nbctl ls-add join net_add n1 @@ -21790,37 +22227,83 @@ for i in $(seq 1 $n); do sim_add hv$i as hv$i - ovs-vsctl add-br br-phys + check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.$i done -AT_FAIL_IF([test x$(ovn-nbctl get nb_global . nb_cfg_timestamp) != x0]) +# get_cfg IDX +# +# Fetches data fields into shell variables: +# - nb_cfg into nbs$IDX +# - nb_cfg_timestamp into nbt$IDX +# - sb_cfg into sbs$IDX +# - sb_cfg_timestamp into sbt$IDX +# - hv_cfg into hvs$IDX +# - hv_cfg_timestamp into hvt$IDX +get_cfg() { + local i=$1 + set -- $(ovn-nbctl get nb_global . nb_cfg nb_cfg_timestamp sb_cfg sb_cfg_timestamp hv_cfg hv_cfg_timestamp) + AS_VAR_SET([nbs$i], [$1]) + AS_VAR_SET([nbt$i], [$2]) + AS_VAR_SET([sbs$i], [$3]) + AS_VAR_SET([sbt$i], [$4]) + AS_VAR_SET([hvs$i], [$5]) + AS_VAR_SET([hvt$i], [$6]) + if test "$i" = 1; then + printf '\n-- %s\n' "$i. *_cfg(*_cfg_timestamp): nb=$1($2) sb=$3($4) hv=$5($6)" + else + AS_VAR_ARITH([p], [$i - 1]) + AS_VAR_COPY([nbtp], [nbt$p]) + AS_VAR_COPY([sbtp], [sbt$p]) + AS_VAR_COPY([hvtp], [hvt$p]) + AS_VAR_ARITH([nbtdelta], [$2 - $nbtp]) + AS_VAR_ARITH([sbtdelta], [$4 - $sbtp]) + AS_VAR_ARITH([hvtdelta], [$6 - $hvtp]) + printf "\n-- $i. *_cfg(*_cfg_timestamp): nb=$1(%+d) sb=$3(%+d) hv=$5(%+d)\n" $nbtdelta $sbtdelta $hvtdelta + fi +} + +# Check initial timestamps +get_cfg 1 +check test "$nbs1" = 0 +check test "$sbs1" = 0 +check test "$hvs1" = 0 + +# Force a sequence number change and check the new timestamps ovn-nbctl --wait=hv ls-add s1 -AT_CHECK([ovn-nbctl get nb_global . nb_cfg], [0], [1 -]) -AT_CHECK([test x$(ovn-nbctl get nb_global . nb_cfg_timestamp) != x0]) -AT_CHECK([test x$(ovn-nbctl get nb_global . sb_cfg_timestamp) != x0]) -AT_CHECK([test x$(ovn-nbctl get nb_global . hv_cfg_timestamp) != x0]) +get_cfg 2 +check test "$nbs2" = 1 +check test "$nbt2" -gt 0 +check test "$sbt2" -gt 0 +check test "$hvt2" -gt 0 -# kill ovn-controller on chassis hv3, so that it wont update nb_cfg -as hv3 ovn-appctl -t ovn-controller exit --restart +# Kill ovn-controller on chassis hv3, so that it won't update nb_cfg. +# Then wait for 9 out of 10 +sleep 1 +check as hv3 ovn-appctl -t ovn-controller exit --restart +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 -ovn-nbctl --timeout=1 --wait=hv sync -AT_CHECK([ovn-nbctl get nb_global . nb_cfg], [0], [2 -]) -AT_CHECK([ovn-sbctl --bare --columns=nb_cfg find chassis_private name=hv3], [0], [1 -]) +get_cfg 3 +check test "$nbs3" = 2 +check test "$nbt3" -gt "$nbt2" +check test "$sbt3" -gt "$sbt2" +check test "$hvt3" -gt 0 # start ovn-controller on hv3 again, so that it will update nb_cfg with latest # timestamp (hv3 will be the slowest one). as hv3 start_daemon ovn-controller -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns=nb_cfg find chassis_private name=hv3) == x2]) +wait_row_count Chassis_Private 1 name=hv3 nb_cfg=2 + +get_cfg 4 hv3_ts=$(ovn-sbctl --bare --columns=nb_cfg_timestamp find chassis_private name=hv3) +echo hv3_ts=$hv3_ts hv_cfg_ts=$(ovn-nbctl get nb_global . hv_cfg_timestamp) -AT_CHECK([test x$hv3_ts == x$hv_cfg_ts]) +check test "$hv3_ts" = "$hvt4" -AT_CHECK([test x$(ovn-nbctl --print-wait-time --wait=sb sync | grep ms | wc -l) == x2]) -AT_CHECK([test x$(ovn-nbctl --print-wait-time --wait=hv sync | grep ms | wc -l) == x3]) +AT_CHECK([test x$(ovn-nbctl --print-wait-time --wait=sb sync | grep ms | wc -l) = x2]) +AT_CHECK([test x$(ovn-nbctl --print-wait-time --wait=hv sync | grep ms | wc -l) = x3]) for i in $(seq 2 $n); do OVN_CLEANUP_SBOX([hv$i]) @@ -21835,7 +22318,7 @@ sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \ @@ -21852,7 +22335,7 @@ sim_add hv2 as hv2 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.2 ovs-vsctl -- add-port br-int hv2-vif1 -- \ set interface hv2-vif1 external-ids:iface-id=sw0-port2 \ @@ -21990,10 +22473,6 @@ options:rxq_pcap=${pcap_file}-rx.pcap } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - send_arp_request() { local eth_src=$1 spa=$2 tpa=$3 local eth_dst=ffffffffffff @@ -22097,35 +22576,35 @@ AT_SETUP([ovn -- conflict or duplicate ACLs resulting in same OVS match]) ovn_start -ovn-nbctl ls-add ls1 +check ovn-nbctl ls-add ls1 -ovn-nbctl lsp-add ls1 lsp1 \ --- lsp-set-addresses lsp1 "f0:00:00:00:00:01 10.0.0.1" +check ovn-nbctl lsp-add ls1 lsp1 \ + -- lsp-set-addresses lsp1 "f0:00:00:00:00:01 10.0.0.1" -ovn-nbctl lsp-add ls1 lsp2 \ --- lsp-set-addresses lsp2 "f0:00:00:00:00:02 10.0.0.2" +check ovn-nbctl lsp-add ls1 lsp2 \ + -- lsp-set-addresses lsp2 "f0:00:00:00:00:02 10.0.0.2" net_add n1 sim_add hv1 as hv1 -ovs-vsctl add-br br-phys +check ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 -ovs-vsctl -- add-port br-int hv1-vif1 -- \ +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ set interface hv1-vif1 external-ids:iface-id=lsp1 \ options:tx_pcap=hv1/vif1-tx.pcap \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 -ovs-vsctl -- add-port br-int hv1-vif2 -- \ +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ set interface hv1-vif2 external-ids:iface-id=lsp2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ ofport-request=2 # Default drop -ovn-nbctl acl-add ls1 to-lport 1000 \ -'outport == "lsp1" && ip4' drop +check ovn-nbctl acl-add ls1 to-lport 1000 \ + 'outport == "lsp1" && ip4' drop # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... # @@ -22147,10 +22626,6 @@ done } -ip_to_hex() { - printf "%02x%02x%02x%02x" "$@" -} - reset_pcap_file() { local iface=$1 local pcap_file=$2 @@ -22163,12 +22638,12 @@ # Create overlapping ACLs resulting in duplicated desired OVS flows -ovn-nbctl acl-add ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' allow -ovn-nbctl acl-add ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' allow +check ovn-nbctl acl-add ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' allow +check ovn-nbctl acl-add ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' allow -ovn-nbctl --wait=hv sync +check ovn-nbctl --wait=hv sync sip=`ip_to_hex 10 0 0 2` dip=`ip_to_hex 10 0 0 1` @@ -22176,30 +22651,33 @@ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) # Delete one of the ACLs. -ovn-nbctl acl-del ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' +check ovn-nbctl acl-del ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' +check ovn-nbctl --wait=hv sync test_ip 2 f00000000002 f00000000001 $sip $dip 1 OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) # Add a conflict ACL with drop action. -ovn-nbctl acl-add ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' drop +check ovn-nbctl acl-add ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' drop # Don't test because it is unpredicatable which rule will take effect. # Delete the ACL that has "allow" action -ovn-nbctl acl-del ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' +check ovn-nbctl acl-del ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' +check ovn-nbctl --wait=hv sync # Packet should be dropped test_ip 2 f00000000002 f00000000001 $sip $dip OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) # Add the ACL back and delete the "drop" ACL -ovn-nbctl acl-add ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' allow -ovn-nbctl acl-del ls1 to-lport 1001 \ -'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' +check ovn-nbctl acl-add ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' allow +check ovn-nbctl acl-del ls1 to-lport 1001 \ + 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' +check ovn-nbctl --wait=hv sync # Packet should be received test_ip 2 f00000000002 f00000000001 $sip $dip 1 @@ -22208,6 +22686,110 @@ OVN_CLEANUP([hv1]) AT_CLEANUP +# This test cases tests a scenario of ACL confliction with address set update. +# It is to cover a corner case when flows are re-processed in the I-P +# iteration, combined with the scenario of conflicting ACLs. +AT_SETUP([ovn -- conflict ACLs with address set]) +ovn_start + +ovn-nbctl ls-add ls1 + +ovn-nbctl lsp-add ls1 lsp1 \ +-- lsp-set-addresses lsp1 "f0:00:00:00:00:01 10.0.0.1" + +ovn-nbctl lsp-add ls1 lsp2 \ +-- lsp-set-addresses lsp2 "f0:00:00:00:00:02 10.0.0.2" + +net_add n1 +sim_add hv1 + +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=lsp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=lsp2 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +# Default drop +ovn-nbctl acl-add ls1 to-lport 1000 \ +'outport == "lsp1" && ip4' drop + +# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... +# +# This shell function causes an ip packet to be received on INPORT. +# The packet's content has Ethernet destination DST and source SRC +# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits). +# The OUTPORTs (zero or more) list the VIFs on which the packet should +# be received. INPORT and the OUTPORTs are specified as logical switch +# 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 packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\ +${dst_ip}0035111100080000 + shift; shift; shift; shift; shift + as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $packet + for outport; do + echo $packet >> $outport.expected + done +} + +reset_pcap_file() { + local iface=$1 + local pcap_file=$2 + ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ +options:rxq_pcap=dummy-rx.pcap + rm -f ${pcap_file}*.pcap + ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ +options:rxq_pcap=${pcap_file}-rx.pcap +} + +# Create an address set +ovn-nbctl create Address_Set name=as1 \ +addresses=\"10.0.0.2\",\"10.0.0.3\" + +# Create overlapping ACLs resulting in conflict desired OVS flows +# Add ACL1 uses the address set +ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \ +'outport == "lsp1" && ip4 && ip4.src == $as1' allow + +# Add ACL2 which uses a single IP, which shouldn't take effect because +# when it is added incrementally there is already a conflict one installed. +ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \ +'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' drop + + +sip=`ip_to_hex 10 0 0 2` +dip=`ip_to_hex 10 0 0 1` +test_ip 2 f00000000002 f00000000001 $sip $dip 1 +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) + +# Update the address set which causes flow reprocessing but the OVS flow +# for allowing 10.0.0.2 should keep unchanged +ovn-nbctl --wait=hv set Address_Set as1 addresses=\"10.0.0.2\" + +test_ip 2 f00000000002 f00000000001 $sip $dip 1 +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) + +# Delete the ACL1 that has "allow" action +ovn-nbctl acl-del ls1 to-lport 1001 \ +'outport == "lsp1" && ip4 && ip4.src == $as1' + +# ACL2 should take effect and packet should be dropped +test_ip 2 f00000000002 f00000000001 $sip $dip +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP + AT_SETUP([ovn -- port bind/unbind change handling with conj flows - IPv6]) ovn_start @@ -22275,9 +22857,9 @@ -- set Interface vif1 external_ids:iface-id=sw0-p1 # Wait for port to be bound. -OVS_WAIT_UNTIL([test $(ovn-sbctl --columns _uuid --bare list chassis hv1 | wc -l) -eq 1]) -ch=$(ovn-sbctl --columns _uuid --bare list chassis hv1) -OVS_WAIT_UNTIL([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p1 | grep $ch -c) -eq 1]) +wait_row_count Chassis 1 name=hv1 +ch=$(fetch_column Chassis _uuid name=hv1) +wait_row_count Port_Binding 1 logical_port=sw0-p1 chassis=$ch as hv1 ovs-vsctl add-br br-temp as hv1 ovs-vsctl \ @@ -22287,13 +22869,13 @@ ovn-nbctl --wait=hv sync # hv1 ovn-controller should not bind sw0-p2. -AT_CHECK([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p2 | grep $ch -c) -eq 0]) +check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c # Trigger recompute and sw0-p2 should not be claimed. as hv1 ovn-appctl -t ovn-controller recompute ovn-nbctl --wait=hv sync -AT_CHECK([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p2 | grep $ch -c) -eq 0]) +check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$ch ovn-sbctl --columns chassis --bare list port_binding sw0-p2 @@ -22340,7 +22922,622 @@ # Check if the encap_rec changed (it should not have) encap_rec_mvtep1=$(ovn-sbctl --data=bare --no-heading --column encaps list chassis hv1) -AT_CHECK([test "$encap_rec_mvtep" == "$encap_rec_mvtep1"], [0], []) +AT_CHECK([test "$encap_rec_mvtep" = "$encap_rec_mvtep1"], [0], []) OVN_CLEANUP([hv1]) AT_CLEANUP + +AT_SETUP([ovn -- Load Balancer LS hairpin OF flows]) +ovn_start + +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=sw0-p1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=sw1-p1 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl -- add-port br-int hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=sw0-p2 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=1 +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=sw1-p2 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +check ovn-nbctl --wait=hv ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 00:00:00:00:00:01 + +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw1 sw1-p1 -- lsp-set-addresses sw1-p1 00:00:00:00:01:01 + +OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup]) +OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw1-p1) = xup]) + +check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp +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 + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-tcp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 1] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl +priority=100,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]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.90:8080 42.42.42.42:4041,52.52.52.52:4042 tcp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl +NXST_FLOW reply (xid=0x8): +]) + +check ovn-nbctl lsp-add sw0 sw0-p2 +# hv2 should bind sw0-p2 and it should install the LB hairpin flows. +OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup]) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3] +) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-udp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-tcp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-udp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +check ovn-nbctl --wait=hv ls-lb-add 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( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,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]] +priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) +]) + +as hv2 ovs-vsctl del-port hv2-vif1 +OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) + +# Trigger recompute on hv2 as sw0 will not be cleared from local_datapaths. +as hv2 ovn-appctl -t ovn-controller recompute + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] +) + +check ovn-nbctl --wait=hv lb-del lb-ipv4-tcp + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] +) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] +]) + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) +priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) +]) + +check ovn-nbctl --wait=hv ls-del sw0 +check ovn-nbctl --wait=hv ls-del sw1 + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] +) + +OVS_WAIT_UNTIL( + [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] +) + +OVN_CLEANUP([hv1], [hv2]) +AT_CLEANUP + +AT_SETUP([ovn -- check ovn-northd and ovn-controller version pinning]) +ovn_start + +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.10 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl lsp-add sw0 sw0-p2 + +as hv1 +ovs-vsctl \ + -- add-port br-int vif1 \ + -- set Interface vif1 external_ids:iface-id=sw0-p1 \ + ofport-request=1 +ovs-vsctl \ + -- add-port br-int vif2 \ + -- set Interface vif2 external_ids:iface-id=sw0-p2 \ + ofport-request=2 + +# 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=sw0-p1 chassis=$ch +wait_row_count Port_Binding 1 logical_port=sw0-p2 chassis=$ch + +northd_version=$(ovn-sbctl get SB_Global . options:northd_internal_version | sed s/\"//g) +echo "northd version = $northd_version" +AT_CHECK([grep -c $northd_version hv1/ovn-controller.log], [0], [1 +]) + +# Stop ovn-northd so that we can modify the northd_version. +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as northd-backup +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +check ovn-sbctl set SB_Global . options:northd_internal_version=foo + +as hv1 +check ovs-vsctl set interface vif2 external_ids:iface-id=foo + +# ovn-controller should release the lport sw0-p2 since ovn-match-northd-version +# is not true. +wait_row_count Port_Binding 1 logical_port=sw0-p2 'chassis=[[]]' + +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [1], [dnl +0 +]) + +echo +echo "__file__:__line__: Pin ovn-controller to ovn-northd version." + +as hv1 +check ovs-vsctl set open . external_ids:ovn-match-northd-version=true + +OVS_WAIT_UNTIL( + [test 1 = $(grep -c "controller version - $northd_version mismatch with northd version - foo" hv1/ovn-controller.log) +]) + +as hv1 +check ovs-vsctl set interface vif2 external_ids:iface-id=sw0-p2 + +# ovn-controller should not claim sw0-p2 since there is version mismatch +as hv1 ovn-appctl -t ovn-controller recompute +wait_row_count Port_Binding 1 logical_port=sw0-p2 'chassis=[[]]' + +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [1], [dnl +0 +]) + +check ovn-sbctl set SB_Global . options:northd_internal_version=$northd_version + +# It should claim sw0-p2 +wait_row_count Port_Binding 1 logical_port=sw0-p2 chassis=$ch + +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [0], [dnl +1 +]) + +as hv1 +ovn_remote=$(ovs-vsctl get open . external_ids:ovn-remote | sed s/\"//g) +ovs-vsctl set open . external_ids:ovn-remote=unix:foo +check ovs-vsctl set interface vif2 external_ids:iface-id=foo + +# ovn-controller is not connected to the SB DB. Even though it +# releases sw0-p2, it will not delete the OF flows. +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [0], [dnl +1 +]) + +# Change the version to incorrect one and reconnect to the SB DB. +check ovn-sbctl set SB_Global . options:northd_internal_version=bar + +as hv1 +check ovs-vsctl set open . external_ids:ovn-remote=$ovn_remote + +sleep 1 + +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [0], [dnl +1 +]) + +wait_row_count Port_Binding 1 logical_port=sw0-p2 chassis=$ch + +# Change the ovn-remote to incorrect and set the correct northd version +# and then change back to the correct ovn-remote +as hv1 +check ovs-vsctl set open . external_ids:ovn-remote=unix:foo + +check ovn-sbctl set SB_Global . options:northd_internal_version=$northd_version + +as hv1 +check ovs-vsctl set open . external_ids:ovn-remote=$ovn_remote + +wait_row_count Port_Binding 1 logical_port=sw0-p2 'chassis=[[]]' +as hv1 ovs-ofctl dump-flows br-int table=0 > offlows_table0.txt +AT_CAPTURE_FILE([offlows_table0.txt]) +AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [1], [dnl +0 +]) + +as hv1 +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]) + +AT_CLEANUP diff -Nru ovn-20.09.0/tests/ovn-controller.at ovn-20.12.0/tests/ovn-controller.at --- ovn-20.09.0/tests/ovn-controller.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-controller.at 2020-12-17 17:53:32.000000000 +0000 @@ -187,49 +187,27 @@ test "${expected_iface_types}" = "${chassis_iface_types}" ]) -# Change the value of external_ids:system-id and make sure it's mirrored -# in the Chassis record in the OVN_Southbound database. +# Change the value of external_ids:system-id. +# This requires operator intervention and removal of the stale chassis and +# chassis_private records. Until that happens ovn-controller fails to +# create the records due to constraint violation on the Encap table. sysid=${sysid}-foo ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}" -OVS_WAIT_UNTIL([ - chassis_id=$(ovn-sbctl get Chassis "${sysid}" name) - test "${sysid}" = "${chassis_id}" -]) -OVS_WAIT_UNTIL([ - chassis_id=$(ovn-sbctl get Chassis_Private "${sysid}" name) - test "${sysid}" = "${chassis_id}" -]) -# Only one Chassis_Private record should exist. OVS_WAIT_UNTIL([ - test $(ovn-sbctl --columns _uuid list chassis_private | wc -l) -eq 1 + grep -q 'Transaction causes multiple rows in \\"Encap\\" table to have identical values (geneve and \\"192.168.0.1\\") for index on columns \\"type\\" and \\"ip\\".' hv/ovn-controller.log ]) -# Simulate system-id changing while ovn-controller is disconnected from the -# SB. -valid_remote=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-remote) -invalid_remote=tcp:0.0.0.0:4242 -ovs-vsctl set Open_vSwitch . external_ids:ovn-remote=${invalid_remote} -expected_state="not connected" -OVS_WAIT_UNTIL([ - test "${expected_state}" = "$(ovn-appctl -t ovn-controller connection-status)" -]) -sysid=${sysid}-bar -ovs-vsctl set Open_vSwitch . external-ids:system-id="${sysid}" -ovs-vsctl set Open_vSwitch . external_ids:ovn-remote=${valid_remote} -OVS_WAIT_UNTIL([ - chassis_id=$(ovn-sbctl get Chassis "${sysid}" name) - test "${sysid}" = "${chassis_id}" -]) -OVS_WAIT_UNTIL([ - chassis_id=$(ovn-sbctl get Chassis_Private "${sysid}" name) - test "${sysid}" = "${chassis_id}" -]) +# Destroy the stale entries manually and ovn-controller should now be able +# to create new ones. +check ovn-sbctl destroy chassis_private . -- destroy chassis . -# Only one Chassis_Private record should exist. -OVS_WAIT_UNTIL([ - test $(ovn-sbctl --columns _uuid list chassis_private | wc -l) -eq 1 -]) +wait_row_count Chassis_Private 1 name=${sysid} +wait_row_count Chassis 1 name=${sysid} + +# Only one Chassis_Private/Chassis record should exist. +wait_row_count Chassis_Private 1 +wait_row_count Chassis 1 # Gracefully terminate daemons OVN_CLEANUP_SBOX([hv]) @@ -415,3 +393,24 @@ OVN_CLEANUP([hv]) AT_CLEANUP + +AT_SETUP([ovn -- nb_cfg sync to OVS]) +ovn_start + +net_add n1 +sim_add hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 + +# Wait for ovn-controller to register in the SB. +wait_row_count Chassis 1 + +# Increment nb_cfg. +check ovn-nbctl --wait=hv sync + +# And check that it gets propagated to br-int external_ids. +as hv1 +OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP diff -Nru ovn-20.09.0/tests/ovn-controller-vtep.at ovn-20.12.0/tests/ovn-controller-vtep.at --- ovn-20.09.0/tests/ovn-controller-vtep.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-controller-vtep.at 2020-12-17 17:53:32.000000000 +0000 @@ -181,7 +181,7 @@ AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p1 300 lswitch0]) # adds logical switch port in ovn-nb database, and sets the type and options. OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' +check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' # should see one binding, associated to chassis of 'br-vtep'. chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ') AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl @@ -192,7 +192,7 @@ AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1]) # adds logical switch port in ovn-nb database for lswitch1. OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]' +check ovn-sbctl wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]' # This is allowed, but not recommended, to have two vlan_bindings (to different vtep logical switches) # from one vtep gateway physical port in one ovn-nb logical swithch. AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl @@ -203,7 +203,20 @@ # adds another logical switch port in ovn-nb database for lswitch0. OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0_dup chassis!='[[]]' + +# confirms the warning log. +OVS_WAIT_UNTIL([ + test -n "$(grep 'has already been associated with logical port' ovn-controller-vtep.log)" +]) + +OVS_WAIT_UNTIL([ + ls0_ch=$(fetch_column Port_Binding chassis logical_port=br-vtep_lswitch0) + ls0_dup_ch=$(fetch_column Port_Binding chassis logical_port=br-vtep_lswitch0_dup) + echo "ls0_ch = $ls0_ch" + echo "ls0_dup_ch = $ls0_dup_ch" + test "$ls0_ch" != "$ls0_dup_ch" +]) + # it is not allowed to have more than one ovn-nb logical port for the same # vtep logical switch on a vtep gateway chassis, so should still see only # two port_binding entries bound. @@ -214,10 +227,6 @@ ${chassis_uuid} ${chassis_uuid} ]) -# confirms the warning log. -AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g' | uniq], [0], [dnl -|WARN|logical switch (), on vtep gateway chassis () has already been associated with logical port (), ignore logical port () -]) # deletes physical ports from vtep. AT_CHECK([ovs-vsctl del-port p0 -- del-port p1]) @@ -246,7 +255,7 @@ AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0]) # adds logical switch port in ovn-nb database, and sets the type and options. OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' +check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' # adds another lswitch 'br-void' in ovn-nb database. AT_CHECK([ovn-nbctl ls-add br-void]) @@ -255,7 +264,7 @@ # adds a conflicting logical port (both br-vtep_lswitch0 and br-vtep-void_lswitch0 # are bound to the same logical switch, but they are on different datapath). OVN_NB_ADD_VTEP_PORT([br-void], [br-vtep-void_lswitch0], [br-vtep-void], [lswitch0]) -ovn-sbctl --timeout=10 wait-until Port_Binding br-vtep_lswitch0 +check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 OVS_WAIT_UNTIL([test -n "`grep WARN ovn-controller-vtep.log`"]) # confirms the warning log. AT_CHECK([sed -n 's/^.*\(|WARN|.*\)$/\1/p' ovn-controller-vtep.log | sed 's/([[-_0-9a-z]][[-_0-9a-z]]*)/()/g;s/(with tunnel key [[0-9]][[0-9]]*)/()/g' | uniq], [0], [dnl @@ -337,7 +346,7 @@ # 'ch0'. AT_CHECK([ovn-nbctl lsp-add br-test vif0]) AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) +AT_CHECK([ovn-nbctl --wait=sb sync]) AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5]) AT_CHECK([ovn-sbctl lsp-bind vif0 ch0]) @@ -352,7 +361,7 @@ # adds fake hv chassis 'ch1'. AT_CHECK([ovn-nbctl lsp-add br-void vif1]) AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) +AT_CHECK([ovn-nbctl --wait=sb sync]) AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6]) AT_CHECK([ovn-sbctl lsp-bind vif1 ch1]) @@ -375,7 +384,7 @@ ]) # adds another mac to logical switch port. -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02 f0:ab:cd:ef:01:03]) +AT_CHECK([ovn-nbctl --wait=sb lsp-set-addresses vif0 f0:ab:cd:ef:01:02 f0:ab:cd:ef:01:03]) OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep 03`"]) AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl @@ -383,8 +392,24 @@ "f0:ab:cd:ef:01:03" ]) +# adds MAC-IP pair to logical switch port. +AT_CHECK([ovn-nbctl lsp-set-addresses vif0 "f0:ab:cd:ef:01:04 192.168.0.1"]) +OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep 'f0:ab:cd:ef:01:04'`"]) +AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl +"f0:ab:cd:ef:01:04" +]) + +# adds another MAC-IP pair to logical switch port. +AT_CHECK([ovn-nbctl lsp-set-addresses vif0 "f0:ab:cd:ef:01:04 192.168.0.1" "f0:ab:cd:ef:01:05 192.168.0.2"]) +OVS_WAIT_UNTIL([test -n "`vtep-ctl list Ucast_Macs_Remote | grep 'f0:ab:cd:ef:01:05'`"]) +AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl + +"f0:ab:cd:ef:01:04" +"f0:ab:cd:ef:01:05" +]) + # removes one mac to logical switch port. -AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:03]) +AT_CHECK([ovn-nbctl --wait=sb lsp-set-addresses vif0 f0:ab:cd:ef:01:03]) OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 02`"]) AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl "f0:ab:cd:ef:01:03" @@ -392,7 +417,7 @@ # migrates mac to logical switch port vif1 on 'br-void'. AT_CHECK([ovn-nbctl lsp-set-addresses vif0]) -AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:03]) +AT_CHECK([ovn-nbctl --wait=sb lsp-set-addresses vif1 f0:ab:cd:ef:01:03]) OVS_WAIT_UNTIL([test -z "`vtep-ctl --columns=MAC list Ucast_Macs_Remote | grep 03`"]) AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl ]) @@ -409,14 +434,14 @@ # 'ch0'. AT_CHECK([ovn-nbctl lsp-add br-test vif0]) AT_CHECK([ovn-nbctl lsp-set-addresses vif0 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) +AT_CHECK([ovn-nbctl --wait=sb sync]) AT_CHECK([ovn-sbctl chassis-add ch0 vxlan 1.2.3.5]) AT_CHECK([ovn-sbctl lsp-bind vif0 ch0]) # creates another vif in the same logical switch with duplicate mac. AT_CHECK([ovn-nbctl lsp-add br-test vif1]) AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) +AT_CHECK([ovn-nbctl --wait=sb sync]) AT_CHECK([ovn-sbctl lsp-bind vif1 ch0]) # creates the logical switch in vtep and adds the corresponding logical @@ -438,14 +463,14 @@ ]) # deletes vif1. -AT_CHECK([ovn-nbctl lsp-del vif1]) +AT_CHECK([ovn-nbctl --wait=sb lsp-del vif1]) # adds another lswitch 'br-void' in ovn-nb database. AT_CHECK([ovn-nbctl ls-add br-void]) # adds fake hv chassis 'ch1' and vif1 with same mac address as vif0. AT_CHECK([ovn-nbctl lsp-add br-void vif1]) AT_CHECK([ovn-nbctl lsp-set-addresses vif1 f0:ab:cd:ef:01:02]) -AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync]) +AT_CHECK([ovn-nbctl --wait=sb sync]) AT_CHECK([ovn-sbctl chassis-add ch1 vxlan 1.2.3.6]) AT_CHECK([ovn-sbctl lsp-bind vif1 ch1]) OVS_WAIT_UNTIL([test -n "`ovn-sbctl list Port_Binding | grep vif1`"]) diff -Nru ovn-20.09.0/tests/ovn-ic.at ovn-20.12.0/tests/ovn-ic.at --- ovn-20.09.0/tests/ovn-ic.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-ic.at 2020-12-17 17:53:32.000000000 +0000 @@ -5,7 +5,7 @@ ovn_start az1 ovn_start az2 -OVS_WAIT_UNTIL([test `ovn-ic-sbctl show | wc -l` -eq 2]) +wait_row_count ic-sb:Availability_Zone 2 AT_CHECK([ovn-ic-sbctl show], [0], [dnl availability-zone az1 availability-zone az2 @@ -39,32 +39,22 @@ AT_CHECK([ovn-ic-nbctl ts-add ts2]) # Check ISB -OVS_WAIT_UNTIL([ovn-ic-sbctl list datapath | grep ts2]) -AT_CHECK([ovn-ic-sbctl -f csv -d bare --no-headings --columns transit_switch list datapath | sort], [0], [dnl -ts1 -ts2 -]) +wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts1 +wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts2 +check_column "ts1 ts2" ic-sb:Datapath_Binding transit_switch +check_column "ts1 ts2" nb:Logical_Switch name -# Check NB -AT_CHECK([ovn-nbctl -f csv -d bare --no-headings --columns name list logical_switch | sort], [0], [dnl -ts1 -ts2 -]) +ovn-nbctl --wait=hv sync # Check SB DP key -ts1_key=$(ovn-ic-sbctl -f csv -d bare --no-headings --columns tunnel_key find datapath transit_switch=ts1) -sb_ts1_key=$(ovn-sbctl -f csv -d bare --no-headings --columns tunnel_key find datapath_binding external_ids:interconn-ts=ts1) -AT_CHECK([test $ts1_key = $sb_ts1_key]) +ts1_key=$(fetch_column ic-sb:Datapath_Binding tunnel_key transit_switch=ts1) +check_column "$ts1_key" Datapath_Binding tunnel_key external_ids:interconn-ts=ts1 # Test delete AT_CHECK([ovn-ic-nbctl ts-del ts1]) -OVS_WAIT_WHILE([ovn-ic-sbctl list datapath | grep ts1]) -AT_CHECK([ovn-ic-sbctl -f csv -d bare --no-headings --columns transit_switch list datapath], [0], [dnl -ts2 -]) -AT_CHECK([ovn-nbctl -f csv -d bare --no-headings --columns name list logical_switch | sort], [0], [dnl -ts2 -]) +wait_row_count ic-sb:Datapath_Binding 0 transit_switch=ts1 +check_column ts2 ic-sb:Datapath_Binding transit_switch +check_column ts2 nb:Logical_Switch name OVN_CLEANUP_IC([az1]) @@ -154,7 +144,7 @@ ovn-nbctl lsp-add ts1 lsp-ts1-lr1 ovn-nbctl lsp-set-addresses lsp-ts1-lr1 router ovn-nbctl lsp-set-type lsp-ts1-lr1 router -ovn-nbctl lsp-set-options lsp-ts1-lr1 router-port=lrp-lr1-ts1 +ovn-nbctl --wait=hv lsp-set-options lsp-ts1-lr1 router-port=lrp-lr1-ts1 AT_CHECK([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl switch <0> (ts1) diff -Nru ovn-20.09.0/tests/ovn-macros.at ovn-20.12.0/tests/ovn-macros.at --- ovn-20.09.0/tests/ovn-macros.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-macros.at 2020-12-17 17:53:32.000000000 +0000 @@ -3,6 +3,8 @@ # Gracefully terminate vswitch daemons in the # specified sandbox. m4_define([OVN_CLEANUP_VSWITCH],[ + echo + echo "$1: clean up vswitch" as $1 OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -15,6 +17,8 @@ # as a special case, and is assumed to have ovn-controller-vtep # and ovs-vtep daemons running instead of ovn-controller. m4_define([OVN_CLEANUP_SBOX],[ + echo + echo "$1: clean up sandbox" as $1 if test "$1" = "vtep"; then OVS_APP_EXIT_AND_WAIT([ovn-controller-vtep]) @@ -33,6 +37,9 @@ m4_foreach([sbox], [$@], [ OVN_CLEANUP_SBOX([sbox]) ]) + + echo + echo "clean up OVN" as ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -53,6 +60,8 @@ # Gracefully terminate all OVN daemons, including those in the # specified sandbox instances. m4_define([OVN_CLEANUP_AZ],[ + echo + echo "$1: clean up availability zone" as $1/ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -77,6 +86,9 @@ m4_foreach([az], [$@], [ OVN_CLEANUP_AZ([az]) ]) + + echo + echo "clean up interconnection" as ovn-ic-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -99,7 +111,7 @@ # # Usually invoked from ovn_start. ovn_init_db () { - echo "creating $1 database" + echo "${AZ:+$AZ: }creating $1 database" local as_d=$1 if test -n "$2"; then as_d=$2/$as_d @@ -108,7 +120,7 @@ mkdir "$d" || return 1 : > "$d"/.$1.db.~lock~ as $as_d ovsdb-tool create "$d"/$1.db "$abs_top_srcdir"/$1.ovsschema - as $as_d start_daemon ovsdb-server --remote=punix:"$d"/$1.sock "$d"/$1.db + as $as_d start_daemon ovsdb-server -vjsonrpc --remote=punix:"$d"/$1.sock "$d"/$1.db local var=`echo $1_db | tr a-z- A-Z_` AS_VAR_SET([$var], [unix:"$d"/$1.sock]); export $var } @@ -286,4 +298,125 @@ } m4_divert_pop([PREPARE_TESTS]) +OVS_START_SHELL_HELPERS +# check COMMAND... +# +# Runs COMMAND and checks that it succeeds without any output. +check() { + echo "$@" + AT_CHECK(["$@"]) +} + +parse_db() { + case $1 in + (*:*) echo ${1%%:*} ;; + (*) echo sb ;; + esac +} + +parse_table() { + case $1 in + (*:*) echo ${1##*:} ;; + (*) echo $1 ;; + esac +} + +# count_rows TABLE [CONDITION...] +# +# Prints the number of rows in TABLE (that satisfy CONDITION). +# Uses the southbound db by default; set DB=nb for the northbound database. +count_rows() { + local db=$(parse_db $1) table=$(parse_table $1); shift + ovn-${db}ctl --format=table --no-headings find $table "$@" | wc -l +} + +# check_row_count [DATABASE:]TABLE COUNT [CONDITION...] +# +# Checks that TABLE contains COUNT rows (that satisfy CONDITION). +# The default DATABASE is "sb". +check_row_count() { + local db=$(parse_db $1) table=$(parse_table $1); shift + local count=$1; shift + local found=$(count_rows $db:$table "$@") + echo + echo "Checking for $count rows in $db $table${1+ with $*}... found $found" + if test "$count" != "$found"; then + ovn-${db}ctl list $table + AT_FAIL_IF([:]) + fi +} + +# wait_row_count [DATABASE:]TABLE COUNT [CONDITION...] +# +# Waits until TABLE contains COUNT rows (that satisfy CONDITION). +# The default DATABASE is "sb". +wait_row_count() { + local db=$(parse_db $1) table=$(parse_table $1); shift + local count=$1; shift + local a=$1 b=$2 c=$3 d=$4 e=$5 + echo "Waiting until $count rows in $db $table${1+ with $*}..." + OVS_WAIT_UNTIL([test $count = $(count_rows $db:$table $a $b $c $d $e)],[ + echo "$db table $table has the following rows. $(count_rows $db:$table $a $b $c $d $e) rows match instead of expected $count:" + ovn-${db}ctl list $table]) +} + +# fetch_column [DATABASE:]TABLE COLUMN [CONDITION...] +# +# Fetches and prints all the values of COLUMN in the rows of TABLE +# (that satisfy CONDITION), sorting the results lexicographically. +# The default DATABASE is "sb". +fetch_column() { + local db=$(parse_db $1) table=$(parse_table $1) column=${2-_uuid}; shift; shift + # Using "echo" removes spaces and newlines. + echo $(ovn-${db}ctl --bare --columns $column find $table "$@" | sort) +} + +# check_column EXPECTED [DATABASE:]TABLE COLUMN [CONDITION...] +# +# Fetches all of the values of COLUMN in the rows of TABLE (that +# satisfy CONDITION), and compares them against EXPECTED (ignoring +# order). +# +# The default DATABASE is "sb". +check_column() { + local expected=$1 db=$(parse_db $2) table=$(parse_table $2) column=${3-_uuid}; shift; shift; shift + local found=$(ovn-${db}ctl --bare --columns $column find $table "$@") + + # Sort the expected and found values. + local found=$(for d in $found; do echo $d; done | sort) + local expected=$(for d in $expected; do echo $d; done | sort) + + echo + echo "Checking values in $db $table${1+ with $*} against $expected... found $found" + if test "$found" != "$expected"; then + ovn-${db}ctl list $table + AT_FAIL_IF([:]) + fi +} + +# wait_column EXPECTED [DATABASE:]TABLE [COLUMN [CONDITION...]] +# +# Wait until all of the values of COLUMN in the rows of TABLE (that +# satisfy CONDITION) equal EXPECTED (ignoring order). +# +# The default DATABASE is "sb". +# +# COLUMN defaults to _uuid if unspecified. +wait_column() { + local expected=$(for d in $1; do echo $d; done | sort) + local db=$(parse_db $2) table=$(parse_table $2) column=${3-_uuid}; shift; shift; shift + local a=$1 b=$2 c=$3 d=$4 e=$5 + + echo + echo "Waiting until $column in $db $table${1+ with $*} is $expected..." + OVS_WAIT_UNTIL([ + found=$(ovn-${db}ctl --bare --columns $column find $table $a $b $c $d $e) + found=$(for d in $found; do echo $d; done | sort) + test "$expected" = "$found" + ], [ + echo "$column in $db table $table has value $found, from the following rows:" + ovn-${db}ctl list $table]) +} +OVS_END_SHELL_HELPERS + m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) diff -Nru ovn-20.09.0/tests/ovn-nbctl.at ovn-20.12.0/tests/ovn-nbctl.at --- ovn-20.09.0/tests/ovn-nbctl.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-nbctl.at 2020-12-17 17:53:32.000000000 +0000 @@ -346,7 +346,7 @@ AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps]) AT_CHECK([ovn-nbctl meter-add meter2 drop 3 kbps 2]) AT_CHECK([ovn-nbctl meter-add meter3 drop 100 kbps 200]) -AT_CHECK([ovn-nbctl meter-add meter4 drop 10 pktps 30]) +AT_CHECK([ovn-nbctl --fair meter-add meter4 drop 10 pktps 30]) dnl Add duplicate meter name AT_CHECK([ovn-nbctl meter-add meter1 drop 10 kbps], [1], [], [stderr]) @@ -382,7 +382,7 @@ drop: 3 kbps, 2 kb burst meter3: bands: drop: 100 kbps, 200 kb burst -meter4: bands: +meter4: (fair) bands: drop: 10 pktps, 30 packet burst ]) @@ -393,7 +393,7 @@ drop: 10 kbps meter3: bands: drop: 100 kbps, 200 kb burst -meter4: bands: +meter4: (fair) bands: drop: 10 pktps, 30 packet burst ]) @@ -536,18 +536,12 @@ snat fd01::1 fd11::/64 ]) -AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0], -[0 -]) +check_row_count nb:NAT 0 options:stateless=true AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat 40.0.0.2 192.168.1.4]) -AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0], -[1 -]) +check_row_count nb:NAT 1 options:stateless=true AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat fd21::1 fd11::2]) -AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0], -[2 -]) +check_row_count nb:NAT 2 options:stateless=true AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat fd21::1]) @@ -1651,6 +1645,8 @@ [ovn-nbctl: Same routing policy already existed on the logical router lr0. ]) +AT_CHECK([ovn-nbctl --may-exist lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop]) + dnl Add duplicated policy AT_CHECK([ovn-nbctl lr-policy-add lr0 103 "ip4.src == 1.1.1.0/24" deny], [1], [], [ovn-nbctl: deny: action must be one of "allow", "drop", and "reroute" @@ -1675,10 +1671,13 @@ dnl Delete policy by specified uuid -AT_CHECK([ovn-nbctl lr-policy-del lr0 $(ovn-nbctl --bare --column _uuid list logical_router_policy)]) +uuid=$(ovn-nbctl --bare --column _uuid list logical_router_policy) +AT_CHECK([ovn-nbctl lr-policy-del lr0 $uuid]) AT_CHECK([ovn-nbctl list logical-router-policy], [0], [dnl ]) +AT_CHECK([ovn-nbctl --if-exists lr-policy-del lr0 $uuid]) + dnl Add policy with reroute action AT_CHECK([ovn-nbctl lr-policy-add lr0 102 "ip4.src == 3.1.2.0/24" reroute 3.3.3.3]) @@ -1977,6 +1976,18 @@ ]) +dnl --------------------------------------------------------------------- + +OVN_NBCTL_TEST([ovn_nbctl_negative], [basic negative tests], [ +AT_CHECK([ovn-nbctl --id=@ls create logical_switch name=foo -- \ + set logical_switch foo1 name=bar], + [1], [], [dnl +ovn-nbctl: no row "foo1" in table Logical_Switch +]) +]) + +dnl --------------------------------------------------------------------- + AT_SETUP([ovn-nbctl - daemon retry connection]) OVN_NBCTL_TEST_START daemon AT_CHECK([kill `cat ovsdb-server.pid`]) diff -Nru ovn-20.09.0/tests/ovn-northd.at ovn-20.12.0/tests/ovn-northd.at --- ovn-20.09.0/tests/ovn-northd.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-northd.at 2020-12-17 17:53:32.000000000 +0000 @@ -1,5 +1,5 @@ AT_BANNER([OVN northd]) -AT_SETUP([ovn -- check from NBDB to SBDB]) +AT_SETUP([ovn -- check from NBDB to SBDB]) ovn_start ovn-nbctl create Logical_Router name=R1 @@ -22,178 +22,47 @@ # With the new ha_chassis_group table added, there should be no rows in # gateway_chassis table in SB DB. -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -# There should be one ha_chassis_group with the name "alice" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="alice"` - -AT_CHECK([test $ha_chassi_grp_name = alice]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) +check_row_count Gateway_Chassis 0 # There should be one ha_chassis_group with the name "alice" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="alice"` - -AT_CHECK([test $ha_chassi_grp_name = alice]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -# Trim the spaces. -ha_ch=`echo $ha_ch | sed 's/ //g'` - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done - -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` +check_row_count HA_Chassis_Group 1 name=alice +ha_chgrp_uuid=$(fetch_column HA_Chassis_Group _uuid name=alice) +check_row_count Port_Binding 1 logical_port=cr-alice ha_chassis_group=$ha_chgrp_uuid -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) +ha_ch=$(fetch_column HA_Chassis_Group ha_chassis name=alice) +check_column "$ha_ch" HA_Chassis _uuid # Delete chassis - gw2 in SB DB. # ovn-northd should not recreate ha_chassis rows # repeatedly when gw2 is deleted. ovn-sbctl chassis-del gw2 -ha_ch_list_1='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_1="$ha_ch_list_1 $i" -done - -# Trim the spaces. -ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'` - -ha_ch_list_2='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_2="$ha_ch_list_2 $i" -done - -# Trim the spaces. -ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"]) +ha_ch_list=$(fetch_column HA_Chassis _uuid) +check_column "$ha_ch_list" HA_Chassis _uuid # Add back the gw2 chassis ovn-sbctl chassis-add gw2 geneve 1.2.4.8 # delete the 2nd Gateway_Chassis on NBDB for alice port -gw_ch=`ovn-sbctl --bare --columns gateway_chassis find port_binding \ -logical_port="cr-alice"` -AT_CHECK([test "$gw_ch" = ""]) - -ha_ch=`ovn-sbctl --bare --columns ha_chassis find ha_chassis_group` -ha_ch=`echo $ha_ch | sed 's/ //g'` -# Trim the spaces. -echo "ha ch in grp = $ha_ch" - -ha_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list="$ha_ch_list $i" -done - -# Trim the spaces. -ha_ch_list=`echo $ha_ch_list | sed 's/ //g'` +check_column '' Port_Binding gateway_chassis logical_port=cr-alice -AT_CHECK([test "$ha_ch_list" = "$ha_ch"]) +ha_ch=$(fetch_column HA_Chassis_Group ha_chassis) +check_column "$ha_ch" HA_Chassis _uuid # delete the 2nd Gateway_Chassis on NBDB for alice port ovn-nbctl --wait=sb set Logical_Router_Port alice gateway_chassis=${nb_gwc1_uuid} # There should be only 1 row in ha_chassis SB DB table. -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -# There should be only 1 row in ha_chassis SB DB table. -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) +check_row_count HA_Chassis 1 +check_row_count Gateway_Chassis 0 # delete all the gateway_chassis on NBDB for alice port - ovn-nbctl --wait=sb clear Logical_Router_Port alice gateway_chassis # expect that the ha_chassis doesn't exist anymore -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) - -# expect that the ha_chassis doesn't exist anymore -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) - -AT_CLEANUP - -AT_SETUP([ovn -- check Gateway_Chassis propagation from NBDB to SBDB backwards compatibility]) -ovn_start - -ovn-nbctl create Logical_Router name=R1 -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 -ovn-sbctl chassis-add gw2 geneve 1.2.4.8 - -ovn-nbctl --wait=sb lrp-add R1 bob 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port bob options:redirect-chassis="gw1" - - -# It should be converted to ha_chassis_group entries in SBDB, and -# still redirect-chassis is kept for backwards compatibility - -AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis_group | wc -l], [0], [1 -]) - -# There should be one ha_chassis_group with the name "bob_gw1" -ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \ -ha_chassis_group name="bob_gw1"` - -AT_CHECK([test $ha_chassi_grp_name = bob_gw1]) - -ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=bob_gw1` - -AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \ -logical_port="cr-bob" | grep $ha_chgrp_uuid | wc -l], [0], [1 -]) - -ovn-nbctl --wait=sb remove Logical_Router_Port bob options redirect-chassis - -# expect that the ha_chassis/ha_chassis_group doesn't exist anymore - -AT_CHECK([ovn-sbctl find Gateway_Chassis name=bob_gw1], [0], []) -AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0 -]) +check_row_count HA_Chassis 0 +check_row_count Gateway_Chassis 0 +check_row_count Ha_Chassis_Group 0 AT_CLEANUP @@ -202,11 +71,11 @@ ovn-nbctl ls-add S1 ovn-nbctl --wait=sb lsp-add S1 S1-vm1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xdown]) +wait_row_count nb:Logical_Switch_Port 1 name=S1-vm1 'up!=true' ovn-sbctl chassis-add hv1 geneve 127.0.0.1 ovn-sbctl lsp-bind S1-vm1 hv1 -AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xup]) +wait_row_count nb:Logical_Switch_Port 1 name=S1-vm1 'up=true' AT_CLEANUP @@ -382,25 +251,25 @@ ovn-nbctl ls-add sw ovn-nbctl --wait=sb lsp-add sw p1 # northd created with unixctl option successfully created port_binding entry -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p1" | wc -l], [0], [1 -]) +check_row_count Port_Binding 1 logical_port=p1 AT_CHECK([ovn-nbctl --wait=sb lsp-del p1]) # ovs-appctl exit with unixctl option OVS_APP_EXIT_AND_WAIT_BY_TARGET(["$ovs_base"/northd/ovn-northd.ctl], ["$ovs_base"/northd/ovn-northd.pid]) # Check no port_binding entry for new port as ovn-northd is not running +# +# 142 is 128+14, the exit status that the shell reports when a +# process exits due to SIGARLM (signal 14). ovn-nbctl lsp-add sw p2 -ovn-nbctl --timeout=10 --wait=sb sync -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p2" | wc -l], [0], [0 -]) +AT_CHECK([ovn-nbctl --timeout=10 --wait=sb sync], [142], [], [ignore]) +check_row_count Port_Binding 0 logical_port=p2 # test default unixctl path as northd start_daemon ovn-northd --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock ovn-nbctl --wait=sb lsp-add sw p3 # northd created with default unixctl path successfully created port_binding entry -AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p3" | wc -l], [0], [1 -]) +check_row_count Port_Binding 1 logical_port=p3 as ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -414,222 +283,161 @@ AT_SETUP([ovn -- check HA_Chassis_Group propagation from NBDB to SBDB]) ovn_start -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 +check ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 # ovn-northd should not create HA chassis group and HA chassis rows # unless the HA chassis group in OVN NB DB is associated to # a logical router port or logical port of type external. -AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \ -| wc -l], [0], [0 -]) +check_row_count HA_Chassis_Group 0 name=hagrp1 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 # There should be no HA_Chassis rows in SB DB. -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l ], [0], [0 -]) +check_row_count HA_Chassis 0 # Add chassis ch1. -ovn-sbctl chassis-add ch1 geneve 127.0.0.2 +check ovn-sbctl chassis-add ch1 geneve 127.0.0.2 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list chassis | grep ch1 | wc -l`]) +wait_row_count Chassis 1 name=ch1 # There should be no HA_Chassis rows -AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l ], [0], [0 -]) +check_row_count HA_Chassis 0 # Create a logical router port and attach ha chassis group. -ovn-nbctl lr-add lr0 -ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 +check ovn-nbctl lr-add lr0 +check ovn-nbctl --wait=sb lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1` -ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid +echo "hagrp1_uuid=$hagrp1_uuid" +check ovn-nbctl --wait=sb set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +check_row_count HA_Chassis 3 +# ovn-northd has a weird history regarding HA_Chassis and missing +# Chassis records, see commit f879850b5f2c ("ovn-northd: Fix the +# HA_Chassis sync issue in OVN SB DB"). +# # Make sure that ovn-northd doesn't recreate the ha_chassis # records if the chassis record is missing in SB DB. - -ha_ch_list_1='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_1="$ha_ch_list_1 $i" -done - -# Trim the spaces. -ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'` - -ha_ch_list_2='' -for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort` -do - ha_ch_list_2="$ha_ch_list_2 $i" -done - -# Trim the spaces. -ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'` - -AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"]) +ha_ch_list=$(fetch_column HA_Chassis _uuid) +check_column "$ha_ch_list" HA_Chassis _uuid # 2 HA chassis should be created with 'chassis' column empty because # we have not added hv1 and hv2 chassis to the SB DB. -AT_CHECK([test 2 = `ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \ -| grep -v '-' | wc -l`]) +check_row_count HA_Chassis 2 'chassis=[[]]' # We should have 1 ha chassis with 'chassis' column set for hv1 -AT_CHECK([test 1 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | awk '{print $3}' \ -| grep '-' | wc -l`]) +check_row_count HA_Chassis 1 'chassis!=[[]]' # Create another logical router port and associate to the same ha_chasis_group -ovn-nbctl lr-add lr1 -ovn-nbctl lrp-add lr1 lr1-public 00:00:20:20:12:14 182.168.0.100/24 +check ovn-nbctl lr-add lr1 +check ovn-nbctl lrp-add lr1 lr1-public 00:00:20:20:12:14 182.168.0.100/24 -ovn-nbctl set logical_router_port lr1-public ha_chassis_group=$hagrp1_uuid +check ovn-nbctl set logical_router_port lr1-public ha_chassis_group=$hagrp1_uuid # We should still have 1 HA chassis group and 3 HA chassis in SB DB. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +check_row_count HA_Chassis 3 # Change the priority of ch1 - ha chassis in NB DB. It should get # reflected in SB DB. ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 100 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns priority find \ -ha_chassis | grep 100 | wc -l`]) +wait_row_count HA_Chassis 1 priority=100 # Delete ch1 HA chassis in NB DB. ovn-nbctl --wait=sb ha-chassis-group-remove-chassis hagrp1 ch1 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis 2 # Add back the ha chassis ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 40 -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis 3 # Delete lr0-public. We should still have 1 HA chassis group and # 3 HA chassis in SB DB. ovn-nbctl --wait=sb lrp-del lr0-public -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +wait_row_count HA_Chassis 3 # Delete lr1-public. There should be no HA chassis group in SB DB. ovn-nbctl --wait=sb lrp-del lr1-public -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 name=hagrp1 +wait_row_count HA_Chassis 0 # Add lr0-public again ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +wait_row_count HA_Chassis 3 # Create a Gateway chassis. ovn-northd should ignore this. -ovn-nbctl lrp-set-gateway-chassis lr0-public ch-1 20 +check ovn-nbctl --wait=sb lrp-set-gateway-chassis lr0-public ch1 20 # There should be only 1 HA chassis group in SB DB with the # name hagrp1. -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 +wait_row_count HA_Chassis_Group 1 name=hagrp1 +wait_row_count HA_Chassis 3 # Now delete HA chassis group. ovn-northd should create HA chassis group # with the Gateway chassis name ovn-nbctl clear logical_router_port lr0-public ha_chassis_group ovn-nbctl ha-chassis-group-del hagrp1 -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \ -find ha_chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 name=hagrp1 +wait_row_count HA_Chassis_Group 1 name=lr0-public +wait_row_count HA_Chassis 1 ovn-nbctl lrp-set-gateway-chassis lr0-public ch2 10 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=lr0-public ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis 2 # Test if 'ref_chassis' column is properly set or not in # SB DB ha_chassis_group. -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 -ovn-sbctl chassis-add ch2 geneve 127.0.0.3 -ovn-sbctl chassis-add ch3 geneve 127.0.0.4 -ovn-sbctl chassis-add comp1 geneve 127.0.0.5 -ovn-sbctl chassis-add comp2 geneve 127.0.0.6 - -ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24 -ovn-nbctl lsp-add sw0 sw0-lr0 -ovn-nbctl lsp-set-type sw0-lr0 router -ovn-nbctl lsp-set-addresses sw0-lr0 router -ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 +check ovn-sbctl chassis-add ch2 geneve 127.0.0.3 +check ovn-sbctl chassis-add ch3 geneve 127.0.0.4 +check ovn-sbctl chassis-add comp1 geneve 127.0.0.5 +check ovn-sbctl chassis-add comp2 geneve 127.0.0.6 + +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:20:20:12:14 10.0.0.1/24 +check ovn-nbctl lsp-add sw0 sw0-lr0 +check ovn-nbctl lsp-set-type sw0-lr0 router +check ovn-nbctl lsp-set-addresses sw0-lr0 router +check ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0 ovn-sbctl lsp-bind sw0-p1 comp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xup]) +wait_row_count nb:Logical_Switch_Port 1 name=sw0-p1 up=true -comp1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"` -comp2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp2"` -ch2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"` +comp1_ch_uuid=$(fetch_column Chassis _uuid name=comp1) +comp2_ch_uuid=$(fetch_column Chassis _uuid name=comp2) +ch2_ch_uuid=$comp1_ch_uuid +# Check ref_chassis. echo "comp1_ch_uuid = $comp1_ch_uuid" -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp1_ch_uuid" = "$ref_ch_list"]) +wait_column "$comp1_ch_uuid" HA_Chassis_Group ref_chassis # unbind sw0-p1 ovn-sbctl lsp-unbind sw0-p1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "" = "$ref_ch_list"]) +wait_row_count nb:Logical_Switch_Port 1 name=sw0-p1 up=false +wait_column "" HA_Chassis_Group ref_chassis # Bind sw0-p1 in comp2 ovn-sbctl lsp-bind sw0-p1 comp2 -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) +wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis ovn-nbctl ls-add sw1 ovn-nbctl lsp-add sw1 sw1-p1 @@ -638,179 +446,119 @@ ovn-nbctl lsp-add sw1 sw1-lr1 ovn-nbctl lsp-set-type sw1-lr1 router ovn-nbctl lsp-set-addresses sw1-lr1 router -ovn-nbctl lsp-set-options sw1-lr1 router-port=lr1-sw1 +check ovn-nbctl --wait=sb lsp-set-options sw1-lr1 router-port=lr1-sw1 # Bind sw1-p1 in comp1. -ovn-sbctl lsp-bind sw1-p1 comp1 +check ovn-sbctl lsp-bind sw1-p1 comp1 # Wait until sw1-p1 is up -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xup]) +wait_row_count nb:Logical_Switch_Port 1 name=sw1-p1 up=true # sw1-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) +wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis # Now attach sw0 to lr1 -ovn-nbctl lrp-add lr1 lr1-sw0 00:00:20:20:12:16 10.0.0.10/24 -ovn-nbctl lsp-add sw0 sw0-lr1 -ovn-nbctl lsp-set-type sw0-lr1 router -ovn-nbctl lsp-set-addresses sw0-lr1 router -ovn-nbctl lsp-set-options sw0-lr1 router-port=lr1-sw0 +check ovn-nbctl lrp-add lr1 lr1-sw0 00:00:20:20:12:16 10.0.0.10/24 +check ovn-nbctl lsp-add sw0 sw0-lr1 +check ovn-nbctl lsp-set-type sw0-lr1 router +check ovn-nbctl lsp-set-addresses sw0-lr1 router +check ovn-nbctl --wait=sb lsp-set-options sw0-lr1 router-port=lr1-sw0 # Both comp1 and comp2 should be in 'ref_chassis' as sw1 is indirectly # connected to lr0 -exp_ref_ch_list='' -for i in `ovn-sbctl --bare --columns _uuid list chassis | sort` -do - if test $i = $comp1_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - elif test $i = $comp2_ch_uuid; then - exp_ref_ch_list="${exp_ref_ch_list}$i" - fi -done +exp_ref_ch_list="$comp1_ch_uuid $comp2_ch_uuid" -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # Unind sw1-p1. comp2 should not be in the ref_chassis. ovn-sbctl lsp-unbind sw1-p1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) +wait_row_count nb:Logical_Switch_Port 1 name=sw1-p1 up=false +wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis # Create sw2 and attach it to lr2 -ovn-nbctl ls-add sw2 -ovn-nbctl lsp-add sw2 sw2-p1 -ovn-nbctl lr-add lr2 -ovn-nbctl lrp-add lr2 lr2-sw2 00:00:20:20:12:17 30.0.0.1/24 -ovn-nbctl lsp-add sw2 sw2-lr2 -ovn-nbctl lsp-set-type sw2-lr2 router -ovn-nbctl lsp-set-addresses sw2-lr2 router -ovn-nbctl lsp-set-options sw2-lr2 router-port=lr2-sw2 +check ovn-nbctl ls-add sw2 +check ovn-nbctl lsp-add sw2 sw2-p1 +check ovn-nbctl lr-add lr2 +check ovn-nbctl lrp-add lr2 lr2-sw2 00:00:20:20:12:17 30.0.0.1/24 +check ovn-nbctl lsp-add sw2 sw2-lr2 +check ovn-nbctl lsp-set-type sw2-lr2 router +check ovn-nbctl lsp-set-addresses sw2-lr2 router +check ovn-nbctl --wait=sb lsp-set-options sw2-lr2 router-port=lr2-sw2 # Bind sw2-p1 to comp1 -ovn-sbctl lsp-bind sw2-p1 comp1 +check ovn-sbctl lsp-bind sw2-p1 comp1 # Wait until sw2-p1 is up -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw2-p1` = xup]) +wait_row_count nb:Logical_Switch_Port 1 name=sw2-p1 up=true # sw2-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) +wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis # Now attach sw1 to lr2. With this sw2-p1 is indirectly connected to lr0. -ovn-nbctl lrp-add lr2 lr2-sw1 00:00:20:20:12:18 20.0.0.10/24 -ovn-nbctl lsp-add sw1 sw1-lr2 -ovn-nbctl lsp-set-type sw1-lr2 router -ovn-nbctl lsp-set-addresses sw1-lr2 router -ovn-nbctl lsp-set-options sw1-lr2 router-port=lr2-sw1 +check ovn-nbctl lrp-add lr2 lr2-sw1 00:00:20:20:12:18 20.0.0.10/24 +check ovn-nbctl lsp-add sw1 sw1-lr2 +check ovn-nbctl lsp-set-type sw1-lr2 router +check ovn-nbctl lsp-set-addresses sw1-lr2 router +check ovn-nbctl --wait=sb lsp-set-options sw1-lr2 router-port=lr2-sw1 # sw2-p1 is indirectly connected to lr0. So comp1 (and comp2) should be in # 'ref_chassis' -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # Create sw0-p2 and bind it to comp1 -ovn-nbctl lsp-add sw0 sw0-p2 +check ovn-nbctl --wait=sb lsp-add sw0 sw0-p2 ovn-sbctl lsp-bind sw0-p2 comp1 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xup]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_row_count nb:Logical_Switch_Port 1 name=sw0-p2 up=true +wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # unbind sw0-p2 ovn-sbctl lsp-unbind sw0-p2 -OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xdown]) -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$exp_ref_ch_list" = "$ref_ch_list"]) +wait_row_count nb:Logical_Switch_Port 1 name=sw0-p2 up=false +wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # Delete lr1-sw0. comp1 should be deleted from ref_chassis as there is no link # from sw1 and sw2 to lr0. -ovn-nbctl lrp-del lr1-sw0 +check ovn-nbctl lrp-del lr1-sw0 -OVS_WAIT_UNTIL( - [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort` - # Trim the spaces. - ref_ch_list=`echo $ref_ch_list | sed 's/ //g'` - test "$comp2_ch_uuid" = "$ref_ch_list"]) +wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis -# Set redirect-chassis option to lr0-public. It should be ignored. -ovn-nbctl set logical_router_port lr0-public options:redirect-chassis=ch1 +# Set redirect-chassis option to lr0-public. It should be ignored +# (because redirect-chassis is obsolete). +check ovn-nbctl set logical_router_port lr0-public options:redirect-chassis=ch1 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) +wait_row_count HA_Chassis_Group 1 +wait_row_count HA_Chassis_Group 1 name=lr0-public -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public" | wc -l`]) +wait_row_count HA_Chassis 2 -ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) - -# Delete the gateway chassis. HA chassis group should be created in SB DB -# for the redirect-chassis option. -ovn-nbctl clear logical_router_port lr0-public gateway_chassis - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group | wc -l`]) - -ovn-sbctl list ha_chassis_group - -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="lr0-public_ch1" | wc -l`]) +# Delete the gateway chassis. +check ovn-nbctl clear logical_router_port lr0-public gateway_chassis -ovn-sbctl --bare --columns _uuid find ha_chassis -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list ha_chassis | grep chassis | -grep -v chassis-name | wc -l`]) - -# Clear the redirect-chassis option. -ovn-nbctl clear logical_router_port lr0-public options - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 +check_row_count HA_Chassis 0 # Delete old sw0. -ovn-nbctl ls-del sw0 +check ovn-nbctl --wait=sb ls-del sw0 # Create external logical ports and associate ha_chassis_group -ovn-nbctl ls-add sw0 -ovn-nbctl lsp-add sw0 sw0-pext1 -ovn-nbctl lsp-add sw0 sw0-pext2 -ovn-nbctl lsp-add sw0 sw0-p1 - -ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3" -ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4" -ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5" - -ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 - -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 -ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-pext1 +check ovn-nbctl lsp-add sw0 sw0-pext2 +check ovn-nbctl lsp-add sw0 sw0-p1 + +check ovn-nbctl lsp-set-addresses sw0-pext1 "00:00:00:00:00:03 10.0.0.3" +check ovn-nbctl lsp-set-addresses sw0-pext2 "00:00:00:00:00:03 10.0.0.4" +check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:03 10.0.0.5" + +check ovn-nbctl --wait=sb ha-chassis-group-add hagrp1 + +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30 +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20 +check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10 # ovn-northd should not create HA chassis group and HA chassis rows # unless the HA chassis group in OVN NB DB is associated to # a logical router port or logical port of type external. -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 +check_row_count HA_Chassis 0 hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \ name=hagrp1` @@ -819,69 +567,50 @@ # So ha_chassis_group should be ignored. ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`]) +wait_row_count HA_Chassis_Group 0 name=hagrp1 +check_row_count HA_Chassis 0 # Set the type of sw0-pext1 to external ovn-nbctl lsp-set-type sw0-pext1 external -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +check_row_count HA_Chassis 3 sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \ name=hagrp1` -AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext1`]) +check_row_count Port_Binding 1 logical_port=sw0-pext1 ha_chassis_group=$sb_hagrp1_uuid # Set the type of sw0-pext2 to external and associate ha_chassis_group ovn-nbctl lsp-set-type sw0-pext2 external ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) - -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | -grep -v chassis-name | wc -l`]) -AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext1`]) - -OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \ -ha_chassis_group find port_binding logical_port=sw0-pext2`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +check_row_count HA_Chassis 3 +check_row_count Port_Binding 1 logical_port=sw0-pext1 ha_chassis_group=$sb_hagrp1_uuid +wait_row_count Port_Binding 1 logical_port=sw0-pext2 ha_chassis_group=$sb_hagrp1_uuid # sw0-p1 is a normal port. So ha_chassis_group should not be set # in port_binding. ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \ ha_chassis_group=$hagrp1_uuid -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-p1) = x], [0], []) +wait_row_count Port_Binding 0 logical_port=sw0-p1 'chassis!=[[]]' # Clear ha_chassis_group for sw0-pext1 -ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group - -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-pext1) = x], [0], []) +check ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \ -ha_chassis_group name="hagrp1" | wc -l`]) +wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]' -AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \ -grep -v chassis-name | wc -l`]) +wait_row_count HA_Chassis_Group 1 name=hagrp1 +wait_row_count HA_Chassis 3 # Clear ha_chassis_group for sw0-pext2 ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group -OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ -logical_port=sw0-pext2) = x], [0], []) - -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`]) -AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`]) +wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]' +wait_row_count HA_Chassis_Group 0 +check_row_count HA_Chassis 0 as ovn-sb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) @@ -965,21 +694,15 @@ ovn-nbctl lsp-add S1 S1-R1 ovn-nbctl lsp-set-type S1-R1 router ovn-nbctl lsp-set-addresses S1-R1 router -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 +ovn-nbctl lsp-set-options S1-R1 router-port=R1-S1 -ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1 - -uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1` -echo "CR-LRP UUID is: " $uuid +check ovn-nbctl --wait=sb lrp-set-gateway-chassis R1-S1 gw1 ovn-nbctl lrp-set-redirect-type R1-S1 bridged -OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0], [bridged -]) +wait_row_count Port_Binding 1 logical_port=cr-R1-S1 options:redirect-type=bridged ovn-nbctl lrp-set-redirect-type R1-S1 overlay -OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0], [overlay -]) - +wait_row_count Port_Binding 1 logical_port=cr-R1-S1 options:redirect-type=overlay AT_CLEANUP AT_SETUP([ovn -- check stateless dnat_and_snat rule]) @@ -994,85 +717,49 @@ ovn-nbctl lsp-add S1 S1-R1 ovn-nbctl lsp-set-type S1-R1 router ovn-nbctl lsp-set-addresses S1-R1 router -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 - -ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1 +ovn-nbctl lsp-set-options S1-R1 router-port=R1-S1 -uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1` -echo "CR-LRP UUID is: " $uuid - -# IPV4 -ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0 -]) +check ovn-nbctl --wait=sb lrp-set-gateway-chassis R1-S1 gw1 +check_flow_matches() { + local regex=$1 count=$2 + local found=$(grep -c "$1" r1-flows) + echo "checking for $count flows matching $regex... found $found" + AT_FAIL_IF([test $found != $count]) +} + +check_flow_match_sets() { + ovn-sbctl dump-flows R1 > r1-flows + AT_CAPTURE_FILE([r1-flows]) + + for regex in lr_in_unsnat ct_snat ct_dnat ip4.dst= ip4.src= ip6.dst= ip6.src=; do + check_flow_matches $regex $1 + shift + done +} + +echo +echo "IPv4: stateful" +ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 +check_flow_match_sets 2 2 2 0 0 0 0 ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1 -ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2 -]) +echo +echo "IPv4: stateless" +ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 +check_flow_match_sets 2 0 0 2 2 0 0 ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1 -# IPV6 -ovn-nbctl lr-nat-add R1 dnat_and_snat fd01::1 fd11::2 - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip6.dst=| wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip6.src=| wc -l], [0], [0 -]) - +echo +echo "IPv6: stateful" +ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat fd01::1 fd11::2 +check_flow_match_sets 2 2 2 0 0 0 0 ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1 -ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2 - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0 -]) -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip6.dst=| wc -l], [0], [2 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ip6.src=| wc -l], [0], [2 -]) +echo +echo "IPv6: stateless" +ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2 +check_flow_match_sets 2 0 0 0 0 2 2 AT_CLEANUP @@ -1088,9 +775,9 @@ ovn-nbctl lsp-add S1 S1-R1 ovn-nbctl lsp-set-type S1-R1 router ovn-nbctl lsp-set-addresses S1-R1 router -ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 +ovn-nbctl lsp-set-options S1-R1 router-port=R1-S1 -ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1 +check ovn-nbctl --wait=sb lrp-set-gateway-chassis R1-S1 gw1 uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1` echo "CR-LRP UUID is: " $uuid @@ -1098,43 +785,29 @@ # IPV4 ovn-nbctl --portrange lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 1-3000 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | grep 3000 | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | grep 3000 | wc -l], [0], [1 +AT_CAPTURE_FILE([sbflows]) +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows && test 2 = `grep -c lr_in_unsnat sbflows`]) +AT_CHECK([grep -c 'ct_snat.*3000' sbflows && grep -c 'ct_dnat.*3000' sbflows], + [0], [1 +1 ]) - ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1 +ovn-nbctl --wait=sb --portrange lr-nat-add R1 snat 172.16.1.1 50.0.0.11 1-3000 -ovn-nbctl --portrange lr-nat-add R1 snat 172.16.1.1 50.0.0.11 1-3000 - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | grep 3000 | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | grep 3000 | wc -l], [0], [0 +AT_CAPTURE_FILE([sbflows2]) +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows2 && test 2 = `grep -c lr_in_unsnat sbflows`]) +AT_CHECK([grep -c 'ct_snat.*3000' sbflows2 && grep -c 'ct_dnat.*3000' sbflows2], + [1], [1 +0 ]) ovn-nbctl lr-nat-del R1 snat 172.16.1.1 +ovn-nbctl --wait=sb --portrange --stateless lr-nat-add R1 dnat_and_snat 172.16.1.2 50.0.0.12 1-3000 -ovn-nbctl --portrange --stateless lr-nat-add R1 dnat_and_snat 172.16.1.2 50.0.0.12 1-3000 -ovn-sbctl dump-flows R1 - -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows R1 | grep lr_in_unsnat | \ -wc -l`]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | grep 3000 | grep 172.16.1.2 | wc -l], [0], [0 -]) - -AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | grep 3000 | grep 172.16.1.2 | wc -l], [0], [0 -]) - +AT_CAPTURE_FILE([sbflows3]) +OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 > sbflows3 && test 3 = `grep -c lr_in_unsnat sbflows3`]) +AT_CHECK([grep 'ct_[s]dnat.*172\.16\.1\.2.*3000' sbflows3], [1]) ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1 @@ -1150,95 +823,103 @@ # # DR is connected to S1 and CR is connected to S2 -ovn-sbctl chassis-add gw1 geneve 127.0.0.1 +check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 -ovn-nbctl lr-add DR -ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24 +check ovn-nbctl lr-add DR +check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24 cr_uuid=$(ovn-nbctl create Logical_Router name=CR) -ovn-nbctl lrp-add CR CR-S2 02:ac:10:01:00:01 172.16.1.1/24 +check ovn-nbctl lrp-add CR CR-S2 02:ac:10:01:00:01 172.16.1.1/24 -ovn-nbctl ls-add S1 -ovn-nbctl lsp-add S1 S1-DR -ovn-nbctl lsp-set-type S1-DR router -ovn-nbctl lsp-set-addresses S1-DR router -ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1 - -ovn-nbctl ls-add S2 -ovn-nbctl lsp-add S2 S2-CR -ovn-nbctl lsp-set-type S2-CR router -ovn-nbctl lsp-set-addresses S2-CR router -ovn-nbctl --wait=sb lsp-set-options S2-CR router-port=CR-S2 +check ovn-nbctl ls-add S1 +check ovn-nbctl lsp-add S1 S1-DR +check ovn-nbctl lsp-set-type S1-DR router +check ovn-nbctl lsp-set-addresses S1-DR router +check ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1 + +check ovn-nbctl ls-add S2 +check ovn-nbctl lsp-add S2 S2-CR +check ovn-nbctl lsp-set-type S2-CR router +check ovn-nbctl lsp-set-addresses S2-CR router +check ovn-nbctl --wait=sb lsp-set-options S2-CR router-port=CR-S2 -ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1 +check ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1 -uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-DR-S1` +uuid=$(fetch_column Port_Binding _uuid logical_port=cr-DR-S1) echo "CR-LRP UUID is: " $uuid -ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1 -ovn-nbctl --wait=hv sync +check ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1 +check ovn-nbctl --wait=hv sync ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\" ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\" # SNAT with ALLOWED_IPs -ovn-nbctl lr-nat-add DR snat 172.16.1.1 50.0.0.11 -ovn-nbctl lr-nat-update-ext-ip DR snat 50.0.0.11 allowed_range +check ovn-nbctl lr-nat-add DR snat 172.16.1.1 50.0.0.11 +check ovn-nbctl lr-nat-update-ext-ip DR snat 50.0.0.11 allowed_range -ovn-nbctl lr-nat-add CR snat 172.16.1.1 50.0.0.11 -ovn-nbctl lr-nat-update-ext-ip CR snat 50.0.0.11 allowed_range +check ovn-nbctl lr-nat-add CR snat 172.16.1.1 50.0.0.11 +check ovn-nbctl lr-nat-update-ext-ip CR snat 50.0.0.11 allowed_range -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | wc -l`]) -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | wc -l`]) +check ovn-nbctl --wait=sb sync -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1 -]) -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1 +ovn-sbctl dump-flows DR > drflows +AT_CAPTURE_FILE([drflows]) +ovn-sbctl dump-flows CR > crflows +AT_CAPTURE_FILE([crflows]) + +AT_CHECK([ + grep -c lr_out_snat drflows + grep -c lr_out_snat crflows + grep lr_out_snat drflows | grep "ip4.src == 50.0.0.11" | grep -c "ip4.dst == $allowed_range" + grep lr_out_snat crflows | grep "ip4.src == 50.0.0.11" | grep -c "ip4.dst == $allowed_range"], [0], [dnl +3 +3 +1 +1 ]) # SNAT with DISALLOWED_IPs -ovn-nbctl lr-nat-del DR snat 50.0.0.11 -ovn-nbctl lr-nat-del CR snat 50.0.0.11 - -ovn-nbctl lr-nat-add DR snat 172.16.1.1 50.0.0.11 -ovn-nbctl lr-nat-add CR snat 172.16.1.1 50.0.0.11 +check ovn-nbctl lr-nat-del DR snat 50.0.0.11 +check ovn-nbctl lr-nat-del CR snat 50.0.0.11 -ovn-nbctl --is-exempted lr-nat-update-ext-ip DR snat 50.0.0.11 disallowed_range -ovn-nbctl --is-exempted lr-nat-update-ext-ip CR snat 50.0.0.11 disallowed_range +check ovn-nbctl lr-nat-add DR snat 172.16.1.1 50.0.0.11 +check ovn-nbctl lr-nat-add CR snat 172.16.1.1 50.0.0.11 -ovn-sbctl dump-flows DR -ovn-sbctl dump-flows CR - -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \ -wc -l`]) -OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \ -wc -l`]) - -ovn-nbctl show DR -ovn-sbctl dump-flows DR +check ovn-nbctl --is-exempted lr-nat-update-ext-ip DR snat 50.0.0.11 disallowed_range +check ovn-nbctl --is-exempted lr-nat-update-ext-ip CR snat 50.0.0.11 disallowed_range -ovn-nbctl show CR -ovn-sbctl dump-flows CR - -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1 -]) -AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "priority=161" | wc -l], [0], [1 -]) - -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1 -]) -AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "priority=33" | wc -l], [0], [1 +check ovn-nbctl --wait=sb sync + +ovn-sbctl dump-flows DR > drflows2 +AT_CAPTURE_FILE([drflows2]) +ovn-sbctl dump-flows CR > crflows2 +AT_CAPTURE_FILE([crflows2]) + +AT_CHECK([ + grep -c lr_out_snat drflows2 + grep -c lr_out_snat crflows2 + grep lr_out_snat drflows2 | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep -c "priority=162" + grep lr_out_snat drflows2 | grep "ip4.src == 50.0.0.11" | grep -c "priority=161" + grep lr_out_snat crflows2 | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep -c "priority=34" + grep lr_out_snat crflows2 | grep "ip4.src == 50.0.0.11" | grep -c "priority=33"], [0], [dnl +4 +4 +1 +1 +1 +1 ]) # Stateful FIP with ALLOWED_IPs -ovn-nbctl lr-nat-del DR snat 50.0.0.11 -ovn-nbctl lr-nat-del CR snat 50.0.0.11 +check ovn-nbctl lr-nat-del DR snat 50.0.0.11 +check ovn-nbctl lr-nat-del CR snat 50.0.0.11 -ovn-nbctl lr-nat-add DR dnat_and_snat 172.16.1.2 50.0.0.11 -ovn-nbctl lr-nat-add CR dnat_and_snat 172.16.1.2 50.0.0.11 +check ovn-nbctl lr-nat-add DR dnat_and_snat 172.16.1.2 50.0.0.11 +check ovn-nbctl lr-nat-add CR dnat_and_snat 172.16.1.2 50.0.0.11 -ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range -ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range +check ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range +check ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range ovn-nbctl show DR ovn-sbctl dump-flows DR @@ -1351,217 +1032,206 @@ AT_CLEANUP AT_SETUP([ovn -- check Load balancer health check and Service Monitor sync]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start -ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 +check ovn-nbctl 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 -ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1 +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1 +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1 -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor | wc -l`]) +wait_row_count Service_Monitor 0 -ovn-nbctl --wait=sb -- --id=@hc create \ +AT_CHECK([ovn-nbctl --wait=sb -- --id=@hc create \ Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \ -health_check @hc +health_check @hc | uuidfilt], [0], [<0> +]) -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor | wc -l`]) +wait_row_count Service_Monitor 0 # create logical switches and ports ovn-nbctl ls-add sw0 ovn-nbctl --wait=sb lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \ "00:00:00:00:00:03 10.0.0.3" -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | wc -l`]) +wait_row_count Service_Monitor 0 ovn-nbctl ls-add sw1 ovn-nbctl --wait=sb lsp-add sw1 sw1-p1 -- lsp-set-addresses sw1-p1 \ "02:00:00:00:00:03 20.0.0.3" -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) +wait_row_count Service_Monitor 0 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | wc -l`]) +wait_row_count Service_Monitor 1 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 +wait_row_count Service_Monitor 2 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) - -ovn-nbctl --wait=sb ls-lb-add sw0 lb1 +check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +AT_CAPTURE_FILE([sbflows]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed 's/table=..//'], 0, [dnl + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) ]) # Delete the Load_Balancer_Health_Check ovn-nbctl --wait=sb clear load_balancer . health_check -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor | wc -l`]) +wait_row_count Service_Monitor 0 -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +AT_CAPTURE_FILE([sbflows2]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) ]) # Create the Load_Balancer_Health_Check again. ovn-nbctl --wait=sb -- --id=@hc create \ Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \ health_check @hc - -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) +wait_row_count Service_Monitor 2 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) ]) # Get the uuid of both the service_monitor -sm_sw0_p1=`ovn-sbctl --bare --columns _uuid find service_monitor logical_port=sw0-p1` -sm_sw1_p1=`ovn-sbctl --bare --columns _uuid find service_monitor logical_port=sw1-p1` +sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1) +sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1) -# Set the service monitor for sw1-p1 to offline -ovn-sbctl set service_monitor $sm_sw1_p1 status=offline +AT_CAPTURE_FILE([sbflows3]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +]) -OVS_WAIT_UNTIL([ - status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1` - test "$status" = "offline"]) +# Set the service monitor for sw1-p1 to offline +check ovn-sbctl set service_monitor sw1-p1 status=offline +wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=offline -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) +AT_CAPTURE_FILE([sbflows4]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) ]) # Set the service monitor for sw0-p1 to offline ovn-sbctl set service_monitor $sm_sw0_p1 status=offline -OVS_WAIT_UNTIL([ - status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw0-p1` - test "$status" = "offline"]) +wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl -]) - -ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \ -| grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;) +AT_CAPTURE_FILE([sbflows5]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*ct_lb'], 1) + +AT_CAPTURE_FILE([sbflows6]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows6 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;) ]) # Set the service monitor for sw0-p1 and sw1-p1 to online ovn-sbctl set service_monitor $sm_sw0_p1 status=online ovn-sbctl set service_monitor $sm_sw1_p1 status=online -OVS_WAIT_UNTIL([ - status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1` - test "$status" = "online"]) +wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) +AT_CAPTURE_FILE([sbflows7]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) ]) # Set the service monitor for sw1-p1 to error ovn-sbctl set service_monitor $sm_sw1_p1 status=error -OVS_WAIT_UNTIL([ - status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1` - test "$status" = "error"]) +wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \ | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) +AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) ]) # Add one more vip to lb1 - -ovn-nbctl set load_balancer . vip:"10.0.0.40\:1000"="10.0.0.3:1000,20.0.0.3:80" +check ovn-nbctl set load_balancer . vip:10.0.0.40\\:1000=10.0.0.3:1000,20.0.0.3:80 # create health_check for new vip - 10.0.0.40 -ovn-nbctl --wait=sb -- --id=@hc create \ -Load_Balancer_Health_Check vip="10.0.0.40\:1000" -- add Load_Balancer . \ -health_check @hc +AT_CHECK( + [ovn-nbctl --wait=sb \ + -- --id=@hc create Load_Balancer_Health_Check vip=10.0.0.40\\:1000 \ + -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0> +]) # There should be totally 3 rows in service_monitor for - # * 10.0.0.3:80 # * 10.0.0.3:1000 # * 20.0.0.3:80 -OVS_WAIT_UNTIL([test 3 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) - -# There should be 2 rows with logical_port=sw0-p1 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor logical_port=sw0-p1 | sed '/^$/d' | wc -l`]) - -# There should be 1 row1 with port=1000 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor port=1000 | sed '/^$/d' | wc -l`]) - -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);) +wait_row_count Service_Monitor 3 +wait_row_count Service_Monitor 2 logical_port=sw0-p1 +wait_row_count Service_Monitor 1 port=1000 + +AT_CAPTURE_FILE([sbflows9]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], + 0, +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);) + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);) ]) # Set the service monitor for sw1-p1 to online -ovn-sbctl set service_monitor $sm_sw1_p1 status=online +check ovn-sbctl set service_monitor sw1-p1 status=online -OVS_WAIT_UNTIL([ - status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1` - test "$status" = "online"]) +wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online -ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) +AT_CAPTURE_FILE([sbflows10]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], + 0, +[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) ]) # Associate lb1 to sw1 -ovn-nbctl --wait=sb ls-lb-add sw1 lb1 -ovn-sbctl dump-flows sw1 | grep ct_lb | grep priority=120 > lflows.txt -AT_CHECK([cat lflows.txt], [0], [dnl - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - table=11(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) +check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 +AT_CAPTURE_FILE([sbflows11]) +OVS_WAIT_FOR_OUTPUT( + [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], + 0, [dnl + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) + (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) ]) # Now create lb2 same as lb1 but udp protocol. -ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp -lb2_uuid=`ovn-nbctl lb-list | grep udp | awk '{print $1}'` -ovn-nbctl --wait=sb set load_balancer $lb2_uuid ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 -ovn-nbctl --wait=sb set load_balancer $lb2_uuid ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 +check ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp +check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 +check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -ovn-nbctl -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer $lb2_uuid health_check @hc +AT_CHECK([ovn-nbctl -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer lb2 health_check @hc | uuidfilt], + [0], [<0> +]) -ovn-nbctl ls-lb-add sw0 lb2 -ovn-nbctl ls-lb-add sw1 lb2 -ovn-nbctl lr-lb-add lr0 lb2 +check ovn-nbctl ls-lb-add sw0 lb2 +check ovn-nbctl ls-lb-add sw1 lb2 -OVS_WAIT_UNTIL([test 5 = `ovn-sbctl --bare --columns _uuid find \ -service_monitor | sed '/^$/d' | wc -l`]) +wait_row_count Service_Monitor 5 # Change the svc_monitor_mac. This should get reflected in service_monitor table rows. -ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03" +check ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03" -OVS_WAIT_UNTIL([test 5 = `ovn-sbctl --bare --columns src_mac find \ -service_monitor | grep "fe:a0:65:a2:01:03" | wc -l`]) +wait_row_count Service_Monitor 5 src_mac='"fe:a0:65:a2:01:03"' # Change the source ip for 10.0.0.3 backend ip in lb2 -ovn-nbctl --wait=sb set load_balancer $lb2_uuid ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100 +check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100 -OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns src_ip find \ -service_monitor logical_port=sw0-p1 | grep "10.0.0.100" | wc -l`]) +wait_row_count Service_Monitor 1 logical_port=sw0-p1 src_ip=10.0.0.100 ovn-nbctl --wait=sb lb-del lb1 -OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find service_monitor | sed '/^$/d' | wc -l`]) +wait_row_count Service_Monitor 2 ovn-nbctl --wait=sb lb-del lb2 -OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor | wc -l`]) +wait_row_count Service_Monitor 0 AT_CLEANUP @@ -1587,43 +1257,67 @@ ovn-nbctl lr-nat-add lr0 snat 192.168.2.1 10.0.0.0/24 ovn-nbctl lr-nat-add lr0 dnat_and_snat 192.168.2.4 10.0.0.4 -ovn-nbctl lr-nat-add lr0 dnat 192.168.2.5 10.0.0.5 +check ovn-nbctl --wait=sb lr-nat-add lr0 dnat 192.168.2.5 10.0.0.5 + +ovn-sbctl dump-flows lr0 > sbflows +AT_CAPTURE_FILE([sbflows]) -OVS_WAIT_UNTIL([test 1 = $(ovn-sbctl dump-flows lr0 | grep lr_in_unsnat | \ +OVS_WAIT_UNTIL([test 1 = $(grep lr_in_unsnat sbflows | \ grep "ip4 && ip4.dst == 192.168.2.1 && tcp && tcp.dst == 8080" -c) ]) -AT_CHECK([test 1 = $(ovn-sbctl dump-flows lr0 | grep lr_in_unsnat | \ +AT_CHECK([test 1 = $(grep lr_in_unsnat sbflows | \ grep "ip4 && ip4.dst == 192.168.2.4 && udp && udp.dst == 8080" -c) ]) -AT_CHECK([test 1 = $(ovn-sbctl dump-flows lr0 | grep lr_in_unsnat | \ +AT_CHECK([test 1 = $(grep lr_in_unsnat sbflows | \ grep "ip4 && ip4.dst == 192.168.2.5 && tcp && tcp.dst == 8080" -c) ]) -AT_CHECK([test 0 = $(ovn-sbctl dump-flows lr0 | grep lr_in_unsnat | \ +AT_CHECK([test 0 = $(grep lr_in_unsnat sbflows | \ grep "ip4 && ip4.dst == 192.168.2.6 && tcp && tcp.dst == 8080" -c) ]) AT_CLEANUP -AT_SETUP([ovn -- check reconcile stale Datapath_Binding]) +AT_SETUP([ovn -- DNAT force snat IP]) ovn_start -ovn-nbctl lr-add lr -ovn-nbctl lrp-add lr p 00:00:00:00:00:01 1.1.1.1/24 +ovn-nbctl lr-add lr0 +ovn-nbctl lrp-add lr0 lr0-public 00:00:01:01:02:04 192.168.2.1/24 +ovn-nbctl lrp-add lr0 lr0-join 00:00:01:01:02:04 10.10.0.1/24 -AT_CHECK([ovn-nbctl --wait=sb sync], [0]) +ovn-nbctl set logical_router lr0 options:chassis=ch1 +ovn-nbctl lr-nat-add lr0 dnat 192.168.2.2 10.0.0.5 +ovn-nbctl set logical_router lr0 options:dnat_force_snat_ip=192.168.2.3 +ovn-nbctl --wait=sb sync + +AT_CHECK([ovn-sbctl lflow-list lr0 | grep lr_in_unsnat | sort], [0], [dnl + table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) + table=5 (lr_in_unsnat ), priority=110 , match=(ip4 && ip4.dst == 192.168.2.3), action=(ct_snat;) +]) + +AT_CLEANUP + +AT_SETUP([ovn -- check reconcile stale Datapath_Binding]) +ovn_start + +check ovn-nbctl lr-add lr +check ovn-nbctl lrp-add lr p 00:00:00:00:00:01 1.1.1.1/24 +check ovn-nbctl --wait=sb sync # Create a MAC_Binding referring the router datapath. -dp=$(ovn-sbctl --bare --columns _uuid list datapath .) -ovn-sbctl create mac_binding logical_port="p" ip="1.1.1.2" datapath="$dp" +AT_CHECK([ovn-sbctl --id=@dp get datapath . -- create mac_binding logical_port=p ip=1.1.1.2 datapath=@dp | uuidfilt], [0], [<0> +]) -ovn-nbctl lrp-del p -- lr-del lr -- \ - lr-add lr -- lrp-add lr p 00:00:00:00:00:01 1.1.1.1/24 -AT_CHECK([ovn-nbctl --wait=sb sync], [0]) +check ovn-nbctl --wait=sb \ + -- lrp-del p \ + -- lr-del lr \ + -- lr-add lr \ + -- lrp-add lr p 00:00:00:00:00:01 1.1.1.1/24 -AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Datapath_Binding | wc -l)]) +check_row_count Datapath_Binding 1 nb_uuid=$(ovn-sbctl get Datapath_Binding . external_ids:logical-router) -lr_uuid=$(ovn-nbctl --columns _uuid list Logical_Router .) -AT_CHECK[test ${nb_uuid} = ${lr_uuid}] +lr_uuid=\"$(ovn-nbctl get Logical_Router . _uuid)\" +echo nb_uuid="$nb_uuid" lr_uuid="$lr_uuid" +AT_CHECK([test "${nb_uuid}" = "${lr_uuid}"]) AT_CLEANUP @@ -1638,22 +1332,14 @@ # Ports are bound on different datapaths so it's expected that they both # get tunnel_key == 1. -AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \ -port_binding logical_port=lsp1)]) -AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \ -port_binding logical_port=lsp2)]) +check_column 1 Port_Binding tunnel_key logical_port=lsp1 +check_column 1 Port_Binding tunnel_key logical_port=lsp2 ovn-nbctl lsp-del lsp2 -- lsp-add ls1 lsp2 AT_CHECK([ovn-nbctl --wait=sb sync], [0]) -AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \ -port_binding logical_port=lsp1)]) -AT_CHECK([test 2 = $(ovn-sbctl --bare --columns tunnel_key find \ -port_binding logical_port=lsp2)]) - -# ovn-northd should allocate a new tunnel_key for lsp1 or lsp2 to maintain -# unique DB indices. -AT_CHECK([test ${pb1_key} != ${pb2_key}]) +check_column 1 Port_Binding tunnel_key logical_port=lsp1 +check_column 2 Port_Binding tunnel_key logical_port=lsp2 AT_CLEANUP @@ -1679,7 +1365,7 @@ ovn-nbctl lsp-del lsp2 -- lsp-add ls1 lsp2 AT_CHECK([ovn-nbctl --wait=sb sync], [0]) -AT_CHECK([test 0 = $(ovn-sbctl list Ha_Chassis_Group | wc -l)]) +check_row_count HA_Chassis_Group 0 AT_CLEANUP @@ -1690,7 +1376,7 @@ ovn-nbctl ls-add ls2 ovn-nbctl lsp-add ls1 lsp1 ovn-nbctl lsp-add ls2 lsp2 -ovn-nbct --wait=sb sync +ovn-nbctl --wait=sb sync ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1) ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2) @@ -1738,27 +1424,21 @@ ovn-nbctl ls-add ls2 ovn-nbctl lsp-add ls1 lsp1 ovn-nbctl lsp-add ls2 lsp2 -ovn-nbct --wait=sb sync +ovn-nbctl --wait=sb sync ls1_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls1) ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2) # Add lsp1 & lsp2 to a port group. This should generate two entries in the # SB (one per logical switch). ovn-nbctl --wait=sb pg-add pg_test lsp1 lsp2 -AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)]) -AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls1_key}_pg_test], [0], [dnl -lsp1 -]) -AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl -lsp2 -]) +wait_row_count Port_Group 2 +check_row_count Port_Group 1 name=${ls1_key}_pg_test +check_row_count Port_Group 1 name=${ls2_key}_pg_test # Delete logical switch ls1. This should remove the associated SB Port_Group. ovn-nbctl --wait=sb ls-del ls1 -AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)]) -AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl -lsp2 -]) +wait_row_count Port_Group 1 +check_row_count Port_Group 1 name=${ls2_key}_pg_test AT_CLEANUP @@ -2010,3 +1690,486 @@ AT_CHECK([ovn-sbctl lflow-list | grep arp | grep 10\.0\.0\.1], [0], [ignore]) AT_CLEANUP + +AT_SETUP([ovn-northd -- reject ACL]) +ovn_start + +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 + +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw1 sw1-p1 + +check ovn-nbctl pg-add pg0 sw0-p1 sw1-p1 +check ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip4 && tcp && tcp.dst == 80" reject +check ovn-nbctl acl-add pg0 to-lport 1003 "outport == @pg0 && ip6 && udp" reject + +check ovn-nbctl --wait=hv sync + +AS_BOX([1]) + +ovn-sbctl dump-flows sw0 > sw0flows +AT_CAPTURE_FILE([sw0flows]) +ovn-sbctl dump-flows sw1 > sw1flows +AT_CAPTURE_FILE([sw1flows]) + +AT_CHECK([grep "ls_in_acl" sw0flows | grep pg0 | sort], [0], [dnl + table=7 (ls_in_acl ), priority=2002 , dnl +match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) +]) + +AT_CHECK([grep "ls_in_acl" sw1flows | grep pg0 | sort], [0], [dnl + table=7 (ls_in_acl ), priority=2002 , dnl +match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) +]) + +AT_CHECK([grep "ls_out_acl" sw0flows | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2003 , dnl +match=(outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AT_CHECK([grep "ls_out_acl" sw1flows | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2003 , dnl +match=(outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AS_BOX([2]) + +ovn-nbctl --wait=sb acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject + +ovn-sbctl dump-flows sw0 > sw0flows2 +AT_CAPTURE_FILE([sw0flows2]) +ovn-sbctl dump-flows sw1 > sw1flows2 +AT_CAPTURE_FILE([sw1flows2]) + +AT_CHECK([grep "ls_out_acl" sw0flows2 | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2002 , dnl +match=(outport == @pg0 && ip4 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=(outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AT_CHECK([grep "ls_out_acl" sw1flows2 | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2002 , dnl +match=(outport == @pg0 && ip4 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=(outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AS_BOX([3]) + +ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related + +ovn-sbctl dump-flows sw0 > sw0flows3 +AT_CAPTURE_FILE([sw0flows3]) +ovn-sbctl dump-flows sw1 > sw1flows3 +AT_CAPTURE_FILE([sw1flows3]) + +AT_CHECK([grep "ls_out_acl" sw0flows3 | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2001 , dnl +match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) + table=5 (ls_out_acl ), priority=2001 , dnl +match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) + table=5 (ls_out_acl ), priority=2002 , dnl +match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl +action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2002 , dnl +match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl +action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AT_CHECK([grep "ls_out_acl" sw1flows3 | grep pg0 | sort], [0], [dnl + table=5 (ls_out_acl ), priority=2001 , dnl +match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) + table=5 (ls_out_acl ), priority=2001 , dnl +match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) + table=5 (ls_out_acl ), priority=2002 , dnl +match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl +action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2002 , dnl +match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl +action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) + table=5 (ls_out_acl ), priority=2003 , dnl +match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) +]) + +AT_CLEANUP + +AT_SETUP([ovn -- ACL fair Meters]) +AT_KEYWORDS([acl log meter fair]) +ovn_start + +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "50:54:00:00:00:01 10.0.0.11" +check ovn-nbctl lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 "50:54:00:00:00:02 10.0.0.12" +check ovn-nbctl lsp-add sw0 sw0-p3 -- lsp-set-addresses sw0-p3 "50:54:00:00:00:03 10.0.0.13" + +check ovn-nbctl meter-add meter_me drop 1 pktps +nb_meter_uuid=$(fetch_column nb:Meter _uuid name=meter_me) + +check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.0.0.12' allow +check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.0.0.13' allow + +acl1=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.12' | head -1) +acl2=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.13' | head -1) +check ovn-nbctl set acl $acl1 log=true severity=alert meter=meter_me name=acl_one +check ovn-nbctl set acl $acl2 log=true severity=info meter=meter_me name=acl_two +check ovn-nbctl --wait=sb sync + +check_row_count nb:meter 1 +check_column meter_me nb:meter name + +check_acl_lflow() { + acl_log_name=$1 + meter_name=$2 + # echo checking that logical flow for acl log $acl_log_name has $meter_name + AT_CHECK([ovn-sbctl lflow-list | grep ls_out_acl | \ + grep "\"${acl_log_name}\"" | \ + grep -c "meter=\"${meter_name}\""], [0], [1 +]) +} + +check_meter_by_name() { + [test "$1" = "NOT"] && { expected_count=0; shift; } || expected_count=1 + for meter_name in $* ; do + # echo checking for $expected_count $meter_name in sb meter table + check_row_count meter $expected_count name=$meter_name + done +} + +# Make sure 'fair' value properly affects the Meters in SB +check_meter_by_name meter_me +check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} + +check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=true +check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} + +check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=false +check_meter_by_name meter_me +check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} + +check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=true +check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} + +# Change template meter and make sure that is reflected on acl meters as well +template_band=$(fetch_column nb:meter bands name=meter_me) +check ovn-nbctl --wait=sb set meter_band $template_band rate=123 +# Make sure that every Meter_Band has the right rate. (ovn-northd +# creates 3 identical Meter_Band rows, all identical; ovn-northd-ddlog +# creates just 1. It doesn't matter, they work just as well.) +n_meter_bands=$(count_rows meter_band) +AT_FAIL_IF([test "$n_meter_bands" != 1 && test "$n_meter_bands" != 3]) +check_row_count meter_band $n_meter_bands rate=123 + +# Check meter in logical flows for acl logs +check_acl_lflow acl_one meter_me__${acl1} +check_acl_lflow acl_two meter_me__${acl2} + +# Stop using meter for acl1 +check ovn-nbctl --wait=sb clear acl $acl1 meter +check_meter_by_name meter_me meter_me__${acl2} +check_meter_by_name NOT meter_me__${acl1} +check_acl_lflow acl_two meter_me__${acl2} + +# Remove template Meter should remove all others as well +check ovn-nbctl --wait=sb meter-del meter_me +check_row_count meter 0 +# Check that logical flow remains but uses non-unique meter since fair +# attribute is lost by the removal of the Meter row. +check_acl_lflow acl_two meter_me + +# Re-add template meter and make sure acl2's meter is back in sb +check ovn-nbctl --wait=sb --fair meter-add meter_me drop 1 pktps +check_meter_by_name meter_me meter_me__${acl2} +check_meter_by_name NOT meter_me__${acl1} +check_acl_lflow acl_two meter_me__${acl2} + +# Remove acl2 +sw0=$(fetch_column nb:logical_switch _uuid name=sw0) +check ovn-nbctl --wait=sb remove logical_switch $sw0 acls $acl2 +check_meter_by_name meter_me +check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} + +AT_CLEANUP + +AT_SETUP([datapath requested-tnl-key]) +AT_KEYWORDS([requested tnl tunnel key keys]) +ovn_start + +get_tunnel_keys() { + set $(ovn-sbctl get datapath_binding ls0 tunnel_key \ + -- get datapath_binding ls1 tunnel_key \ + -- get datapath_binding ls2 tunnel_key) + echo "ls0=$ls0 ls1=$ls1 ls2=$ls2" + ls0=$1 ls1=$2 ls2=$3 + AT_CHECK([test "$ls0" != "$ls1" && \ + test "$ls1" != "$ls2" && \ + test "$ls0" != "$ls2"]) +} + +echo +echo "__file__:__line__: Add three logical switches, check tunnel ids" +AT_CHECK( + [ovn-nbctl --wait=sb ls-add ls0 + ovn-nbctl --wait=sb ls-add ls1 + ovn-nbctl --wait=sb ls-add ls2]) +get_tunnel_keys +AT_CHECK([test $ls0 = 1 && test $ls1 = 2 && test $ls2 = 3]) + +echo +echo "__file__:__line__: Assign ls0 new tunnel key, others don't change." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch ls0 other-config:requested-tnl-key=4]) +get_tunnel_keys +AT_CHECK([test $ls0 = 4 && test $ls1 = 2 && test $ls2 = 3]) + +echo +echo "__file__:__line__: Assign ls0 a conflict with ls1, which moves aside." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch ls0 other-config:requested-tnl-key=2]) +get_tunnel_keys +AT_CHECK([test $ls0 = 2 && test $ls2 = 3]) + +echo +echo "__file__:__line__: Assign ls0 and ls1 conflicts and verify that they end up different and ls2 doesn't change." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch ls1 other-config:requested-tnl-key=2]) +get_tunnel_keys +AT_CHECK([test $ls2 = 3]) +AT_CLEANUP +]) + +AT_SETUP([port requested-tnl-key]) +AT_KEYWORDS([requested tnl tunnel key keys]) +ovn_start + +get_tunnel_keys() { + set $(ovn-sbctl get port_binding lsp00 tunnel_key \ + -- get port_binding lsp01 tunnel_key \ + -- get port_binding lsp02 tunnel_key \ + -- get port_binding lsp10 tunnel_key \ + -- get port_binding lsp11 tunnel_key \ + -- get port_binding lsp12 tunnel_key) + lsp00=$1 lsp01=$2 lsp02=$3 lsp10=$4 lsp11=$5 lsp12=$6 + ls0=$1$2$3 ls1=$4$5$6 + echo "ls0=$1$2$3 ls1=$4$5$6" + AT_CHECK([test "$lsp00" != "$lsp01" && \ + test "$lsp01" != "$lsp02" && \ + test "$lsp00" != "$lsp02"]) + AT_CHECK([test "$lsp10" != "$lsp11" && \ + test "$lsp11" != "$lsp12" && \ + test "$lsp10" != "$lsp12"]) +} + +echo +echo "__file__:__line__: Add two logical switches with three ports each, check tunnel ids" +AT_CHECK( + [for i in 0 1; do + ovn-nbctl --wait=sb ls-add ls$i || exit $? + for j in 0 1 2; do + ovn-nbctl --wait=sb lsp-add ls$i lsp$i$j || exit $? + done + done]) +get_tunnel_keys +AT_CHECK([test $ls0 = 123 && test $ls1 = 123]) + +echo +echo "__file__:__line__: Assign lsp00 new tunnel key, others don't change." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch-port lsp00 options:requested-tnl-key=4]) +get_tunnel_keys +AT_CHECK([test $ls0 = 423 && test $ls1 = 123]) + +echo +echo "__file__:__line__: Assign lsp00 a conflict with lsp01, which moves aside." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch-port lsp00 options:requested-tnl-key=2]) +get_tunnel_keys +AT_CHECK([test $lsp00 = 2 && test $lsp02 = 3 && test $ls1 = 123]) + +echo +echo "__file__:__line__: Assign lsp00 and lsp01 conflicts and verify that they end up different and lsp02 doesn't change." +AT_CHECK( + [ovn-nbctl --wait=sb set logical-switch-port lsp01 options:requested-tnl-key=2]) +get_tunnel_keys +AT_CHECK([test $lsp02 = 3 && test $ls1 = 123]) + +AT_CLEANUP + +AT_SETUP([ovn -- NB to SB load balancer sync]) +ovn_start + +check ovn-nbctl --wait=sb lb-add lb0 10.0.0.10:80 10.0.0.4:8080 +check_row_count nb:load_balancer 1 + +echo +echo "__file__:__line__: Check that there are no SB load balancer rows." +check_row_count sb:load_balancer 0 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl --wait=sb ls-lb-add sw0 lb0 +sw0_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw0) + +echo +echo "__file__:__line__: Check that there is one SB load balancer row for lb0." +check_row_count sb:load_balancer 1 +check_column "10.0.0.10:80=10.0.0.4:8080 tcp" sb:load_balancer vips,protocol name=lb0 + +lb0_uuid=$(fetch_column sb:load_balancer _uuid name=lb0) + +echo +echo "__file__:__line__: Check that SB lb0 has sw0 in datapaths column." + +check_column "$sw0_sb_uuid" sb:load_balancer datapaths name=lb0 +check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw0 + +check ovn-nbctl --wait=sb set load_balancer . vips:"10.0.0.20\:90"="20.0.0.4:8080,30.0.0.4:8080" + +echo +echo "__file__:__line__: Check that SB lb0 has vips and protocol columns are set properly." + +check_column "10.0.0.10:80=10.0.0.4:8080 10.0.0.20:90=20.0.0.4:8080,30.0.0.4:8080 tcp" \ +sb:load_balancer vips,protocol name=lb0 + +check ovn-nbctl lr-add lr0 +check ovn-nbctl --wait=sb lr-lb-add lr0 lb0 + +echo +echo "__file__:__line__: Check that SB lb0 has only sw0 in datapaths column." +check_column "$sw0_sb_uuid" sb:load_balancer datapaths name=lb0 + +check ovn-nbctl ls-add sw1 +check ovn-nbctl --wait=sb ls-lb-add sw1 lb0 +sw1_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw1) + +echo +echo "__file__:__line__: Check that SB lb0 has sw0 and sw1 in datapaths column." +check_column "$sw0_sb_uuid $sw1_sb_uuid" sb:load_balancer datapaths name=lb0 +check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw1 + +check ovn-nbctl --wait=sb lb-add lb1 10.0.0.30:80 20.0.0.50:8080 udp +check_row_count sb:load_balancer 1 + +check ovn-nbctl --wait=sb lr-lb-add lr0 lb1 +check_row_count sb:load_balancer 1 + +echo +echo "__file__:__line__: Associate lb1 to sw1 and check that lb1 is created in SB DB." + +check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 +check_row_count sb:load_balancer 2 + +echo +echo "__file__:__line__: Check that SB lb1 has vips and protocol columns are set properly." +check_column "10.0.0.30:80=20.0.0.50:8080 udp" sb:load_balancer vips,protocol name=lb1 + +lb1_uuid=$(fetch_column sb:load_balancer _uuid name=lb1) + +echo +echo "__file__:__line__: Check that SB lb1 has sw1 in datapaths column." + +check_column "$sw1_sb_uuid" sb:load_balancer datapaths name=lb1 + +echo +echo "__file__:__line__: check that datapath sw1 has lb0 and lb1 set in the load_balancers column." +check_column "$lb0_uuid $lb1_uuid" sb:datapath_binding load_balancers external_ids:name=sw1 + +echo +echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's load_balancers are updated accordingly." + +ovn-nbctl --wait=sb lb-del lb1 +check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw1 + +AT_CLEANUP + +AT_SETUP([ovn -- logical gatapath groups]) +AT_KEYWORDS([use_logical_dp_groups]) +ovn_start + +dnl Disabling datapath groups. +ovn-nbctl --wait=sb set NB_Global . options:use_logical_dp_groups=false + +ovn-nbctl ls-add sw1 +ovn-nbctl ls-add sw2 +ovn-nbctl lsp-add sw1 swp1 +ovn-nbctl lsp-add sw2 swp2 +ovn-nbctl --wait=sb sync + +sw1_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw1) +sw2_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw2) + +dnl Check that we have no datapath groups. +check_row_count Logical_DP_Group 0 + +dnl Number of logical flows that depends on logical switch or multicast group. +dnl These will not be combined. +n_flows_specific=$(ovn-sbctl --bare find Logical_Flow | grep -cE 'swp|_MC_') +echo "Number of specific flows: "${n_flows_specific} + +dnl Both logical switches configured identically, so there should be same +dnl number of logical flows per logical switch/logical datapath. +n_flows=$(count_rows Logical_Flow) +echo "Total number of flows with datapath groups disabled: "${n_flows} +n_flows_half=$((${n_flows} / 2)) +check_row_count Logical_Flow ${n_flows_half} logical_datapath=${sw1_sb_uuid} +check_row_count Logical_Flow ${n_flows_half} logical_datapath=${sw2_sb_uuid} + +dnl Enabling datapath groups. +ovn-nbctl --wait=sb set NB_Global . options:use_logical_dp_groups=true + +dnl Check that one datapath group created. +check_row_count Logical_DP_Group 1 +dp_group_uuid=$(fetch_column logical_dp_group _uuid) + +dnl Check that datapath group contains both datapaths. +check_column "${sw1_sb_uuid} ${sw2_sb_uuid}" Logical_DP_Group datapaths + +dnl Calculating number of flows that should be combined for a datapath group. +n_flows=$(count_rows Logical_Flow) +echo "Total number of flows with datapath groups enabled: "${n_flows} +n_flows_common=$((${n_flows} - ${n_flows_specific})) + +check_row_count Logical_Flow ${n_flows_common} logical_dp_group=${dp_group_uuid} +check_row_count Logical_Flow ${n_flows_common} logical_datapath=[[]] +check_row_count Logical_Flow ${n_flows_common} \ + logical_dp_group=${dp_group_uuid} logical_datapath=[[]] + +dnl Adding 8 more logical switches and ports. +for i in $(seq 3 10); do + ovn-nbctl ls-add sw${i} + ovn-nbctl lsp-add sw${i} swp${i} +done +ovn-nbctl --wait=sb sync + +dnl Number of logical flows should be increased only due to specific flows. +expected_n_flows=$((${n_flows_common} + 5 * ${n_flows_specific})) +echo "Total number of flows with 10 logical switches should be: " \ + ${expected_n_flows} +check_row_count Logical_Flow ${expected_n_flows} + +dnl Should be still only one datapath group. +check_row_count Logical_DP_Group 1 +dp_group_uuid=$(fetch_column logical_dp_group _uuid) + +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 diff -Nru ovn-20.09.0/tests/ovn-sbctl.at ovn-20.12.0/tests/ovn-sbctl.at --- ovn-20.09.0/tests/ovn-sbctl.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovn-sbctl.at 2020-12-17 17:53:32.000000000 +0000 @@ -124,7 +124,7 @@ AT_CHECK([ovn-nbctl lsp-set-type vtep0 vtep]) AT_CHECK([ovn-nbctl lsp-set-options vtep0 vtep_physical_switch=p0 vtep_logical_switch=l0]) -AT_CHECK([ovn-sbctl --timeout=10 wait-until Port_Binding vtep0 options!={}]) +AT_CHECK([ovn-sbctl wait-until Port_Binding vtep0 options!={}]) AT_CHECK([ovn-sbctl --columns=logical_port,mac,type,options list Port_Binding vtep0], [0], [dnl logical_port : vtep0 mac : [[]] diff -Nru ovn-20.09.0/tests/ovs-macros.at ovn-20.12.0/tests/ovs-macros.at --- ovn-20.09.0/tests/ovs-macros.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/ovs-macros.at 2020-12-17 17:53:32.000000000 +0000 @@ -23,9 +23,11 @@ m4_define([OVS_START_SHELL_HELPERS], [m4_ifdef([AT_ingroup], [m4_fatal([$0: AT_SETUP and OVS_DEFINE_SHELL_HELPERS may not nest])]) m4_define([AT_ingroup]) + m4_define([AT_capture_files]) m4_divert_push([PREPARE_TESTS])]) m4_define([OVS_END_SHELL_HELPERS], [ m4_divert_pop([PREPARE_TESTS]) + m4_undefine([AT_capture_files]) m4_undefine([AT_ingroup])]) m4_divert_push([PREPARE_TESTS]) @@ -35,11 +37,33 @@ # directory. ovs_init() { ovs_base=`pwd` - trap '. "$ovs_base/cleanup"' 0 + trap ovs_on_exit 0 : > cleanup ovs_setenv } +# Catch testsuite error condition and cleanup test environment by tearing down +# all interfaces and processes spawned. +# User has an option to leave the test environment in error state so that system +# can be poked around to get more information. User can enable this option by setting +# environment variable OVS_PAUSE_TEST=1. User needs to press CTRL-D to resume the +# cleanup operation. +ovs_pause() { + echo "=====================================================" + echo "Set following environment variable to use various ovs utilities" + echo "export OVS_RUNDIR=$ovs_base" + echo "Press ENTER to continue: " + read +} + +ovs_on_exit () { + if [ ! -z "${OVS_PAUSE_TEST}" ] && [ -z $at_verbose ]; then + trap '' INT + ovs_pause + fi + . "$ovs_base/cleanup" +} + # With no parameter or an empty parameter, sets the OVS_*DIR # environment variables to point to $ovs_base, the base directory in # which the test is running. @@ -209,13 +233,13 @@ if ovs_wait_cond; then echo "$1: wait succeeded quickly" >&AS_MESSAGE_LOG_FD; return 0; fi # Then wait up to OVS_CTL_TIMEOUT seconds. - local d - for d in `seq 1 "$OVS_CTL_TIMEOUT"`; do + local timer + for timer in `seq 1 "$OVS_CTL_TIMEOUT"`; do sleep 1 - if ovs_wait_cond; then echo "$1: wait succeeded after $d seconds" >&AS_MESSAGE_LOG_FD; return 0; fi + if ovs_wait_cond; then echo "$1: wait succeeded after $timer seconds" >&AS_MESSAGE_LOG_FD; return 0; fi done - echo "$1: wait failed after $d seconds" >&AS_MESSAGE_LOG_FD + echo "$1: wait failed after $timer seconds" >&AS_MESSAGE_LOG_FD ovs_wait_failed AT_FAIL_IF([:]) } @@ -231,21 +255,52 @@ ovs_wait "AS_ESCAPE([$3])" "AS_ESCAPE([$4])" ]) -dnl OVS_WAIT_UNTIL(COMMAND) +dnl OVS_WAIT_UNTIL(COMMAND[, IF-FAILED]) dnl dnl Executes shell COMMAND in a loop until it returns dnl zero return code. If COMMAND did not return dnl zero code within reasonable time limit, then -dnl the test fails. +dnl the test fails. In that case, runs IF-FAILED +dnl before aborting. m4_define([OVS_WAIT_UNTIL], [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])]) -dnl OVS_WAIT_WHILE(COMMAND) +dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR) +dnl +dnl Executes shell COMMAND in a loop until it exits with status EXIT-STATUS, +dnl prints STDOUT on stdout, and prints STDERR on stderr. If this doesn't +dnl happen within a reasonable time limit, then the test fails. +m4_define([OVS_WAIT_FOR_OUTPUT], [dnl +wait_expected_status=m4_if([$2], [], [0], [$2]) +AT_DATA([wait-expected-stdout], [$3]) +AT_DATA([wait-expected-stderr], [$4]) +ovs_wait_command() { + $1 +} +ovs_wait_cond() { + ovs_wait_command >wait-stdout 2>wait-stderr + wait_status=$? + (test $wait_status = $wait_expected_status && + $at_diff wait-expected-stdout wait-stdout && + $at_diff wait-expected-stderr wait-stderr) >/dev/null 2>&1 +} +ovs_wait_failed () { + if test $wait_status != $wait_expected_status; then + echo "exit status $wait_status != expected $wait_expected_status" + fi + $at_diff wait-expected-stdout wait-stdout + $at_diff wait-expected-stderr wait-stderr +} +ovs_wait "AS_ESCAPE([AT_LINE])" "for output from AS_ESCAPE([$1])" +]) + +dnl OVS_WAIT_WHILE(COMMAND[, IF-FAILED]) dnl dnl Executes shell COMMAND in a loop until it returns dnl non-zero return code. If COMMAND did not return dnl non-zero code within reasonable time limit, then -dnl the test fails. +dnl the test fails. In that case, runs IF-FAILED +dnl before aborting. m4_define([OVS_WAIT_WHILE], [OVS_WAIT([if $1; then return 1; else return 0; fi], [$2], [AT_LINE], [while $1])]) diff -Nru ovn-20.09.0/tests/system-ovn.at ovn-20.12.0/tests/system-ovn.at --- ovn-20.09.0/tests/system-ovn.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/system-ovn.at 2020-12-17 17:53:32.000000000 +0000 @@ -2984,7 +2984,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -3125,7 +3125,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 fd01::1/64 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 fd02::1/64 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 fd72::1/64 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -3266,7 +3266,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -3445,7 +3445,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 fd11::1/64 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 fd12::1/64 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 fd20::1/64 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -3601,7 +3601,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -3778,7 +3778,7 @@ ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 fd11::1/64 ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 fd12::1/64 ovn-nbctl lrp-add R1 alice 00:00:02:01:02:03 fd20::1/64 \ - -- set Logical_Router_Port alice options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis alice hv1 # Connect foo to R1 ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \ @@ -4473,9 +4473,6 @@ ovn-nbctl lsp-set-addresses sw0-p2-rej "50:54:00:00:00:04 10.0.0.4 aef0::4" ovn-nbctl lsp-set-port-security sw0-p2-rej "50:54:00:00:00:04 10.0.0.4 aef0::4" -#ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p1\" && tcp && tcp.dst == 80" reject -#ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p2\" && ip6 && tcp && tcp.dst == 80" reject - # Create port group and ACLs for sw0 ports. ovn-nbctl pg-add pg0_drop sw0-p1-rej sw0-p2-rej ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop @@ -4638,6 +4635,49 @@ test $c -eq 1 ]) +# Delete all the ACLs of pg0 and add the ACL with a generic match with reject action. +ovn-nbctl pg-del pg0 +ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej +ovn-nbctl --log acl-add pg0 from-lport 1004 "inport == @pg0 && ip && (tcp || udp)" reject + +OVS_WAIT_UNTIL([ + ip netns exec sw0-p1-rej nc 10.0.0.4 80 2> r + res=$(cat r) + echo "result = $res" + test "$res" = "Ncat: Connection refused." +]) + +OVS_WAIT_UNTIL([ + ip netns exec sw0-p2-rej nc -6 aef0::3 80 2> r + res=$(cat r) + test "$res" = "Ncat: Connection refused." +]) + +rm -f *.pcap + +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -n -c 1 -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap &], [0]) + +printf '.%.0s' {1..100} > foo +OVS_WAIT_UNTIL([ + ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo + c=$(cat sw0-p1-rej-icmp.pcap | grep \ +"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port dnsix unreachable" | uniq | wc -l) + test $c -eq 1 +]) + +rm -f *.pcap +# Now test for IPv6 UDP. +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -n -c 1 -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap &], [0]) + +OVS_WAIT_UNTIL([ + ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo + c=$(cat sw0-p2-rej-icmp6.pcap | grep \ +"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \ +aef0::3 udp port dnsix" | uniq | wc -l) + test $c -eq 1 +]) + + OVS_APP_EXIT_AND_WAIT([ovn-controller]) as ovn-sb @@ -4687,7 +4727,7 @@ ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port rp-public options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis rp-public hv1 ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ type=router options:router-port=rp-sw0 \ @@ -4768,19 +4808,28 @@ [2001:1db8:3333] ]) -kill $(pidof dibbler-server) - prefix=$(ovn-nbctl list logical_router_port rp-public | awk -F/ '/ipv6_prefix/{print substr($1,25,9)}' | sed 's/://g') ovn-nbctl set logical_router_port rp-sw0 options:prefix=false ovn-nbctl set logical_router_port rp-sw1 options:prefix=false -NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[95:4]]=0x${prefix} > public.pcap &]) +# Renew message +NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix} > renew.pcap &]) +# Reply message with Status OK +NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x07 and ip6[[81:4]]=0x${prefix} and ip6[[98:1]]=0x0d and ip6[[101:2]]=0x0000 > reply.pcap &]) + +OVS_WAIT_UNTIL([ + total_pkts=$(cat renew.pcap | wc -l) + test "${total_pkts}" = "1" +]) OVS_WAIT_UNTIL([ - total_pkts=$(cat public.pcap | wc -l) + total_pkts=$(cat reply.pcap | wc -l) test "${total_pkts}" = "1" ]) +kill $(pidof dibbler-server) +kill $(pidof tcpdump) + ovn-nbctl set logical_router_port rp-sw0 options:prefix=false ovn-nbctl clear logical_router_port rp-sw0 ipv6_prefix OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16)" = "[2001:1db8:3333]"]) @@ -4788,7 +4837,6 @@ [] ]) -kill $(pidof tcpdump) kill $(pidof ovn-controller) as ovn-sb @@ -5335,7 +5383,7 @@ ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ - -- set Logical_Router_Port rp-public options:redirect-chassis=hv1 + -- lrp-set-gateway-chassis rp-public hv1 ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ type=router options:router-port=rp-sw0 \ @@ -5440,7 +5488,7 @@ OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public']) AT_CHECK([ovn-nbctl remove Logical_Switch_Port public options qos_burst=1000]) -OVS_WAIT_UNTIL([test "$(tc qdisc show | grep 'htb 1: dev ovs-public')" == ""]) +OVS_WAIT_UNTIL([test "$(tc qdisc show | grep 'htb 1: dev ovs-public')" = ""]) kill $(pidof ovn-controller) diff -Nru ovn-20.09.0/tests/system-userspace-packet-type-aware.at ovn-20.12.0/tests/system-userspace-packet-type-aware.at --- ovn-20.09.0/tests/system-userspace-packet-type-aware.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/system-userspace-packet-type-aware.at 1970-01-01 00:00:00.000000000 +0000 @@ -1,425 +0,0 @@ -AT_BANNER([packet-type-aware pipeline]) - -AT_SETUP([ptap - triangle bridge setup with L2 and L3 GRE tunnels]) - -######################## -# GRE tunneling test setup for PTAP bridge -# -# 192.168.10.10 192.168.10.20 192.168.10.30 -# n1 n2 n3 -# |ovs-n1 |ovs-n2 |ovs-n3 -# +------o------+ +------o------+ +------o------+ -# | br-in1 | | br-in2 | | br-in3 | -# | | | (PTAP) | | | -# +------o------+ +------o------+ +------o------+ -# gre gre gre -# 10.0.0.1 (10.0.0.2) (10.0.0.3) -# (20.0.0.1) 20.0.0.2 (20.0.0.3) -# (30.0.0.1) LOCAL (30.0.0.2) LOCAL 30.0.0.3 LOCAL -# +-----------o-+ +-----------o-+ +-----------o-+ -# | br-p1 | | br-p2 | | br-p3 | -# +------o------+ +------o------+ +------o------+ -# p1-0 | | p2-0 | p3-0 -# p0-1 | | p0-2 | p0-3 -# +--o------------------------o-------------------------o--+ -# | br0 | -# +--------------------------------------------------------+ -#" -# GRE tunnel ports: -# No Bridge Name Packet-type Remote bridge & ports -# ----------------------------------------------------------------------- -# 1020 br-in1 gre-12 l2 br-in2 2010 (ptap) -# 1021 br-in1 gre-12_l3 l3 same -# 1030 br-in1 gre-13 l2 br-in3 3010 (l2) -# 2010 br-in2 gre-21 ptap br-in1 1020 (l2), 1021 (l3) -# 2030 br-in2 gre-23 ptap br-in3 3020 (l2), 3021 (l3) -# 3010 br-in3 gre-31 l2 br-in1 1030 (l2) -# 3020 br-in3 gre-32 l2 br-in2 2010 (ptap) -# 3021 br-in3 gre-32_l3 l3 same - - -AT_SKIP_IF([test $HAVE_NC = no]) -OVS_TRAFFIC_VSWITCHD_START() - -HWADDR_BRP1=aa:55:00:00:00:01 -HWADDR_BRP2=aa:55:00:00:00:02 -HWADDR_BRP3=aa:55:00:00:00:03 - -dnl Create veth ports to connect br0 with br-p1, br-p2 and br-p3 -AT_CHECK([ip link add p1-0 type veth peer name p0-1]) -AT_CHECK([ip link set p1-0 up]) -AT_CHECK([ip link set p0-1 up]) -AT_CHECK([ip link set dev p1-0 mtu 3300]) -AT_CHECK([ip link set dev p0-1 mtu 3300]) -on_exit 'ip link del p0-1' - -AT_CHECK([ip link add p2-0 type veth peer name p0-2]) -AT_CHECK([ip link set p2-0 up]) -AT_CHECK([ip link set p0-2 up]) -AT_CHECK([ip link set dev p2-0 mtu 3300]) -AT_CHECK([ip link set dev p0-2 mtu 3300]) -on_exit 'ip link del p0-2' - -AT_CHECK([ip link add p3-0 type veth peer name p0-3]) -AT_CHECK([ip link set p3-0 up]) -AT_CHECK([ip link set p0-3 up]) -AT_CHECK([ip link set dev p3-0 mtu 3300]) -AT_CHECK([ip link set dev p0-3 mtu 3300]) -on_exit 'ip link del p0-3' - -# Setup bridge infrastructure -AT_CHECK([ - ovs-vsctl add-br br-in1 -- \ - set bridge br-in1 datapath_type=netdev fail-mode=standalone - ovs-vsctl add-br br-in2 -- \ - set bridge br-in2 datapath_type=netdev fail-mode=standalone - ovs-vsctl add-br br-in3 -- \ - set bridge br-in3 datapath_type=netdev fail-mode=standalone - ovs-vsctl add-br br-p1 -- \ - set bridge br-p1 datapath_type=netdev fail-mode=standalone other-config:hwaddr=$HWADDR_BRP1 - ovs-vsctl add-br br-p2 -- \ - set bridge br-p2 datapath_type=netdev fail-mode=standalone other-config:hwaddr=$HWADDR_BRP2 - ovs-vsctl add-br br-p3 -- \ - set bridge br-p3 datapath_type=netdev fail-mode=standalone other-config:hwaddr=$HWADDR_BRP3 - - ovs-vsctl add-port br-p1 p1-0 -- set interface p1-0 ofport_request=2 - ovs-vsctl add-port br-p2 p2-0 -- set interface p2-0 ofport_request=2 - ovs-vsctl add-port br-p3 p3-0 -- set interface p3-0 ofport_request=2 - ovs-vsctl add-port br0 p0-1 -- set interface p0-1 ofport_request=10 - ovs-vsctl add-port br0 p0-2 -- set interface p0-2 ofport_request=20 - ovs-vsctl add-port br0 p0-3 -- set interface p0-3 ofport_request=30 - - # Populate the MAC table of br0 - ovs-ofctl del-flows br0 - ovs-ofctl add-flow br0 dl_dst=$HWADDR_BRP1,actions=10 - ovs-ofctl add-flow br0 dl_dst=$HWADDR_BRP2,actions=20 - ovs-ofctl add-flow br0 dl_dst=$HWADDR_BRP3,actions=30 - - ovs-ofctl del-flows br-in1 - ovs-ofctl del-flows br-in2 - ovs-ofctl del-flows br-in3 - ovs-ofctl del-flows br-p1 - ovs-ofctl del-flows br-p2 - ovs-ofctl del-flows br-p3 -], [0]) - -### Setup GRE tunnels -AT_CHECK([ - ovs-vsctl add-port br-in1 gre12 -- \ - set interface gre12 type=gre options:remote_ip=10.0.0.2 ofport_request=1020 - ovs-vsctl add-port br-in1 gre12_l3 -- \ - set interface gre12_l3 type=gre options:remote_ip=10.0.0.2 ofport_request=1021 options:packet_type=legacy_l3 - ovs-vsctl add-port br-in1 gre13 -- \ - set interface gre13 type=gre options:remote_ip=10.0.0.3 ofport_request=1030 - - ovs-vsctl add-port br-in2 gre21 -- \ - set interface gre21 type=gre options:remote_ip=20.0.0.1 ofport_request=2010 options:packet_type=ptap - ovs-vsctl add-port br-in2 gre23 -- \ - set interface gre23 type=gre options:remote_ip=20.0.0.3 ofport_request=2030 options:packet_type=ptap - - ovs-vsctl add-port br-in3 gre31 -- \ - set interface gre31 type=gre options:remote_ip=30.0.0.1 ofport_request=3010 - ovs-vsctl add-port br-in3 gre32 -- \ - set interface gre32 type=gre options:remote_ip=30.0.0.2 ofport_request=3020 - ovs-vsctl add-port br-in3 gre32_l3 -- \ - set interface gre32_l3 type=gre options:remote_ip=30.0.0.2 ofport_request=3021 options:packet_type=legacy_l3 -], [0], [stdout]) - -AT_CHECK([ - ip addr add 10.0.0.1/24 dev br-p1 - ip link set br-p1 up -], [0], [stdout]) - -AT_CHECK([ - ovs-appctl ovs/route/add 10.0.0.0/24 br-p1 - ovs-appctl tnl/arp/set br-p1 10.0.0.1 $HWADDR_BRP1 - ovs-appctl tnl/arp/set br-p1 10.0.0.2 $HWADDR_BRP2 - ovs-appctl tnl/arp/set br-p1 10.0.0.3 $HWADDR_BRP3 -], [0], [stdout]) - -AT_CHECK([ - ip addr add 20.0.0.2/24 dev br-p2 - ip link set br-p2 up -], [0], [stdout]) - -AT_CHECK([ - ovs-appctl ovs/route/add 20.0.0.0/24 br-p2 - ovs-appctl tnl/arp/set br-p2 20.0.0.1 $HWADDR_BRP1 - ovs-appctl tnl/arp/set br-p2 20.0.0.2 $HWADDR_BRP2 - ovs-appctl tnl/arp/set br-p2 20.0.0.3 $HWADDR_BRP3 -], [0], [stdout]) - -AT_CHECK([ - ip addr add 30.0.0.3/24 dev br-p3 - ip link set br-p3 up -], [0], [stdout]) - -AT_CHECK([ - ovs-appctl ovs/route/add 30.0.0.0/24 br-p3 - ovs-appctl tnl/arp/set br-p3 30.0.0.1 $HWADDR_BRP1 - ovs-appctl tnl/arp/set br-p3 30.0.0.2 $HWADDR_BRP2 - ovs-appctl tnl/arp/set br-p3 30.0.0.3 $HWADDR_BRP3 -], [0], [stdout]) - -AT_CHECK([ - ovs-appctl ovs/route/show | grep User: -], [0], [dnl -User: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 -User: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 -User: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 -]) - -AT_CHECK([ - ovs-appctl tnl/neigh/show | grep br-p | sort -], [0], [stdout]) - - -### Flows in br-pto twist TEP IP addresses in tunnel IP headers -AT_CHECK([ - ovs-ofctl add-flow br-p1 in_port:LOCAL,ip,actions=2 - ovs-ofctl add-flow br-p1 in_port:2,ip,nw_dst:20.0.0.1,actions=mod_nw_dst:10.0.0.1,mod_nw_src:10.0.0.2,LOCAL - ovs-ofctl add-flow br-p1 in_port:2,ip,nw_dst:30.0.0.1,actions=mod_nw_dst:10.0.0.1,mod_nw_src:10.0.0.3,LOCAL - - ovs-ofctl add-flow br-p2 in_port:LOCAL,ip,actions=2 - ovs-ofctl add-flow br-p2 in_port:2,ip,nw_dst:10.0.0.2,actions=mod_nw_dst:20.0.0.2,mod_nw_src:20.0.0.1,LOCAL - ovs-ofctl add-flow br-p2 in_port:2,ip,nw_dst:30.0.0.2,actions=mod_nw_dst:20.0.0.2,mod_nw_src:20.0.0.3,LOCAL - - ovs-ofctl add-flow br-p3 in_port:LOCAL,ip,actions=2 - ovs-ofctl add-flow br-p3 in_port:2,ip,nw_dst:10.0.0.3,actions=mod_nw_dst:30.0.0.3,mod_nw_src:30.0.0.1,LOCAL - ovs-ofctl add-flow br-p3 in_port:2,ip,nw_dst:20.0.0.3,actions=mod_nw_dst:30.0.0.3,mod_nw_src:30.0.0.2,LOCAL -], [0]) - -# Strips 'n_packets=...' from ovs-ofctl output. -strip_n_packets () { - sed 's/n_packets=[[0-9]]*, //' -} - -# Strips 'n_bytes=...' from ovs-ofctl output. -strip_n_bytes () { - sed 's/n_bytes=[[0-9]]*, //' -} - -AT_CHECK([ - ovs-ofctl dump-flows br-p1 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions - ovs-ofctl dump-flows br-p2 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions - ovs-ofctl dump-flows br-p3 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions -], [0], [dnl - ip,in_port=2,nw_dst=20.0.0.1 actions=mod_nw_dst:10.0.0.1,mod_nw_src:10.0.0.2,LOCAL - ip,in_port=2,nw_dst=30.0.0.1 actions=mod_nw_dst:10.0.0.1,mod_nw_src:10.0.0.3,LOCAL - ip,in_port=LOCAL actions=output:2 - ip,in_port=2,nw_dst=10.0.0.2 actions=mod_nw_dst:20.0.0.2,mod_nw_src:20.0.0.1,LOCAL - ip,in_port=2,nw_dst=30.0.0.2 actions=mod_nw_dst:20.0.0.2,mod_nw_src:20.0.0.3,LOCAL - ip,in_port=LOCAL actions=output:2 - ip,in_port=2,nw_dst=10.0.0.3 actions=mod_nw_dst:30.0.0.3,mod_nw_src:30.0.0.1,LOCAL - ip,in_port=2,nw_dst=20.0.0.3 actions=mod_nw_dst:30.0.0.3,mod_nw_src:30.0.0.2,LOCAL - ip,in_port=LOCAL actions=output:2 -]) - -### Setup test ports for traffic injection -N1_IP=192.168.10.10 -N2_IP=192.168.10.20 -N3_IP=192.168.10.30 -N1_MAC=aa:55:aa:55:00:01 -N2_MAC=aa:55:aa:55:00:02 -N3_MAC=aa:55:aa:55:00:03 -N1_OFPORT=10 -N2_OFPORT=20 -N3_OFPORT=30 - -ADD_NAMESPACES(ns1, ns2, ns3) -ADD_VETH(n1, ns1, br-in1, "$N1_IP/24", $N1_MAC) -ADD_VETH(n2, ns2, br-in2, "$N2_IP/24", $N2_MAC) -ADD_VETH(n3, ns3, br-in3, "$N3_IP/24", $N3_MAC) - -NS_EXEC([ns1], [arp -s $N2_IP $N2_MAC]) -NS_EXEC([ns1], [arp -s $N3_IP $N3_MAC]) - -NS_EXEC([ns2], [arp -s $N1_IP $N1_MAC]) -NS_EXEC([ns2], [arp -s $N3_IP $N3_MAC]) - -NS_EXEC([ns3], [arp -s $N2_IP $N2_MAC]) -NS_EXEC([ns3], [arp -s $N1_IP $N1_MAC]) - -AT_CHECK([ - ovs-vsctl set interface ovs-n1 ofport_request=$N1_OFPORT - ovs-vsctl set interface ovs-n2 ofport_request=$N2_OFPORT - ovs-vsctl set interface ovs-n3 ofport_request=$N3_OFPORT -], [0]) - -#N1_DPPORT=$(ovs-appctl dpif/show | grep "n1 10" | sed 's|.*/\([[0-9]]*\):.*|\1|') -#N2_DPPORT=$(ovs-appctl dpif/show | grep "n2 20" | sed 's|.*/\([[0-9]]*\):.*|\1|') -#N3_DPPORT=$(ovs-appctl dpif/show | grep "n3 30" | sed 's|.*/\([[0-9]]*\):.*|\1|') - -### Verify datapath configuration -AT_CHECK([ - ovs-appctl dpif/show | grep -v hit | sed 's/\t/ /g' -], [0], [dnl - br-in1: - br-in1 65534/2: (tap) - gre12 1020/14: (gre: remote_ip=10.0.0.2) - gre12_l3 1021/14: (gre: packet_type=legacy_l3, remote_ip=10.0.0.2) - gre13 1030/14: (gre: remote_ip=10.0.0.3) - ovs-n1 10/15: (system) - br-in2: - br-in2 65534/3: (tap) - gre21 2010/14: (gre: packet_type=ptap, remote_ip=20.0.0.1) - gre23 2030/14: (gre: packet_type=ptap, remote_ip=20.0.0.3) - ovs-n2 20/16: (system) - br-in3: - br-in3 65534/4: (tap) - gre31 3010/14: (gre: remote_ip=30.0.0.1) - gre32 3020/14: (gre: remote_ip=30.0.0.2) - gre32_l3 3021/14: (gre: packet_type=legacy_l3, remote_ip=30.0.0.2) - ovs-n3 30/17: (system) - br-p1: - br-p1 65534/5: (tap) - p1-0 2/8: (system) - br-p2: - br-p2 65534/6: (tap) - p2-0 2/9: (system) - br-p3: - br-p3 65534/7: (tap) - p3-0 2/10: (system) - br0: - br0 65534/1: (tap) - p0-1 10/11: (system) - p0-2 20/12: (system) - p0-3 30/13: (system) -]) - -### Test L3 forwarding flows -AT_CHECK([ - ovs-ofctl add-flow br-in1 ip,nw_dst=$N1_IP,actions=mod_dl_dst:$N1_MAC,$N1_OFPORT # Local route to N1 - ovs-ofctl add-flow br-in1 ip,nw_dst=$N2_IP,actions=1020 # Route to N2 via the L2 tunnel to br-in2 - ovs-ofctl add-flow br-in1 ip,nw_dst=$N3_IP,actions=1030 # Route to N3 direct through L2 tunnel - - ovs-ofctl add-flow br-in2 ip,nw_dst=$N2_IP,actions=mod_dl_dst:$N2_MAC,$N2_OFPORT # Local route to N2 for ethernet packets - ovs-ofctl add-flow br-in2 ip,nw_dst=$N1_IP,actions=2010 # Route to N1 for ethernet packet - ovs-ofctl add-flow br-in2 packet_type=\(1,0x800\),nw_dst=$N1_IP,actions=2010 # Route to N1 for IP packets - ovs-ofctl add-flow br-in2 ip,nw_dst=$N3_IP,actions=2010 # Indirect route to N3 via br-in1 for ethernet packet - ovs-ofctl add-flow br-in2 packet_type=\(1,0x800\),nw_dst=$N3_IP,actions=2030 # Direct route to N3 for IP packets - - ovs-ofctl add-flow br-in3 ip,nw_dst=$N3_IP,actions=mod_dl_dst:$N3_MAC,$N3_OFPORT # Local route to N1 - ovs-ofctl add-flow br-in3 ip,nw_dst=$N2_IP,actions=3020 # Route to N2 via the L2 tunnel - ovs-ofctl add-flow br-in3 ip,nw_dst=$N1_IP,actions=3021 # Route to N1 via br-in2 through L3 tunnel -], [0]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in1 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions -], [0], [dnl - ip,nw_dst=192.168.10.10 actions=mod_dl_dst:aa:55:aa:55:00:01,output:10 - ip,nw_dst=192.168.10.20 actions=output:1020 - ip,nw_dst=192.168.10.30 actions=output:1030 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in2 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions -], [0], [dnl - ip,nw_dst=192.168.10.10 actions=output:2010 - ip,nw_dst=192.168.10.20 actions=mod_dl_dst:aa:55:aa:55:00:02,output:20 - ip,nw_dst=192.168.10.30 actions=output:2010 - packet_type=(1,0x800),nw_dst=192.168.10.10 actions=output:2010 - packet_type=(1,0x800),nw_dst=192.168.10.30 actions=output:2030 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in3 | ofctl_strip | strip_n_packets | strip_n_bytes | sort | grep actions -], [0], [dnl - ip,nw_dst=192.168.10.10 actions=output:3021 - ip,nw_dst=192.168.10.20 actions=output:3020 - ip,nw_dst=192.168.10.30 actions=mod_dl_dst:aa:55:aa:55:00:03,output:30 -]) - - -# Ping between N1 and N3, via the L2 GRE tunnel between br-in1 and br-in3 -NS_CHECK_EXEC([ns1], [ping -q -c 3 -i 0.3 -w 2 $N3_IP | FORMAT_PING], [0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -sleep 1 - -AT_CHECK([ - ovs-ofctl dump-flows br-in1 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=252, ip,nw_dst=192.168.10.10 actions=mod_dl_dst:aa:55:aa:55:00:01,output:10 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.30 actions=output:1030 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in2 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=252, packet_type=(1,0x800),nw_dst=192.168.10.10 actions=output:2010 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in3 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.10 actions=output:3021 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.30 actions=mod_dl_dst:aa:55:aa:55:00:03,output:30 -]) - - -# Ping between N1 and N2, via the L2 GRE tunnel between br-in1 and br-in2 -NS_CHECK_EXEC([ns1], [ping -q -c 3 -i 0.3 -w 2 $N2_IP | FORMAT_PING], [0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -sleep 1 - -AT_CHECK([ - ovs-ofctl dump-flows br-in1 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.20 actions=output:1020 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.30 actions=output:1030 - n_packets=6, n_bytes=546, ip,nw_dst=192.168.10.10 actions=mod_dl_dst:aa:55:aa:55:00:01,output:10 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in2 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=252, packet_type=(1,0x800),nw_dst=192.168.10.10 actions=output:2010 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.10 actions=output:2010 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.20 actions=mod_dl_dst:aa:55:aa:55:00:02,output:20 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in3 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.10 actions=output:3021 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.30 actions=mod_dl_dst:aa:55:aa:55:00:03,output:30 -]) - - -# Ping between N3 and N2, via the L3 GRE tunnel between br-in3 and br-in2 -NS_CHECK_EXEC([ns3], [ping -q -c 3 -i 0.3 -w 2 $N1_IP | FORMAT_PING], [0], [dnl -3 packets transmitted, 3 received, 0% packet loss, time 0ms -]) - -sleep 1 - -AT_CHECK([ - ovs-ofctl dump-flows br-in1 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.20 actions=output:1020 - n_packets=6, n_bytes=588, ip,nw_dst=192.168.10.30 actions=output:1030 - n_packets=9, n_bytes=798, ip,nw_dst=192.168.10.10 actions=mod_dl_dst:aa:55:aa:55:00:01,output:10 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in2 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.10 actions=output:2010 - n_packets=3, n_bytes=294, ip,nw_dst=192.168.10.20 actions=mod_dl_dst:aa:55:aa:55:00:02,output:20 - n_packets=6, n_bytes=504, packet_type=(1,0x800),nw_dst=192.168.10.10 actions=output:2010 -]) - -AT_CHECK([ - ovs-ofctl dump-flows br-in3 | ofctl_strip | sort | grep n_packets -], [0], [dnl - n_packets=6, n_bytes=588, ip,nw_dst=192.168.10.10 actions=output:3021 - n_packets=6, n_bytes=588, ip,nw_dst=192.168.10.30 actions=mod_dl_dst:aa:55:aa:55:00:03,output:30 -]) - - -OVS_TRAFFIC_VSWITCHD_STOP -AT_CLEANUP diff -Nru ovn-20.09.0/tests/test-ovn.c ovn-20.12.0/tests/test-ovn.c --- ovn-20.09.0/tests/test-ovn.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/test-ovn.c 2020-12-17 17:53:32.000000000 +0000 @@ -191,6 +191,8 @@ dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32"); dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32"); dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains"); + dhcp_opt_add(dhcp_opts, "bootfile_name_alt", 254, "str"); + dhcp_opt_add(dhcp_opts, "broadcast_address", 28, "ipv4"); /* DHCPv6 options. */ hmap_init(dhcpv6_opts); @@ -917,7 +919,7 @@ } else if (operation >= OP_SIMPLIFY) { modified = expr_simplify(expr_clone(expr)); modified = expr_evaluate_condition( - expr_clone(modified), tree_shape_is_chassis_resident_cb, + modified, tree_shape_is_chassis_resident_cb, NULL, NULL); ovs_assert(expr_honors_invariants(modified)); @@ -1341,6 +1343,9 @@ .output_ptable = OFTABLE_SAVE_INPORT, .mac_bind_ptable = OFTABLE_MAC_BINDING, .mac_lookup_ptable = OFTABLE_MAC_LOOKUP, + .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN, + .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY, + .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP, }; struct ofpbuf ofpacts; ofpbuf_init(&ofpacts, 0); diff -Nru ovn-20.09.0/tests/testsuite.at ovn-20.12.0/tests/testsuite.at --- ovn-20.09.0/tests/testsuite.at 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/tests/testsuite.at 2020-12-17 17:53:32.000000000 +0000 @@ -20,6 +20,7 @@ m4_include([tests/ovsdb-macros.at]) m4_include([tests/ofproto-macros.at]) m4_include([tests/ovn-macros.at]) +m4_include([tests/network-functions.at]) m4_include([tests/ovn.at]) m4_include([tests/ovn-performance.at]) diff -Nru ovn-20.09.0/TODO.rst ovn-20.12.0/TODO.rst --- ovn-20.09.0/TODO.rst 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/TODO.rst 2020-12-17 17:53:32.000000000 +0000 @@ -152,3 +152,11 @@ . This causes an additional hashtable lookup in parse_port_group() which can be avoided when we are sure that the Southbound DB uses the new format. + +* IP Multicast Relay + + * When connecting bridged logical switches (localnet) to logical routers + with IP Multicast Relay enabled packets might get duplicated. We need + to find a way of determining if routing has already been executed (on a + different hypervisor) for the IP multicast packet being processed locally + in the router pipeline. diff -Nru ovn-20.09.0/.travis/linux-build.sh ovn-20.12.0/.travis/linux-build.sh --- ovn-20.09.0/.travis/linux-build.sh 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/.travis/linux-build.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -#!/bin/bash - -set -o errexit -set -x - -CFLAGS="-Werror" -SPARSE_FLAGS="" -EXTRA_OPTS="" -TARGET="x86_64-native-linuxapp-gcc" - -function configure_ovs() -{ - git clone https://github.com/openvswitch/ovs.git ovs_src - pushd ovs_src - ./boot.sh && ./configure $* || { cat config.log; exit 1; } - make -j4 || { cat config.log; exit 1; } - popd -} - -function configure_ovn() -{ - configure_ovs $* - ./boot.sh && ./configure --with-ovs-source=$PWD/ovs_src $* || \ - { cat config.log; exit 1; } -} - -OPTS="$EXTRA_OPTS $*" - -if [ "$CC" = "clang" ]; then - export OVS_CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" -elif [[ $BUILD_ENV =~ "-m32" ]]; then - # Disable sparse for 32bit builds on 64bit machine - export OVS_CFLAGS="$CFLAGS $BUILD_ENV" -else - OPTS="$OPTS --enable-sparse" - export OVS_CFLAGS="$CFLAGS $BUILD_ENV $SPARSE_FLAGS" -fi - -if [ "$TESTSUITE" ]; then - # 'distcheck' will reconfigure with required options. - # Now we only need to prepare the Makefile without sparse-wrapped CC. - configure_ovn - - export DISTCHECK_CONFIGURE_FLAGS="$OPTS --with-ovs-source=$PWD/ovs_src" - if ! make distcheck -j4 TESTSUITEFLAGS="-j4" RECHECK=yes; then - # testsuite.log is necessary for debugging. - cat */_build/sub/tests/testsuite.log - exit 1 - fi -else - configure_ovn $OPTS - make -j4 || { cat config.log; exit 1; } -fi - -exit 0 diff -Nru ovn-20.09.0/.travis/linux-prepare.sh ovn-20.12.0/.travis/linux-prepare.sh --- ovn-20.09.0/.travis/linux-prepare.sh 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/.travis/linux-prepare.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -#!/bin/bash - -set -ev - -# Build and install sparse. -# -# Explicitly disable sparse support for llvm because some travis -# environments claim to have LLVM (llvm-config exists and works) but -# linking against it fails. -# Disabling sqlite support because sindex build fails and we don't -# really need this utility being installed. -git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git -cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd .. - -pip install --disable-pip-version-check --user six flake8 hacking -pip install --user --upgrade docutils diff -Nru ovn-20.09.0/.travis/osx-build.sh ovn-20.12.0/.travis/osx-build.sh --- ovn-20.09.0/.travis/osx-build.sh 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/.travis/osx-build.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -#!/bin/bash - -set -o errexit - -CFLAGS="-Werror $CFLAGS" -EXTRA_OPTS="" - -function configure_ovs() -{ - git clone https://github.com/openvswitch/ovs.git ovs_src - pushd ovs_src - ./boot.sh && ./configure $* - make -j4 || { cat config.log; exit 1; } - popd -} - -function configure_ovn() -{ - configure_ovs $* - ./boot.sh && ./configure $* --with-ovs-source=$PWD/ovs_src -} - -configure_ovn $EXTRA_OPTS $* - -if [ "$CC" = "clang" ]; then - set make CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" -else - set make CFLAGS="$CFLAGS $BUILD_ENV" -fi -if ! "$@"; then - cat config.log - exit 1 -fi -if [ "$TESTSUITE" ] && [ "$CC" != "clang" ]; then - export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS --with-ovs-source=$PWD/ovs_src" - if ! make distcheck RECHECK=yes; then - # testsuite.log is necessary for debugging. - cat */_build/sub/tests/testsuite.log - exit 1 - fi -fi - -exit 0 diff -Nru ovn-20.09.0/.travis/osx-prepare.sh ovn-20.12.0/.travis/osx-prepare.sh --- ovn-20.09.0/.travis/osx-prepare.sh 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/.travis/osx-prepare.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -#!/bin/bash -set -ev -pip3 install --user six -pip3 install --user --upgrade docutils diff -Nru ovn-20.09.0/.travis.yml ovn-20.12.0/.travis.yml --- ovn-20.09.0/.travis.yml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -language: c -compiler: - - gcc - - clang - -os: - - linux - -addons: - apt: - packages: - - bc - - gcc-multilib - - libssl-dev - - llvm-dev - - libjemalloc1 - - libjemalloc-dev - - libnuma-dev - - python-sphinx - - libelf-dev - - selinux-policy-dev - - libunbound-dev - - libunbound-dev:i386 - -before_install: ./.travis/${TRAVIS_OS_NAME}-prepare.sh - -before_script: export PATH=$PATH:$HOME/bin - -env: - - OPTS="--disable-ssl" - - TESTSUITE=1 KERNEL=4.18.20 - - TESTSUITE=1 OPTS="--enable-shared" - - BUILD_ENV="-m32" OPTS="--disable-ssl" - - TESTSUITE=1 LIBS=-ljemalloc - -matrix: - include: - - os: osx - compiler: clang - env: OPTS="--disable-ssl" - -script: ./.travis/${TRAVIS_OS_NAME}-build.sh $OPTS - -notifications: - email: - recipients: - - ovs-build@openvswitch.org diff -Nru ovn-20.09.0/utilities/ovn-detrace.in ovn-20.12.0/utilities/ovn-detrace.in --- ovn-20.09.0/utilities/ovn-detrace.in 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-detrace.in 2020-12-17 17:53:32.000000000 +0000 @@ -98,9 +98,12 @@ OVSDB.STREAM_TIMEOUT_MS) if not error and strm: break + + sys.stderr.write('Unable to connect to {}, error: {}\n'.format(r, + os.strerror(error))) strm = None if not strm: - raise Exception("Unable to connect to %s" % self.remote) + raise Exception('Unable to connect to %s' % self.remote) rpc = jsonrpc.Connection(strm) req = jsonrpc.Message.create_request('get_schema', [schema_name]) @@ -117,18 +120,27 @@ def _find_rows(self, table_name, find_fn): return filter(find_fn, self.get_table(table_name).rows.values()) - def _find_rows_by_name(self, table_name, value): + def find_rows_by_name(self, table_name, value): return self._find_rows(table_name, lambda row: row.name == value) def find_rows_by_partial_uuid(self, table_name, value): return self._find_rows(table_name, lambda row: str(row.uuid).startswith(value)) + def get_first_record(self, table_name): + table_rows = self.get_table(table_name).rows.values() + if len(table_rows) == 0: + return None + return next(iter(table_rows)) + class CookieHandler(object): def __init__(self, db, table): self._db = db self._table = table + def print(self, msg): + print_h(msg) + def get_records(self, cookie): return [] @@ -258,8 +270,12 @@ ] def print_record(self, lflow): - print_p('Logical datapath: %s [%s]' % - (datapath_str(lflow.logical_datapath), lflow.pipeline)) + print_p('Logical datapaths:') + datapaths = lflow.logical_datapath + if lflow.logical_dp_group: + datapaths.extend(lflow.logical_dp_group[0].datapaths) + for datapath in datapaths: + print_p(' %s [%s]' % (datapath_str(datapath), lflow.pipeline)) print_p('Logical flow: table=%s (%s), priority=%s, ' 'match=(%s), actions=(%s)' % (lflow.table_id, lflow.external_ids.get('stage-name'), @@ -316,14 +332,37 @@ def print_record(self, chassis): print_p('Chassis: %s' % (chassis_str([chassis]))) +class SBLoadBalancerHandler(CookieHandlerByUUUID): + def __init__(self, ovnsb_db): + super(SBLoadBalancerHandler, self).__init__(ovnsb_db, 'Load_Balancer') + + def print_record(self, lb): + print_p('Load Balancer: %s protocol %s vips %s' % ( + lb.name, lb.protocol, lb.vips)) + class OvsInterfaceHandler(CookieHandler): def __init__(self, ovs_db): super(OvsInterfaceHandler, self).__init__(ovs_db, 'Interface') + # Store the interfaces connected to the integration bridge in a dict + # indexed by ofport. + br = self.get_br_int() + self._intfs = { + i.ofport[0] : i for p in br.ports + for i in p.interfaces if len(i.ofport) > 0 + } + + def get_br_int(self): + ovsrec = self._db.get_first_record('Open_vSwitch') + if ovsrec: + br_name = ovsrec.external_ids.get('ovn-bridge', 'br-int') + else: + br_name = 'br-int' + return next(iter(self._db.find_rows_by_name('Bridge', br_name))) + def get_records(self, ofport): - return self._db._find_rows(self._table, - lambda intf: len(intf.ofport) > 0 and - str(intf.ofport[0]) == ofport) + intf = self._intfs.get(int(ofport)) + return [intf] if intf else [] def print_record(self, intf): print_p('OVS Interface: %s (%s)' % @@ -331,7 +370,8 @@ def print_record_from_cookie(ovnnb_db, cookie_handlers, cookie): for handler in cookie_handlers: - for i, record in enumerate(handler.get_records(cookie)): + records = list(handler.get_records(cookie)) + for i, record in enumerate(records): if i > 0: handler.print('[Duplicate uuid cookie]') handler.print_record(record) @@ -424,7 +464,8 @@ PortBindingHandler(ovsdb_ovnsb), MacBindingHandler(ovsdb_ovnsb), MulticastGroupHandler(ovsdb_ovnsb), - ChassisHandler(ovsdb_ovnsb) + ChassisHandler(ovsdb_ovnsb), + SBLoadBalancerHandler(ovsdb_ovnsb) ] regex_cookie = re.compile(r'^.*cookie 0x([0-9a-fA-F]+)') diff -Nru ovn-20.09.0/utilities/ovn-nbctl.8.xml ovn-20.12.0/utilities/ovn-nbctl.8.xml --- ovn-20.09.0/utilities/ovn-nbctl.8.xml 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-nbctl.8.xml 2020-12-17 17:53:32.000000000 +0000 @@ -737,8 +737,9 @@

    Logical Router Policy Commands

    -
    lr-policy-add router priority - match action [nexthop] +
    [--may-exist]lr-policy-add + router priority match + action [nexthop] [options key=value]]

    @@ -754,6 +755,13 @@ The supported option is : pkt_mark.

    +

    + If --may-exist is specified, adding a duplicated + routing policy with the same priority and match string is not + really created. Without --may-exist, adding a + duplicated routing policy results in error. +

    +

    The following example shows a policy to lr1, which will drop packets from192.168.100.0/24. @@ -771,8 +779,8 @@

    -
    lr-policy-del router [{priority | uuid} - [match]]
    +
    [--if-exists] lr-policy-del + router [{priority | uuid} [match]]

    Deletes polices from router. If only router @@ -784,7 +792,9 @@

    If router and uuid are supplied, then the - policy with sepcified uuid is deleted. + policy with sepcified uuid is deleted. It is an error if + uuid does not exist, unless --if-exists + is specified.

    diff -Nru ovn-20.09.0/utilities/ovn-nbctl.c ovn-20.12.0/utilities/ovn-nbctl.c --- ovn-20.09.0/utilities/ovn-nbctl.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-nbctl.c 2020-12-17 17:53:32.000000000 +0000 @@ -139,7 +139,8 @@ nbctl_cmd_init(); /* Check if options are set via env var. */ - argv = ovs_cmdl_env_parse_all(&argc, argv, getenv("OVN_NBCTL_OPTIONS")); + char **argv_ = ovs_cmdl_env_parse_all(&argc, argv, + getenv("OVN_NBCTL_OPTIONS")); /* ovn-nbctl has three operation modes: * @@ -155,13 +156,11 @@ * depends on the command line. So, for now we transform the command line * into a parsed form, and figure out what to do with it later. */ - char *args = process_escape_args(argv); struct ovs_cmdl_parsed_option *parsed_options; size_t n_parsed_options; - char *error_s = ovs_cmdl_parse_all(argc, argv, get_all_options(), + char *error_s = ovs_cmdl_parse_all(argc, argv_, get_all_options(), &parsed_options, &n_parsed_options); if (error_s) { - free(args); ctl_fatal("%s", error_s); } @@ -177,7 +176,7 @@ || has_option(parsed_options, n_parsed_options, 'u')) && !will_detach(parsed_options, n_parsed_options)) { nbctl_client(socket_name, parsed_options, n_parsed_options, - argc, argv); + argc, argv_); } /* Parse command line. */ @@ -188,7 +187,6 @@ bool daemon_mode = false; if (get_detach()) { if (argc != optind) { - free(args); ctl_fatal("non-option arguments not supported with --detach " "(use --help for help)"); } @@ -202,18 +200,19 @@ ovsdb_idl_set_leader_only(idl, leader_only); if (daemon_mode) { - server_loop(idl, argc, argv); + server_loop(idl, argc, argv_); } else { struct ctl_command *commands; size_t n_commands; char *error; - error = ctl_parse_commands(argc - optind, argv + optind, + error = ctl_parse_commands(argc - optind, argv_ + optind, &local_options, &commands, &n_commands); if (error) { - free(args); ctl_fatal("%s", error); } + + char *args = process_escape_args(argv_); VLOG(ctl_might_write_to_db(commands, n_commands) ? VLL_INFO : VLL_DBG, "Called as %s", args); @@ -221,15 +220,13 @@ error = run_prerequisites(commands, n_commands, idl); if (error) { - free(args); - ctl_fatal("%s", error); + goto cleanup; } error = main_loop(args, commands, n_commands, idl, NULL); - if (error) { - free(args); - ctl_fatal("%s", error); - } + +cleanup: + free(args); struct ctl_command *c; for (c = commands; c < &commands[n_commands]; c++) { @@ -239,12 +236,18 @@ shash_destroy_free_data(&c->options); } free(commands); + if (error) { + ctl_fatal("%s", error); + } } ovsdb_idl_destroy(idl); idl = the_idl = NULL; - free(args); + for (int i = 0; i < argc; i++) { + free(argv_[i]); + } + free(argv_); exit(EXIT_SUCCESS); } @@ -617,6 +620,7 @@ qos-list SWITCH print QoS rules for SWITCH\n\ \n\ Meter commands:\n\ + [--fair]\n\ meter-add NAME ACTION RATE UNIT [BURST]\n\ add a meter\n\ meter-del [NAME] remove meters\n\ @@ -768,7 +772,7 @@ ha-chassis-group-list List the HA chassis groups\n\ ha-chassis-group-add-chassis GRP CHASSIS [PRIORITY] Adds an HA\ chassis with optional PRIORITY to the HA chassis group GRP\n\ - ha-chassis-group-del-chassis GRP CHASSIS Deletes the HA chassis\ + ha-chassis-group-remove-chassis GRP CHASSIS Removes the HA chassis\ CHASSIS from the HA chassis group GRP\n\ \n\ %s\ @@ -1683,6 +1687,7 @@ error = lsp_contains_duplicates(ls, lsp, ctx->argv[i]); if (error) { ctl_error(ctx, "%s", error); + free(error); return; } } @@ -2693,7 +2698,12 @@ for (size_t i = 0; i < n_meters; i++) { meter = meters[i]; - ds_put_format(&ctx->output, "%s: bands:\n", meter->name); + ds_put_format(&ctx->output, "%s:", meter->name); + if (meter->fair) { + ds_put_format(&ctx->output, " (%s)", + *meter->fair ? "fair" : "shared"); + } + ds_put_format(&ctx->output, " bands:\n"); for (size_t j = 0; j < meter->n_bands; j++) { const struct nbrec_meter_band *band = meter->bands[j]; @@ -2770,6 +2780,12 @@ nbrec_meter_set_name(meter, name); nbrec_meter_set_unit(meter, unit); nbrec_meter_set_bands(meter, &band, 1); + + /* Fair option */ + bool fair = shash_find(&ctx->options, "--fair") != NULL; + if (fair) { + nbrec_meter_set_fair(meter, &fair, 1); + } } static void @@ -3559,7 +3575,7 @@ free(error); return NULL; } - return normalize_ipv6_prefix(ipv6, plen); + return normalize_ipv6_prefix(&ipv6, plen); } /* The caller must free the returned string. */ @@ -3596,7 +3612,7 @@ return NULL; } - return normalize_ipv6_prefix(ipv6, 128); + return normalize_ipv6_prefix(&ipv6, 128); } /* Similar to normalize_prefix_str but must be an un-masked address. @@ -3648,12 +3664,15 @@ /* 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"); for (int i = 0; i < lr->n_policies; i++) { const struct nbrec_logical_router_policy *policy = lr->policies[i]; if (policy->priority == priority && !strcmp(policy->match, ctx->argv[3])) { - ctl_error(ctx, "Same routing policy already existed on the " - "logical router %s.", ctx->argv[1]); + if (!may_exist) { + ctl_error(ctx, "Same routing policy already existed on the " + "logical router %s.", ctx->argv[1]); + } return; } } @@ -3684,7 +3703,9 @@ smap_add(&options, key, value); } else { ctl_error(ctx, "No value specified for the option : %s", key); + smap_destroy(&options); free(key); + free(next_hop); return; } free(key); @@ -3733,7 +3754,6 @@ ctx->error = error; return; } - } /* If uuid was specified, delete routing policy with the * specified uuid. */ @@ -3751,7 +3771,10 @@ } } if (n_policies == lr->n_policies) { - ctl_error(ctx, "Logical router policy uuid is not found."); + if (!shash_find(&ctx->options, "--if-exists")) { + ctl_error(ctx, "Logical router policy uuid is not found."); + } + free(new_policies); return; } @@ -4306,7 +4329,7 @@ if (strcmp(nat_type, "dnat_and_snat") && stateless) { ctl_error(ctx, "stateless is not applicable to dnat or snat types"); - return; + goto cleanup; } int is_snat = !strcmp("snat", nat_type); @@ -4596,8 +4619,11 @@ } else { nbrec_nat_set_allowed_ext_ips(nat, addr_set); } + free(nat_ip); + free(old_ip); return; } + free(old_ip); } if (!nat_found) { @@ -6103,8 +6129,29 @@ [NBREC_TABLE_ACL].row_ids[0] = {&nbrec_acl_col_name, NULL, NULL}, + [NBREC_TABLE_HA_CHASSIS].row_ids[0] + = {&nbrec_ha_chassis_col_chassis_name, NULL, NULL}, + [NBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0] = {&nbrec_ha_chassis_group_col_name, NULL, NULL}, + + [NBREC_TABLE_LOAD_BALANCER].row_ids[0] + = {&nbrec_load_balancer_col_name, NULL, NULL}, + + [NBREC_TABLE_LOAD_BALANCER_HEALTH_CHECK].row_ids[0] + = {&nbrec_load_balancer_health_check_col_vip, NULL, NULL}, + + [NBREC_TABLE_FORWARDING_GROUP].row_ids[0] + = {&nbrec_forwarding_group_col_name, NULL, NULL}, + + [NBREC_TABLE_METER].row_ids[0] + = {&nbrec_meter_col_name, NULL, NULL}, + + [NBREC_TABLE_NAT].row_ids[0] + = {&nbrec_nat_col_external_ip, NULL, NULL}, + + [NBREC_TABLE_CONNECTION].row_ids[0] + = {&nbrec_connection_col_target, NULL, NULL}, }; static char * @@ -6332,7 +6379,7 @@ NBREC_NB_GLOBAL_FOR_EACH (nb, idl) { int64_t cur_cfg = (wait_type == NBCTL_WAIT_SB ? nb->sb_cfg - : nb->hv_cfg); + : MIN(nb->sb_cfg, nb->hv_cfg)); if (cur_cfg >= next_cfg) { if (print_wait_time) { printf("Time spent on processing nb_cfg %"PRId64":\n", @@ -6383,12 +6430,6 @@ the_idl_txn = NULL; ovsdb_symbol_table_destroy(symtab); - for (c = commands; c < &commands[n_commands]; c++) { - ds_destroy(&c->output); - table_destroy(c->table); - free(c->table); - } - return error; } @@ -6439,7 +6480,7 @@ /* meter commands. */ { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", NULL, - nbctl_meter_add, NULL, "", RW }, + nbctl_meter_add, NULL, "--fair", RW }, { "meter-del", 0, 1, "[NAME]", NULL, nbctl_meter_del, NULL, "", RW }, { "meter-list", 0, 0, "", NULL, nbctl_meter_list, NULL, "", RO }, @@ -6529,9 +6570,9 @@ /* Policy commands */ { "lr-policy-add", 4, INT_MAX, "ROUTER PRIORITY MATCH ACTION [NEXTHOP] [OPTIONS - KEY=VALUE ...]", - NULL, nbctl_lr_policy_add, NULL, "", RW }, + NULL, nbctl_lr_policy_add, NULL, "--may-exist", RW }, { "lr-policy-del", 1, 3, "ROUTER [{PRIORITY | UUID} [MATCH]]", NULL, - nbctl_lr_policy_del, NULL, "", RW }, + nbctl_lr_policy_del, NULL, "--if-exists", RW }, { "lr-policy-list", 1, 1, "ROUTER", NULL, nbctl_lr_policy_list, NULL, "", RO }, @@ -6804,17 +6845,18 @@ } else { ds_put_cstr(&output, ds_cstr_ro(&c->output)); } - - ds_destroy(&c->output); - table_destroy(c->table); - free(c->table); } unixctl_command_reply(conn, ds_cstr_ro(&output)); ds_destroy(&output); out: free(error); - for (struct ctl_command *c = commands; c < &commands[n_commands]; c++) { + + struct ctl_command *c; + for (c = commands; c < &commands[n_commands]; c++) { + ds_destroy(&c->output); + table_destroy(c->table); + free(c->table); shash_destroy_free_data(&c->options); } free(commands); diff -Nru ovn-20.09.0/utilities/ovn-sbctl.c ovn-20.12.0/utilities/ovn-sbctl.c --- ovn-20.09.0/utilities/ovn-sbctl.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-sbctl.c 2020-12-17 17:53:32.000000000 +0000 @@ -109,13 +109,14 @@ sbctl_cmd_init(); /* Check if options are set via env var. */ - argv = ovs_cmdl_env_parse_all(&argc, argv, getenv("OVN_SBCTL_OPTIONS")); + char **argv_ = ovs_cmdl_env_parse_all(&argc, argv, + getenv("OVN_SBCTL_OPTIONS")); /* Parse command line. */ - char *args = process_escape_args(argv); + char *args = process_escape_args(argv_); shash_init(&local_options); - parse_options(argc, argv, &local_options); - char *error = ctl_parse_commands(argc - optind, argv + optind, + parse_options(argc, argv_, &local_options); + char *error = ctl_parse_commands(argc - optind, argv_ + optind, &local_options, &commands, &n_commands); if (error) { ctl_fatal("%s", error); @@ -149,8 +150,7 @@ if (seqno != ovsdb_idl_get_seqno(idl)) { seqno = ovsdb_idl_get_seqno(idl); if (do_sbctl(args, commands, n_commands, idl)) { - free(args); - exit(EXIT_SUCCESS); + break; } } @@ -159,6 +159,13 @@ poll_block(); } } + + for (int i = 0; i < argc; i++) { + free(argv_[i]); + } + free(argv_); + free(args); + exit(EXIT_SUCCESS); } static void @@ -521,6 +528,7 @@ ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath); + ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_pipeline); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_actions); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_priority); @@ -528,6 +536,8 @@ ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_match); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_external_ids); + ovsdb_idl_add_column(ctx->idl, &sbrec_logical_dp_group_col_datapaths); + ovsdb_idl_add_column(ctx->idl, &sbrec_datapath_binding_col_external_ids); ovsdb_idl_add_column(ctx->idl, &sbrec_ip_multicast_col_datapath); @@ -542,6 +552,11 @@ ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_logical_port); ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_ip); ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_mac); + + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapaths); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_vips); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_name); + ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_protocol); } static struct cmd_show_table cmd_show_tables[] = { @@ -705,38 +720,43 @@ OVS_NOT_REACHED(); } +struct sbctl_lflow { + const struct sbrec_logical_flow *lflow; + const struct sbrec_datapath_binding *dp; +}; + static int -lflow_cmp(const void *lf1_, const void *lf2_) +sbctl_lflow_cmp(const void *a_, const void *b_) { - const struct sbrec_logical_flow *const *lf1p = lf1_; - const struct sbrec_logical_flow *const *lf2p = lf2_; - const struct sbrec_logical_flow *lf1 = *lf1p; - const struct sbrec_logical_flow *lf2 = *lf2p; - - int pl1 = pipeline_encode(lf1->pipeline); - int pl2 = pipeline_encode(lf2->pipeline); - -#define CMP(expr) \ - do { \ - int res; \ - res = (expr); \ - if (res) { \ - return res; \ - } \ - } while (0) - - CMP(uuid_compare_3way(&lf1->logical_datapath->header_.uuid, - &lf2->logical_datapath->header_.uuid)); - CMP(pl1 - pl2); - CMP(lf1->table_id > lf2->table_id ? 1 : - (lf1->table_id < lf2->table_id ? -1 : 0)); - CMP(lf1->priority > lf2->priority ? -1 : - (lf1->priority < lf2->priority ? 1 : 0)); - CMP(strcmp(lf1->match, lf2->match)); + const struct sbctl_lflow *a_ctl_lflow = a_; + const struct sbctl_lflow *b_ctl_lflow = b_; -#undef CMP + const struct sbrec_logical_flow *a = a_ctl_lflow->lflow; + const struct sbrec_logical_flow *b = b_ctl_lflow->lflow; - return 0; + const struct sbrec_datapath_binding *adb = a_ctl_lflow->dp; + const struct sbrec_datapath_binding *bdb = b_ctl_lflow->dp; + const char *a_name = smap_get_def(&adb->external_ids, "name", ""); + const char *b_name = smap_get_def(&bdb->external_ids, "name", ""); + int cmp = strcmp(a_name, b_name); + if (cmp) { + return cmp; + } + + cmp = uuid_compare_3way(&adb->header_.uuid, &bdb->header_.uuid); + if (cmp) { + return cmp; + } + + int a_pipeline = pipeline_encode(a->pipeline); + int b_pipeline = pipeline_encode(b->pipeline); + return (a_pipeline > b_pipeline ? 1 + : a_pipeline < b_pipeline ? -1 + : a->table_id > b->table_id ? 1 + : a->table_id < b->table_id ? -1 + : a->priority > b->priority ? -1 + : a->priority < b->priority ? 1 + : strcmp(a->match, b->match)); } static char * @@ -1011,6 +1031,85 @@ } static void +cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn, + const struct sbrec_datapath_binding *datapath, + bool stats, bool print_uuid) +{ + const struct sbrec_load_balancer *lb; + const struct sbrec_load_balancer *lb_prev = NULL; + SBREC_LOAD_BALANCER_FOR_EACH (lb, ctx->idl) { + bool dp_found = false; + if (datapath) { + size_t i; + for (i = 0; i < lb->n_datapaths; i++) { + if (datapath == lb->datapaths[i]) { + dp_found = true; + break; + } + } + if (!dp_found) { + continue; + } + } + + if (!lb_prev) { + printf("\nLoad Balancers:\n"); + } + + printf(" "); + print_uuid_part(&lb->header_.uuid, print_uuid); + printf("name=\"%s\", protocol=\"%s\", ", lb->name, lb->protocol); + if (!dp_found) { + for (size_t i = 0; i < lb->n_datapaths; i++) { + print_vflow_datapath_name(lb->datapaths[i], true); + } + } + + printf("\n vips:\n"); + struct smap_node *node; + SMAP_FOR_EACH (node, &lb->vips) { + printf(" %s = %s\n", node->key, node->value); + } + printf("\n"); + + if (vconn) { + sbctl_dump_openflow(vconn, &lb->header_.uuid, stats); + } + + lb_prev = lb; + } +} + +static bool +datapath_group_contains_datapath(const struct sbrec_logical_dp_group *g, + const struct sbrec_datapath_binding *dp) +{ + if (!g || !dp) { + return false; + } + for (size_t i = 0; i < g->n_datapaths; i++) { + if (g->datapaths[i] == dp) { + return true; + } + } + return false; +} + +static void +sbctl_lflow_add(struct sbctl_lflow **lflows, + size_t *n_flows, size_t *n_capacity, + const struct sbrec_logical_flow *lflow, + const struct sbrec_datapath_binding *dp) +{ + if (*n_flows == *n_capacity) { + *lflows = x2nrealloc(*lflows, n_capacity, sizeof **lflows); + } + (*lflows)[*n_flows].lflow = lflow; + (*lflows)[*n_flows].dp = dp; + (*n_flows)++; +} + +static void cmd_lflow_list(struct ctl_context *ctx) { const struct sbrec_datapath_binding *datapath = NULL; @@ -1041,31 +1140,42 @@ struct vconn *vconn = sbctl_open_vconn(&ctx->options); bool stats = shash_find(&ctx->options, "--stats") != NULL; - const struct sbrec_logical_flow **lflows = NULL; + struct sbctl_lflow *lflows = NULL; size_t n_flows = 0; size_t n_capacity = 0; const struct sbrec_logical_flow *lflow; + const struct sbrec_logical_dp_group *dp_group; SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) { - if (datapath && lflow->logical_datapath != datapath) { + if (datapath + && lflow->logical_datapath != datapath + && !datapath_group_contains_datapath(lflow->logical_dp_group, + datapath)) { continue; } - - if (n_flows == n_capacity) { - lflows = x2nrealloc(lflows, &n_capacity, sizeof *lflows); + if (datapath) { + sbctl_lflow_add(&lflows, &n_flows, &n_capacity, lflow, datapath); + continue; + } + if (lflow->logical_datapath) { + sbctl_lflow_add(&lflows, &n_flows, &n_capacity, + lflow, lflow->logical_datapath); + } + dp_group = lflow->logical_dp_group; + for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) { + sbctl_lflow_add(&lflows, &n_flows, &n_capacity, + lflow, dp_group->datapaths[i]); } - lflows[n_flows] = lflow; - n_flows++; } if (n_flows) { - qsort(lflows, n_flows, sizeof *lflows, lflow_cmp); + qsort(lflows, n_flows, sizeof *lflows, sbctl_lflow_cmp); } bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL; - const struct sbrec_logical_flow *prev = NULL; + const struct sbctl_lflow *curr, *prev = NULL; for (size_t i = 0; i < n_flows; i++) { - lflow = lflows[i]; + curr = &lflows[i]; /* Figure out whether to print this particular flow. By default, we * print all flows, but if any UUIDs were listed on the command line @@ -1074,7 +1184,7 @@ if (ctx->argc > 1) { include = false; for (size_t j = 1; j < ctx->argc; j++) { - if (is_partial_uuid_match(&lflow->header_.uuid, + if (is_partial_uuid_match(&curr->lflow->header_.uuid, ctx->argv[j])) { include = true; break; @@ -1090,27 +1200,28 @@ /* Print a header line for this datapath or pipeline, if we haven't * already done so. */ if (!prev - || prev->logical_datapath != lflow->logical_datapath - || strcmp(prev->pipeline, lflow->pipeline)) { + || prev->dp != curr->dp + || strcmp(prev->lflow->pipeline, curr->lflow->pipeline)) { printf("Datapath: "); - print_datapath_name(lflow->logical_datapath); + print_datapath_name(curr->dp); printf(" ("UUID_FMT") Pipeline: %s\n", - UUID_ARGS(&lflow->logical_datapath->header_.uuid), - lflow->pipeline); + UUID_ARGS(&curr->dp->header_.uuid), + curr->lflow->pipeline); } /* Print the flow. */ printf(" "); - print_uuid_part(&lflow->header_.uuid, print_uuid); + print_uuid_part(&curr->lflow->header_.uuid, print_uuid); printf("table=%-2"PRId64"(%-19s), priority=%-5"PRId64 ", match=(%s), action=(%s)\n", - lflow->table_id, - smap_get_def(&lflow->external_ids, "stage-name", ""), - lflow->priority, lflow->match, lflow->actions); + curr->lflow->table_id, + smap_get_def(&curr->lflow->external_ids, "stage-name", ""), + curr->lflow->priority, curr->lflow->match, + curr->lflow->actions); if (vconn) { - sbctl_dump_openflow(vconn, &lflow->header_.uuid, stats); + sbctl_dump_openflow(vconn, &curr->lflow->header_.uuid, stats); } - prev = lflow; + prev = curr; } bool vflows = shash_find(&ctx->options, "--vflows") != NULL; @@ -1119,6 +1230,7 @@ cmd_lflow_list_mac_bindings(ctx, vconn, datapath, stats, print_uuid); cmd_lflow_list_mc_groups(ctx, vconn, datapath, stats, print_uuid); cmd_lflow_list_chassis(ctx, vconn, stats, print_uuid); + cmd_lflow_list_load_balancers(ctx, vconn, datapath, stats, print_uuid); } vconn_close(vconn); @@ -1410,11 +1522,41 @@ [SBREC_TABLE_ADDRESS_SET].row_ids[0] = {&sbrec_address_set_col_name, NULL, NULL}, + [SBREC_TABLE_PORT_GROUP].row_ids[0] + = {&sbrec_port_group_col_name, NULL, NULL}, + [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0] = {&sbrec_ha_chassis_group_col_name, NULL, NULL}, [SBREC_TABLE_HA_CHASSIS].row_ids[0] = {&sbrec_ha_chassis_col_chassis, NULL, NULL}, + + [SBREC_TABLE_METER].row_ids[0] + = {&sbrec_meter_col_name, NULL, NULL}, + + [SBREC_TABLE_SERVICE_MONITOR].row_ids[0] + = {&sbrec_service_monitor_col_logical_port, NULL, NULL}, + + [SBREC_TABLE_DHCP_OPTIONS].row_ids[0] + = {&sbrec_dhcp_options_col_name, NULL, NULL}, + + [SBREC_TABLE_DHCPV6_OPTIONS].row_ids[0] + = {&sbrec_dhcpv6_options_col_name, NULL, NULL}, + + [SBREC_TABLE_CONNECTION].row_ids[0] + = {&sbrec_connection_col_target, NULL, NULL}, + + [SBREC_TABLE_RBAC_ROLE].row_ids[0] + = {&sbrec_rbac_role_col_name, NULL, NULL}, + + [SBREC_TABLE_RBAC_PERMISSION].row_ids[0] + = {&sbrec_rbac_permission_col_table, NULL, NULL}, + + [SBREC_TABLE_GATEWAY_CHASSIS].row_ids[0] + = {&sbrec_gateway_chassis_col_name, NULL, NULL}, + + [SBREC_TABLE_LOAD_BALANCER].row_ids[0] + = {&sbrec_load_balancer_col_name, NULL, NULL}, }; diff -Nru ovn-20.09.0/utilities/ovn-sim.in ovn-20.12.0/utilities/ovn-sim.in --- ovn-20.09.0/utilities/ovn-sim.in 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-sim.in 2020-12-17 17:53:32.000000000 +0000 @@ -241,7 +241,7 @@ export -f ovn_start ovn_attach() { - if test "$1" == --help; then + if test "$1" = --help; then cat <ovn-trace -- Open Virtual Network logical network tracing utility

    Synopsis

    -

    ovn-trace [options] datapath microflow

    +

    ovn-trace [options] [datapath] microflow

    ovn-trace [options] --detach

    Description

    @@ -40,18 +40,20 @@

    - The simplest way to use ovn-trace is to provide - datapath and microflow arguments on the command + The simplest way to use ovn-trace is to provide the + microflow (and optional datapath) arguments on the command line. In this case, it simulates the behavior of a single packet and exits. For an alternate usage model, see Daemon Mode below.

    - The datapath argument specifies the name of a logical + The optional datapath argument specifies the name of a logical datapath. Acceptable names are the name from the northbound Logical_Switch or Logical_Router table, the UUID of a record from one of those tables, or the UUID of a record from - the southbound Datapath_Binding table. + the southbound Datapath_Binding table. (The datapath + is optional because ovn-trace can figure it out from the + inport that the microflow matches.)

    @@ -289,7 +291,7 @@

    -
    trace [options] datapath microflow
    +
    trace [options] [datapath] microflow
    Traces microflow through datapath and replies with the results of the trace. Accepts the options described under diff -Nru ovn-20.09.0/utilities/ovn-trace.c ovn-20.12.0/utilities/ovn-trace.c --- ovn-20.09.0/utilities/ovn-trace.c 2020-09-28 15:38:39.000000000 +0000 +++ ovn-20.12.0/utilities/ovn-trace.c 2020-12-17 17:53:32.000000000 +0000 @@ -115,8 +115,8 @@ "(use --help for help)"); } } else { - if (argc != 2) { - ovs_fatal(0, "exactly two non-option arguments are required " + if (argc != 1 && argc != 2) { + ovs_fatal(0, "one or two non-option arguments are required " "(use --help for help)"); } } @@ -134,8 +134,8 @@ ovs_fatal(error, "failed to create unixctl server"); } unixctl_command_register("exit", "", 0, 0, ovntrace_exit, &exiting); - unixctl_command_register("trace", "[OPTIONS] DATAPATH MICROFLOW", - 2, INT_MAX, ovntrace_trace, NULL); + unixctl_command_register("trace", "[OPTIONS] [DATAPATH] MICROFLOW", + 1, INT_MAX, ovntrace_trace, NULL); } ovnsb_idl = ovsdb_idl_create(db, &sbrec_idl_class, true, false); @@ -157,7 +157,9 @@ daemonize_complete(); if (!get_detach()) { - char *output = trace(argv[0], argv[1]); + const char *dp_s = argc > 1 ? argv[0] : NULL; + const char *flow_s = argv[argc - 1]; + char *output = trace(dp_s, flow_s); fputs(output, stdout); free(output); return 0; @@ -360,7 +362,7 @@ { printf("\ %s: OVN trace utility\n\ -usage: %s [OPTIONS] DATAPATH MICROFLOW\n\ +usage: %s [OPTIONS] [DATAPATH] MICROFLOW\n\ %s [OPTIONS] --detach\n\ \n\ Output format options:\n\ @@ -872,18 +874,14 @@ } static void -read_flows(void) +parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf, + const struct sbrec_datapath_binding *sbdb) { - ovn_init_symtab(&symtab); - - const struct sbrec_logical_flow *sblf; - SBREC_LOGICAL_FLOW_FOR_EACH (sblf, ovnsb_idl) { - const struct sbrec_datapath_binding *sbdb = sblf->logical_datapath; struct ovntrace_datapath *dp = ovntrace_datapath_find_by_sb_uuid(&sbdb->header_.uuid); if (!dp) { VLOG_WARN("logical flow missing datapath"); - continue; + return; } char *error; @@ -895,7 +893,7 @@ VLOG_WARN("%s: parsing expression failed (%s)", sblf->match, error); free(error); - continue; + return; } struct ovnact_parse_params pp = { @@ -917,7 +915,7 @@ VLOG_WARN("%s: parsing actions failed (%s)", sblf->actions, error); free(error); expr_destroy(match); - continue; + return; } match = expr_combine(EXPR_T_AND, match, prereqs); @@ -928,7 +926,7 @@ expr_destroy(match); ovnacts_free(ovnacts.data, ovnacts.size); ofpbuf_uninit(&ovnacts); - continue; + return; } if (match) { match = expr_simplify(match); @@ -958,6 +956,30 @@ sizeof *dp->flows); } dp->flows[dp->n_flows++] = flow; +} + +static void +read_flows(void) +{ + ovn_init_symtab(&symtab); + + const struct sbrec_logical_flow *sblf; + SBREC_LOGICAL_FLOW_FOR_EACH (sblf, ovnsb_idl) { + bool missing_datapath = true; + + if (sblf->logical_datapath) { + parse_lflow_for_datapath(sblf, sblf->logical_datapath); + missing_datapath = false; + } + + const struct sbrec_logical_dp_group *g = sblf->logical_dp_group; + for (size_t i = 0; g && i < g->n_datapaths; i++) { + parse_lflow_for_datapath(sblf, g->datapaths[i]); + missing_datapath = false; + } + if (missing_datapath) { + VLOG_WARN("logical flow missing datapath"); + } } const struct ovntrace_datapath *dp; @@ -1638,16 +1660,23 @@ static void execute_icmp4(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, + const struct flow *uflow, uint8_t table_id, bool loopback, enum ovnact_pipeline pipeline, struct ovs_list *super) { struct flow icmp4_flow = *uflow; /* Update fields for ICMP. */ - icmp4_flow.dl_dst = uflow->dl_dst; - icmp4_flow.dl_src = uflow->dl_src; - icmp4_flow.nw_dst = uflow->nw_dst; - icmp4_flow.nw_src = uflow->nw_src; + if (loopback) { + icmp4_flow.dl_dst = uflow->dl_src; + icmp4_flow.dl_src = uflow->dl_dst; + icmp4_flow.nw_dst = uflow->nw_src; + icmp4_flow.nw_src = uflow->nw_dst; + } else { + icmp4_flow.dl_dst = uflow->dl_dst; + icmp4_flow.dl_src = uflow->dl_src; + icmp4_flow.nw_dst = uflow->nw_dst; + icmp4_flow.nw_src = uflow->nw_src; + } icmp4_flow.nw_proto = IPPROTO_ICMP; icmp4_flow.nw_ttl = 255; icmp4_flow.tp_src = htons(ICMP4_DST_UNREACH); /* icmp type */ @@ -1663,16 +1692,23 @@ static void execute_icmp6(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, + const struct flow *uflow, uint8_t table_id, bool loopback, enum ovnact_pipeline pipeline, struct ovs_list *super) { struct flow icmp6_flow = *uflow; /* Update fields for ICMPv6. */ - icmp6_flow.dl_dst = uflow->dl_dst; - icmp6_flow.dl_src = uflow->dl_src; - icmp6_flow.ipv6_dst = uflow->ipv6_dst; - icmp6_flow.ipv6_src = uflow->ipv6_src; + if (loopback) { + icmp6_flow.dl_dst = uflow->dl_src; + icmp6_flow.dl_src = uflow->dl_dst; + icmp6_flow.ipv6_dst = uflow->ipv6_src; + icmp6_flow.ipv6_src = uflow->ipv6_dst; + } else { + icmp6_flow.dl_dst = uflow->dl_dst; + icmp6_flow.dl_src = uflow->dl_src; + icmp6_flow.ipv6_dst = uflow->ipv6_dst; + icmp6_flow.ipv6_src = uflow->ipv6_src; + } icmp6_flow.nw_proto = IPPROTO_ICMPV6; icmp6_flow.nw_ttl = 255; icmp6_flow.tp_src = htons(ICMP6_DST_UNREACH); /* icmp type */ @@ -1686,18 +1722,60 @@ } static void -execute_tcp_reset(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, - const struct flow *uflow, uint8_t table_id, - enum ovnact_pipeline pipeline, struct ovs_list *super) +execute_tcp4_reset(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) +{ + struct flow tcp_flow = *uflow; + + /* Update fields for TCP segment. */ + if (loopback) { + tcp_flow.dl_dst = uflow->dl_src; + tcp_flow.dl_src = uflow->dl_dst; + tcp_flow.nw_dst = uflow->nw_src; + tcp_flow.nw_src = uflow->nw_dst; + } else { + tcp_flow.dl_dst = uflow->dl_dst; + tcp_flow.dl_src = uflow->dl_src; + tcp_flow.nw_dst = uflow->nw_dst; + tcp_flow.nw_src = uflow->nw_src; + } + tcp_flow.nw_proto = IPPROTO_TCP; + tcp_flow.nw_ttl = 255; + tcp_flow.tp_src = uflow->tp_src; + tcp_flow.tp_dst = uflow->tp_dst; + tcp_flow.tcp_flags = htons(TCP_RST); + + struct ovntrace_node *node = ovntrace_node_append( + super, OVNTRACE_NODE_TRANSFORMATION, "tcp_reset"); + + trace_actions(on->nested, on->nested_len, dp, &tcp_flow, + table_id, pipeline, &node->subs); +} + +static void +execute_tcp6_reset(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) { struct flow tcp_flow = *uflow; /* Update fields for TCP segment. */ - tcp_flow.dl_dst = uflow->dl_dst; - tcp_flow.dl_src = uflow->dl_src; - tcp_flow.nw_dst = uflow->nw_dst; - tcp_flow.nw_src = uflow->nw_src; + if (loopback) { + tcp_flow.dl_dst = uflow->dl_src; + tcp_flow.dl_src = uflow->dl_dst; + tcp_flow.ipv6_dst = uflow->ipv6_src; + tcp_flow.ipv6_src = uflow->ipv6_dst; + } else { + tcp_flow.dl_dst = uflow->dl_dst; + tcp_flow.dl_src = uflow->dl_src; + tcp_flow.ipv6_dst = uflow->ipv6_dst; + tcp_flow.ipv6_src = uflow->ipv6_src; + } tcp_flow.nw_proto = IPPROTO_TCP; tcp_flow.nw_ttl = 255; tcp_flow.tp_src = uflow->tp_src; @@ -1712,6 +1790,36 @@ } static void +execute_tcp_reset(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) +{ + if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) { + execute_tcp4_reset(on, dp, uflow, table_id, loopback, pipeline, super); + } else { + execute_tcp6_reset(on, dp, uflow, table_id, loopback, pipeline, super); + } +} +static void +execute_reject(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + enum ovnact_pipeline pipeline, struct ovs_list *super) +{ + if (uflow->nw_proto == IPPROTO_TCP) { + execute_tcp_reset(on, dp, uflow, table_id, true, pipeline, super); + } else { + if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) { + execute_icmp4(on, dp, uflow, table_id, true, pipeline, super); + } else { + execute_icmp6(on, dp, uflow, table_id, true, pipeline, super); + } + } +} + +static void execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, const struct ovntrace_datapath *dp, struct flow *uflow, struct ovs_list *super) @@ -1888,12 +1996,15 @@ enum ovnact_pipeline pipeline, struct ovs_list *super) { if (pipeline != next->pipeline) { - ovs_assert(next->pipeline == OVNACT_P_INGRESS); - - uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0]; + uint16_t key = next->pipeline == OVNACT_P_INGRESS + ? uflow->regs[MFF_LOG_INPORT - MFF_REG0] + : uflow->regs[MFF_LOG_OUTPORT - MFF_REG0]; struct ovntrace_node *node = ovntrace_node_append( - super, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")", - dp->friendly_name, ovntrace_port_key_to_name(dp, in_key)); + super, OVNTRACE_NODE_PIPELINE, "%s(dp=\"%s\", %s=\"%s\")", + next->pipeline == OVNACT_P_INGRESS ? "ingress" : "egress", + dp->friendly_name, + next->pipeline == OVNACT_P_INGRESS ? "inport" : "outport", + ovntrace_port_key_to_name(dp, key)); super = &node->subs; } trace__(dp, uflow, next->ltable, next->pipeline, super); @@ -1901,7 +2012,7 @@ static void -execute_dns_lookup(const struct ovnact_dns_lookup *dl, struct flow *uflow, +execute_dns_lookup(const struct ovnact_result *dl, struct flow *uflow, struct ovs_list *super) { struct mf_subfield sf = expr_resolve_field(&dl->dst); @@ -2134,6 +2245,57 @@ } static void +execute_chk_lb_hairpin(const struct ovnact_result *dl, struct flow *uflow, + struct ovs_list *super) +{ + int family = (uflow->dl_type == htons(ETH_TYPE_IP) ? AF_INET + : uflow->dl_type == htons(ETH_TYPE_IPV6) ? AF_INET6 + : AF_UNSPEC); + uint8_t res = 0; + if (family != AF_UNSPEC && uflow->ct_state & CS_DST_NAT) { + if (family == AF_INET) { + res = (uflow->nw_src == uflow->nw_dst) ? 1 : 0; + } else { + res = ipv6_addr_equals(&uflow->ipv6_src, &uflow->ipv6_dst) ? 1 : 0; + } + } + + struct mf_subfield sf = expr_resolve_field(&dl->dst); + union mf_subvalue sv = { .u8_val = res }; + mf_write_subfield_flow(&sf, &sv, uflow); + + struct ds s = DS_EMPTY_INITIALIZER; + expr_field_format(&dl->dst, &s); + ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, + "%s = %d", ds_cstr(&s), res); + ds_destroy(&s); +} + +static void +execute_chk_lb_hairpin_reply(const struct ovnact_result *dl, + struct flow *uflow, + struct ovs_list *super) +{ + struct mf_subfield sf = expr_resolve_field(&dl->dst); + union mf_subvalue sv = { .u8_val = 0 }; + mf_write_subfield_flow(&sf, &sv, uflow); + ovntrace_node_append(super, OVNTRACE_NODE_ERROR, + "*** chk_lb_hairpin_reply action not implemented"); + struct ds s = DS_EMPTY_INITIALIZER; + expr_field_format(&dl->dst, &s); + ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, + "%s = 0", ds_cstr(&s)); + ds_destroy(&s); +} + +static void +execute_ct_snat_to_vip(struct flow *uflow OVS_UNUSED, struct ovs_list *super) +{ + ovntrace_node_append(super, OVNTRACE_NODE_ERROR, + "*** ct_snat_to_vip action not implemented"); +} + +static void trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, const struct ovntrace_datapath *dp, struct flow *uflow, uint8_t table_id, enum ovnact_pipeline pipeline, @@ -2194,7 +2356,8 @@ execute_ct_next(ovnact_get_CT_NEXT(a), dp, uflow, pipeline, super); break; - case OVNACT_CT_COMMIT: + case OVNACT_CT_COMMIT_V1: + case OVNACT_CT_COMMIT_V2: /* Nothing to do. */ break; @@ -2312,23 +2475,23 @@ break; case OVNACT_ICMP4: - execute_icmp4(ovnact_get_ICMP4(a), dp, uflow, table_id, pipeline, - super); + execute_icmp4(ovnact_get_ICMP4(a), dp, uflow, table_id, false, + pipeline, super); break; case OVNACT_ICMP4_ERROR: execute_icmp4(ovnact_get_ICMP4_ERROR(a), dp, uflow, table_id, - pipeline, super); + false, pipeline, super); break; case OVNACT_ICMP6: - execute_icmp6(ovnact_get_ICMP6(a), dp, uflow, table_id, pipeline, - super); + execute_icmp6(ovnact_get_ICMP6(a), dp, uflow, table_id, false, + pipeline, super); break; case OVNACT_ICMP6_ERROR: execute_icmp6(ovnact_get_ICMP6_ERROR(a), dp, uflow, table_id, - pipeline, super); + false, pipeline, super); break; case OVNACT_IGMP: @@ -2337,13 +2500,30 @@ case OVNACT_TCP_RESET: execute_tcp_reset(ovnact_get_TCP_RESET(a), dp, uflow, table_id, - pipeline, super); + false, pipeline, super); break; case OVNACT_OVNFIELD_LOAD: execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super); break; + case OVNACT_REJECT: + execute_reject(ovnact_get_REJECT(a), dp, uflow, table_id, + pipeline, super); + break; + + case OVNACT_CHK_LB_HAIRPIN: + execute_chk_lb_hairpin(ovnact_get_CHK_LB_HAIRPIN(a), uflow, super); + break; + + case OVNACT_CHK_LB_HAIRPIN_REPLY: + execute_chk_lb_hairpin_reply(ovnact_get_CHK_LB_HAIRPIN_REPLY(a), + uflow, super); + break; + case OVNACT_CT_SNAT_TO_VIP: + execute_ct_snat_to_vip(uflow, super); + break; + case OVNACT_TRIGGER_EVENT: break; @@ -2465,24 +2645,76 @@ } } -static char * -trace(const char *dp_s, const char *flow_s) +static char * OVS_WARN_UNUSED_RESULT +trace_parse_error(char *error) { - const struct ovntrace_datapath *dp = ovntrace_datapath_find_by_name(dp_s); - if (!dp) { - return xasprintf("unknown datapath \"%s\"\n", dp_s); + char *s = xasprintf("error parsing flow: %s\n", error); + free(error); + return s; +} + +static char * OVS_WARN_UNUSED_RESULT +trace_parse(const char *dp_s, const char *flow_s, + const struct ovntrace_datapath **dpp, struct flow *uflow) +{ + *dpp = NULL; + if (dp_s) { + /* Use the specified datapath. */ + *dpp = ovntrace_datapath_find_by_name(dp_s); + if (!dpp) { + return xasprintf("unknown datapath \"%s\"\n", dp_s); + } + } else { + /* Figure out the datapath from the flow, based on its inport. + * + * First make sure that the expression parses. */ + char *error; + struct expr *e = expr_parse_string(flow_s, &symtab, &address_sets, + &port_groups, NULL, NULL, 0, + &error); + if (!e) { + return trace_parse_error(error); + } + + /* Extract the name of the inport. */ + char *port_name = expr_find_inport(e, &error); + expr_destroy(e); + if (!port_name) { + return trace_parse_error(error); + } + + /* Find the port by name. */ + const struct ovntrace_port *port = shash_find_data(&ports, port_name); + if (!port) { + char *s = xasprintf("unknown port \"%s\"\n", port_name); + free(port_name); + return s; + } + + /* Use the port's datapath. */ + *dpp = port->dp; + free(port_name); } - struct flow uflow; char *error = expr_parse_microflow(flow_s, &symtab, &address_sets, &port_groups, ovntrace_lookup_port, - dp, &uflow); + *dpp, uflow); if (error) { - char *s = xasprintf("error parsing flow: %s\n", error); - free(error); - return s; + return trace_parse_error(error); } + return NULL; +} + +static char * +trace(const char *dp_s, const char *flow_s) +{ + const struct ovntrace_datapath *dp; + struct flow uflow; + char *error = trace_parse(dp_s, flow_s, &dp, &uflow); + if (error) { + return error; + } uint32_t in_key = uflow.regs[MFF_LOG_INPORT - MFF_REG0]; if (!in_key) { VLOG_WARN("microflow does not specify ingress port"); @@ -2577,13 +2809,15 @@ detailed = true; } - if (argc != 3) { + if (argc != 2 && argc != 3) { unixctl_command_reply_error( - conn, "exactly 2 non-option arguments are required"); + conn, "one or two non-option arguments are required"); return; } - char *output = trace(argv[1], argv[2]); + const char *dp_s = argc > 2 ? argv[1] : NULL; + const char *flow_s = argv[argc - 1]; + char *output = trace(dp_s, flow_s); unixctl_command_reply(conn, output); free(output); }