diff --git a/AUTHORS.rst b/AUTHORS.rst
index 8572c24c8..d3747f8d1 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -147,6 +147,7 @@ Fabrizio D'Angelo fdangelo@redhat.com
Flavio Fernandes flavio@flaviof.com
Flavio Leitner fbl@redhat.com
Francesco Fusco ffusco@redhat.com
+François Rigault frigo@amadeus.com
Frank Wang wangpeihuixyz@126.com
Frédéric Tobias Christ fchrist@live.de
Frode Nordahl frode.nordahl@gmail.com
diff --git a/NEWS b/NEWS
index 9f3ce3cf3..15fa545d2 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,7 @@
-OVN v22.03.0 - XX XXX XXXX
+OVN v22.03.1 - xx xxx xxxx
+--------------------------
+
+OVN v22.03.0 - 11 Mar 2022
--------------------------
- Refactor CoPP commands introducing a unique name index in CoPP NB table.
Add following new CoPP commands to manage CoPP table:
diff --git a/configure.ac b/configure.ac
index 283381b4e..70f86e1f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
# limitations under the License.
AC_PREREQ(2.63)
-AC_INIT(ovn, 22.03.0, bugs@openvswitch.org)
+AC_INIT(ovn, 22.03.1, bugs@openvswitch.org)
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c
index 01d5a16d2..1ee52b592 100644
--- a/controller-vtep/binding.c
+++ b/controller-vtep/binding.c
@@ -109,12 +109,10 @@ update_pb_chassis(const struct sbrec_port_binding *port_binding_rec,
port_binding_rec->chassis->name,
chassis_rec->name);
}
-
sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec);
- if (port_binding_rec->n_up) {
- bool up = true;
- sbrec_port_binding_set_up(port_binding_rec, &up, 1);
- }
+ } else if (port_binding_rec->n_up) {
+ bool up = true;
+ sbrec_port_binding_set_up(port_binding_rec, &up, 1);
}
}
diff --git a/controller/binding.c b/controller/binding.c
index 4d62b0858..1259e6b3b 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -481,6 +481,16 @@ remove_related_lport(const struct sbrec_port_binding *pb,
}
}
+static void
+delete_active_pb_ras_pd(const struct sbrec_port_binding *pb,
+ struct shash *ras_pd_map)
+{
+ struct pb_ld_binding *ras_pd =
+ shash_find_and_delete(ras_pd_map, pb->logical_port);
+
+ free(ras_pd);
+}
+
static void
update_active_pb_ras_pd(const struct sbrec_port_binding *pb,
struct hmap *local_datapaths,
@@ -2251,6 +2261,9 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
continue;
}
+ delete_active_pb_ras_pd(pb, b_ctx_out->local_active_ports_ipv6_pd);
+ delete_active_pb_ras_pd(pb, b_ctx_out->local_active_ports_ras);
+
enum en_lport_type lport_type = get_lport_type(pb);
struct binding_lport *b_lport =
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index a7c2d2011..3b9d71733 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -943,7 +943,12 @@ link_installed_to_desired(struct installed_flow *i, struct desired_flow *d)
break;
}
}
- ovs_list_insert(&f->installed_ref_list_node, &d->installed_ref_list_node);
+ if (!f) {
+ ovs_list_insert(&i->desired_refs, &d->installed_ref_list_node);
+ } else {
+ ovs_list_insert(&f->installed_ref_list_node,
+ &d->installed_ref_list_node);
+ }
d->installed_flow = i;
return installed_flow_get_active(i) == d;
}
@@ -2324,7 +2329,20 @@ deleted_flow_lookup(struct hmap *deleted_flows, struct ovn_flow *target)
&& f->cookie == target->cookie
&& ofpacts_equal(f->ofpacts, f->ofpacts_len, target->ofpacts,
target->ofpacts_len)) {
- return d;
+ /* del_f must have been installed, otherwise it should have
+ * been removed during track_flow_del. */
+ ovs_assert(d->installed_flow);
+
+ /* Now we also need to make sure the desired flow being
+ * added/updated has exact same action and cookie as the installed
+ * flow of d. Otherwise, don't merge them, so that the
+ * installed flow can be updated later. */
+ struct ovn_flow *f_i = &d->installed_flow->flow;
+ if (f_i->cookie == target->cookie
+ && ofpacts_equal(f_i->ofpacts, f_i->ofpacts_len,
+ target->ofpacts, target->ofpacts_len)) {
+ return d;
+ }
}
}
return NULL;
@@ -2353,10 +2371,6 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table)
continue;
}
- /* 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. */
replace_installed_to_desired(del_f->installed_flow, del_f, f);
@@ -2665,6 +2679,13 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
EXTEND_TABLE_FOR_EACH_INSTALLED (m_installed, next_meter, meters) {
/* Delete the meter. */
ofctrl_meter_bands_erase(m_installed, &msgs);
+ if (!strncmp(m_installed->name, "__string: ", 10)) {
+ struct ofputil_meter_mod mm = {
+ .command = OFPMC13_DELETE,
+ .meter = { .meter_id = m_installed->table_id },
+ };
+ add_meter_mod(&mm, &msgs);
+ }
ovn_extend_table_remove_existing(meters, m_installed);
}
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 25b37ee88..2f718aca7 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -5523,7 +5523,7 @@ get_localnet_vifs_l3gwports(
}
const struct sbrec_port_binding *pb
= lport_lookup_by_name(sbrec_port_binding_by_name, iface_id);
- if (!pb) {
+ if (!pb || pb->chassis != chassis) {
continue;
}
struct local_datapath *ld
@@ -5554,7 +5554,7 @@ get_localnet_vifs_l3gwports(
sbrec_port_binding_index_set_datapath(target, ld->datapath);
SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
sbrec_port_binding_by_datapath) {
- if (!strcmp(pb->type, "l3gateway")
+ if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
|| !strcmp(pb->type, "patch")) {
sset_add(local_l3gw_ports, pb->logical_port);
}
@@ -5781,7 +5781,8 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
const struct sbrec_port_binding *pb = lport_lookup_by_name(
sbrec_port_binding_by_name, iface_id);
if (pb) {
- send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+ send_garp_rarp_update(ovnsb_idl_txn,
+ sbrec_mac_binding_by_lport_ip,
local_datapaths, pb, &nat_addresses);
}
}
diff --git a/debian/changelog b/debian/changelog
index f1167591b..18a1a042e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+OVN (22.03.1-1) unstable; urgency=low
+ [ OVN team ]
+ * New upstream version
+
+ -- OVN team <dev@openvswitch.org> Fri, 11 Mar 2022 13:22:32 -0500
+
ovn (22.03.0-1) unstable; urgency=low
* New upstream version
diff --git a/lib/actions.c b/lib/actions.c
index 5d3caaf2b..2219c5b37 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2336,7 +2336,7 @@ validate_empty_lb_backends(struct action_context *ctx,
switch (o->option->code) {
case EMPTY_LB_VIP:
- if (!inet_parse_active(c->string, 0, &ss, false)) {
+ if (!inet_parse_active(c->string, 0, &ss, false, NULL)) {
lexer_error(ctx->lexer, "Invalid load balancer VIP '%s'",
c->string);
return;
diff --git a/lib/expr.c b/lib/expr.c
index 47ef6108e..058390a16 100644
--- a/lib/expr.c
+++ b/lib/expr.c
@@ -211,16 +211,17 @@ expr_combine(enum expr_type type, struct expr *a, struct expr *b)
}
static void
-expr_insert_andor(struct expr *andor, struct expr *before, struct expr *new)
+expr_insert_andor(struct expr *andor, struct ovs_list *before,
+ struct expr *new)
{
if (new->type == andor->type) {
if (andor->type == EXPR_T_AND) {
/* Conjunction junction, what's your function? */
}
- ovs_list_splice(&before->node, new->andor.next, &new->andor);
+ ovs_list_splice(before, new->andor.next, &new->andor);
expr_destroy(new);
} else {
- ovs_list_insert(&before->node, &new->node);
+ ovs_list_insert(before, &new->node);
}
}
@@ -2101,7 +2102,8 @@ expr_annotate__(struct expr *expr, const struct shash *symtab,
expr_destroy(expr);
return NULL;
}
- expr_insert_andor(expr, next, new_sub);
+ expr_insert_andor(expr, next ? &next->node : &expr->andor,
+ new_sub);
}
*errorp = NULL;
return expr;
@@ -2301,7 +2303,7 @@ expr_evaluate_condition(struct expr *expr,
struct expr *e = expr_evaluate_condition(sub, is_chassis_resident,
c_aux);
e = expr_fix(e);
- expr_insert_andor(expr, next, e);
+ expr_insert_andor(expr, next ? &next->node : &expr->andor, e);
}
return expr_fix(expr);
@@ -2334,7 +2336,8 @@ expr_simplify(struct expr *expr)
case EXPR_T_OR:
LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
ovs_list_remove(&sub->node);
- expr_insert_andor(expr, next, expr_simplify(sub));
+ expr_insert_andor(expr, next ? &next->node : &expr->andor,
+ expr_simplify(sub));
}
return expr_fix(expr);
@@ -2444,12 +2447,13 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
* EXPR_T_OR with EXPR_T_CMP subexpressions. */
struct expr *sub, *next = NULL;
LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ struct ovs_list *next_list = next ? &next->node : &expr->andor;
ovs_list_remove(&sub->node);
struct expr *new = crush_cmps(sub, symbol);
switch (new->type) {
case EXPR_T_CMP:
if (!singleton) {
- ovs_list_insert(&next->node, &new->node);
+ ovs_list_insert(next_list, &new->node);
singleton = new;
} else {
bool match = !strcmp(new->cmp.string, singleton->cmp.string);
@@ -2463,7 +2467,7 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
case EXPR_T_AND:
OVS_NOT_REACHED();
case EXPR_T_OR:
- ovs_list_insert(&next->node, &new->node);
+ ovs_list_insert(next_list, &new->node);
break;
case EXPR_T_BOOLEAN:
if (!new->boolean) {
@@ -2559,7 +2563,7 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
case EXPR_T_AND:
OVS_NOT_REACHED();
case EXPR_T_OR:
- ovs_list_insert(&next->node, &new->node);
+ ovs_list_insert(next ? &next->node : &expr->andor, &new->node);
break;
case EXPR_T_BOOLEAN:
if (!new->boolean) {
@@ -2725,7 +2729,8 @@ crush_or(struct expr *expr, const struct expr_symbol *symbol)
* is now a disjunction of cmps over the same symbol. */
LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
ovs_list_remove(&sub->node);
- expr_insert_andor(expr, next, crush_cmps(sub, symbol));
+ expr_insert_andor(expr, next ? &next->node : &expr->andor,
+ crush_cmps(sub, symbol));
}
expr = expr_fix(expr);
if (expr->type != EXPR_T_OR) {
@@ -2886,8 +2891,7 @@ expr_normalize_and(struct expr *expr)
struct expr *a, *b;
LIST_FOR_EACH_SAFE (a, b, node, &expr->andor) {
- if (&b->node == &expr->andor
- || a->type != EXPR_T_CMP || b->type != EXPR_T_CMP
+ if (!b || a->type != EXPR_T_CMP || b->type != EXPR_T_CMP
|| a->cmp.symbol != b->cmp.symbol) {
continue;
} else if (a->cmp.symbol->width
@@ -2964,7 +2968,8 @@ expr_normalize_or(struct expr *expr)
}
expr_destroy(new);
} else {
- expr_insert_andor(expr, next, new);
+ expr_insert_andor(expr, next ? &next->node : &expr->andor,
+ new);
}
} else {
ovs_assert(sub->type == EXPR_T_CMP ||
diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c
index 7b4391700..575b774ae 100644
--- a/lib/inc-proc-eng.c
+++ b/lib/inc-proc-eng.c
@@ -354,14 +354,11 @@ engine_recompute(struct engine_node *node, bool allowed,
const char *reason_fmt, ...)
{
char *reason = NULL;
+ va_list reason_args;
- if (VLOG_IS_DBG_ENABLED()) {
- va_list reason_args;
-
- va_start(reason_args, reason_fmt);
- reason = xvasprintf(reason_fmt, reason_args);
- va_end(reason_args);
- }
+ va_start(reason_args, reason_fmt);
+ reason = xvasprintf(reason_fmt, reason_args);
+ va_end(reason_args);
if (!allowed) {
VLOG_DBG("node: %s, recompute (%s) aborted", node->name, reason);
diff --git a/lib/ovn-parallel-hmap.h b/lib/ovn-parallel-hmap.h
index 897208ef8..0f7d68770 100644
--- a/lib/ovn-parallel-hmap.h
+++ b/lib/ovn-parallel-hmap.h
@@ -58,11 +58,11 @@ extern "C" {
* ThreadID + step * i as the JOBID parameter.
*/
-#define HMAP_FOR_EACH_IN_PARALLEL(NODE, MEMBER, JOBID, HMAP) \
- for (INIT_CONTAINER(NODE, hmap_first_in_bucket_num(HMAP, JOBID), MEMBER); \
- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) \
- || ((NODE = NULL), false); \
- ASSIGN_CONTAINER(NODE, hmap_next_in_bucket(&(NODE)->MEMBER), MEMBER))
+#define HMAP_FOR_EACH_IN_PARALLEL(NODE, MEMBER, JOBID, HMAP) \
+ for (INIT_MULTIVAR(NODE, MEMBER, hmap_first_in_bucket_num(HMAP, JOBID), \
+ struct hmap_node); \
+ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \
+ UPDATE_MULTIVAR(NODE, hmap_next_in_bucket(ITER_VAR(NODE))))
/* We do not have a SAFE version of the macro, because the hash size is not
* atomic and hash removal operations would need to be wrapped with
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index a22ae84fe..2f2f7ae39 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -735,7 +735,7 @@ 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)) {
+ if (!inet_parse_active(key, 0, &ss, false, NULL)) {
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);
diff --git a/northd/northd.c b/northd/northd.c
index b22da67e9..217f180eb 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -763,16 +763,6 @@ init_nat_entries(struct ovn_datapath *od)
return;
}
- if (od->n_l3dgw_ports > 1) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_WARN_RL(&rl, "NAT is configured on logical router %s, which has %"
- PRIuSIZE" distributed gateway ports. NAT is not supported"
- " yet when there is more than one distributed gateway "
- "port on the router.",
- od->nbr->name, od->n_l3dgw_ports);
- return;
- }
-
od->nat_entries = xmalloc(od->nbr->n_nat * sizeof *od->nat_entries);
for (size_t i = 0; i < od->nbr->n_nat; i++) {
@@ -6418,6 +6408,72 @@ ovn_port_group_destroy(struct hmap *pgs, struct ovn_port_group *pg)
}
}
+static void
+copy_ra_to_sb(struct ovn_port *op, const char *address_mode);
+
+static void
+ovn_update_ipv6_options(struct hmap *ports)
+{
+ struct ovn_port *op;
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (!op->nbrp || op->nbrp->peer || !op->peer) {
+ continue;
+ }
+
+ if (!op->lrp_networks.n_ipv6_addrs) {
+ continue;
+ }
+
+ struct smap options;
+ smap_clone(&options, &op->sb->options);
+
+ /* enable IPv6 prefix delegation */
+ bool prefix_delegation = smap_get_bool(&op->nbrp->options,
+ "prefix_delegation", false);
+ if (!lrport_is_enabled(op->nbrp)) {
+ prefix_delegation = false;
+ }
+ if (smap_get_bool(&options, "ipv6_prefix_delegation",
+ false) != prefix_delegation) {
+ smap_add(&options, "ipv6_prefix_delegation",
+ prefix_delegation ? "true" : "false");
+ }
+
+ bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
+ "prefix", false);
+ if (!lrport_is_enabled(op->nbrp)) {
+ ipv6_prefix = false;
+ }
+ if (smap_get_bool(&options, "ipv6_prefix", false) != ipv6_prefix) {
+ smap_add(&options, "ipv6_prefix",
+ ipv6_prefix ? "true" : "false");
+ }
+ sbrec_port_binding_set_options(op->sb, &options);
+
+ smap_destroy(&options);
+
+ const char *address_mode = smap_get(
+ &op->nbrp->ipv6_ra_configs, "address_mode");
+
+ if (!address_mode) {
+ continue;
+ }
+ if (strcmp(address_mode, "slaac") &&
+ strcmp(address_mode, "dhcpv6_stateful") &&
+ strcmp(address_mode, "dhcpv6_stateless")) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
+ address_mode);
+ continue;
+ }
+
+ if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
+ false)) {
+ copy_ra_to_sb(op, address_mode);
+ }
+ }
+}
+
static void
build_port_group_lswitches(struct northd_input *input_data,
struct hmap *pgs,
@@ -10759,6 +10815,12 @@ build_neigh_learning_flows_for_lrouter(
copp_meter_get(COPP_ARP, od->nbr->copp,
meter_groups));
+ ovn_lflow_metered(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 95,
+ "nd_na && nd.tll == 0",
+ "put_nd(inport, nd.target, eth.src); next;",
+ copp_meter_get(COPP_ND_NA, od->nbr->copp,
+ meter_groups));
+
ovn_lflow_metered(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
"nd_na", "put_nd(inport, nd.target, nd.tll); next;",
copp_meter_get(COPP_ND_NA, od->nbr->copp,
@@ -10851,34 +10913,6 @@ build_ND_RA_flows_for_lrouter_port(
return;
}
- struct smap options;
- smap_clone(&options, &op->sb->options);
-
- /* enable IPv6 prefix delegation */
- bool prefix_delegation = smap_get_bool(&op->nbrp->options,
- "prefix_delegation", false);
- if (!lrport_is_enabled(op->nbrp)) {
- prefix_delegation = false;
- }
- if (smap_get_bool(&options, "ipv6_prefix_delegation",
- false) != prefix_delegation) {
- smap_add(&options, "ipv6_prefix_delegation",
- prefix_delegation ? "true" : "false");
- }
-
- bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
- "prefix", false);
- if (!lrport_is_enabled(op->nbrp)) {
- ipv6_prefix = false;
- }
- if (smap_get_bool(&options, "ipv6_prefix", false) != ipv6_prefix) {
- smap_add(&options, "ipv6_prefix",
- ipv6_prefix ? "true" : "false");
- }
- sbrec_port_binding_set_options(op->sb, &options);
-
- smap_destroy(&options);
-
const char *address_mode = smap_get(
&op->nbrp->ipv6_ra_configs, "address_mode");
@@ -10894,11 +10928,6 @@ build_ND_RA_flows_for_lrouter_port(
return;
}
- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
- false)) {
- copy_ra_to_sb(op, address_mode);
- }
-
ds_clear(match);
ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
op->json_key);
@@ -12805,6 +12834,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
ds_put_format(actions, "ip%s.src=%s; next;",
is_v6 ? "6" : "4", nat->external_ip);
} else {
+ ds_put_format(match, " && (!ct.trk || !ct.rpl)");
ds_put_format(actions, "ct_snat(%s", nat->external_ip);
if (nat->external_port_range[0]) {
@@ -13155,6 +13185,18 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
return;
}
+ /* NAT rules are not currently supported on logical routers with multiple
+ * distributed gateway ports. */
+ if (od->n_l3dgw_ports > 1) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_WARN_RL(&rl, "NAT is configured on logical router %s, which has %"
+ PRIuSIZE" distributed gateway ports. NAT is not supported"
+ " yet when there is more than one distributed gateway "
+ "port on the router.",
+ od->nbr->name, od->n_l3dgw_ports);
+ return;
+ }
+
struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
bool dnat_force_snat_ip =
@@ -15048,6 +15090,7 @@ ovnnb_db_run(struct northd_input *input_data,
build_meter_groups(input_data, &data->meter_groups);
stopwatch_stop(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
stopwatch_start(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
+ ovn_update_ipv6_options(&data->ports);
ovn_update_ipv6_prefix(&data->ports);
sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 1c9d408af..4cb70e185 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -2301,6 +2301,12 @@ next;
<code>put_arp(inport, arp.spa, arp.sha); next;</code>
</li>
+ <li>
+ A priority-95 flow with the match <code>nd_na &&
+ nd.tll == 0</code> and applies the action
+ <code>put_nd(inport, nd.target, eth.src); next;</code>
+ </li>
+
<li>
A priority-90 flow with the match <code>nd_na</code> and
applies the action
@@ -4452,7 +4458,8 @@ nd_ns {
to change the source IP address of a packet from an IP address of
<var>A</var> or to change the source IP address of a packet that
belongs to network <var>A</var> to <var>B</var>, a flow matches
- <code>ip && ip4.src == <var>A</var></code> with an action
+ <code>ip && ip4.src == <var>A</var> &&
+ (!ct.trk || !ct.rpl)</code> with an action
<code>ct_snat(<var>B</var>);</code>. The priority of the flow
is calculated based on the mask of <var>A</var>, with matches
having larger masks getting higher priorities. If the NAT rule is
diff --git a/rhel/ovn-fedora.spec.in b/rhel/ovn-fedora.spec.in
index 3fb854a37..821eb03cc 100644
--- a/rhel/ovn-fedora.spec.in
+++ b/rhel/ovn-fedora.spec.in
@@ -323,7 +323,7 @@ ln -sf ovn_detrace.py %{_bindir}/ovn-detrace
%if %{with libcapng}
if [ $1 -eq 1 ]; then
sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn
- sed -i 's:\(.*su\).*:\1 ovn ovn:' %{_sysconfdir}/logrotate.d/ovn
+ sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn
fi
%endif
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 3865003bf..b7dfcd151 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1030,7 +1030,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0
AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
])
@@ -1062,7 +1062,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [
AT_CHECK([grep -e "lr_out_snat" crflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
table=??(lr_out_snat ), priority=35 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
])
@@ -1091,7 +1091,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [
AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.2);)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
])
# Stateful FIP with DISALLOWED_IPs
@@ -1120,7 +1120,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows4 | sed 's/table=../table=??/' | sort], [
AT_CHECK([grep -e "lr_out_snat" crflows4 | sed 's/table=../table=??/' | sort], [0], [dnl
table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11), action=(ct_snat(172.16.1.2);)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
table=??(lr_out_snat ), priority=35 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
])
@@ -3447,7 +3447,7 @@ ls_copp_uuid=$(fetch_column nb:Logical_Switch copp)
AT_CHECK([test "$ls_copp_uuid" = "$copp_uuid"])
check ovn-nbctl --wait=hv copp-add $copp_uuid igmp meter0
-AT_CHECK([ovn-nbctl copp-list copp0], [0], [dnl
+AT_CHECK([ovn-nbctl copp-list copp0 | sort], [0], [dnl
arp: meter0
igmp: meter0
])
@@ -5140,11 +5140,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
table=? (lr_out_snat ), priority=0 , match=(1), action=(next;)
table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
+ table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
])
+
# Set lb force snat logical router.
check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
check ovn-nbctl --wait=sb sync
@@ -5201,9 +5202,9 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
+ table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
])
# Add a LB VIP same as router ip.
@@ -5266,9 +5267,9 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.10);)
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
+ table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
])
# Add IPv6 router port and LB.
@@ -5346,9 +5347,9 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-public"), action=(ct_snat(def0::10);)
table=? (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw0"), action=(ct_snat(aef0::1);)
table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24), action=(ct_snat(172.168.0.10);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10), action=(ct_snat(172.168.0.30);)
- table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3), action=(ct_snat(172.168.0.20);)
+ table=? (lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.10 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+ table=? (lr_out_snat ), priority=33 , match=(ip && ip4.src == 10.0.0.3 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
])
check ovn-nbctl lrp-del lr0-sw0
@@ -5804,6 +5805,12 @@ AT_CHECK([grep lr_in_gw_redirect lrflows | grep cr-DR | sed 's/table=../table=??
table=??(lr_in_gw_redirect ), priority=50 , match=(outport == "DR-S3"), action=(outport = "cr-DR-S3"; next;)
])
+# Check that ovn-northd logs a warning when trying to configure NAT
+# on the router with multiple distributed gw ports. Such configurations are
+# not supported yet.
+check ovn-nbctl lr-nat-add DR dnat_and_snat 42.42.42.1 20.0.0.2
+AT_CHECK([grep -q 'NAT is configured on logical router DR, which has 2 distributed gateway ports. NAT is not supported yet when there is more than one distributed gateway port on the router.' northd/ovn-northd.log], [0])
+
AT_CLEANUP
])
@@ -6426,3 +6433,28 @@ AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort],
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([LR neighbor lookup and learning flows])
+ovn_start
+
+# Create logical routers
+ovn-nbctl --wait=sb lr-add lr0
+
+ovn-sbctl dump-flows lr0 > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+AT_CHECK([cat lrflows | grep -e lr_in_lookup_neighbor -e lr_in_learn_neighbor | sort], [0], [dnl
+ table=1 (lr_in_lookup_neighbor), priority=0 , match=(1), action=(reg9[[2]] = 1; next;)
+ table=1 (lr_in_lookup_neighbor), priority=100 , match=(arp.op == 2), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
+ table=1 (lr_in_lookup_neighbor), priority=100 , match=(nd_na), action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); next;)
+ table=1 (lr_in_lookup_neighbor), priority=100 , match=(nd_ns), action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); next;)
+ table=2 (lr_in_learn_neighbor), priority=100 , match=(reg9[[2]] == 1), action=(next;)
+ table=2 (lr_in_learn_neighbor), priority=90 , match=(arp), action=(put_arp(inport, arp.spa, arp.sha); next;)
+ table=2 (lr_in_learn_neighbor), priority=90 , match=(nd_na), action=(put_nd(inport, nd.target, nd.tll); next;)
+ table=2 (lr_in_learn_neighbor), priority=90 , match=(nd_ns), action=(put_nd(inport, ip6.src, nd.sll); next;)
+ table=2 (lr_in_learn_neighbor), priority=95 , match=(nd_na && nd.tll == 0), action=(put_nd(inport, nd.target, eth.src); next;)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 4f65d1ecd..4ee9aebc9 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -8588,6 +8588,114 @@ OVN_CLEANUP([hv1])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([send gratuitous arp for l3gateway only on selected chassis])
+ovn_start
+
+# Create logical switch
+ovn-nbctl ls-add ls0
+# Create gateway router
+ovn-nbctl lr-add lr0
+# Add router port to gateway router
+ovn-nbctl lrp-add lr0 lr0-ls0 f0:00:00:00:00:01 192.168.0.1/24
+ovn-nbctl lsp-add ls0 ls0-lr0 -- set Logical_Switch_Port ls0-lr0 \
+ type=router options:router-port=lr0-ls0 addresses='"f0:00:00:00:00:01"'
+
+# Create a localnet port.
+ovn-nbctl lsp-add ls0 ln_port
+ovn-nbctl lsp-set-addresses ln_port unknown
+ovn-nbctl lsp-set-type ln_port localnet
+ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1
+
+# Prepare packets
+touch empty_expected
+echo "fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001" > arp_expected
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl \
+ -- add-br br-phys \
+ -- add-br br-eth0
+
+ovn_attach n1 br-phys 192.168.0.10
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
+AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
+
+sim_add hv2
+as hv2
+ovs-vsctl \
+ -- add-br br-phys \
+ -- add-br br-eth0
+
+ovn_attach n1 br-phys 192.168.0.20
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
+AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv2/snoopvif-tx.pcap options:rxq_pcap=hv2/snoopvif-rx.pcap])
+
+ovn-sbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+# Wait until the patch ports are created in hv1 and hv2 to connect br-int to br-eth0
+AT_CHECK([ovn-nbctl set logical_router lr0 options:chassis=hv1])
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-vsctl show | \
+grep "Port patch-br-int-to-ln_port" | wc -l`])
+AT_CHECK([ovn-nbctl set logical_router lr0 options:chassis=hv2])
+OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \
+grep "Port patch-br-int-to-ln_port" | wc -l`])
+
+# Temporarily remove lr0 chassis
+AT_CHECK([ovn-nbctl remove logical_router lr0 options chassis])
+
+reset_pcap_file() {
+ local hv=$1
+ local iface=$2
+ local pcap_file=$3
+ as $hv
+ 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
+}
+
+reset_pcap_file hv1 snoopvif hv1/snoopvif
+reset_pcap_file hv2 snoopvif hv2/snoopvif
+
+hv1_uuid=$(ovn-sbctl --bare --columns _uuid list chassis hv1)
+AT_CHECK([ovn-nbctl set logical_router lr0 options:chassis=hv1])
+OVS_WAIT_UNTIL([
+ ls0_lr0=$(ovn-sbctl --bare --columns chassis list port_binding ls0-lr0)
+ test "$ls0_lr0" = $hv1_uuid
+])
+
+sleep 2
+OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [arp_expected])
+OVN_CHECK_PACKETS([hv2/snoopvif-tx.pcap], [empty_expected])
+
+# Temporarily remove lr0 chassis
+AT_CHECK([ovn-nbctl remove logical_router lr0 options chassis])
+
+reset_pcap_file hv1 snoopvif hv1/snoopvif
+reset_pcap_file hv2 snoopvif hv2/snoopvif
+
+hv2_uuid=$(ovn-sbctl --bare --columns _uuid list chassis hv2)
+AT_CHECK([ovn-nbctl set logical_router lr0 options:chassis=hv2])
+OVS_WAIT_UNTIL([
+ ls0_lr0=$(ovn-sbctl --bare --columns chassis list port_binding ls0-lr0)
+ test "$ls0_lr0" = $hv2_uuid
+])
+
+sleep 2
+OVN_CHECK_PACKETS_CONTAIN([hv2/snoopvif-tx.pcap], [arp_expected])
+OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [empty_expected])
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([send gratuitous arp with nat-addresses router in localnet])
ovn_start
@@ -9403,6 +9511,10 @@ check ovn-nbctl --wait=hv qos-add lsw0 to-lport 1002 'inport=="lp2" && is_chassi
AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep meter | wc -l], [0], [4
])
+check ovn-nbctl qos-del lsw0
+AT_CHECK([as hv ovs-ofctl dump-meters br-int -O OpenFlow13 | grep meter | wc -l], [0], [0
+])
+
OVN_CLEANUP([hv])
AT_CLEANUP
])
@@ -13988,6 +14100,99 @@ OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([garps disabled when port no longer bound to chassis])
+ovn_start
+
+net_add n1
+for i in 1 2; do
+ sim_add hv$i
+ as hv$i
+ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+ check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+done
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 port
+check ovn-nbctl lsp-set-addresses port "00:00:00:00:00:01 10.0.0.1"
+
+check ovn-nbctl lsp-add ls0 public
+check ovn-nbctl lsp-set-addresses public unknown
+check ovn-nbctl lsp-set-type public localnet
+check ovn-nbctl lsp-set-options public network_name=phys
+
+for hv in hv1 hv2; do
+ as $hv check ovs-vsctl -- add-port br-int port -- \
+ set Interface port external-ids:iface-id=port \
+ options:tx_pcap=$hv/port-tx.pcap \
+ options:rxq_pcap=$hv/port-rx.pcap
+done
+
+reset_pcap_file() {
+ local hv=$1
+ local iface=$2
+ local pcap_file=$3
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+ options:rxq_pcap=dummy-rx.pcap
+ check rm -f ${pcap_file}*.pcap
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+ options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+reset_env() {
+ reset_pcap_file hv1 br-phys_n1 hv1/br-phys_n1
+ reset_pcap_file hv2 br-phys_n1 hv2/br-phys_n1
+
+ for port in hv1/n1 hv2/n1; do
+ : > $port.expected
+ done
+}
+
+for hv in hv1 hv2; do
+ wait_row_count Chassis 1 name=$hv
+done
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+
+OVN_POPULATE_ARP
+
+# Activate port on each hv giving a chance to each chassis to enable garps
+check ovn-nbctl lsp-set-options port requested-chassis=hv1
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=port
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=port
+wait_for_ports_up
+reset_env
+
+# give chassis some time to generate garps
+sleep 2
+
+expected_garp=ffffffffffff000000000001080600010800060400010000000000010a0000010000000000000a000001
+
+# check hv1 sends garps and hv2 doesn't
+echo $expected_garp >> hv1/n1.expected
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [hv1/n1.expected])
+OVN_CHECK_PACKETS([hv2/br-phys_n1-tx.pcap], [hv2/n1.expected])
+
+check ovn-nbctl lsp-set-options port requested-chassis=hv2
+wait_column "$hv2_uuid" Port_Binding chassis logical_port=port
+wait_column "$hv2_uuid" Port_Binding requested_chassis logical_port=port
+wait_for_ports_up
+reset_env
+
+# give chassis some time to generate garps
+sleep 2
+
+# check hv2 sends garps and hv1 doesn't
+echo $expected_garp >> hv2/n1.expected
+OVN_CHECK_PACKETS([hv1/br-phys_n1-tx.pcap], [hv1/n1.expected])
+OVN_CHECK_PACKETS_CONTAIN([hv2/br-phys_n1-tx.pcap], [hv2/n1.expected])
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([IPv6 periodic RA disabled for localnet adjacent switch ports])
ovn_start
@@ -15243,6 +15448,92 @@ OVN_CLEANUP([hv1])
AT_CLEANUP
])
+# This test ensures that the incremental flow installation works well when
+# handling update->delete->add/update for the same OVS flow.
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL conjunction append and reprocess])
+ovn_start
+
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+# Setup the desired state:
+# - Two ACLs, each matches its own port-group (pg1 & pg2), and matches the same
+# set of IP addresses.
+# - pg1 includes p1, p2, p3
+# - pg2 includes p4, p5
+check ovn-nbctl ls-add sw
+check ovn-nbctl lsp-add sw p1 -- lsp-set-addresses p1 "00:00:00:00:00:02 192.168.0.2"
+check ovn-nbctl lsp-add sw p2 -- lsp-set-addresses p2 "00:00:00:00:00:03 192.168.0.3"
+check ovn-nbctl lsp-add sw p3 -- lsp-set-addresses p3 "00:00:00:00:00:04 192.168.0.4"
+check ovn-nbctl lsp-add sw p4 -- lsp-set-addresses p4 "00:00:00:00:00:05 192.168.0.5"
+check ovn-nbctl lsp-add sw p5 -- lsp-set-addresses p5 "00:00:00:00:00:06 192.168.0.6"
+check ovn-nbctl pg-add pg1 p1 p2 p3
+check ovn-nbctl pg-add pg2 p4 p5
+check ovs-vsctl add-port br-int p1 -- set Interface p1 external_ids:iface-id=p1
+check ovs-vsctl add-port br-int p2 -- set Interface p2 external_ids:iface-id=p2
+check ovs-vsctl add-port br-int p3 -- set Interface p3 external_ids:iface-id=p3
+check ovs-vsctl add-port br-int p4 -- set Interface p4 external_ids:iface-id=p4
+check ovs-vsctl add-port br-int p5 -- set Interface p5 external_ids:iface-id=p5
+check ovn-nbctl acl-add pg1 to-lport 1000 "outport==@pg1 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}" allow
+check ovn-nbctl acl-add pg2 to-lport 1000 "outport==@pg2 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}" allow
+check ovn-nbctl --wait=hv sync
+
+# Now we should have two flows with combined conjunctions.
+OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep conjunction.*conjunction | wc -l`])
+
+
+# Test the scenario 10 times to give enough chance to hit the
+# "update->delete->add/update" scenario, because we can't decide the order of
+# change handling inside ovn-controller.
+for i in $(seq 10); do
+# Unbind the p3 and p5, the combined conjunctions should be gone.
+ovs-vsctl del-port br-int p3
+ovs-vsctl del-port br-int p5
+OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep conjunction.*conjunction | wc -l`])
+
+# Delete and re-add the ACLs, just to bring some randomness in the lflow
+# processing order, so that there is a chance that the order of adding and
+# appending are the same before & after the flow deletion, so that the
+# generated combined conjunctions are the same before & after the flow
+# deletion. (If the order is different, the combined conjunctions order is
+# different and the action comparison would fail, so won't trigger the tracked
+# flow merging. We want to make sure that we test the merging scenario)
+ovn-nbctl acl-del pg1 to-lport 1000 "outport==@pg1 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}"
+ovn-nbctl acl-del pg2 to-lport 1000 "outport==@pg2 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}"
+ovn-nbctl acl-add pg1 to-lport 1000 "outport==@pg1 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}" allow
+ovn-nbctl acl-add pg2 to-lport 1000 "outport==@pg2 && ip4 && ip4.src == {10.0.0.1, 10.0.0.2}" allow
+ovn-nbctl --wait=hv sync
+
+# Now re-bind p3 and p5 in the same transaction, so that pg1 and pg2 update are
+# handled in the same I-P engine run. The order of pg1 and pg2 can be random.
+# If the order is pg2 -> pg1, then it should trigger the OVS flow
+# "update->delete->add/update" scenario:
+# 1) when pg2 update is handled, the ACL-2 would append conjunctions to
+# the conjunction flows of ACL-1
+# 2) when pg1 update is handled, it would flood remove flows of both ACL-1 and
+# ACL-2, including the "appended" conjunction flows. And then reprocess
+# ACL-1 and ACL-2 would re-add and re-append the conjunction flows with
+# combined conjunctions.
+ovs-vsctl add-port br-int p3 -- set Interface p3 external_ids:iface-id=p3 -- \
+ add-port br-int p5 -- set Interface p5 external_ids:iface-id=p5
+ovn-nbctl --wait=hv sync
+
+# Now making sure we end up with two combined conjunctions.
+OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep conjunction.*conjunction | wc -l`])
+
+done
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Superseding ACLs with conjunction])
ovn_start
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index c4a2c39f6..018dcea2a 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -6922,10 +6922,10 @@ p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
EOF
-# 1pps + 1 burst size
+# 1pps
OVS_WAIT_UNTIL([
n_reject=$(grep unreachable reject.pcap | wc -l)
- test "${n_reject}" = "2"
+ test "${n_reject}" = "1"
])
kill $(pidof tcpdump)
rm -f reject.pcap
@@ -6938,10 +6938,10 @@ p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
EOF
-# 10pps + 1 burst size
+# 10pps
OVS_WAIT_UNTIL([
n_reject=$(grep unreachable reject.pcap | wc -l)
- test "${n_reject}" = "20"
+ test "${n_reject}" = "10"
])
kill $(pidof tcpdump)
@@ -6974,10 +6974,10 @@ p = IP(src="192.168.1.2", dst="172.16.1.100") / TCP(dport = 80, flags="S") / Raw
send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
EOF
-# 1pps + 1 burst size
+# 1pps
OVS_WAIT_UNTIL([
n_arp=$(grep ARP arp.pcap | wc -l)
- test "${n_arp}" = "2"
+ test "${n_arp}" = "1"
])
kill $(pidof tcpdump)
@@ -6994,10 +6994,10 @@ p = IP(src="192.168.1.2", dst="172.16.1.100", ttl=1) / TCP(dport = 8080, flags="
send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
EOF
-# 1pps + 1 burst size
+# 1pps
OVS_WAIT_UNTIL([
n_icmp=$(grep ICMP icmp.pcap | wc -l)
- test "${n_icmp}" = "2"
+ test "${n_icmp}" = "1"
])
kill $(pidof tcpdump)
@@ -7010,7 +7010,7 @@ bfd: bfd-meter
check ovn-nbctl --bfd lr-route-add R1 240.0.0.0/8 172.16.1.50 rp-public
printf "%08x" $(ovn-sbctl get bfd . disc) > /tmp/disc
-NS_EXEC([server], [tcpdump -l -n -i s1 udp port 3784 -Q in > bfd.pcap &])
+NS_EXEC([server], [tcpdump -l -nn -i s1 udp port 3784 and ip[[29]]==0x90 -Q in > bfd.pcap &])
ip netns exec server scapy -H <<-EOF
import binascii
f = open("/tmp/disc", "r")
@@ -7023,10 +7023,10 @@ f.close()
EOF
rm /tmp/disc
-# 1pps + 1 burst size
+# 1pps
OVS_WAIT_UNTIL([
- n_tcp_rst=$(grep Final bfd.pcap | wc -l)
- test "${n_tcp_rst}" = "2"
+ n_bfd=$(grep 3784 bfd.pcap | wc -l)
+ test "${n_bfd}" = "1"
])
kill $(pidof tcpdump)
@@ -7992,3 +7992,122 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([East-West traffic with gateway router if DNAT configured])
+AT_KEYWORDS([ovnnat])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+# Logical network:
+# One LR - R1 has two switches: sw0 and sw1
+# sw0 -- R1 -- sw1
+# Logical port 'sw01' in switch 'sw0'.
+# Logical port 'sw11' in switch 'sw1'.
+# nc server running in sw01
+# nc client running on sw11
+
+check ovn-nbctl lr-add R1
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add sw1
+
+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
+check ovn-nbctl set logical_router R1 options:chassis=hv1
+
+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+ type=router options:router-port=rp-sw0 \
+ -- lsp-set-addresses sw0-rp router
+check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
+ type=router options:router-port=rp-sw1 \
+ -- lsp-set-addresses sw1-rp router
+
+ADD_NAMESPACES(sw01)
+ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+ "192.168.1.1")
+check ovn-nbctl lsp-add sw0 sw01 \
+ -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
+
+ADD_NAMESPACES(sw11)
+ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
+ "192.168.2.1")
+check ovn-nbctl lsp-add sw1 sw11 \
+ -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
+
+NETNS_DAEMONIZE([sw01], [nc -k -l 8000], [nc-sw01.pid])
+
+test_ping() {
+ NS_CHECK_EXEC([$1], [ping -q -c 1 $2 -w 2 | FORMAT_PING], \
+[0], [dnl
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+])
+}
+
+# Only SNAT
+check ovn-nbctl --wait=hv lr-nat-add R1 snat 172.16.1.21 192.168.2.0/24
+
+echo "foo" > foo
+NS_CHECK_EXEC([sw11], [nc 192.168.1.2 8000 < foo])
+test_ping sw11 192.168.1.2
+
+# Ensure nat has been hit
+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep -v "n_packets=0" | grep 'nat(src=172.16.1.21)'])
+# Ensure conntrack entry is present
+OVS_WAIT_FOR_OUTPUT([
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=192.168.2.2,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared>
+tcp,orig=(src=192.168.2.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# SNAT and DNAT. using Logical IP
+ovn-nbctl --wait=hv lr-nat-add R1 dnat_and_snat 172.16.1.2 192.168.1.2
+NS_CHECK_EXEC([sw11], [nc 192.168.1.2 8000 < foo ])
+test_ping sw11 192.168.1.2
+
+# Ensure conntrack entry is present
+OVS_WAIT_FOR_OUTPUT([
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=192.168.2.2,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared>
+tcp,orig=(src=192.168.2.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# SNAT and DNAT. using floating IP
+NS_CHECK_EXEC([sw11], [nc 172.16.1.2 8000 < foo ])
+test_ping sw11 172.16.1.2
+
+# Ensure conntrack entry is present
+OVS_WAIT_FOR_OUTPUT([
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=192.168.2.2,id=<cleared>,type=0,code=0),zone=<cleared>
+tcp,orig=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+AT_CLEANUP
+])
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 7bcc2c66a..7fcfa50d4 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -2856,7 +2856,7 @@ nbctl_lb_add(struct ctl_context *ctx)
}
struct sockaddr_storage ss_vip;
- if (!inet_parse_active(lb_vip, 0, &ss_vip, false)) {
+ if (!inet_parse_active(lb_vip, 0, &ss_vip, false, NULL)) {
ctl_error(ctx, "%s: should be an IP address (or an IP address "
"and a port number with : as a separator).", lb_vip);
return;
@@ -2886,7 +2886,7 @@ nbctl_lb_add(struct ctl_context *ctx)
struct sockaddr_storage ss_dst;
if (lb_vip_port) {
- if (!inet_parse_active(token, -1, &ss_dst, false)) {
+ if (!inet_parse_active(token, -1, &ss_dst, false, NULL)) {
ctl_error(ctx, "%s: should be an IP address and a port "
"number with : as a separator.", token);
goto out;
@@ -3032,7 +3032,7 @@ lb_info_add_smap(const struct nbrec_load_balancer *lb,
const struct smap_node *node = nodes[i];
struct sockaddr_storage ss;
- if (!inet_parse_active(node->key, 0, &ss, false)) {
+ if (!inet_parse_active(node->key, 0, &ss, false, NULL)) {
continue;
}
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index ece5803f2..096d691d5 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -214,7 +214,7 @@ static void
parse_lb_option(const char *s)
{
struct sockaddr_storage ss;
- if (!inet_parse_active(s, 0, &ss, false)) {
+ if (!inet_parse_active(s, 0, &ss, false, NULL)) {
ovs_fatal(0, "%s: bad address", s);
}
@@ -1388,7 +1388,8 @@ ovntrace_node_prune_summary(struct ovs_list *nodes)
sub->type == OVNTRACE_NODE_TABLE) {
/* Replace 'sub' by its children, if any, */
ovs_list_remove(&sub->node);
- ovs_list_splice(&next->node, sub->subs.next, &sub->subs);
+ ovs_list_splice(next ? &next->node : nodes, sub->subs.next,
+ &sub->subs);
ovntrace_node_destroy(sub);
}
}
@@ -1432,7 +1433,8 @@ ovntrace_node_prune_hard(struct ovs_list *nodes)
sub->type == OVNTRACE_NODE_OUTPUT) {
/* Replace 'sub' by its children, if any, */
ovs_list_remove(&sub->node);
- ovs_list_splice(&next->node, sub->subs.next, &sub->subs);
+ ovs_list_splice(next ? &next->node : nodes, sub->subs.next,
+ &sub->subs);
ovntrace_node_destroy(sub);
}
}