From 5c758677050699dba0e4ba0821c860389dc5ebc4 Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Mar 21 2022 09:29:45 +0000 Subject: Import ovn-2021-21.12.0-11 From Fast DataPath --- diff --git a/.ovn.metadata b/.ovn.metadata index 20291e2..d14e5a4 100644 --- a/.ovn.metadata +++ b/.ovn.metadata @@ -1,5 +1,5 @@ 002450621b33c5690060345b0aac25bc2426d675 SOURCES/docutils-0.12.tar.gz -90c634ea30bd1f8c09eb1e65dea10b5ce4dbb3fb SOURCES/openvswitch-e6ad4d8.tar.gz -20793f6d7758400adb7c51c1df631083fd5fee4b SOURCES/ovn-21.06.0.tar.gz +1a15e20ba189049e6bb9f6dde87ff04483351623 SOURCES/openvswitch-91e1ff5.tar.gz +347346dae160f28d6e56b1dee8fa8b701a50748e SOURCES/ovn-21.12.0.tar.gz d34f96421a86004aa5d26ecf975edefd09f948b1 SOURCES/Pygments-1.4.tar.gz 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a SOURCES/Sphinx-1.1.3.tar.gz diff --git a/SOURCES/0001-Allow-VLAN-traffic-when-LS-vlan-passthru-true.patch b/SOURCES/0001-Allow-VLAN-traffic-when-LS-vlan-passthru-true.patch deleted file mode 100644 index 7ac6694..0000000 --- a/SOURCES/0001-Allow-VLAN-traffic-when-LS-vlan-passthru-true.patch +++ /dev/null @@ -1,190 +0,0 @@ -From 04a31bac15dec703643ed70c7bb42725bf5ed676 Mon Sep 17 00:00:00 2001 -From: Ihar Hrachyshka -Date: Mon, 9 Nov 2020 21:34:49 -0500 -Subject: [PATCH] Allow VLAN traffic when LS:vlan-passthru=true - -A new other_config:vlan-passthru knob is added to Logical-Switches. When -true, VLAN tagged incoming traffic is allowed. This option can be used -to implement OpenStack Network VLAN transparency API extension [1]. - -[1] https://docs.openstack.org/api-ref/network/v2/index.html#vlan-transparency-extension - -Signed-off-by: Ihar Hrachyshka -Signed-off-by: Numan Siddique -(cherry picked from commit c29221d9322a34501cb74e255023827220c23a8b) ---- - NEWS | 5 +++ - northd/ovn-northd.c | 14 +++++-- - ovn-nb.xml | 7 ++++ - tests/ovn.at | 92 +++++++++++++++++++++++++++++++++++++++++++++ - 4 files changed, 115 insertions(+), 3 deletions(-) - -diff --git a/NEWS b/NEWS -index 0f4252347..46140208f 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,8 @@ -+Post-v20.09.0 -+--------------------- -+ - Support other_config:vlan-passthru=true to allow VLAN tagged incoming -+ traffic. -+ - OVN v20.09.0 - 28 Sep 2020 - -------------------------- - - Added packet marking support for traffic routed with -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index a158a73a7..d2540a315 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6781,6 +6781,12 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op, - 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, -@@ -6828,9 +6834,11 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - continue; - } - -- /* Logical VLANs not supported. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present", -- "drop;"); -+ 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]", -diff --git a/ovn-nb.xml b/ovn-nb.xml -index 43694535e..451842588 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -512,6 +512,13 @@ - - - -+ -+ -+ Determines whether VLAN tagged incoming traffic should be allowed. -+ -+ -+ - - - See External IDs at the beginning of this document. -diff --git a/tests/ovn.at b/tests/ovn.at -index 180fb91e3..f6523a109 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -3015,6 +3015,98 @@ OVN_CLEANUP([hv-1],[hv-2]) - - AT_CLEANUP - -+AT_SETUP([ovn -- VLAN transparency, passthru=true]) -+ovn_start -+ -+ovn-nbctl ls-add ls -+ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true -+for i in 1 2; do -+ ovn-nbctl lsp-add ls lsp$i -+ 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 -+ -+ovn-nbctl ls-add ls -+ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=false -+for i in 1 2; do -+ ovn-nbctl lsp-add ls lsp$i -+ 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 - --- -2.28.0 - diff --git a/SOURCES/0001-Fix-OVN-update-issue-when-ovn-controller-is-updated-.patch b/SOURCES/0001-Fix-OVN-update-issue-when-ovn-controller-is-updated-.patch deleted file mode 100644 index 0fd0391..0000000 --- a/SOURCES/0001-Fix-OVN-update-issue-when-ovn-controller-is-updated-.patch +++ /dev/null @@ -1,297 +0,0 @@ -From 224b2ba1c53279f33eafe76ac46ffdf347f8a077 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Fri, 20 Nov 2020 16:22:53 +0530 -Subject: [PATCH] Fix OVN update issue when ovn-controller is updated first - from 20.06 to 20.09. - -The commit in the Fixes tag changed the ct_commit signature from -ct_commit(..) to ct_commit{ ..}. When ovn-controllers are updated -to a vesion which has this change and if ovn-northd is still not -udated to this version, then the logical flow with the action -ct_commit(..) will be rejected by ovn-controllers resulting in -datapath disruptions. OVN recommends ovn-controller updates before -ovn-northd, but it is broken now. This patch fixes this issue by -adding back the support for the old ct_commit(..). We now support -both the versions of ct_commit. - -Fixes: 6cfb44a76c61("Used nested actions in ct_commit) -CC: Mark Michelson -Signed-off-by: Numan Siddique -Acked-by: Flavio Fernandes ---- - include/ovn/actions.h | 10 ++- - lib/actions.c | 137 ++++++++++++++++++++++++++++++++++++++++-- - tests/ovn.at | 44 ++++++++++++++ - utilities/ovn-trace.c | 3 +- - 4 files changed, 186 insertions(+), 8 deletions(-) - -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 7ba24cd60..9c1ebf4aa 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -61,7 +61,8 @@ struct ovn_extend_table; - 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) \ -@@ -230,6 +231,13 @@ struct ovnact_ct_next { - 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; -diff --git a/lib/actions.c b/lib/actions.c -index 3219ab3be..4a9813218 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -627,16 +627,75 @@ ovnact_ct_next_free(struct ovnact_ct_next *a OVS_UNUSED) - { - } - -+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 @@ parse_CT_COMMIT(struct action_context *ctx) - } - - 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 @@ format_CT_COMMIT(const struct ovnact_nest *on, struct ds *s) - } - - 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; -diff --git a/tests/ovn.at b/tests/ovn.at -index f56f8a696..396e60eeb 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1102,6 +1102,50 @@ ct_commit { ct_label=0x181716151413121110090807060504030201; }; - 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 -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index cc1cd1b16..8421c0682 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -2334,7 +2334,8 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - 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; - --- -2.28.0 - diff --git a/SOURCES/0001-Provide-the-option-to-pin-ovn-controller-and-ovn-nor.patch b/SOURCES/0001-Provide-the-option-to-pin-ovn-controller-and-ovn-nor.patch deleted file mode 100644 index a0d1d96..0000000 --- a/SOURCES/0001-Provide-the-option-to-pin-ovn-controller-and-ovn-nor.patch +++ /dev/null @@ -1,488 +0,0 @@ -From f1b715c3f0f222cbf6bf07733792a16c6442a085 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Fri, 13 Nov 2020 23:44:10 +0530 -Subject: [PATCH 01/16] Provide the option to pin ovn-controller and ovn-northd - to a specific version. - -OVN recommends updating/upgrading ovn-controllers first and then -ovn-northd and OVN DB ovsdb-servers. This is to ensure that any -new functionality specified by the database or logical flows created -by ovn-northd is understood by ovn-controller. - -However certain deployments may upgrade ovn-northd services first and -then ovn-controllers. In a large scal deployment, this can result in -downtime during upgrades as old ovn-controllers may not understand -new logical flows or new actions added by ovn-northd. - -Even upgrading ovn-controllers first can result in ovn-controllers -rejecting some of the logical flows if an existing OVN action is -changed. One such example is ct_commit action which recently was updated -to take new arguments. - -To avoid such downtimes during upgrades, this patch adds the -functionality of pinning ovn-controller and ovn-northd to a specific -version. An internal OVN version is generated and this version is stored -by ovn-northd in the Southbound SB_Global table's -options:northd_internal_version. - -When ovn-controller notices that the internal version has changed, it -stops handling the database changes - both Southbound and OVS. All the -existing OF flows are preserved. When ovn-controller is upgraded to the -same version as ovn-northd services, it will process the database -changes. - -This feature is made optional and disabled by default. A CMS can -enable it by configuring the OVS local database with the option - -ovn-match-northd-version=true. - -Change-Id: I5f6c693953db1a5532b62cf0a7d588f78bb353c1 -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique ---- - .../contributing/submitting-patches.rst | 12 ++ - NEWS | 3 + - controller/ovn-controller.8.xml | 11 ++ - controller/ovn-controller.c | 52 ++++++- - include/ovn/actions.h | 4 + - lib/ovn-util.c | 14 ++ - lib/ovn-util.h | 4 + - northd/ovn-northd.c | 18 ++- - tests/ovn.at | 147 ++++++++++++++++++ - 9 files changed, 260 insertions(+), 5 deletions(-) - -diff --git a/Documentation/internals/contributing/submitting-patches.rst b/Documentation/internals/contributing/submitting-patches.rst -index 0a9de5866..31a3ca747 100644 ---- a/Documentation/internals/contributing/submitting-patches.rst -+++ b/Documentation/internals/contributing/submitting-patches.rst -@@ -397,6 +397,18 @@ Remember to follow-up and actually remove the feature from OVN codebase once - 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 --git a/NEWS b/NEWS -index 46140208f..5968ca341 100644 ---- a/NEWS -+++ b/NEWS -@@ -2,6 +2,9 @@ Post-v20.09.0 - --------------------- - - Support other_config:vlan-passthru=true to allow VLAN tagged incoming - traffic. -+ - 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. - - OVN v20.09.0 - 28 Sep 2020 - -------------------------- -diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml -index 16bc47b20..b5c0800f0 100644 ---- a/controller/ovn-controller.8.xml -+++ b/controller/ovn-controller.8.xml -@@ -233,6 +233,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. -+
- - -

-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index e5479cf3e..faae787f3 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -2159,6 +2159,49 @@ struct ovn_controller_exit_args { - 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[]) - { -@@ -2453,6 +2496,9 @@ main(int argc, char *argv[]) - .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; -@@ -2506,7 +2552,9 @@ main(int argc, char *argv[]) - - engine_set_context(&eng_ctx); - -- if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl)) { -+ if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl) && -+ check_northd_version(ovs_idl_loop.idl, ovnsb_idl_loop.idl, -+ ovn_version)) { - /* Contains the transport zones that this Chassis belongs to */ - struct sset transport_zones = SSET_INITIALIZER(&transport_zones); - sset_from_delimited_string(&transport_zones, -@@ -2795,6 +2843,7 @@ loop_done: - } - } - -+ free(ovn_version); - unixctl_server_destroy(unixctl); - lflow_destroy(); - ofctrl_destroy(); -@@ -2847,6 +2896,7 @@ parse_options(int argc, char *argv[]) - - 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 --git a/include/ovn/actions.h b/include/ovn/actions.h -index 630bbe79e..7ba24cd60 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -48,6 +48,10 @@ struct ovn_extend_table; - * 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) \ -diff --git a/lib/ovn-util.c b/lib/ovn-util.c -index 8722f7a48..e94a16422 100644 ---- a/lib/ovn-util.c -+++ b/lib/ovn-util.c -@@ -20,6 +20,7 @@ - #include - - #include "daemon.h" -+#include "include/ovn/actions.h" - #include "openvswitch/ofp-parse.h" - #include "openvswitch/vlog.h" - #include "ovn-dirs.h" -@@ -720,3 +721,16 @@ ip_address_and_port_from_lb_key(const char *key, char **ip_address, - *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 --git a/lib/ovn-util.h b/lib/ovn-util.h -index f72a801df..aa737a20c 100644 ---- a/lib/ovn-util.h -+++ b/lib/ovn-util.h -@@ -231,4 +231,8 @@ char *str_tolower(const char *orig); - 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 --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index bb31e04fa..3884e08eb 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -11939,7 +11939,8 @@ ovnnb_db_run(struct northd_context *ctx, - 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; -@@ -12016,6 +12017,8 @@ ovnnb_db_run(struct northd_context *ctx, - 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); - -@@ -12626,7 +12629,8 @@ ovnsb_db_run(struct northd_context *ctx, - 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; -@@ -12636,7 +12640,8 @@ ovn_db_run(struct northd_context *ctx, - - 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); - } -@@ -13003,6 +13008,9 @@ main(int argc, char *argv[]) - 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; -@@ -13044,7 +13052,8 @@ main(int argc, char *argv[]) - } - - 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); -@@ -13106,6 +13115,7 @@ main(int argc, char *argv[]) - } - } - -+ free(ovn_internal_version); - unixctl_server_destroy(unixctl); - ovsdb_idl_loop_destroy(&ovnnb_idl_loop); - ovsdb_idl_loop_destroy(&ovnsb_idl_loop); -diff --git a/tests/ovn.at b/tests/ovn.at -index 8f18ca9e5..f771b7563 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -23235,3 +23235,150 @@ OVS_WAIT_UNTIL( - - 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 --- -2.28.0 - diff --git a/SOURCES/0001-Revert-ovsdb-idl-Avoid-sending-redundant-conditional.patch b/SOURCES/0001-Revert-ovsdb-idl-Avoid-sending-redundant-conditional.patch deleted file mode 100644 index 9f9f93d..0000000 --- a/SOURCES/0001-Revert-ovsdb-idl-Avoid-sending-redundant-conditional.patch +++ /dev/null @@ -1,67 +0,0 @@ -From e8922fefa62184cdd0ff808f4e52de74e8d9bbe5 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 25 Mar 2020 21:15:23 +0100 -Subject: [PATCH 1/4] Revert "ovsdb-idl: Avoid sending redundant conditional - monitoring updates" - -This reverts commit 5351980b047f4dd40be7a59a1e4b910df21eca0a. - -If the ovsdb-server reply to "monitor_cond_since" requests has -"found" == false then ovsdb_idl_db_parse_monitor_reply() calls -ovsdb_idl_db_clear() which iterates through all tables and -unconditionally sets table->cond_changed to false. - -However, if the client had already set a new condition for some of the -tables, this new condition request will never be sent to ovsdb-server -until the condition is reset to a different value. This is due to the -check in ovsdb_idl_db_set_condition(). - -One way to replicate the issue is described in the bugzilla reporting -the bug, when ovn-controller is configured to use "ovn-monitor-all": -https://bugzilla.redhat.com/show_bug.cgi?id=1808125#c6 - -Commit 5351980b047f tried to optimize sending redundant conditional -monitoring updates but the chances that this scenario happens with the -latest code is quite low since commit 403a6a0cb003 ("ovsdb-idl: Fast -resync from server when connection reset.") changed the behavior of -ovsdb_idl_db_parse_monitor_reply() to avoid calling ovsdb_idl_db_clear() -in most cases. - -Reported-by: Dan Williams -Reported-at: https://bugzilla.redhat.com/1808125 -CC: Andy Zhou -Fixes: 5351980b047f ("ovsdb-idl: Avoid sending redundant conditional monitoring updates") -Acked-by: Han Zhou -Acked-by: Ilya Maximets -Signed-off-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -(cherry picked from upstream OVS commit 2b7e536fa5e20be10e620b959e05557f88862d2c) - -Change-Id: Iaa24bc949d648e8fa29abea1fe8fb5878ba45864 ---- - openvswitch-2.13.0/lib/ovsdb-idl.c | 2 -- - 1 file changed, 2 deletions(-) - -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl.c b/openvswitch-2.13.0/lib/ovsdb-idl.c -index 190143f36..1535ad7b5 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl.c -+++ b/openvswitch-2.13.0/lib/ovsdb-idl.c -@@ -610,7 +610,6 @@ ovsdb_idl_db_clear(struct ovsdb_idl_db *db) - struct ovsdb_idl_table *table = &db->tables[i]; - struct ovsdb_idl_row *row, *next_row; - -- table->cond_changed = false; - if (hmap_is_empty(&table->rows)) { - continue; - } -@@ -634,7 +633,6 @@ ovsdb_idl_db_clear(struct ovsdb_idl_db *db) - } - ovsdb_idl_row_destroy_postprocess(db); - -- db->cond_changed = false; - db->cond_seqno = 0; - ovsdb_idl_db_track_clear(db); - --- -2.26.2 - diff --git a/SOURCES/0001-Support-configuring-Load-Balancer-hairpin-source-IP.patch b/SOURCES/0001-Support-configuring-Load-Balancer-hairpin-source-IP.patch deleted file mode 100644 index f135d78..0000000 --- a/SOURCES/0001-Support-configuring-Load-Balancer-hairpin-source-IP.patch +++ /dev/null @@ -1,762 +0,0 @@ -From 8788ac191a4e0689f0287695c181fe1a781b0d31 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Fri, 15 Jan 2021 19:26:13 +0100 -Subject: [PATCH 1/2] Support configuring Load Balancer hairpin source IP. - -In case traffic that gets load balanced is DNAT-ed to a backend IP that -happens to be the source of the traffic then OVN performs an additional -SNAT to ensure that return traffic is directed through OVN. - -Until now the load balancer VIP was chosen as SNAT IP. However, in -specific scenarios, the CMS may prefer a different IP, e.g., a single -cluster-wide IP. This commit adds support, through the newly added -Load_Balancer.option 'hairpin_snat_ip', to allow the CMS to explicitly -chose a SNAT IP. - -Due to the fact that now traffic that was hairpinned might need to be -SNAT-ed to different IPs for different load balancers that share the -same VIP address value we need to also explicitly match on L4 protocol -and ports in the 'OFTABLE_CT_SNAT_FOR_VIP' table. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit cc4d5520064f294d2b011be10ec5ff5f1a85bfd0) - -Conflicts: - NEWS - -Change-Id: Ie5ba5d9f3811ee577377e3e2cd700d2949a174da ---- - NEWS | 3 + - controller/lflow.c | 53 +++++++++------ - lib/lb.c | 26 ++++++++ - lib/lb.h | 11 ++++ - lib/ovn-util.c | 21 ++++++ - lib/ovn-util.h | 1 + - northd/ovn-northd.c | 9 +-- - ovn-nb.xml | 8 +++ - ovn-sb.ovsschema | 9 ++- - ovn-sb.xml | 8 +++ - tests/ovn-northd.at | 6 ++ - tests/ovn.at | 182 ++++++++++++++++++++++++++++++++++++++-------------- - 12 files changed, 261 insertions(+), 76 deletions(-) - -diff --git a/NEWS b/NEWS -index e89c5f4..57a9ba9 100644 ---- a/NEWS -+++ b/NEWS -@@ -10,6 +10,9 @@ Post-v20.12.0 - "ovn-installed". This external-id is set by ovn-controller only after all - openflow operations corresponding to the OVS interface being added have - been processed. -+ - Add a new option to Load_Balancer.options, "hairpin_snat_ip", to allow -+ users to explicitly select which source IP should be used for load -+ balancer hairpin traffic. - - OVN v20.12.0 - 18 Dec 2020 - -------------------------- -diff --git a/controller/lflow.c b/controller/lflow.c -index 9f6aece..946c1e0 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -1189,26 +1189,30 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - 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); -+ ovs_be32 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip); -+ ovs_be32 vip4 = lb->hairpin_snat_ips.n_ipv4_addrs -+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr -+ : in6_addr_get_mapped_ipv4(&lb_vip->vip); - - 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)); -+ match_set_nw_src(&hairpin_match, bip4); -+ match_set_nw_dst(&hairpin_match, bip4); -+ -+ match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IP)); -+ match_set_nw_src(&hairpin_reply_match, bip4); -+ match_set_nw_dst(&hairpin_reply_match, vip4); - } else { -+ struct in6_addr *bip6 = &lb_backend->ip; -+ struct in6_addr *vip6 = lb->hairpin_snat_ips.n_ipv6_addrs -+ ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr -+ : &lb_vip->vip; - 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_ipv6_src(&hairpin_match, bip6); -+ match_set_ipv6_dst(&hairpin_match, bip6); - -- 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); -+ match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IPV6)); -+ match_set_ipv6_src(&hairpin_reply_match, bip6); -+ match_set_ipv6_dst(&hairpin_reply_match, vip6); - } - - if (lb_backend->port) { -@@ -1254,6 +1258,7 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - static void - add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, - struct ovn_lb_vip *lb_vip, -+ uint8_t lb_proto, - struct ovn_desired_flow_table *flow_table) - { - uint64_t stub[1024 / 8]; -@@ -1277,10 +1282,16 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, - - 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); -+ nat->range.addr.ipv4.min = -+ lb->hairpin_snat_ips.n_ipv4_addrs -+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr -+ : in6_addr_get_mapped_ipv4(&lb_vip->vip); - } else { - nat->range_af = AF_INET6; -- nat->range.addr.ipv6.min = lb_vip->vip; -+ nat->range.addr.ipv6.min -+ = lb->hairpin_snat_ips.n_ipv6_addrs -+ ? lb->hairpin_snat_ips.ipv6_addrs[0].addr -+ : lb_vip->vip; - } - ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset); - ofpact_finish(&ofpacts, &ct->ofpact); -@@ -1288,12 +1299,16 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, - 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); -+ match_set_ct_nw_dst(&match, in6_addr_get_mapped_ipv4(&lb_vip->vip)); - } else { - match_set_dl_type(&match, htons(ETH_TYPE_IPV6)); - match_set_ct_ipv6_dst(&match, &lb_vip->vip); - } - -+ match_set_nw_proto(&match, lb_proto); -+ match_set_ct_nw_proto(&match, lb_proto); -+ match_set_ct_tp_dst(&match, htons(lb_vip->vip_port)); -+ - uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT; - match_set_ct_state_masked(&match, ct_state, ct_state); - -@@ -1349,7 +1364,7 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, - flow_table); - } - -- add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table); -+ add_lb_ct_snat_vip_flows(lb, lb_vip, lb_proto, flow_table); - } - - ovn_controller_lb_destroy(lb); -diff --git a/lib/lb.c b/lib/lb.c -index 2517c02..e11ac00 100644 ---- a/lib/lb.c -+++ b/lib/lb.c -@@ -170,6 +170,24 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip) - free(vip->backends_nb); - } - -+static void -+ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid, -+ const struct smap *lb_options, -+ struct lport_addresses *hairpin_addrs) -+{ -+ const char *addresses = smap_get(lb_options, "hairpin_snat_ip"); -+ -+ if (!addresses) { -+ return; -+ } -+ -+ if (!extract_ip_address(addresses, hairpin_addrs)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT, -+ addresses, UUID_ARGS(lb_uuid)); -+ } -+} -+ - struct ovn_northd_lb * - ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - struct hmap *ports, -@@ -224,6 +242,9 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - ds_chomp(&sel_fields, ','); - lb->selection_fields = ds_steal_cstr(&sel_fields); - } -+ -+ ovn_lb_get_hairpin_snat_ip(&nbrec_lb->header_.uuid, &nbrec_lb->options, -+ &lb->hairpin_snat_ips); - return lb; - } - -@@ -260,6 +281,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb) - free(lb->vips); - free(lb->vips_nb); - free(lb->selection_fields); -+ destroy_lport_addresses(&lb->hairpin_snat_ips); - free(lb->dps); - free(lb); - } -@@ -289,6 +311,9 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) - * correct value. - */ - lb->n_vips = n_vips; -+ -+ ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options, -+ &lb->hairpin_snat_ips); - return lb; - } - -@@ -299,5 +324,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb) - ovn_lb_vip_destroy(&lb->vips[i]); - } - free(lb->vips); -+ destroy_lport_addresses(&lb->hairpin_snat_ips); - free(lb); - } -diff --git a/lib/lb.h b/lib/lb.h -index 42c580b..dfce51c 100644 ---- a/lib/lb.h -+++ b/lib/lb.h -@@ -20,6 +20,7 @@ - #include - #include - #include "openvswitch/hmap.h" -+#include "ovn-util.h" - - struct nbrec_load_balancer; - struct sbrec_load_balancer; -@@ -37,6 +38,11 @@ struct ovn_northd_lb { - struct ovn_northd_lb_vip *vips_nb; - size_t n_vips; - -+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used -+ * as source for hairpinned -+ * traffic. -+ */ -+ - size_t n_dps; - size_t n_allocated_dps; - const struct sbrec_datapath_binding **dps; -@@ -89,6 +95,11 @@ struct ovn_controller_lb { - - struct ovn_lb_vip *vips; - size_t n_vips; -+ -+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used -+ * as source for hairpinned -+ * traffic. -+ */ - }; - - struct ovn_controller_lb *ovn_controller_lb_create( -diff --git a/lib/ovn-util.c b/lib/ovn-util.c -index 2136f90..b647106 100644 ---- a/lib/ovn-util.c -+++ b/lib/ovn-util.c -@@ -232,6 +232,27 @@ extract_ip_addresses(const char *address, struct lport_addresses *laddrs) - return false; - } - -+/* Extracts at most one IPv4 and at most one IPv6 address from 'address' -+ * which should be of the format 'IP1 [IP2]'. -+ * -+ * Return true if at most one IPv4 address and at most one IPv6 address -+ * is found in 'address'. IPs must be host IPs, i.e., no unmasked bits. -+ * -+ * The caller must call destroy_lport_addresses(). -+ */ -+bool extract_ip_address(const char *address, struct lport_addresses *laddrs) -+{ -+ if (!extract_ip_addresses(address, laddrs) || -+ laddrs->n_ipv4_addrs > 1 || -+ laddrs->n_ipv6_addrs > 1 || -+ (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) || -+ (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) { -+ destroy_lport_addresses(laddrs); -+ return false; -+ } -+ return true; -+} -+ - /* Extracts the mac, IPv4 and IPv6 addresses from the - * "nbrec_logical_router_port" parameter 'lrp'. Stores the IPv4 and - * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of -diff --git a/lib/ovn-util.h b/lib/ovn-util.h -index 679f47a..a711363 100644 ---- a/lib/ovn-util.h -+++ b/lib/ovn-util.h -@@ -72,6 +72,7 @@ bool extract_addresses(const char *address, struct lport_addresses *, - int *ofs); - bool extract_lsp_addresses(const char *address, struct lport_addresses *); - bool extract_ip_addresses(const char *address, struct lport_addresses *); -+bool extract_ip_address(const char *address, struct lport_addresses *); - bool extract_lrp_networks(const struct nbrec_logical_router_port *, - struct lport_addresses *); - bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding, -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 62d45f9..d9bcd6f 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -3606,6 +3606,7 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - sbrec_load_balancer_set_name(lb->slb, lb->nlb->name); - sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips); - sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol); -+ sbrec_load_balancer_set_options(lb->slb, &lb->nlb->options); - sbrec_load_balancer_set_datapaths( - lb->slb, (struct sbrec_datapath_binding **)lb->dps, - lb->n_dps); -@@ -8593,15 +8594,10 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, - return false; - } - -- if (!extract_ip_addresses(addresses, laddrs) || -- laddrs->n_ipv4_addrs > 1 || -- laddrs->n_ipv6_addrs > 1 || -- (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) || -- (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) { -+ if (!extract_ip_address(addresses, laddrs)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"", - addresses, UUID_ARGS(&od->key)); -- destroy_lport_addresses(laddrs); - return false; - } - -@@ -13852,6 +13848,7 @@ main(int argc, char *argv[]) - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_vips); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_protocol); -+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_options); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_load_balancer_col_external_ids); - -diff --git a/ovn-nb.xml b/ovn-nb.xml -index 105d869..86aa438 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -1644,6 +1644,14 @@ - Please note using --reject option will disable empty_lb - SB controller event for this load balancer. - -+ -+ -+ IP to be used as source IP for packets that have been hair-pinned -+ after load balancing. The default behavior when the option is not set -+ is to use the load balancer VIP as source IP. This option may have -+ exactly one IPv4 and/or one IPv6 address on it, separated by a space -+ character. -+ - - - -diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema -index b418434..0d20f08 100644 ---- a/ovn-sb.ovsschema -+++ b/ovn-sb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Southbound", -- "version": "20.14.0", -- "cksum": "1412040198 25748", -+ "version": "20.15.0", -+ "cksum": "539683023 25965", - "tables": { - "SB_Global": { - "columns": { -@@ -482,6 +482,11 @@ - "type": {"key": {"type": "uuid", - "refTable": "Datapath_Binding"}, - "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", -+ "value": "string", -+ "min": 0, -+ "max": "unlimited"}}, - "external_ids": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, -diff --git a/ovn-sb.xml b/ovn-sb.xml -index 980a096..2f251bd 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -4238,6 +4238,14 @@ tcp.flags = RST; - Datapaths to which this load balancer applies to. - - -+ -+ -+ IP to be used as source IP for packets that have been hair-pinned after -+ load balancing. This value is automatically populated by -+ ovn-northd. -+ -+ -+ - - - See External IDs at the beginning of this document. -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index d52aeed..c02c5d7 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2131,6 +2131,12 @@ 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__: Set hairpin_snat_ip on lb1 and check that SB DB is updated." -+check ovn-nbctl --wait=sb set Load_Balancer lb1 options:hairpin_snat_ip="42.42.42.42 4242::4242" -+check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 options='{hairpin_snat_ip="42.42.42.42 4242::4242"}' -+ - echo - echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's load_balancers are updated accordingly." - -diff --git a/tests/ovn.at b/tests/ovn.at -index 9f2e152..14072ec 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -20742,8 +20742,9 @@ build_tcp_syn() { - - send_ipv4_pkt() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 -- local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 ip_chksum=$9 -- local l4_payload=${10} -+ local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 -+ local l4_payload=$9 -+ local hp_ip_src=${10} - local hp_l4_payload=${11} - local outfile=${12} - -@@ -20751,8 +20752,10 @@ send_ipv4_pkt() { - - local eth=${eth_dst}${eth_src}0800 - local hp_eth=${eth_src}${eth_dst}0800 -- local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_src}${ip_dst} -- local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_dst}${ip_src} -+ local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${ip_src}${ip_dst} -+ ip=$(ip4_csum_inplace $ip) -+ local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${hp_ip_src}${ip_src} -+ hp_ip=$(ip4_csum_inplace ${hp_ip}) - local packet=${eth}${ip}${l4_payload} - local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} - -@@ -20764,15 +20767,16 @@ send_ipv6_pkt() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 - local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 - local l4_payload=$9 -- local hp_l4_payload=${10} -- local outfile=${11} -+ local hp_ip_src=${10} -+ local hp_l4_payload=${11} -+ local outfile=${12} - - local ip_ttl=40 - - local eth=${eth_dst}${eth_src}86dd - local hp_eth=${eth_src}${eth_dst}86dd - local ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_src}${ip_dst} -- local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_dst}${ip_src} -+ local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${hp_ip_src}${ip_src} - local packet=${eth}${ip}${l4_payload} - local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} - -@@ -20814,18 +20818,35 @@ ovn-nbctl lsp-add sw sw-rtr \ - - ovn-nbctl --wait=hv sync - --# Inject IPv4 TCP packet from lsp. -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - > expected -+ -+# Inject IPv4 TCP packet from lsp. - tcp_payload=$(build_tcp_syn 84d0 1f90 05a7) - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e) - send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -- 06 0028 35f5 \ -- ${tcp_payload} ${hp_tcp_payload} \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 88) ${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]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp options:hairpin_snat_ip="88.88.88.87" -+# Inject IPv4 TCP packet from lsp. -+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \ -+ expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -@@ -20835,8 +20856,25 @@ udp_payload=$(build_udp 84d0 0fc8 6666) - hp_udp_payload=$(build_udp 84d0 07e5 6e49) - send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -- 11 001e 35f4 \ -- ${udp_payload} ${hp_udp_payload} \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv4-udp options:hairpin_snat_ip="88.88.88.87" -+# Inject IPv4 UDP packet from lsp. -+hp_udp_payload=$(build_udp 84d0 07e5 6e4a) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 87) ${hp_udp_payload} \ - expected - - # Check that traffic is hairpinned. -@@ -20848,7 +20886,25 @@ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) - send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - 42000000000000000000000000000001 88000000000000000000000000000088 \ - 06 0014 \ -- ${tcp_payload} ${hp_tcp_payload} \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000088 ${hp_tcp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv6-tcp options:hairpin_snat_ip="8800::0087" -+ -+# Inject IPv6 TCP packet from lsp. -+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc1) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 06 0014 \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000087 ${hp_tcp_payload} \ - expected - - # Check that traffic is hairpinned. -@@ -20860,12 +20916,27 @@ hp_udp_payload=$(build_udp 84d0 07e5 a89b) - send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - 42000000000000000000000000000001 88000000000000000000000000000088 \ - 11 000a \ -- ${udp_payload} ${hp_udp_payload} \ -+ ${udp_payload} \ -+ 88000000000000000000000000000088 ${hp_udp_payload} \ - expected - --# Check that traffic is hairpinned. -+Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv6-udp options:hairpin_snat_ip="8800::0087" -+ -+# Inject IPv6 UDP packet from lsp. -+hp_udp_payload=$(build_udp 84d0 07e5 a89b) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 11 000a \ -+ ${udp_payload} \ -+ 88000000000000000000000000000087 ${hp_udp_payload} \ -+ expected -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP - -@@ -23156,7 +23227,7 @@ priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 a - ]) - - 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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23190,8 +23261,8 @@ priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23227,8 +23298,8 @@ priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23256,8 +23327,9 @@ priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23275,8 +23347,9 @@ priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23306,9 +23379,10 @@ priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23328,9 +23402,10 @@ priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23362,9 +23437,11 @@ priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23386,9 +23463,11 @@ priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23423,10 +23502,12 @@ priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23449,10 +23530,12 @@ priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac - ]) - - 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)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23501,9 +23584,10 @@ priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac - ]) - - 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_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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 --- -1.8.3.1 - diff --git a/SOURCES/0001-bfd-introduce-IPv6-support.patch b/SOURCES/0001-bfd-introduce-IPv6-support.patch deleted file mode 100644 index f219ccd..0000000 --- a/SOURCES/0001-bfd-introduce-IPv6-support.patch +++ /dev/null @@ -1,261 +0,0 @@ -From 9f42e93b6a25bff87074156586505a6e8968f8cb Mon Sep 17 00:00:00 2001 -Message-Id: <9f42e93b6a25bff87074156586505a6e8968f8cb.1610538323.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Tue, 12 Jan 2021 13:10:56 +0100 -Subject: [PATCH] bfd: introduce IPv6 support - -Introduce IPv6 support to ovn controller BFD implementation - -Signed-off-by: Lorenzo Bianconi -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique ---- - NEWS | 2 +- - controller/pinctrl.c | 112 ++++++++++++++++++++++++++++--------------- - tests/system-ovn.at | 16 ++++++- - 3 files changed, 89 insertions(+), 41 deletions(-) - ---- a/NEWS -+++ b/NEWS -@@ -2,7 +2,7 @@ Post-v20.12.0 - ------------------------- - - Support ECMP multiple nexthops for reroute router policies. - - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD -- availability check for OVN static routes. IPv6 is not suported yet. -+ availability check for OVN static routes. - [0] https://tools.ietf.org/html/rfc5880) - - OVN v20.12.0 - 18 Dec 2020 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -6393,10 +6393,10 @@ struct bfd_entry { - - /* L2 source address */ - struct eth_addr src_mac; -- /* IPv4 source address */ -- ovs_be32 ip_src; -- /* IPv4 destination address */ -- ovs_be32 ip_dst; -+ /* IP source address */ -+ struct in6_addr ip_src; -+ /* IP destination address */ -+ struct in6_addr ip_dst; - /* RFC 5881 section 4 - * The source port MUST be in the range 49152 through 65535. - * The same UDP source port number MUST be used for all BFD -@@ -6458,20 +6458,17 @@ pinctrl_find_bfd_monitor_entry_by_port(c - } - - static struct bfd_entry * --pinctrl_find_bfd_monitor_entry_by_disc(ovs_be32 ip, ovs_be32 disc) -+pinctrl_find_bfd_monitor_entry_by_disc(char *ip, ovs_be32 disc) - { -- char *ip_src = xasprintf(IP_FMT, IP_ARGS(ip)); - struct bfd_entry *ret = NULL, *entry; - -- HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip_src, 0), -+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0), - &bfd_monitor_map) { - if (entry->local_disc == disc) { - ret = entry; - break; - } - } -- -- free(ip_src); - return ret; - } - -@@ -6501,33 +6498,28 @@ static void - bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet, - bool final) - { -- struct udp_header *udp; -- struct bfd_msg *msg; -+ int payload_len = sizeof(struct udp_header) + sizeof(struct bfd_msg); - - /* Properly align after the ethernet header */ - dp_packet_reserve(packet, 2); -- struct eth_header *eth = dp_packet_put_uninit(packet, sizeof *eth); -- eth->eth_dst = eth_addr_broadcast; -- eth->eth_src = entry->src_mac; -- eth->eth_type = htons(ETH_TYPE_IP); -- -- struct ip_header *ip = dp_packet_put_zeros(packet, sizeof *ip); -- ip->ip_ihl_ver = IP_IHL_VER(5, 4); -- ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg); -- ip->ip_ttl = MAXTTL; -- ip->ip_tos = IPTOS_PREC_INTERNETCONTROL; -- ip->ip_proto = IPPROTO_UDP; -- put_16aligned_be32(&ip->ip_src, entry->ip_src); -- put_16aligned_be32(&ip->ip_dst, entry->ip_dst); -- /* Checksum has already been zeroed by put_zeros call. */ -- ip->ip_csum = csum(ip, sizeof *ip); -+ if (IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) { -+ ovs_be32 ip_src = in6_addr_get_mapped_ipv4(&entry->ip_src); -+ ovs_be32 ip_dst = in6_addr_get_mapped_ipv4(&entry->ip_dst); -+ pinctrl_compose_ipv4(packet, entry->src_mac, eth_addr_broadcast, -+ ip_src, ip_dst, IPPROTO_UDP, MAXTTL, payload_len); -+ } else { -+ pinctrl_compose_ipv6(packet, entry->src_mac, eth_addr_broadcast, -+ &entry->ip_src, &entry->ip_dst, IPPROTO_UDP, -+ MAXTTL, payload_len); -+ } - -- udp = dp_packet_put_zeros(packet, sizeof *udp); -+ struct udp_header *udp = dp_packet_put_zeros(packet, sizeof *udp); -+ udp->udp_len = htons(payload_len); -+ udp->udp_csum = 0; - udp->udp_src = htons(entry->udp_src); - udp->udp_dst = htons(BFD_DEST_PORT); -- udp->udp_len = htons(sizeof *udp + sizeof *msg); - -- msg = dp_packet_put_zeros(packet, sizeof *msg); -+ struct bfd_msg *msg = dp_packet_put_zeros(packet, sizeof *msg); - msg->vers_diag = (BFD_VERSION << 5); - msg->mult = entry->local_mult; - msg->length = BFD_PACKET_LEN; -@@ -6538,6 +6530,17 @@ bfd_monitor_put_bfd_msg(struct bfd_entry - /* min_tx and min_rx are in us - RFC 5880 page 9 */ - msg->min_tx = htonl(entry->local_min_tx * 1000); - msg->min_rx = htonl(entry->local_min_rx * 1000); -+ -+ if (!IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) { -+ /* IPv6 needs UDP checksum calculated */ -+ uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(packet)); -+ int len = (uint8_t *)udp - (uint8_t *)dp_packet_eth(packet); -+ csum = csum_continue(csum, udp, dp_packet_size(packet) - len); -+ udp->udp_csum = csum_finish(csum); -+ if (!udp->udp_csum) { -+ udp->udp_csum = htons(0xffff); -+ } -+ } - } - - static void -@@ -6736,9 +6739,18 @@ pinctrl_handle_bfd_msg(struct rconn *swc - return; - } - -+ char *ip_src; -+ if (ip_flow->dl_type == htons(ETH_TYPE_IP)) { -+ ip_src = normalize_ipv4_prefix(ip_flow->nw_src, 32); -+ } else { -+ ip_src = normalize_ipv6_prefix(&ip_flow->ipv6_src, 128); -+ } -+ - const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in); -- struct bfd_entry *entry = pinctrl_find_bfd_monitor_entry_by_disc( -- ip_flow->nw_src, msg->your_disc); -+ struct bfd_entry *entry = -+ pinctrl_find_bfd_monitor_entry_by_disc(ip_src, msg->your_disc); -+ free(ip_src); -+ - if (!entry) { - return; - } -@@ -6821,10 +6833,21 @@ static void - bfd_monitor_check_sb_conf(const struct sbrec_bfd *sb_bt, - struct bfd_entry *entry) - { -- ovs_be32 ip_dst; -+ struct lport_addresses dst_addr; -+ -+ if (extract_ip_addresses(sb_bt->dst_ip, &dst_addr)) { -+ struct in6_addr addr; -+ -+ if (dst_addr.n_ipv6_addrs > 0) { -+ addr = dst_addr.ipv6_addrs[0].addr; -+ } else { -+ addr = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr); -+ } - -- if (ip_parse(sb_bt->dst_ip, &ip_dst) && ip_dst != entry->ip_dst) { -- entry->ip_dst = ip_dst; -+ if (!ipv6_addr_equals(&addr, &entry->ip_dst)) { -+ entry->ip_dst = addr; -+ } -+ destroy_lport_addresses(&dst_addr); - } - - if (sb_bt->min_tx != entry->local_min_tx) { -@@ -6889,11 +6912,15 @@ bfd_monitor_run(struct ovsdb_idl_txn *ov - entry = pinctrl_find_bfd_monitor_entry_by_port( - bt->dst_ip, bt->src_port); - if (!entry) { -- ovs_be32 ip_dst, ip_src = htonl(BFD_DEFAULT_SRC_IP); - struct eth_addr ea = eth_addr_zero; -+ struct lport_addresses dst_addr; -+ struct in6_addr ip_src, ip_dst; - int i; - -- if (!ip_parse(bt->dst_ip, &ip_dst)) { -+ ip_dst = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_DST_IP)); -+ ip_src = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_SRC_IP)); -+ -+ if (!extract_ip_addresses(bt->dst_ip, &dst_addr)) { - continue; - } - -@@ -6905,13 +6932,20 @@ bfd_monitor_run(struct ovsdb_idl_txn *ov - } - - ea = laddrs.ea; -- if (laddrs.n_ipv4_addrs > 0) { -- ip_src = laddrs.ipv4_addrs[0].addr; -+ if (dst_addr.n_ipv6_addrs > 0 && laddrs.n_ipv6_addrs > 0) { -+ ip_dst = dst_addr.ipv6_addrs[0].addr; -+ ip_src = laddrs.ipv6_addrs[0].addr; -+ destroy_lport_addresses(&laddrs); -+ break; -+ } else if (laddrs.n_ipv4_addrs > 0) { -+ ip_dst = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr); -+ ip_src = in6_addr_mapped_ipv4(laddrs.ipv4_addrs[0].addr); - destroy_lport_addresses(&laddrs); - break; - } - destroy_lport_addresses(&laddrs); - } -+ destroy_lport_addresses(&dst_addr); - - if (eth_addr_is_zero(ea)) { - continue; ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -5563,7 +5563,7 @@ check ovn-nbctl ls-add public - - check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 - check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 --check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ -+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 1000::a/64 \ - -- lrp-set-gateway-chassis rp-public hv1 - - check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ -@@ -5593,6 +5593,7 @@ ADD_NAMESPACES(server) - NS_CHECK_EXEC([server], [ip link set dev lo up]) - ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \ - "172.16.1.1") -+NS_CHECK_EXEC([server], [ip addr add 1000::b/64 dev s1]) - - AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) - check ovn-nbctl lsp-add public public1 \ -@@ -5652,6 +5653,19 @@ sleep 5 - kill $(pidof tcpdump) - AT_CHECK([grep -qi bfd bfd.pcap],[1]) - -+uuid_v6=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=\"1000::b\") -+check ovn-nbctl lr-route-add R1 2000::/64 1000::b -+route_uuid_v6=$(fetch_column nb:logical_router_static_route _uuid ip_prefix=\"2000::/64\") -+ovn-nbctl set logical_router_static_route $route_uuid_v6 bfd=$uuid_v6 -+check ovn-nbctl --wait=hv sync -+NS_CHECK_EXEC([server], [bfdd-beacon --listen=1000::b], [0]) -+NS_CHECK_EXEC([server], [bfdd-control allow 1000::a], [0], [dnl -+Allowing connections from 1000::a -+]) -+ -+wait_column "up" nb:bfd status logical_port=rp-public -+ovn-nbctl destroy bfd $uuid_v6 -+ - kill $(pidof ovn-controller) - - as ovn-sb diff --git a/SOURCES/0001-binding-Correctly-set-Port_Binding.up-for-container-.patch b/SOURCES/0001-binding-Correctly-set-Port_Binding.up-for-container-.patch deleted file mode 100644 index ccf1785..0000000 --- a/SOURCES/0001-binding-Correctly-set-Port_Binding.up-for-container-.patch +++ /dev/null @@ -1,280 +0,0 @@ -From 36a57e7a388277d1e45f0cadd8e2601490a76b2d Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 3 Feb 2021 20:36:30 +0100 -Subject: [PATCH 1/4] binding: Correctly set Port_Binding.up for - container/virtual ports. - -For port bindings that are children of other port bindings (container -and virtual ports) set Port_Binding.up directly, when claimed, if their -parent bindings are already up. - -For non-VIF port bindings maintain compatibility with older versions -and set Port_Binding.up as soon as they are claimed. - -Reported-by: Ben Pfaff -Fixes: 4d3cb42b076b ("binding: Set Logical_Switch_Port.up when all OVS flows are installed.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit aae25e67b1ba86e34d670eb808de86f7ba66c3c0) - -Conflicts: - tests/ovn.at - -Change-Id: I17c5a4d0f89868190c81c7221caadf29959064b1 ---- - controller-vtep/binding.c | 3 +++ - controller/binding.c | 47 +++++++++++++++++++++++++++++++++++++++++------ - tests/ovn.at | 32 ++++++++++++++++++++++++++------ - 3 files changed, 70 insertions(+), 12 deletions(-) - -diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c -index 8337715..d28a598 100644 ---- a/controller-vtep/binding.c -+++ b/controller-vtep/binding.c -@@ -109,7 +109,10 @@ update_pb_chassis(const struct sbrec_port_binding *port_binding_rec, - port_binding_rec->chassis->name, - chassis_rec->name); - } -+ -+ bool up = true; - sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec); -+ sbrec_port_binding_set_up(port_binding_rec, &up, 1); - } - } - -diff --git a/controller/binding.c b/controller/binding.c -index d44f635..353debe 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -864,21 +864,52 @@ get_lport_type(const struct sbrec_port_binding *pb) - return LP_UNKNOWN; - } - -+/* For newly claimed ports, if 'notify_up' is 'false': -+ * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'. -+ * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for -+ * container and virtual ports). -+ * Otherwise request a notification to be sent when the OVS flows -+ * corresponding to 'pb' have been installed. -+ */ -+static void -+claimed_lport_set_up(const struct sbrec_port_binding *pb, -+ const struct sbrec_port_binding *parent_pb, -+ const struct sbrec_chassis *chassis_rec, -+ bool notify_up) -+{ -+ if (!notify_up) { -+ bool up = true; -+ if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) { -+ sbrec_port_binding_set_up(pb, &up, 1); -+ } -+ return; -+ } -+ -+ if (pb->chassis != chassis_rec) { -+ binding_iface_bound_add(pb->logical_port); -+ } -+} -+ - /* Returns false if lport is not claimed due to 'sb_readonly'. - * Returns true otherwise. - */ - static bool - claim_lport(const struct sbrec_port_binding *pb, -+ const struct sbrec_port_binding *parent_pb, - const struct sbrec_chassis *chassis_rec, - const struct ovsrec_interface *iface_rec, -- bool sb_readonly, struct hmap *tracked_datapaths) -+ bool sb_readonly, bool notify_up, -+ struct hmap *tracked_datapaths) - { -+ if (!sb_readonly) { -+ claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up); -+ } -+ - if (pb->chassis != chassis_rec) { - if (sb_readonly) { - return false; - } - -- binding_iface_bound_add(pb->logical_port); - if (pb->chassis) { - VLOG_INFO("Changing chassis for lport %s from %s to %s.", - pb->logical_port, pb->chassis->name, -@@ -1041,8 +1072,12 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - if (lbinding_set) { - if (can_bind) { - /* We can claim the lport. */ -- if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface, -- !b_ctx_in->ovnsb_idl_txn, -+ const struct sbrec_port_binding *parent_pb = -+ lbinding->parent ? lbinding->parent->pb : NULL; -+ -+ if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec, -+ lbinding->iface, !b_ctx_in->ovnsb_idl_txn, -+ !lbinding->parent, - b_ctx_out->tracked_dp_bindings)){ - return false; - } -@@ -1246,8 +1281,8 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, - b_ctx_out->tracked_dp_bindings); - - update_local_lport_ids(pb, b_ctx_out); -- return claim_lport(pb, b_ctx_in->chassis_rec, NULL, -- !b_ctx_in->ovnsb_idl_txn, -+ return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL, -+ !b_ctx_in->ovnsb_idl_txn, false, - b_ctx_out->tracked_dp_bindings); - } else if (pb->chassis == b_ctx_in->chassis_rec) { - return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -diff --git a/tests/ovn.at b/tests/ovn.at -index dfb94d2..1956f5c 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -8992,6 +8992,7 @@ AT_CHECK([test -z $bar2_zoneid]) - # Add back bar2 - ovn-nbctl lsp-add bar bar2 vm2 1 \ - -- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3" -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2) -@@ -14671,6 +14672,8 @@ OVS_WAIT_UNTIL( - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) - -+wait_for_ports_up ls1-lp_ext1 -+ - # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1 - (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows - as hv1 ovs-ofctl dump-flows br-int > brintflows -@@ -14951,6 +14954,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv2_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - # 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 | \ -@@ -15065,6 +15069,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - ovs-vsctl show -@@ -15145,6 +15150,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv3_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - ovs-vsctl show -@@ -15229,6 +15235,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined - # to router mac. -@@ -15246,6 +15253,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv2_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) -@@ -16604,12 +16612,10 @@ spa=$(ip_to_hex 10 0 0 10) - tpa=$(ip_to_hex 10 0 0 10) - send_garp 1 1 $eth_src $eth_dst $spa $tpa - --OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ --logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) -- --AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ --logical_port=sw0-vir) = xsw0-p1]) -- -+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid -+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1 -+wait_for_ports_up sw0-vir -+check ovn-nbctl --wait=hv sync - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p1's MAC. -@@ -16627,6 +16633,8 @@ ovn-sbctl clear port_binding $pb_uuid virtual_parent - OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir - # and sw0-p1 should be its virtual_parent. - send_garp 1 1 $eth_src $eth_dst $spa $tpa -@@ -16637,6 +16645,8 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p1]) - -+wait_for_ports_up sw0-vir -+ - # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p3 should be its virtual_parent. - eth_src=505400000005 -@@ -16651,6 +16661,7 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) - OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p3]) - -+wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p2's MAC. -@@ -16676,6 +16687,7 @@ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p2]) - -+wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p3's MAC. -@@ -16701,6 +16713,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p1]) - -+wait_for_ports_up sw0-vir -+ - 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 -@@ -16717,6 +16731,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to - # zero if the ip4.dst is the virtual ip. - ovn-sbctl dump-flows lr0 > lr0-flows6 -@@ -16740,6 +16756,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p2]) - -+wait_for_ports_up sw0-vir -+ - 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 -@@ -16754,6 +16772,8 @@ logical_port=sw0-vir) = x], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # 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 - --- -1.8.3.1 - diff --git a/SOURCES/0001-binding-Do-not-clear-container-lbinding-pb-when-pare.patch b/SOURCES/0001-binding-Do-not-clear-container-lbinding-pb-when-pare.patch deleted file mode 100644 index 2ac7a05..0000000 --- a/SOURCES/0001-binding-Do-not-clear-container-lbinding-pb-when-pare.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0ec31292fc29d2c111927382b13ea8af0499f6ac Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 6 Jan 2021 11:53:14 +0100 -Subject: [PATCH] binding: Do not clear container lbinding->pb when parent is - deleted. - -When a parent Port_Binding is deleted we shouldn't clear the children's -'pb' field. Container port bindings have their own Port_Binding SB -record so the child_lbinding->pb field should be cleared only when -their corresponding SB record is deleted. - -Whithout this fix when a parent Port_Binding "remove" followed by "add" -operations are received in the same iteration in ovn-controller, -consider_container_lport() can be called with "pb == NULL" causing a -crash. - -Fixes: 354bdba51abf ("ovn-controller: I-P for SB port binding and OVS interface in runtime_data.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from master commit d3245f69dd6ec613ceb193f728946f7e3b9b3de3) ---- - controller/binding.c | 3 +-- - tests/ovn.at | 7 +++++++ - 2 files changed, 8 insertions(+), 2 deletions(-) - -diff --git a/controller/binding.c b/controller/binding.c -index cb60c5d..e632203 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -958,8 +958,7 @@ release_local_binding_children(const struct sbrec_chassis *chassis_rec, - } - } - -- /* Clear the local bindings' 'pb' and 'iface'. */ -- l->pb = NULL; -+ /* Clear the local bindings' 'iface'. */ - l->iface = NULL; - } - -diff --git a/tests/ovn.at b/tests/ovn.at -index 8bcfcdf..ce6db86 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -9126,6 +9126,13 @@ OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)]) - OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)]) - OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)]) - -+# Move VM1 to a new logical switch. -+ovn-nbctl ls-add mgmt2 -+ovn-nbctl lsp-del vm1 -- lsp-add mgmt2 vm1 -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)]) -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)]) -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)]) -+ - as hv1 ovs-vsctl del-port vm1 - OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up vm1)]) - OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up foo1)]) --- -1.8.3.1 - diff --git a/SOURCES/0001-binding-Fix-container-port-removal-from-local-bindin.patch b/SOURCES/0001-binding-Fix-container-port-removal-from-local-bindin.patch deleted file mode 100644 index af9d3cb..0000000 --- a/SOURCES/0001-binding-Fix-container-port-removal-from-local-bindin.patch +++ /dev/null @@ -1,150 +0,0 @@ -From 44955fb2395677c9d9bb1afa3985b24317c84431 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Mon, 18 Jan 2021 17:50:23 +0100 -Subject: [PATCH 1/2] binding: Fix container port removal from local bindings. - -When the Port_Binding associated to a container port is removed make -sure we also remove it from the parent's 'children' shash. Container -ports don't have any VIFs associated so it's safe to destroy the -container port local binding when the SB.Port_Binding is deleted. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from master commit 68cf9fdceba80ce0c03e3ddb3e0a5531f248fa04) - -Change-Id: I65f4bd461f3f94ca90dbcc0646c0037c301c71a1 ---- - controller/binding.c | 18 +++++++++++++++++- - controller/binding.h | 1 + - tests/ovn.at | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 71 insertions(+), 1 deletion(-) - -diff --git a/controller/binding.c b/controller/binding.c -index e632203..3512a1d 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -688,6 +688,7 @@ local_binding_add_child(struct local_binding *lbinding, - struct local_binding *child) - { - local_binding_add(&lbinding->children, child); -+ child->parent = lbinding; - } - - static struct local_binding * -@@ -697,6 +698,13 @@ local_binding_find_child(struct local_binding *lbinding, - return local_binding_find(&lbinding->children, child_name); - } - -+static void -+local_binding_delete_child(struct local_binding *lbinding, -+ struct local_binding *child) -+{ -+ shash_find_and_delete(&lbinding->children, child->name); -+} -+ - static bool - is_lport_vif(const struct sbrec_port_binding *pb) - { -@@ -2062,6 +2070,14 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - * when the interface change happens. */ - if (is_lport_container(pb)) { - remove_local_lports(pb->logical_port, b_ctx_out); -+ -+ /* If the container port is removed we should also remove it from -+ * its parent's children set. -+ */ -+ if (lbinding->parent) { -+ local_binding_delete_child(lbinding->parent, lbinding); -+ } -+ local_binding_destroy(lbinding); - } - - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -@@ -2147,7 +2163,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - enum en_lport_type lport_type = get_lport_type(pb); - if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) { - handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in, -- b_ctx_out); -+ b_ctx_out); - } else { - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); - } -diff --git a/controller/binding.h b/controller/binding.h -index c974056..2885971 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -100,6 +100,7 @@ struct local_binding { - - /* shash of 'struct local_binding' representing children. */ - struct shash children; -+ struct local_binding *parent; - }; - - static inline struct local_binding * -diff --git a/tests/ovn.at b/tests/ovn.at -index 27cb2e4..2cdc036 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -22144,6 +22144,59 @@ AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl - OVN_CLEANUP([hv1]) - AT_CLEANUP - -+AT_SETUP([ovn -- Container port Incremental Processing]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.10 -+ -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 \ -+ ofport-request=1 -+ -+check ovn-nbctl ls-add ls1 \ -+ -- ls-add ls2 \ -+ -- lsp-add ls1 lsp1 \ -+ -- lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=hv sync -+ -+# 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=lsp-cont1 chassis=$ch -+ -+AS_BOX([delete OVS VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+as hv1 ovs-vsctl del-port vif1 -+ -+check ovn-nbctl --wait=sb lsp-del lsp-cont1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+check_row_count Port_Binding 1 logical_port=lsp1 chassis="[[]]" -+ -+AS_BOX([readd OVS VIF]) -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 \ -+ ofport-request=1 -+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch -+ -+AS_BOX([readd OVN container port]) -+check ovn-nbctl lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=hv sync -+check_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch -+ -+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 --- -1.8.3.1 - diff --git a/SOURCES/0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch b/SOURCES/0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch deleted file mode 100644 index ef4a978..0000000 --- a/SOURCES/0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch +++ /dev/null @@ -1,343 +0,0 @@ -From c0bb41fb85934203616758871a690093009e1d1d Mon Sep 17 00:00:00 2001 -Message-Id: -From: Lorenzo Bianconi -Date: Mon, 12 Oct 2020 11:05:54 +0200 -Subject: [PATCH] controller: IPv6 Prefix-Delegation: introduce RENEW/REBIND - msg support - -Introduce RENEW/REBIND message support to OVN IPv6 PD support -according to RFC-3633 [0] in order to renew IPv6 prefixes when -T1/T2 are elapsed - -[0] https://tools.ietf.org/html/rfc3633 - -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 133 +++++++++++++++++++++++++++++++++---------- - lib/ovn-l7.h | 3 + - tests/system-ovn.at | 18 ++++-- - 3 files changed, 120 insertions(+), 34 deletions(-) - ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -573,11 +573,22 @@ enum { - 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 +792,26 @@ out: - 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 +821,21 @@ pinctrl_parse_dhcpv6_reply(struct dp_pac - 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 +886,22 @@ pinctrl_parse_dhcpv6_reply(struct dp_pac - } - 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 +931,42 @@ pinctrl_handle_dhcp6_server(struct rconn - } - - 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 +977,21 @@ compose_prefixd_solicit(struct dp_packet - 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 +1033,15 @@ ipv6_prefixd_send(struct rconn *swconn, - 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 +1070,9 @@ ipv6_prefixd_send(struct rconn *swconn, - 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 +1083,28 @@ static bool ipv6_prefixd_should_inject(v - - 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 +1190,8 @@ fill_ipv6_prefix_state(struct ovsdb_idl_ - 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 +1290,10 @@ prepare_ipv6_prefixd(struct ovsdb_idl_tx - 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); - } ---- a/lib/ovn-l7.h -+++ b/lib/ovn-l7.h -@@ -189,6 +189,9 @@ struct dhcp_opt6_header { - #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 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -4768,19 +4768,28 @@ AT_CHECK([ovn-nbctl get logical_router_p - [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 +4797,6 @@ AT_CHECK([ovn-nbctl get logical_router_p - [] - ]) - --kill $(pidof tcpdump) - kill $(pidof ovn-controller) - - as ovn-sb diff --git a/SOURCES/0001-controller-fix-pkt_marking-with-IP-buffering.patch b/SOURCES/0001-controller-fix-pkt_marking-with-IP-buffering.patch deleted file mode 100644 index 7bdf589..0000000 --- a/SOURCES/0001-controller-fix-pkt_marking-with-IP-buffering.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 5b75b36198b1cdf66aa0bee5a0a73f1e591af1b2 Mon Sep 17 00:00:00 2001 -Message-Id: <5b75b36198b1cdf66aa0bee5a0a73f1e591af1b2.1611831762.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Mon, 25 Jan 2021 14:28:48 +0100 -Subject: [PATCH] controller: fix pkt_marking with IP buffering - -Reload pkt_marking metadata for cloned packets during ARP/ND address -resolution. - -https://bugzilla.redhat.com/show_bug.cgi?id=1857106 - -Tested-by: Jianlin Shi -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 5 +++++ - tests/ovn.at | 10 ++++++++++ - 2 files changed, 15 insertions(+) - ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -1398,6 +1398,11 @@ buffered_push_packet(struct buffered_pac - ofpbuf_init(&bi->ofpacts, 4096); - - reload_metadata(&bi->ofpacts, md); -+ /* reload pkt_mark field */ -+ const struct mf_field *pkt_mark_field = mf_from_id(MFF_PKT_MARK); -+ union mf_value pkt_mark_value; -+ mf_get_value(pkt_mark_field, &md->flow, &pkt_mark_value); -+ ofpact_put_set_field(&bi->ofpacts, pkt_mark_field, &pkt_mark_value, NULL); - bi->ofp_port = md->flow.in_port.ofp_port; - - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts); ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -15886,6 +15886,14 @@ ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows > sbflows2 - AT_CAPTURE_FILE([sbflows2]) - -+# create a route policy for pkt marking -+check ovn-nbctl lr-policy-add lr0 2000 "ip4.src == 192.168.1.3" allow -+policy=$(fetch_column nb:Logical_Router_Policy _uuid priority=2000) -+check ovn-nbctl set logical_router_policy $policy options:pkt_mark=100 -+as hv2 -+# add a flow in egress pipeline to check pkt marking -+ovs-ofctl --protocols=OpenFlow13 add-flow br-int "table=32,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64 actions=resubmit(,33)" -+ - dst_ip=$(ip_to_hex 172 16 2 10) - fip_ip=$(ip_to_hex 172 16 1 2) - src_ip=$(ip_to_hex 192 168 1 3) -@@ -15896,6 +15904,8 @@ echo $(get_arp_req f00000010204 $fip_ip - send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip - echo "${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}" >> expected - -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep pkt_mark=0x64 | grep -q n_packets=1],[0]) -+ - OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - - OVN_CLEANUP([hv1],[hv2]) diff --git a/SOURCES/0001-dhcp-add-iPXE-support-to-OVN.patch b/SOURCES/0001-dhcp-add-iPXE-support-to-OVN.patch deleted file mode 100644 index 5e08118..0000000 --- a/SOURCES/0001-dhcp-add-iPXE-support-to-OVN.patch +++ /dev/null @@ -1,498 +0,0 @@ -From e52278861714dc22b666a7cf70b2dba687473060 Mon Sep 17 00:00:00 2001 -Message-Id: -From: Lorenzo Bianconi -Date: Fri, 23 Oct 2020 12:27:42 +0200 -Subject: [PATCH] dhcp: add iPXE support to OVN - -Add iPXE support to OVN introducing "bootfile_name_alt" dhcp option. -"bootfile_name_alt" dhcp userdata is encoded as option 254 since -it is not currently used. -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. -"bootfile_name" and "bootfile_name_alt" are placed just after offer_ip -in userdata buffer. - -Tested-by: Lucas Alvares Gomes Martins -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 30 ++++++++++++ - lib/actions.c | 33 ++++++++++--- - lib/ovn-l7.h | 11 +++++ - northd/ovn-northd.c | 1 + - ovn-nb.xml | 10 ++++ - tests/ovn.at | 110 ++++++++++++++++++++++++++++++------------- - tests/test-ovn.c | 1 + - 7 files changed, 158 insertions(+), 38 deletions(-) - ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -1868,6 +1868,7 @@ pinctrl_handle_put_dhcp_opts( - } - 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) { -@@ -1900,6 +1901,9 @@ pinctrl_handle_put_dhcp_opts( - request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); - } - break; -+ case DHCP_OPT_ETHERBOOT: -+ ipxe_req = true; -+ break; - default: - break; - } -@@ -2018,6 +2022,32 @@ pinctrl_handle_put_dhcp_opts( - *| 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; ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -2086,10 +2086,10 @@ parse_gen_opt(struct action_context *ctx - } - - 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; - } - } -@@ -2288,7 +2288,7 @@ parse_put_dhcp_opts(struct action_contex - 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; -@@ -2537,14 +2537,35 @@ encode_PUT_DHCPV4_OPTS(const struct ovna - /* 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); - } - } ---- a/lib/ovn-l7.h -+++ b/lib/ovn-l7.h -@@ -93,6 +93,17 @@ struct gen_opts_map { - #define DHCP_OPT_DOMAIN_SEARCH_LIST \ - DHCP_OPTION("domain_search_list", 119, "domains") - -+#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 \ ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -12419,6 +12419,7 @@ static struct gen_opts_map supported_dhc - DHCP_OPT_ARP_CACHE_TIMEOUT, - DHCP_OPT_TCP_KEEPALIVE_INTERVAL, - DHCP_OPT_DOMAIN_SEARCH_LIST, -+ DHCP_OPT_BOOTFILE_ALT, - }; - - static struct gen_opts_map supported_dhcpv6_opts[] = { ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -3096,6 +3096,16 @@ - 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. -+
-
- - ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1280,7 +1280,7 @@ reg1[0] = put_dhcp_opts(offerip = 1.2.3. - 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) -@@ -1292,10 +1292,10 @@ reg0[15] = put_dhcp_opts(offerip=10.0.0. - 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. -@@ -5332,10 +5332,10 @@ sleep 2 - 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 ... -+# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ... - 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 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 -@@ -5347,11 +5347,21 @@ test_dhcp() { - fi - - 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 -@@ -5392,6 +5402,9 @@ test_dhcp() { - # 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 - -@@ -5487,7 +5500,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 -+test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5513,7 +5526,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts - - # NXT_RESUMEs should be 2. - OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) -@@ -5537,7 +5550,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 -+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5561,7 +5574,7 @@ rm -f 2.expected - 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 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`]) -@@ -5578,18 +5591,18 @@ rm -f 2.expected - # 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 -+test_dhcp 3 f00000000003 01 0 $ciaddr 0 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 -+test_dhcp 4 f00000000004 01 0 $ciaddr 0 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]) -+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. -@@ -5600,7 +5613,7 @@ request_ip=0 - 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 -+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5626,7 +5639,7 @@ request_ip=0 - 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 -+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5652,7 +5665,7 @@ request_ip=0 - 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 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts - - # NXT_RESUMEs should be 7. - OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) -@@ -5678,7 +5691,7 @@ request_ip=0 - 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 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5700,7 +5713,7 @@ rm -f 2.expected - 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 -+test_dhcp 2 f00000000002 03 0 $ciaddr 0 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`]) -@@ -5719,7 +5732,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 -+test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5742,7 +5755,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 -+test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 - - # NXT_RESUMEs should be 10. - OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) -@@ -5769,7 +5782,7 @@ dst_ip=$server_ip - # 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 -+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 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)]) -@@ -5799,7 +5812,7 @@ dst_ip=$server_ip - # 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 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts - - # NXT_RESUMEs should be 12. - OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)]) -@@ -5826,7 +5839,7 @@ dst_ip=$server_ip - # 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 -+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 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)]) -@@ -5857,7 +5870,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts - - # NXT_RESUMEs should be 14. - OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) -@@ -5888,7 +5901,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 -+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 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`]) -@@ -5919,7 +5932,7 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts - - # NXT_RESUMEs should be 16. - OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) -@@ -5937,9 +5950,42 @@ server_ip=`ip_to_hex 10 0 0 1` - 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 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], []) - -+# 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 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts -+ -+$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]) -+ - OVN_CLEANUP([hv1]) - - AT_CLEANUP ---- a/tests/test-ovn.c -+++ b/tests/test-ovn.c -@@ -191,6 +191,7 @@ create_gen_opts(struct hmap *dhcp_opts, - 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"); - - /* DHCPv6 options. */ - hmap_init(dhcpv6_opts); diff --git a/SOURCES/0001-northd-Don-t-poll-ovsdb-before-the-connection-is-ful.patch b/SOURCES/0001-northd-Don-t-poll-ovsdb-before-the-connection-is-ful.patch deleted file mode 100644 index 972daab..0000000 --- a/SOURCES/0001-northd-Don-t-poll-ovsdb-before-the-connection-is-ful.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 64592da65e3cf28d0d3d81caf664e841093cd22d Mon Sep 17 00:00:00 2001 -From: Renat Nurgaliyev -Date: Tue, 10 Nov 2020 15:23:37 +0100 -Subject: [PATCH] northd: Don't poll ovsdb before the connection is fully - established - -Set initial SB and NB DBs probe interval to 0 to avoid connection -flapping. - -Before configured in northd_probe_interval value is actually applied -to southbound and northbound database connections, both connections -must be fully established, otherwise ovnnb_db_run() will return -without retrieving configuration data from northbound DB. In cases -when southbound database is big enough, default interval of 5 seconds -will kill and retry the connection before it is fully established, no -matter what is set in northd_probe_interval. Client reconnect will -cause even more load to ovsdb-server and cause cascade effect, so -northd can never stabilise. We have more than 2000 ports in our lab, -and northd could not start before this patch, holding at 100% CPU -utilisation both itself and ovsdb-server. - -After connections are established, any value in northd_probe_interval, -or default DEFAULT_PROBE_INTERVAL_MSEC is applied correctly. - -Signed-off-by: Renat Nurgaliyev -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 1e59feea933610b28fd4442243162ce35595cfee) ---- - northd/ovn-northd.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index ce291ecb0..a158a73a7 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -100,8 +100,8 @@ static struct eth_addr svc_monitor_mac_ea; - - /* 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 - --- -2.28.0 - diff --git a/SOURCES/0001-northd-Fix-ACL-fair-log-meters-for-Port_Group-ACLs.patch b/SOURCES/0001-northd-Fix-ACL-fair-log-meters-for-Port_Group-ACLs.patch deleted file mode 100644 index e25335c..0000000 --- a/SOURCES/0001-northd-Fix-ACL-fair-log-meters-for-Port_Group-ACLs.patch +++ /dev/null @@ -1,269 +0,0 @@ -From d2b69af321ad8064d42aad2fd3d15857334e2d63 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Fri, 15 Jan 2021 14:41:19 +0100 -Subject: [PATCH] northd: Fix ACL fair log meters for Port_Group ACLs. - -Commit 880dca99eaf7 added support for fair meters but didn't cover the -case when an ACL is configured on a port group instead of a logical -switch. - -Iterate over PG ACLs too when syncing fair meters to the Southbound -database. Due to the fact that a meter might be used for ACLs that are -applied on multiple logical datapaths (through port groups) we also need -to change the logic of deleting stale SB Meter records. - -Fixes: 880dca99eaf7 ("northd: Enhance the implementation of ACL log meters (pre-ddlog merge).") -Reported-by: Dmitry Yusupov -Signed-off-by: Dumitru Ceara -Acked-by: Flavio Fernandes -Signed-off-by: Numan Siddique -(cherry picked from master commit bf4f75f90c3309dbcfac8e098a2c1ff2d822e77d) - -Change-Id: If6f19df6fe0b84abc2fbb7356bf59c2d5eb496e1 ---- - northd/ovn-northd.c | 61 ++++++++++++++++++++++++++++++++++++++++------------- - tests/ovn-northd.at | 42 ++++++++++++++++++++++++------------ - 2 files changed, 74 insertions(+), 29 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index fa2bd73..49afc2f 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -12250,17 +12250,20 @@ 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) -+ struct shash *sb_meters, -+ struct sset *used_sb_meters) - { -+ const struct sbrec_meter *sb_meter; - bool new_sb_meter = false; - -- const struct sbrec_meter *sb_meter = shash_find_and_delete(sb_meters, -- meter_name); -+ sb_meter = shash_find_data(sb_meters, meter_name); - if (!sb_meter) { - sb_meter = sbrec_meter_insert(ctx->ovnsb_txn); - sbrec_meter_set_name(sb_meter, meter_name); -+ shash_add(sb_meters, sb_meter->name, sb_meter); - new_sb_meter = true; - } -+ sset_add(used_sb_meters, meter_name); - - if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { - struct sbrec_meter_band **sb_bands; -@@ -12282,6 +12285,24 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, - sbrec_meter_set_unit(sb_meter, nb_meter->unit); - } - -+static void -+sync_acl_fair_meter(struct northd_context *ctx, struct shash *meter_groups, -+ const struct nbrec_acl *acl, struct shash *sb_meters, -+ struct sset *used_sb_meters) -+{ -+ const struct nbrec_meter *nb_meter = -+ fair_meter_lookup_by_name(meter_groups, acl->meter); -+ -+ if (!nb_meter) { -+ return; -+ } -+ -+ char *meter_name = alloc_acl_log_unique_meter_name(acl); -+ sync_meters_iterate_nb_meter(ctx, meter_name, nb_meter, sb_meters, -+ used_sb_meters); -+ free(meter_name); -+} -+ - /* Each entry in the Meter and Meter_Band tables in OVN_Northbound have - * a corresponding entries in the Meter and Meter_Band tables in - * OVN_Southbound. Additionally, ACL logs that use fair meters have -@@ -12289,9 +12310,10 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, - */ - static void - sync_meters(struct northd_context *ctx, struct hmap *datapaths, -- struct shash *meter_groups) -+ struct shash *meter_groups, struct hmap *port_groups) - { - struct shash sb_meters = SHASH_INITIALIZER(&sb_meters); -+ struct sset used_sb_meters = SSET_INITIALIZER(&used_sb_meters); - - const struct sbrec_meter *sb_meter; - SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) { -@@ -12301,7 +12323,7 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, - const struct nbrec_meter *nb_meter; - NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) { - sync_meters_iterate_nb_meter(ctx, nb_meter->name, nb_meter, -- &sb_meters); -+ &sb_meters, &used_sb_meters); - } - - /* -@@ -12315,19 +12337,28 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, - continue; - } - 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; -+ sync_acl_fair_meter(ctx, meter_groups, od->nbs->acls[i], -+ &sb_meters, &used_sb_meters); -+ } -+ 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++) { -+ sync_acl_fair_meter(ctx, meter_groups, pg->nb_pg->acls[i], -+ &sb_meters, &used_sb_meters); -+ } - } -- -- 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); - } - } - -+ const char *used_meter; -+ const char *used_meter_next; -+ SSET_FOR_EACH_SAFE (used_meter, used_meter_next, &used_sb_meters) { -+ shash_find_and_delete(&sb_meters, used_meter); -+ sset_delete(&used_sb_meters, SSET_NODE_FROM_NAME(used_meter)); -+ } -+ sset_destroy(&used_sb_meters); -+ - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &sb_meters) { - sbrec_meter_delete(node->data); -@@ -12825,7 +12856,7 @@ ovnnb_db_run(struct northd_context *ctx, - - sync_address_sets(ctx); - sync_port_groups(ctx, &port_groups); -- sync_meters(ctx, datapaths, &meter_groups); -+ sync_meters(ctx, datapaths, &meter_groups, &port_groups); - sync_dns_entries(ctx, datapaths); - - struct ovn_northd_lb *lb; -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 91eb9a3..df03b6e 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1843,20 +1843,25 @@ AT_KEYWORDS([acl log meter fair]) - ovn_start - - check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add sw1 - 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 lsp-add sw1 sw1-p3 -- lsp-set-addresses sw1-p3 "50:54:00:00:00:03 10.0.0.13" -+check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 sw1-p3 - - 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 -+check ovn-nbctl acl-add pg0 to-lport 1002 'outport == "pg0" && ip4.src == 10.0.0.11' drop - - 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) -+acl3=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.11' | 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 set acl $acl3 log=true severity=info meter=meter_me name=acl_three - check ovn-nbctl --wait=sb sync - - check_row_count nb:meter 1 -@@ -1865,8 +1870,9 @@ check_column meter_me nb:meter name - check_acl_lflow() { - acl_log_name=$1 - meter_name=$2 -+ ls=$3 - # echo checking that logical flow for acl log $acl_log_name has $meter_name -- AT_CHECK([ovn-sbctl lflow-list | grep ls_out_acl | \ -+ AT_CHECK([ovn-sbctl lflow-list $ls | grep ls_out_acl | \ - grep "\"${acl_log_name}\"" | \ - grep -c "meter=\"${meter_name}\""], [0], [1 - ]) -@@ -1882,55 +1888,63 @@ check_meter_by_name() { - - # 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_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - # 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 4 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]) -+AT_FAIL_IF([test "$n_meter_bands" != 1 && test "$n_meter_bands" != 4]) - 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} -+check_acl_lflow acl_one meter_me__${acl1} sw0 -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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} -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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 -+check_acl_lflow acl_two meter_me sw0 -+check_acl_lflow acl_three meter_me sw0 -+check_acl_lflow acl_three meter_me sw1 - - # 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} -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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 meter_me meter_me__${acl3} - check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} - - AT_CLEANUP --- -1.8.3.1 - diff --git a/SOURCES/0001-northd-Fix-duplicate-logical-port-detection.patch b/SOURCES/0001-northd-Fix-duplicate-logical-port-detection.patch deleted file mode 100644 index baec6e8..0000000 --- a/SOURCES/0001-northd-Fix-duplicate-logical-port-detection.patch +++ /dev/null @@ -1,182 +0,0 @@ -From 46a4e3bb3a6d01c96721761a0e03d093583ab1cc Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Thu, 21 Jan 2021 13:34:11 +0100 -Subject: [PATCH] northd: Fix duplicate logical port detection. - -When reconciling SB Port_Bindings and NB Logical_Switch_Ports or -Logical_Router_Ports make sure we properly detect ports that are -incorrectly attached to multiple logical datapaths. - -Reported-at: https://bugzilla.redhat.com/1918582 -Reported-by: Jianlin Shi -Fixes: 8bf9075968ac ("ovn-northd: Fix tunnel_key allocation for SB Port_Bindings.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from master commit 7b404e68b5b8ea7168b25cb60abb79b5696bcc02) - -Change-Id: I09d958cbfe676aad3b370e2a9fec404119c4e6d8 ---- - northd/ovn-northd.c | 64 ++++++++++++++++++++++++++++++++++------------------- - tests/ovn-northd.at | 45 +++++++++++++++++++++++++++++++++++++ - 2 files changed, 86 insertions(+), 23 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 49afc2f..f11894a 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -1546,17 +1546,38 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) - } - } - -+/* Returns the ovn_port that matches 'name'. If 'prefer_bound' is true and -+ * multiple ports share the same name, gives precendence to ports bound to -+ * an ovn_datapath. -+ */ - static struct ovn_port * --ovn_port_find(const struct hmap *ports, const char *name) -+ovn_port_find__(const struct hmap *ports, const char *name, -+ bool prefer_bound) - { -+ struct ovn_port *matched_op = NULL; - struct ovn_port *op; - - HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) { - if (!strcmp(op->key, name)) { -- return op; -+ matched_op = op; -+ if (!prefer_bound || op->od) { -+ return op; -+ } - } - } -- return NULL; -+ return matched_op; -+} -+ -+static struct ovn_port * -+ovn_port_find(const struct hmap *ports, const char *name) -+{ -+ return ovn_port_find__(ports, name, false); -+} -+ -+static struct ovn_port * -+ovn_port_find_bound(const struct hmap *ports, const char *name) -+{ -+ return ovn_port_find__(ports, name, true); - } - - /* Returns true if the logical switch port 'enabled' column is empty or -@@ -2339,15 +2360,13 @@ join_logical_ports(struct northd_context *ctx, - for (size_t i = 0; i < od->nbs->n_ports; i++) { - const struct nbrec_logical_switch_port *nbsp - = od->nbs->ports[i]; -- struct ovn_port *op = ovn_port_find(ports, nbsp->name); -- if (op && op->sb->datapath == od->sb) { -- if (op->nbsp || op->nbrp) { -- static struct vlog_rate_limit rl -- = VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "duplicate logical port %s", -- nbsp->name); -- continue; -- } -+ struct ovn_port *op = ovn_port_find_bound(ports, nbsp->name); -+ if (op && (op->od || op->nbsp || op->nbrp)) { -+ static struct vlog_rate_limit rl -+ = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "duplicate logical port %s", nbsp->name); -+ continue; -+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - ovn_port_set_nb(op, nbsp, NULL); - ovs_list_remove(&op->list); - -@@ -2438,16 +2457,15 @@ join_logical_ports(struct northd_context *ctx, - continue; - } - -- struct ovn_port *op = ovn_port_find(ports, nbrp->name); -- if (op && op->sb->datapath == od->sb) { -- if (op->nbsp || op->nbrp) { -- static struct vlog_rate_limit rl -- = VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "duplicate logical router port %s", -- nbrp->name); -- destroy_lport_addresses(&lrp_networks); -- continue; -- } -+ struct ovn_port *op = ovn_port_find_bound(ports, nbrp->name); -+ if (op && (op->od || op->nbsp || op->nbrp)) { -+ static struct vlog_rate_limit rl -+ = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "duplicate logical router port %s", -+ nbrp->name); -+ destroy_lport_addresses(&lrp_networks); -+ continue; -+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - ovn_port_set_nb(op, NULL, nbrp); - ovs_list_remove(&op->list); - ovs_list_push_back(both, &op->list); -@@ -2490,7 +2508,7 @@ join_logical_ports(struct northd_context *ctx, - char *redirect_name = - ovn_chassis_redirect_name(nbrp->name); - struct ovn_port *crp = ovn_port_find(ports, redirect_name); -- if (crp && crp->sb->datapath == od->sb) { -+ if (crp && crp->sb && crp->sb->datapath == od->sb) { - crp->derived = true; - ovn_port_set_nb(crp, NULL, nbrp); - ovs_list_remove(&crp->list); -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index df03b6e..d22cad8 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2399,3 +2399,48 @@ ovn-nbctl destroy bfd $uuid - check_row_count bfd 2 - - AT_CLEANUP -+ -+AT_SETUP([ovn -- check LSP attached to multiple LS]) -+ovn_start -+ -+check ovn-nbctl ls-add ls1 \ -+ -- ls-add ls2 \ -+ -- lsp-add ls1 p1 -+check ovn-nbctl --wait=sb sync -+ -+uuid=$(fetch_column nb:Logical_Switch_Port _uuid name=p1) -+check ovn-nbctl set Logical_Switch ls2 ports=$uuid -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check LRP attached to multiple LR]) -+ovn_start -+ -+check ovn-nbctl lr-add lr1 \ -+ -- lr-add lr2 \ -+ -- lrp-add lr1 p1 00:00:00:00:00:01 10.0.0.1/24 -+check ovn-nbctl --wait=sb sync -+ -+uuid=$(fetch_column nb:Logical_Router_Port _uuid name=p1) -+check ovn-nbctl set Logical_Router lr2 ports=$uuid -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical router port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check duplicate LSP/LRP]) -+ovn_start -+ -+check ovn-nbctl ls-add ls \ -+ -- lsp-add ls p1 \ -+ -- lr-add lr \ -+ -- lrp-add lr p1 00:00:00:00:00:01 10.0.0.1/24 -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical.*port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP --- -1.8.3.1 - diff --git a/SOURCES/0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch b/SOURCES/0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch deleted file mode 100644 index 266df95..0000000 --- a/SOURCES/0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 8c5c3ca0fc5c28aac937868e9ce5b11010e693f5 Mon Sep 17 00:00:00 2001 -Message-Id: <8c5c3ca0fc5c28aac937868e9ce5b11010e693f5.1605177623.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Wed, 11 Nov 2020 13:39:17 +0100 -Subject: [PATCH ovn 20.09] northd: Fix lb_action when there are no active backends - for lb health_check. - -Fix the following warning reported by ovn-controller when there are no -active backends for lb health_check and selection_fields has been -configured for hash computation. - -flow|WARN|error parsing actions "drop; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");": -Syntax error at `hash_fields' expecting end of input. - -Tested-by: Flavio Fernandes -Fixes: 5af304e747 ("Support selection fields in load balancer.") -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.c | 5 ++++- - tests/ovn.at | 6 ++++-- - 2 files changed, 8 insertions(+), 3 deletions(-) - ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -3613,6 +3613,8 @@ static void build_lb_vip_ct_lb_actions(s - struct ds *action, - char *selection_fields) - { -+ bool skip_hash_fields = false; -+ - if (lb_vip->health_check) { - ds_put_cstr(action, "ct_lb(backends="); - -@@ -3631,6 +3633,7 @@ static void build_lb_vip_ct_lb_actions(s - } - - if (!n_active_backends) { -+ skip_hash_fields = true; - ds_clear(action); - ds_put_cstr(action, "drop;"); - } else { -@@ -3641,7 +3644,7 @@ static void build_lb_vip_ct_lb_actions(s - ds_put_format(action, "ct_lb(backends=%s);", lb_vip->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); ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -19601,6 +19601,8 @@ ovn-nbctl lsp-set-addresses sw1-lr0 rout - 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_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1) -+ovn-nbctl set load_balancer ${OVN_LB_ID} selection_fields="ip_dst,ip_src,tp_dst,tp_src" - - 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 -@@ -19637,12 +19639,12 @@ service_monitor | 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,20.0.0.3:80);) -+ 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; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) - ]) - - 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);) -+ 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; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) - ]) - - # get the svc monitor mac. diff --git a/SOURCES/0001-northd-Skip-matching-on-ct-flags-for-stateless-confi.patch b/SOURCES/0001-northd-Skip-matching-on-ct-flags-for-stateless-confi.patch deleted file mode 100644 index f83a390..0000000 --- a/SOURCES/0001-northd-Skip-matching-on-ct-flags-for-stateless-confi.patch +++ /dev/null @@ -1,265 +0,0 @@ -From 5336b5cb342b8f81115299540f3268f734a6d009 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 10 Feb 2021 12:20:17 +0100 -Subject: [PATCH] northd: Skip matching on ct flags for stateless - configurations. - -If no load balancers or "allow-related" ACLs are configured on a logical -switch, no packets will be sent to conntrack in the logical switch -pipeline and ACL flows in tables ls_in/out_acl will not match on -conntrack state. In this case there's no need to try to set ACL hints -in tables ls_in/out_acl_hint. - -Furthermore, setting the hints translates to always generating flows -that match on ct.state. Depending on the underlying hardware such flows -may not be offloadable inducing a hit in performance even when no -conntrack recirculations are required. - -To avoid iterating through all configured ACLs and load balancers -multiple times, we now store two new fields in the 'ovn_datapath' -structure: -- has_stateful_acl -- has_lb_vip - -Also, rename the 'has_lb_vip()' and 'has_stateful_acl()' functions, -prefixing them with 'ls_' to match other helper function names. - -Fixes: 209ea46bbf9d ("ovn-northd: Reduce number of flows generated for stateful ACLs.") -Reported-by: Haresh Khandelwal -Reported-at: https://bugzilla.redhat.com/1927211 -Signed-off-by: Dumitru Ceara -Signed-off-by: Mark Michelson -Acked-by: Mark Michelson ---- - northd/ovn-northd.8.xml | 3 +- - northd/ovn-northd.c | 34 +++++++++++------- - tests/ovn-northd.at | 77 +++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 101 insertions(+), 13 deletions(-) - -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index 70065a36d..2f8b4e8c3 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -415,7 +415,8 @@ -

- This table consists of logical flows that set hints - (reg0 bits) to be used in the next stage, in the ACL -- processing table. Multiple hints can be set for the same packet. -+ processing table, if stateful ACLs or load balancers are configured. -+ Multiple hints can be set for the same packet. - The possible hints are: -

-
    -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index db6572a62..b85e6e78a 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -597,6 +597,8 @@ struct ovn_datapath { - struct hmap port_tnlids; - uint32_t port_key_hint; - -+ bool has_stateful_acl; -+ bool has_lb_vip; - bool has_unknown; - - /* IPAM data. */ -@@ -635,6 +637,9 @@ struct ovn_datapath { - struct hmap nb_pgs; - }; - -+static bool ls_has_stateful_acl(struct ovn_datapath *od); -+static bool ls_has_lb_vip(struct ovn_datapath *od); -+ - /* Contains a NAT entry with the external addresses pre-parsed. */ - struct ovn_nat { - const struct nbrec_nat *nb; -@@ -4635,7 +4640,7 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs) - } - - static bool --has_stateful_acl(struct ovn_datapath *od) -+ls_has_stateful_acl(struct ovn_datapath *od) - { - for (size_t i = 0; i < od->nbs->n_acls; i++) { - struct nbrec_acl *acl = od->nbs->acls[i]; -@@ -4814,8 +4819,6 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op, - static void - build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) - { -- bool has_stateful = has_stateful_acl(od); -- - /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are - * allowed by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;"); -@@ -4830,7 +4833,7 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) - /* If there are any stateful ACL rules in this datapath, we must - * send all IP packets through the conntrack action, which handles - * defragmentation, in order to match L4 headers. */ -- if (has_stateful) { -+ if (od->has_stateful_acl) { - for (size_t i = 0; i < od->n_router_ports; i++) { - skip_port_from_conntrack(od, od->router_ports[i], - S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL, -@@ -4933,7 +4936,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - } - - static bool --has_lb_vip(struct ovn_datapath *od) -+ls_has_lb_vip(struct ovn_datapath *od) - { - for (int i = 0; i < od->nbs->n_load_balancer; i++) { - struct nbrec_load_balancer *nb_lb = od->nbs->load_balancer[i]; -@@ -5076,6 +5079,13 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) - for (size_t i = 0; i < ARRAY_SIZE(stages); i++) { - enum ovn_stage stage = stages[i]; - -+ /* In any case, advance to the next stage. */ -+ ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); -+ -+ if (!od->has_stateful_acl && !od->has_lb_vip) { -+ continue; -+ } -+ - /* New, not already established connections, may hit either allow - * or drop ACLs. For allow ACLs, the connection must also be committed - * to conntrack so we set REGBIT_ACL_HINT_ALLOW_NEW. -@@ -5136,9 +5146,6 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) - ovn_lflow_add(lflows, od, stage, 1, "ct.est && ct_label.blocked == 0", - REGBIT_ACL_HINT_BLOCK " = 1; " - "next;"); -- -- /* In any case, advance to the next stage. */ -- ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); - } - } - -@@ -5470,7 +5477,7 @@ static void - build_acls(struct ovn_datapath *od, struct hmap *lflows, - struct hmap *port_groups, const struct shash *meter_groups) - { -- bool has_stateful = (has_stateful_acl(od) || has_lb_vip(od)); -+ bool has_stateful = od->has_stateful_acl || od->has_lb_vip; - - /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by - * default. A related rule at priority 1 is added below if there -@@ -5739,7 +5746,7 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows) - } - } - -- if (has_lb_vip(od)) { -+ if (od->has_lb_vip) { - /* Ingress and Egress LB Table (Priority 65534). - * - * Send established traffic through conntrack for just NAT. */ -@@ -5860,7 +5867,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) - ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;"); - -- if (has_lb_vip(od)) { -+ if (od->has_lb_vip) { - /* 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", -@@ -6597,7 +6604,10 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od, - struct shash *meter_groups, - struct hmap *lbs) - { -- if (od->nbs) { -+ if (od->nbs) { -+ od->has_stateful_acl = ls_has_stateful_acl(od); -+ od->has_lb_vip = ls_has_lb_vip(od); -+ - build_pre_acls(od, lflows); - build_pre_lb(od, lflows, meter_groups, lbs); - build_pre_stateful(od, lflows); -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 7240e22ba..64a788067 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1912,6 +1912,83 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} - - AT_CLEANUP - -+AT_SETUP([ovn -- ACL skip hints for stateless config]) -+AT_KEYWORDS([acl]) -+ovn_start -+ -+check ovn-nbctl --wait=sb \ -+ -- ls-add ls \ -+ -- lsp-add ls lsp \ -+ -- acl-add ls from-lport 1 "ip" allow \ -+ -- acl-add ls to-lport 1 "ip" allow -+ -+AS_BOX([Check no match on ct_state with stateless ACLs]) -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+]) -+ -+AS_BOX([Check match ct_state with stateful ACLs]) -+check ovn-nbctl --wait=sb \ -+ -- acl-add ls from-lport 2 "udp" allow-related \ -+ -- acl-add ls to-lport 2 "udp" allow-related -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=6 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=7 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=7 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=7 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=7 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+]) -+ -+AS_BOX([Check match ct_state with load balancer]) -+check ovn-nbctl --wait=sb \ -+ -- acl-del ls from-lport 2 "udp" \ -+ -- acl-del ls to-lport 2 "udp" \ -+ -- lb-add lb "10.0.0.1" "10.0.0.2" \ -+ -- ls-lb-add ls lb -+ -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=6 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=6 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=7 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=7 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=7 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=7 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+]) -+ -+AT_CLEANUP -+ - AT_SETUP([datapath requested-tnl-key]) - AT_KEYWORDS([requested tnl tunnel key keys]) - ovn_start --- -2.29.2 - diff --git a/SOURCES/0001-northd-Use-enum-ovn_stage-for-the-table-value-in-the.patch b/SOURCES/0001-northd-Use-enum-ovn_stage-for-the-table-value-in-the.patch deleted file mode 100644 index 6ac69e0..0000000 --- a/SOURCES/0001-northd-Use-enum-ovn_stage-for-the-table-value-in-the.patch +++ /dev/null @@ -1,389 +0,0 @@ -From 8466c0de9f209011d82331521bd5c47422963c15 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Mon, 5 Oct 2020 12:52:15 +0530 -Subject: [PATCH 1/5] northd: Use 'enum ovn_stage' for the table value in the - 'next' OVN action. - -Multiple places in ovn-northd.c hard codes the table value in the next() OVN action. -This patch changes those occurrences to use ovn_stage_get_table('enum ovn_stage' value). - -Hard coding of the table number can result in errors if new stages are added (like -the patch [1] which added new stages - ls_in_acl_hint and ls_out_acl_hint). After the patch [1], -the table number was wrong for reject ACLs associated in ingress logical switch pipeline stage. -Although this didn't result in any packet drops. This patch avoids such cases in the future. - -This patch also adds a new test case in ovn-northd.at for reject ACL flows. - -[1] - 209ea46bbf9d("ovn-northd: Reduce number of flows generated for stateful ACLs.") - -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 4ab6b79a81b15d727b0a0f617f267d3169f7b486) ---- - northd/ovn-northd.c | 36 ++++--- - tests/ovn-northd.at | 247 ++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 266 insertions(+), 17 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 73e37985e..b099f705b 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -5379,6 +5379,12 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - struct ds actions = DS_EMPTY_INITIALIZER; - bool ingress = (stage == S_SWITCH_IN_ACL); - -+ 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)); -+ - /* TCP */ - build_acl_log(&actions, acl); - if (extra_match->length > 0) { -@@ -5387,9 +5393,7 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - 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);"); -+ "tcp_reset { outport <-> inport; %s };", next_action); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions), stage_hint); -@@ -5402,9 +5406,7 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - 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);"); -+ "tcp_reset { outport <-> inport; %s };", next_action); - ovn_lflow_add_with_hint(lflows, od, stage, - acl->priority + OVN_ACL_PRI_OFFSET + 10, - ds_cstr(&match), ds_cstr(&actions), stage_hint); -@@ -5422,9 +5424,7 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - } - 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);"); -+ "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); -@@ -5440,13 +5440,12 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - } - 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);"); -+ "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); - } -@@ -9963,7 +9962,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - 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_); -@@ -11145,10 +11145,11 @@ build_check_pkt_len_flows_for_lrouter( - "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), -@@ -11173,10 +11174,11 @@ build_check_pkt_len_flows_for_lrouter( - "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), -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 99a9204f1..a6c32c115 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2010,3 +2010,250 @@ ovn-nbctl --wait=sb set NB_Global . options:ignore_lsp_down=true - 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 -+ -+ovn-nbctl ls-add sw0 -+ovn-nbctl lsp-add sw0 sw0-p1 -+ -+ovn-nbctl ls-add sw1 -+ovn-nbctl lsp-add sw1 sw1-p1 -+ -+ovn-nbctl pg-add pg0 sw0-p1 sw1-p1 -+ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip4 && tcp && tcp.dst == 80" reject -+ovn-nbctl acl-add pg0 to-lport 1003 "outport == @pg0 && ip6 && udp" reject -+ -+ovn-nbctl --wait=hv sync -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_in_acl" | grep pg0 | sort], [0], [dnl -+ table=7 (ls_in_acl ), priority=2002 , dnl -+match=(ip4 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2002 , dnl -+match=(ip6 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2012 , dnl -+match=(ip4 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2012 , dnl -+match=(ip6 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_in_acl" | grep pg0 | sort], [0], [dnl -+ table=7 (ls_in_acl ), priority=2002 , dnl -+match=(ip4 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2002 , dnl -+match=(ip6 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2012 , dnl -+match=(ip4 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+ table=7 (ls_in_acl ), priority=2012 , dnl -+match=(ip6 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=(ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=(ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=(ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=(ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2001 , dnl -+match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), dnl -+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) && ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[10]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -+ table=5 (ls_out_acl ), priority=2001 , dnl -+match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), dnl -+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) && ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2002 , dnl -+match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[10]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2003 , dnl -+match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2012 , dnl -+match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+ table=5 (ls_out_acl ), priority=2013 , dnl -+match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl -+action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+]) -+ -+AT_CLEANUP --- -2.26.2 - diff --git a/SOURCES/0001-northd-add-event-option-to-enable-controller_event-f.patch b/SOURCES/0001-northd-add-event-option-to-enable-controller_event-f.patch deleted file mode 100644 index 97c2dce..0000000 --- a/SOURCES/0001-northd-add-event-option-to-enable-controller_event-f.patch +++ /dev/null @@ -1,141 +0,0 @@ -From 8bcee6092b931caa936ee8ac715cf6ec89d3f18d Mon Sep 17 00:00:00 2001 -Message-Id: <8bcee6092b931caa936ee8ac715cf6ec89d3f18d.1611836178.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Fri, 22 Jan 2021 18:25:54 +0100 -Subject: [PATCH] northd: add --event option to enable controller_event for - empty_lb - -Introduce the --event option to enable empty_lb controller event for a -load_balancer with no backends (doing so the option is per-lb and not -global). - -$ovn-nbctl --event lb-add lb0 192.168.0.100:80 "" - -controller_event_en global variable is not removed for backward -compatibility but it is deprecated - -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.c | 5 ++++- - tests/ovn.at | 7 +++---- - utilities/ovn-nbctl.8.xml | 10 +++++++++- - utilities/ovn-nbctl.c | 13 ++++++++++++- - 4 files changed, 28 insertions(+), 7 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index f11894a4d..9f8fb3b95 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -5110,7 +5110,9 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - struct nbrec_load_balancer *lb, - int pl, struct shash *meter_groups) - { -- if (!controller_event_en || lb_vip->n_backends || -+ bool controller_event = smap_get_bool(&lb->options, "event", false) || -+ controller_event_en; /* deprecated */ -+ if (!controller_event || lb_vip->n_backends || - lb_vip->empty_backend_rej) { - return; - } -@@ -12853,6 +12855,7 @@ ovnnb_db_run(struct northd_context *ctx, - - use_logical_dp_groups = smap_get_bool(&nb->options, - "use_logical_dp_groups", false); -+ /* deprecated, use --event instead */ - controller_event_en = smap_get_bool(&nb->options, - "controller_event", false); - check_lsp_is_up = !smap_get_bool(&nb->options, -diff --git a/tests/ovn.at b/tests/ovn.at -index aab749300..a4fafa5a8 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -16856,16 +16856,15 @@ ovs-vsctl -- add-port br-int vif33 -- \ - options:rxq_pcap=hv$i/vif33-rx.pcap \ - ofport-request=33 - --ovn-nbctl --wait=hv set NB_Global . options:controller_event=true --ovn-nbctl lb-add lb0 192.168.1.100:80 "" -+ovn-nbctl --event lb-add lb0 192.168.1.100:80 "" - ovn-nbctl ls-lb-add sw0 lb0 - uuid_lb0=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb0) - --ovn-nbctl lb-add lb1 192.168.2.100:80 "" -+ovn-nbctl --event lb-add lb1 192.168.2.100:80 "" - ovn-nbctl lr-lb-add lr0 lb1 - uuid_lb1=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb1) - --ovn-nbctl lb-add lb2 [[2001::10]]:50051 "" -+ovn-nbctl --event lb-add lb2 [[2001::10]]:50051 "" - ovn-nbctl ls-lb-add sw0 lb2 - uuid_lb2=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb2) - -diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml -index e6fec9980..6ed8bcb75 100644 ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -905,7 +905,7 @@ - -

    Load Balancer Commands

    -
    --
    [--may-exist | --add-duplicate | --reject] lb-add lb vip ips [protocol]
    -+
    [--may-exist | --add-duplicate | --reject | --event] lb-add lb vip ips [protocol]
    -
    -

    - Creates a new load balancer named lb with the provided -@@ -947,6 +947,14 @@ - empty_lb SB controller event for this load balancer. -

    - -+

    -+ If the load balancer is created with --event option and -+ it has no active backends, whenever the lb receives traffic, the event -+ is reported in the Controller_Event table in the SB db. -+ Please note --event option can't be specified with -+ --reject one. -+

    -+ -

    - The following example adds a load balancer. -

    -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index f61982879..47cb8db9d 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -2836,6 +2836,13 @@ nbctl_lb_add(struct ctl_context *ctx) - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL; - bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL; -+ bool empty_backend_event = shash_find(&ctx->options, "--event") != NULL; -+ -+ if (empty_backend_event && empty_backend_rej) { -+ ctl_error(ctx, -+ "--reject and --event can't specified at the same time"); -+ return; -+ } - - const char *lb_proto; - bool is_update_proto = false; -@@ -2953,6 +2960,10 @@ nbctl_lb_add(struct ctl_context *ctx) - const struct smap options = SMAP_CONST1(&options, "reject", "true"); - nbrec_load_balancer_set_options(lb, &options); - } -+ if (empty_backend_event) { -+ const struct smap options = SMAP_CONST1(&options, "event", "true"); -+ nbrec_load_balancer_set_options(lb, &options); -+ } - out: - ds_destroy(&lb_ips_new); - -@@ -6564,7 +6575,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { - nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, - /* load balancer commands. */ - { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, -- nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject", RW }, -+ nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject,--event", RW }, - { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL, - "--if-exists", RW }, - { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO }, --- -2.29.2 - diff --git a/SOURCES/0001-northd-add-reject-action-for-lb-with-no-backends.patch b/SOURCES/0001-northd-add-reject-action-for-lb-with-no-backends.patch deleted file mode 100644 index 9991763..0000000 --- a/SOURCES/0001-northd-add-reject-action-for-lb-with-no-backends.patch +++ /dev/null @@ -1,392 +0,0 @@ -From 8a9f48c2f146476f1e46da4144b77e38d712673c Mon Sep 17 00:00:00 2001 -From: Lorenzo Bianconi -Date: Wed, 9 Dec 2020 12:25:31 +0100 -Subject: [PATCH 1/7] northd: add reject action for lb with no backends - -Introduce the capability to create a load balancer with no backends and -with --reject option in order to send a TCP reset segment (for tcp) or -an ICMP port unreachable packet (for all other kind of traffic) whenever -an incoming packet is received for this load-balancer. - -Tested-by: Antonio Ojea -Signed-off-by: Lorenzo Bianconi -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique - -(cherry-picked from master commit ebbcd8e8cc5000d50691e72edfde7ede4a906ade) - -Change-Id: I5676901df564ecf978455319cfcddd24c2efdae4 ---- - lib/lb.c | 2 ++ - lib/lb.h | 1 + - northd/ovn-northd.8.xml | 19 +++++++++++++++++ - northd/ovn-northd.c | 44 ++++++++++++++++++++++++++------------- - ovn-nb.ovsschema | 9 ++++++-- - ovn-nb.xml | 10 +++++++++ - tests/ovn-northd.at | 25 ++++++++++++++++++++++ - tests/system-ovn.at | 28 ++++++++++++++++++++++++- - utilities/ovn-nbctl.8.xml | 11 +++++++++- - utilities/ovn-nbctl.c | 7 ++++++- - 10 files changed, 136 insertions(+), 20 deletions(-) - -diff --git a/lib/lb.c b/lib/lb.c -index a90042e58..2517c02ef 100644 ---- a/lib/lb.c -+++ b/lib/lb.c -@@ -189,6 +189,8 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; - struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips]; - -+ lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options, -+ "reject", false); - if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { - continue; - } -diff --git a/lib/lb.h b/lib/lb.h -index 6644ad0d8..42c580bd1 100644 ---- a/lib/lb.h -+++ b/lib/lb.h -@@ -49,6 +49,7 @@ struct ovn_lb_vip { - - struct ovn_lb_backend *backends; - size_t n_backends; -+ bool empty_backend_rej; - }; - - struct ovn_lb_backend { -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index a9a3a9f4f..d86f36ea6 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -700,6 +700,16 @@ - ct_lb(args), where args contains comma - separated IP addresses of the same address family as VIP. - -+ -+
  • -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -+
  • -+ -
  • - A priority-100 flow commits packets to connection tracker using - ct_commit; next; action based on a hint provided by -@@ -2592,6 +2602,15 @@ icmp6 { - packets, the above action will be replaced by - flags.force_snat_for_lb = 1; ct_dnat;. -
  • -+ -+
  • -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -+
  • -
- -

Ingress Table 6: DNAT on Gateway Routers

-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 5a3227568..478f1a339 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -3436,12 +3436,12 @@ ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_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) -+void build_lb_vip_actions(struct ovn_lb_vip *lb_vip, -+ struct ovn_northd_lb_vip *lb_vip_nb, -+ struct ds *action, char *selection_fields, -+ bool ls_dp) - { -- bool skip_hash_fields = false; -+ bool skip_hash_fields = false, reject = false; - - if (lb_vip_nb->lb_health_check) { - ds_put_cstr(action, "ct_lb(backends="); -@@ -3463,18 +3463,30 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, - } - - if (!n_active_backends) { -- skip_hash_fields = true; -- ds_clear(action); -- ds_put_cstr(action, "drop;"); -+ if (!lb_vip->empty_backend_rej) { -+ ds_clear(action); -+ ds_put_cstr(action, "drop;"); -+ skip_hash_fields = true; -+ } else { -+ reject = true; -+ } - } else { - ds_chomp(action, ','); - ds_put_cstr(action, ");"); - } -+ } else if (lb_vip->empty_backend_rej && !lb_vip->n_backends) { -+ reject = true; - } else { - ds_put_format(action, "ct_lb(backends=%s);", lb_vip_nb->backend_ips); - } - -- if (!skip_hash_fields && selection_fields && selection_fields[0]) { -+ if (reject) { -+ int stage = ls_dp ? ovn_stage_get_table(S_SWITCH_OUT_QOS_MARK) -+ : ovn_stage_get_table(S_ROUTER_OUT_SNAT); -+ ds_clear(action); -+ ds_put_format(action, "reg0 = 0; reject { outport <-> inport; " -+ "next(pipeline=egress,table=%d);};", stage); -+ } else if (!skip_hash_fields && selection_fields && selection_fields[0]) { - ds_chomp(action, ';'); - ds_chomp(action, ')'); - ds_put_format(action, "; hash_fields=\"%s\");", selection_fields); -@@ -5084,7 +5096,8 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - struct nbrec_load_balancer *lb, - int pl, struct shash *meter_groups) - { -- if (!controller_event_en || lb_vip->n_backends) { -+ if (!controller_event_en || lb_vip->n_backends || -+ lb_vip->empty_backend_rej) { - return; - } - -@@ -5974,8 +5987,8 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, - - /* New connections in Ingress table. */ - struct ds action = DS_EMPTY_INITIALIZER; -- build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &action, -- lb->selection_fields); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, &action, -+ lb->selection_fields, true); - - struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, -@@ -9685,8 +9698,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - 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, lb_vip_nb, &actions, -- lb->selection_fields); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, &actions, -+ lb->selection_fields, false); - - if (!sset_contains(&all_ips, lb_vip->vip_str)) { - sset_add(&all_ips, lb_vip->vip_str); -@@ -9737,7 +9750,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - prio = 120; - } - -- if (od->l3redirect_port) { -+ if (od->l3redirect_port && -+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } -diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema -index 269e3a888..af77dd138 100644 ---- a/ovn-nb.ovsschema -+++ b/ovn-nb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Northbound", -- "version": "5.28.0", -- "cksum": "610359755 26847", -+ "version": "5.29.0", -+ "cksum": "328602112 27064", - "tables": { - "NB_Global": { - "columns": { -@@ -188,6 +188,11 @@ - ["eth_src", "eth_dst", "ip_src", "ip_dst", - "tp_src", "tp_dst"]]}, - "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", -+ "value": "string", -+ "min": 0, -+ "max": "unlimited"}}, - "external_ids": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, -diff --git a/ovn-nb.xml b/ovn-nb.xml -index c9ab25ceb..e7a8d6833 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -1635,6 +1635,16 @@ - See External IDs at the beginning of this document. - -
-+ -+ -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable empty_lb -+ SB controller event for this load balancer. -+ -+ - - - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 90ca0a4db..50a4cae76 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1233,6 +1233,31 @@ wait_row_count Service_Monitor 2 - ovn-nbctl --wait=sb lb-del lb2 - wait_row_count Service_Monitor 0 - -+check ovn-nbctl --reject lb-add lb3 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 -+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 -+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -+wait_row_count Service_Monitor 0 -+ -+check ovn-nbctl --wait=sb ls-lb-add sw0 lb3 -+AT_CHECK([ovn-nbctl --wait=sb -- --id=@hc create \ -+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer lb3 \ -+health_check @hc | uuidfilt], [0], [<0> -+]) -+wait_row_count Service_Monitor 2 -+ -+# Set the service monitor for sw0-p1 and sw1-p1 to online -+sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1) -+sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1) -+ -+ovn-sbctl set service_monitor $sm_sw0_p1 status=offline -+ovn-sbctl set service_monitor $sm_sw1_p1 status=offline -+ -+AT_CAPTURE_FILE([sbflows12]) -+OVS_WAIT_FOR_OUTPUT( -+ [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};) -+]) -+ - AT_CLEANUP - - AT_SETUP([ovn -- Load balancer VIP in NAT entries]) -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index d59f7c97e..1e73001ab 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -1574,6 +1574,18 @@ OVS_WAIT_UNTIL([ - grep "selection_method=hash,fields(ip_src,ip_dst,sctp_src,sctp_dst)" -c) -eq 2 - ]) - -+ovn-nbctl --reject lb-add lb3 30.0.0.10:80 "" -+ovn-nbctl ls-lb-add foo lb3 -+# Filter reset segments -+NS_CHECK_EXEC([foo1], [tcpdump -c 1 -neei foo1 ip[[33:1]]=0x14 > rst.pcap &]) -+sleep 1 -+NS_CHECK_EXEC([foo1], [wget -q 30.0.0.10],[4]) -+ -+OVS_WAIT_UNTIL([ -+ n_reset=$(cat rst.pcap | wc -l) -+ test "${n_reset}" = "1" -+]) -+ - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -4151,7 +4163,7 @@ ovn-nbctl lsp-set-type sw1-lr0 router - ovn-nbctl lsp-set-addresses sw1-lr0 router - ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - --ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 -+ovn-nbctl --reject lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 - - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -@@ -4266,6 +4278,20 @@ ovn-sbctl list service_monitor - OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ - service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`]) - -+# Stop webserer in sw1-p1 -+pid_file=$(cat l7_pid_file) -+NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)]) -+ -+NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &]) -+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ -+service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`]) -+NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4]) -+ -+OVS_WAIT_UNTIL([ -+ n_reset=$(cat rst.pcap | wc -l) -+ test "${n_reset}" = "1" -+]) -+ - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml -index 59302296b..e5a35f307 100644 ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -903,7 +903,7 @@ - -

Load Balancer Commands

-
--
[--may-exist | --add-duplicate] lb-add lb vip ips [protocol]
-+
[--may-exist | --add-duplicate | --reject] lb-add lb vip ips [protocol]
-
-

- Creates a new load balancer named lb with the provided -@@ -936,6 +936,15 @@ - creates a new load balancer with a duplicate name. -

- -+

-+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -+

-+ -

- The following example adds a load balancer. -

-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index d19e1b6c6..3a95f6b1f 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -2821,6 +2821,7 @@ nbctl_lb_add(struct ctl_context *ctx) - - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL; -+ bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL; - - const char *lb_proto; - bool is_update_proto = false; -@@ -2934,6 +2935,10 @@ nbctl_lb_add(struct ctl_context *ctx) - smap_add(CONST_CAST(struct smap *, &lb->vips), - lb_vip_normalized, ds_cstr(&lb_ips_new)); - nbrec_load_balancer_set_vips(lb, &lb->vips); -+ if (empty_backend_rej) { -+ const struct smap options = SMAP_CONST1(&options, "reject", "true"); -+ nbrec_load_balancer_set_options(lb, &options); -+ } - out: - ds_destroy(&lb_ips_new); - -@@ -6588,7 +6593,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { - nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, - /* load balancer commands. */ - { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, -- nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW }, -+ nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject", RW }, - { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL, - "--if-exists", RW }, - { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO }, --- -2.28.0 - diff --git a/SOURCES/0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch b/SOURCES/0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch deleted file mode 100644 index 5705999..0000000 --- a/SOURCES/0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 9d7ccd55a03eec88c2fd49a0345fc8fb2b429499 Mon Sep 17 00:00:00 2001 -Message-Id: <9d7ccd55a03eec88c2fd49a0345fc8fb2b429499.1602246561.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Fri, 2 Oct 2020 19:31:28 +0200 -Subject: [PATCH] northd: properly reconfigure ipam when subnet is changed - -Reconfigure dynamic assigned addresses if subnet is modified -removing IP out of configured netmask if present - -Signed-off-by: Lorenzo Bianconi -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.c | 2 +- - tests/ovn.at | 18 ++++++++++++++++++ - 2 files changed, 19 insertions(+), 1 deletion(-) - ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -1754,7 +1754,7 @@ dynamic_ip4_changed(const char *lsp_addr - } - - 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, ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -7473,6 +7473,24 @@ AT_CHECK([ovn-nbctl get Logical-Switch-P - ["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 -q 192.10.2.127], [0]) -+AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep -q 192.10.2.128], [0]) -+AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep -q 192.10.2.180], [0]) -+ -+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 -q 192.10.2.127], [1]) -+AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep -q 192.10.2.128], [1]) -+AT_CHECK([ovn-nbctl list Logical-Switch-Port | grep -q 192.10.2.180], [1]) -+ - as ovn-sb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - diff --git a/SOURCES/0001-ofctrl-Rename-nb_cfg-to-req_cfg.patch b/SOURCES/0001-ofctrl-Rename-nb_cfg-to-req_cfg.patch deleted file mode 100644 index cd61e64..0000000 --- a/SOURCES/0001-ofctrl-Rename-nb_cfg-to-req_cfg.patch +++ /dev/null @@ -1,223 +0,0 @@ -From c6f4b3a47571d87149b86c999b78509185da7647 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 13 Jan 2021 10:23:09 +0100 -Subject: [PATCH 1/3] ofctrl: Rename 'nb_cfg' to 'req_cfg'. - -A future commit will extend the ofctrl OVS barrier sync mechanism to -make it usable by multiple components. One of the use cases will be -'nb_cfg' sync but it may not be the only one. - -Signed-off-by: Dumitru Ceara -Acked-by: Mark Michelson -Acked-by: Numan Siddique -Signed-off-by: Numan Siddique -(cherry picked from upstream master commit 2f933fc560330022cfc816ed870da6e5847809c9) - -Change-Id: Iddeeeb6aaee189a3e9918426e0dce3f1dbf6ff49 ---- - controller/ofctrl.c | 70 ++++++++++++++++++++++----------------------- - controller/ofctrl.h | 4 +-- - controller/ovn-controller.c | 6 ++-- - 3 files changed, 40 insertions(+), 40 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index a1ac695..9d62e12 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -268,13 +268,14 @@ enum ofctrl_state { - /* An in-flight update to the switch's flow table. - * - * When we receive a barrier reply from the switch with the given 'xid', we -- * know that the switch is caught up to northbound database sequence number -- * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so -- * that it can store it into our Chassis record's nb_cfg column). */ -+ * know that the switch is caught up to the requested sequence number -+ * 'req_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), -+ * so that it can store it into external state, e.g., our Chassis record's -+ * nb_cfg column). */ - struct ofctrl_flow_update { - struct ovs_list list_node; /* In 'flow_updates'. */ - ovs_be32 xid; /* OpenFlow transaction ID for barrier. */ -- int64_t nb_cfg; /* Northbound database sequence number. */ -+ uint64_t req_cfg; /* Requested sequence number. */ - }; - - static struct ofctrl_flow_update * -@@ -286,8 +287,8 @@ ofctrl_flow_update_from_list_node(const struct ovs_list *list_node) - /* Currently in-flight updates. */ - static struct ovs_list flow_updates; - --/* nb_cfg of latest committed flow update. */ --static int64_t cur_cfg; -+/* req_cfg of latest committed flow update. */ -+static uint64_t cur_cfg; - - /* Current state. */ - static enum ofctrl_state state; -@@ -632,8 +633,8 @@ recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type, - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_front(&flow_updates)); - if (fup->xid == oh->xid) { -- if (fup->nb_cfg >= cur_cfg) { -- cur_cfg = fup->nb_cfg; -+ if (fup->req_cfg >= cur_cfg) { -+ cur_cfg = fup->req_cfg; - } - ovs_list_remove(&fup->list_node); - free(fup); -@@ -763,7 +764,7 @@ ofctrl_destroy(void) - shash_destroy(&symtab); - } - --int64_t -+uint64_t - ofctrl_get_cur_cfg(void) - { - return cur_cfg; -@@ -2024,28 +2025,28 @@ void - ofctrl_put(struct ovn_desired_flow_table *flow_table, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *meter_table, -- int64_t nb_cfg, -+ uint64_t req_cfg, - bool flow_changed) - { - static bool skipped_last_time = false; -- static int64_t old_nb_cfg = 0; -+ static uint64_t old_req_cfg = 0; - 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) { -+ old_req_cfg = req_cfg; -+ } else if (req_cfg != old_req_cfg) { -+ /* req_cfg changed since last ofctrl_put() call */ -+ if (cur_cfg == old_req_cfg) { - /* If there are no updates pending, we were up-to-date already, -- * update with the new nb_cfg. -+ * update with the new req_cfg. - */ - if (ovs_list_is_empty(&flow_updates)) { -- cur_cfg = nb_cfg; -- old_nb_cfg = nb_cfg; -+ cur_cfg = req_cfg; -+ old_req_cfg = req_cfg; - } - } else { - need_put = true; -- old_nb_cfg = nb_cfg; -+ old_req_cfg = req_cfg; - } - } - -@@ -2187,24 +2188,23 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - /* Track the flow update. */ - struct ofctrl_flow_update *fup, *prev; - LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) { -- if (nb_cfg < fup->nb_cfg) { -+ if (req_cfg < fup->req_cfg) { - /* This ofctrl_flow_update is for a configuration later than -- * 'nb_cfg'. This should not normally happen, because it means -- * that 'nb_cfg' in the SB_Global table of the southbound -- * database decreased, and it should normally be monotonically -- * increasing. */ -- VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64, -- fup->nb_cfg, nb_cfg); -+ * 'req_cfg'. This should not normally happen, because it -+ * means that the local seqno decreased and it should normally -+ * be monotonically increasing. */ -+ VLOG_WARN("req_cfg regressed from %"PRId64" to %"PRId64, -+ fup->req_cfg, req_cfg); - ovs_list_remove(&fup->list_node); - free(fup); -- } else if (nb_cfg == fup->nb_cfg) { -+ } else if (req_cfg == fup->req_cfg) { - /* This ofctrl_flow_update is for the same configuration as -- * 'nb_cfg'. Probably, some change to the physical topology -+ * 'req_cfg'. Probably, some change to the physical topology - * means that we had to revise the OpenFlow flow table even - * though the logical topology did not change. Update fp->xid, - * so that we don't send a notification that we're up-to-date - * until we're really caught up. */ -- VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg); -+ VLOG_DBG("advanced xid target for req_cfg=%"PRId64, req_cfg); - fup->xid = xid_; - goto done; - } else { -@@ -2216,18 +2216,18 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - fup = xmalloc(sizeof *fup); - ovs_list_push_back(&flow_updates, &fup->list_node); - fup->xid = xid_; -- fup->nb_cfg = nb_cfg; -+ fup->req_cfg = req_cfg; - done:; - } else if (!ovs_list_is_empty(&flow_updates)) { -- /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table -- * changes, so whenever we get up-to-date with the most recent flow -- * table update, we're also up-to-date with 'nb_cfg'. */ -+ /* Getting up-to-date with 'req_cfg' didn't require any extra flow -+ * table changes, so whenever we get up-to-date with the most recent -+ * flow table update, we're also up-to-date with 'req_cfg'. */ - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_back(&flow_updates)); -- fup->nb_cfg = nb_cfg; -+ fup->req_cfg = req_cfg; - } else { - /* We were completely up-to-date before and still are. */ -- cur_cfg = nb_cfg; -+ cur_cfg = req_cfg; - } - - flow_table->change_tracked = true; -diff --git a/controller/ofctrl.h b/controller/ofctrl.h -index 64b0ea5..8876956 100644 ---- a/controller/ofctrl.h -+++ b/controller/ofctrl.h -@@ -55,12 +55,12 @@ enum mf_field_id ofctrl_get_mf_field_id(void); - void ofctrl_put(struct ovn_desired_flow_table *, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *, -- int64_t nb_cfg, -+ uint64_t nb_cfg, - bool flow_changed); - bool ofctrl_can_put(void); - void ofctrl_wait(void); - void ofctrl_destroy(void); --int64_t ofctrl_get_cur_cfg(void); -+uint64_t ofctrl_get_cur_cfg(void); - - void ofctrl_ct_flush_zone(uint16_t zone_id); - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 7551287..42883b4 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -798,11 +798,11 @@ restore_ct_zones(const struct ovsrec_bridge_table *bridge_table, - } - } - --static int64_t -+static uint64_t - 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; -+ static uint64_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 -@@ -826,7 +826,7 @@ 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) -+ uint64_t cur_cfg) - { - if (!cur_cfg) { - return; --- -1.8.3.1 - diff --git a/SOURCES/0001-ofctrl.c-Fix-duplicated-flow-handling-in-I-P-while-m.patch b/SOURCES/0001-ofctrl.c-Fix-duplicated-flow-handling-in-I-P-while-m.patch deleted file mode 100644 index 78547ec..0000000 --- a/SOURCES/0001-ofctrl.c-Fix-duplicated-flow-handling-in-I-P-while-m.patch +++ /dev/null @@ -1,238 +0,0 @@ -From 9ac0a75fd1995dee44f80ba4d7856cbf7b0e456d Mon Sep 17 00:00:00 2001 -From: Han Zhou -Date: Thu, 1 Oct 2020 22:49:04 -0700 -Subject: [PATCH 1/7] ofctrl.c: Fix duplicated flow handling in I-P while - merging opposite changes. - -In a scenario in I-P when a desired flow is removed and an exactly same flow is -added back in the same iteration, the merge_tracked_flows() function will merge -the operation without firing any OpenFlow action. However, if there are -multiple desired flows (e.g. F1 and F2) matching the same installed flow, and -if the one being re-added (say, F1) is the one currently installed in OVS, the -current implementation will update the currently installed flow to F2 in the -data structure while the actual OVS flow is not updated (still F1). So far -there is still no problem, but the member desired_flow of the installed flow -is pointing to the wrong desired flow. - -Now there is only one place where the desired_flow member is consulted, in -update_installed_flows_by_track() for handling a tracked *modified* flow. In -reality flow modification happens only when conjunction flows need to be -combined, which would never happen together with the above scenario of -merge_tracked_flows(). So this wrong desired_flow pointer doesn't cause any -real problem yet. - -However, there is a place that can already utilize this active desired_flow -information, which is when handling a tracked flow deletion in -update_installed_flows_by_track(): we can check if the flow being deleted is -the currently active one installed in OVS. If not, we don't need to send and -OpenFlow modify to OVS. This optimization is added in this patch, apart from -fixing the problem of merge_tracked_flows(). Without the fix, this optimization -would cause the test case added in this patch fail. - -Fixes: f4e508dd7 ("ofctrl.c: Merge opposite changes of tracked flows before installing.") -Acked-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit 9d2e8d32fb9865513b70408a665184a67564390d) - -Change-Id: Ieb30dd8f4d07aae472c222920fcb773a1deace4d ---- - controller/ofctrl.c | 28 +++++++++++++- - tests/ovn.at | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 134 insertions(+), 2 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 81a00c8..e725c00 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -788,6 +788,24 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type) - } - } - -+/* Returns true if a desired flow is active (the one currently installed -+ * among the list of desired flows that are linked to the same installed -+ * flow). */ -+static inline bool -+desired_flow_is_active(struct desired_flow *d) -+{ -+ return (d->installed_flow && d->installed_flow->desired_flow == d); -+} -+ -+/* Set a desired flow as the active one among the list of desired flows -+ * that are linked to the same installed flow. */ -+static inline void -+desired_flow_set_active(struct desired_flow *d) -+{ -+ ovs_assert(d->installed_flow); -+ d->installed_flow->desired_flow = d; -+} -+ - static void - link_installed_to_desired(struct installed_flow *i, struct desired_flow *d) - { -@@ -1740,6 +1758,8 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table) - /* del_f must have been installed, otherwise it should have been - * removed during track_flow_add_or_modify. */ - ovs_assert(del_f->installed_flow); -+ -+ bool del_f_was_active = desired_flow_is_active(del_f); - if (!f->installed_flow) { - /* f is not installed yet. */ - struct installed_flow *i = del_f->installed_flow; -@@ -1751,6 +1771,9 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table) - ovs_assert(f->installed_flow == del_f->installed_flow); - unlink_installed_to_desired(del_f->installed_flow, del_f); - } -+ if (del_f_was_active) { -+ desired_flow_set_active(f); -+ } - hmap_remove(&deleted_flows, &del_f->match_hmap_node); - ovs_list_remove(&del_f->track_list_node); - desired_flow_destroy(del_f); -@@ -1778,6 +1801,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - /* The desired flow was deleted */ - if (f->installed_flow) { - struct installed_flow *i = f->installed_flow; -+ bool was_active = desired_flow_is_active(f); - unlink_installed_to_desired(i, f); - - if (!i->desired_flow) { -@@ -1786,7 +1810,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - - 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, -@@ -1810,7 +1834,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - 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 (desired_flow_is_active(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)"); -diff --git a/tests/ovn.at b/tests/ovn.at -index e3f3153..6f1ab59 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -22266,6 +22266,114 @@ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected]) - 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 -+} -+ -+ip_to_hex() { -+ printf "%02x%02x%02x%02x" "$@" -+} -+ -+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 - --- -1.8.3.1 - diff --git a/SOURCES/0001-ovn-controller-Fix-wrong-conj_id-match-flows-when-ca.patch b/SOURCES/0001-ovn-controller-Fix-wrong-conj_id-match-flows-when-ca.patch deleted file mode 100644 index 5bb885d..0000000 --- a/SOURCES/0001-ovn-controller-Fix-wrong-conj_id-match-flows-when-ca.patch +++ /dev/null @@ -1,205 +0,0 @@ -From 11f75ad5bef3d2f6a9d72a8b27468fc3ccfc3d7e Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Fri, 22 Jan 2021 13:52:29 +0530 -Subject: [PATCH] ovn-controller: Fix wrong conj_id match flows when caching is - enabled. - -When the below ACL is added - -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-controller installs the below OF flows - -table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2) -table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2) -table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2) -table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(2,2/2) -table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) - -When a full recompute is triggered, ovn-controller deletes the last -OF flow with the match conj_id=2 and adds the below OF flow - -table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) - -For subsequent recomputes, the conj_id keeps increasing by 1. - -This disrupts the traffic which matches on conjuction action flows. - -This patch fixes this issue. - -Fixes: 1213bc8270("ovn-controller: Cache logical flow expr matches.") -Suggested-by: Dumitru Ceara -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from master commit c83294970c62f662015a7979b12250580bee3001) ---- - controller/lflow.c | 30 ++++++++++++++---------------- - include/ovn/expr.h | 1 + - lib/expr.c | 19 +++++++++++++++++++ - tests/ovn.at | 28 ++++++++++++++++++++++++++++ - 4 files changed, 62 insertions(+), 16 deletions(-) - -diff --git a/controller/lflow.c b/controller/lflow.c -index c02585b1e..9f6aece9a 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -668,9 +668,8 @@ update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs) - 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, -+ struct hmap *matches, uint8_t ptable, -+ uint8_t output_ptable, struct ofpbuf *ovnacts, - bool ingress, struct lflow_ctx_in *l_ctx_in, - struct lflow_ctx_out *l_ctx_out) - { -@@ -708,9 +707,6 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - struct expr_match *m; - HMAP_FOR_EACH (m, hmap_node, matches) { - 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(dp)) { - unsigned int reg_index - = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0; -@@ -744,7 +740,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - struct ofpact_conjunction *dst; - - dst = ofpact_put_CONJUNCTION(&conj); -- dst->id = src->id + conj_id_ofs; -+ dst->id = src->id; - dst->clause = src->clause; - dst->n_clauses = src->n_clauses; - } -@@ -915,9 +911,9 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - return true; - } - -- 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); -+ expr_matches_prepare(&matches, *l_ctx_out->conj_id_ofs); -+ add_matches_to_flow_table(lflow, dp, &matches, ptable, output_ptable, -+ &ovnacts, ingress, l_ctx_in, l_ctx_out); - - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); -@@ -930,10 +926,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - lflow_cache_get(l_ctx_out->lflow_cache_map, lflow); - - if (lc && lc->type == LCACHE_T_MATCHES) { -- /* 'matches' is cached. No need to do expr parsing. -+ /* 'matches' is cached. No need to do expr parsing and no need -+ * to call expr_matches_prepare() to update the conj ids. - * Add matches to flow table and return. */ -- add_matches_to_flow_table(lflow, dp, lc->expr_matches, lc->conj_id_ofs, -- ptable, output_ptable, &ovnacts, ingress, -+ add_matches_to_flow_table(lflow, dp, lc->expr_matches, ptable, -+ output_ptable, &ovnacts, ingress, - l_ctx_in, l_ctx_out); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); -@@ -1009,10 +1006,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - } - } - -+ expr_matches_prepare(matches, lc->conj_id_ofs); -+ - /* Encode OVN logical actions into OpenFlow. */ -- add_matches_to_flow_table(lflow, dp, matches, lc->conj_id_ofs, -- ptable, output_ptable, &ovnacts, ingress, -- l_ctx_in, l_ctx_out); -+ add_matches_to_flow_table(lflow, dp, matches, ptable, output_ptable, -+ &ovnacts, ingress, l_ctx_in, l_ctx_out); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - -diff --git a/include/ovn/expr.h b/include/ovn/expr.h -index 0a83ec7a8..c2c821818 100644 ---- a/include/ovn/expr.h -+++ b/include/ovn/expr.h -@@ -477,6 +477,7 @@ uint32_t expr_to_matches(const struct expr *, - const void *aux, - struct hmap *matches); - void expr_matches_destroy(struct hmap *matches); -+void expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs); - void expr_matches_print(const struct hmap *matches, FILE *); - - /* Action parsing helper. */ -diff --git a/lib/expr.c b/lib/expr.c -index 4566d9110..796e88ac7 100644 ---- a/lib/expr.c -+++ b/lib/expr.c -@@ -3125,6 +3125,25 @@ expr_to_matches(const struct expr *expr, - return n_conjs; - } - -+/* Prepares the expr matches in the hmap 'matches' by updating the -+ * conj id offsets specified in 'conj_id_ofs'. -+ */ -+void -+expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs) -+{ -+ struct expr_match *m; -+ HMAP_FOR_EACH (m, hmap_node, matches) { -+ if (m->match.wc.masks.conj_id) { -+ m->match.flow.conj_id += conj_id_ofs; -+ } -+ -+ for (size_t i = 0; i < m->n; i++) { -+ struct cls_conjunction *src = &m->conjunctions[i]; -+ src->id += conj_id_ofs; -+ } -+ } -+} -+ - /* Destroys all of the 'struct expr_match'es in 'matches', as well as the - * 'matches' hmap itself. */ - void -diff --git a/tests/ovn.at b/tests/ovn.at -index e2d2d8a9d..b890592ae 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -13824,6 +13824,34 @@ reset_pcap_file hv1-vif2 hv1/vif2 - rm -f 2.packets - > 2.expected - -+# Trigger recompute and make sure that the traffic still works as expected. -+as hv1 ovn-appctl -t ovn-controller recompute -+ -+# 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 --- -2.29.2 - diff --git a/SOURCES/0001-ovn-ctl-Add-support-for-ovsdb-server-disable-file-co.patch b/SOURCES/0001-ovn-ctl-Add-support-for-ovsdb-server-disable-file-co.patch deleted file mode 100644 index 1b1f9c0..0000000 --- a/SOURCES/0001-ovn-ctl-Add-support-for-ovsdb-server-disable-file-co.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 15eefe928ea2a51c7ad03356821f0665ca6abb6d Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Thu, 21 Jan 2021 20:23:27 +0100 -Subject: [PATCH] ovn-ctl: Add support for ovsdb-server - --disable-file-column-diff. - -There is a change of a database file format in OVS version 2.15 that -doesn't allow older versions of ovsdb-server to read the database file -modified by the ovsdb-server version 2.15 or later. This also affects -runtime communications between servers in active-backup and cluster -service models. - -For the upgrade scenario OVS introduced special command line argument -for ovsdb-server: --disable-file-column-diff. -More datails in ovsdb(7) or here: - https://docs.openvswitch.org/en/latest/ref/ovsdb.7/#upgrading-from-version-2-14-and-earlier-to-2-15-and-later - -In order to support upgrades of OVN databases introducing new option -'--ovsdb-disable-file-column-diff' for ovn-ctl script that will pass -aforementioned argument to ovsdb-server processes. - -To simplify upgrades for users, ovn-ctl will add requested argument -to ovsdb-server only if ovsdb-server actually supports it. - -Signed-off-by: Ilya Maximets -Acked-by: Han Zhou -Signed-off-by: Numan Siddique - -(cherry-picked from upstream master commit 668b0d02aeff42d361bad36c2b247195c8d2c6f0) - -Change-Id: Id5a70bd76f24f13ab0357f8e3c40159f77bc3141 ---- - utilities/ovn-ctl | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl -index c44201ccf..211c764a6 100755 ---- a/utilities/ovn-ctl -+++ b/utilities/ovn-ctl -@@ -251,6 +251,11 @@ $cluster_remote_port - - [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER" - -+ if test X"$OVSDB_DISABLE_FILE_COLUMN_DIFF" = Xyes; then -+ (ovsdb-server --help | grep -q disable-file-column-diff) \ -+ && set "$@" --disable-file-column-diff -+ fi -+ - if test X"$detach" != Xno; then - set "$@" --detach --monitor - else -@@ -715,6 +720,8 @@ set_defaults () { - OVSDB_NB_WRAPPER= - OVSDB_SB_WRAPPER= - -+ OVSDB_DISABLE_FILE_COLUMN_DIFF=no -+ - OVN_USER= - - OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info" -@@ -932,6 +939,11 @@ Options: - --ovs-user="user[:group]" pass the --user flag to ovs daemons - --ovsdb-nb-wrapper=WRAPPER run with a wrapper like valgrind for debugging - --ovsdb-sb-wrapper=WRAPPER run with a wrapper like valgrind for debugging -+ --ovsdb-disable-file-column-diff=no|yes -+ Specifies whether or not ovsdb-server -+ processes should be started with -+ --disable-file-column-diff. -+ More details in ovsdb(7). (default: no) - -h, --help display this help message - - File location options: --- -2.29.2 - diff --git a/SOURCES/0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch b/SOURCES/0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch deleted file mode 100644 index 7e6b0fb..0000000 --- a/SOURCES/0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 3b3a88ed94814b44ba958785ea93b4e2e7bbb4ac Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Fri, 23 Oct 2020 10:07:21 +0200 -Subject: [PATCH 1/2] ovn-detrace: Only decode br-int OVS interfaces. - -Do not assume 'ofport' is unique for all OVS interfaces in the system. This -is true only for interfaces within the same OVS bridge. Also, only decode -br-int related interfaces. - -Also, fix printing of potential duplicate UUIDs decoded from cookies. - -Reported-by: Michael Cambria -Reported-at: https://bugzilla.redhat.com/1890803 -Fixes: 8051499a6c1b ("ovn-detrace: Add support for other types of SB cookies.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit fcdac56feb20203e1bcc8e960624a8bdd9162cc7) - -Change-Id: I9e7fa37fa9f0276f260345f9596b1bca9717045e ---- - utilities/ovn-detrace.in | 35 ++++++++++++++++++++++++++++++----- - 1 file changed, 30 insertions(+), 5 deletions(-) - -diff --git a/utilities/ovn-detrace.in b/utilities/ovn-detrace.in -index 4f8dd5f..2344f52 100755 ---- a/utilities/ovn-detrace.in -+++ b/utilities/ovn-detrace.in -@@ -117,18 +117,27 @@ class OVSDB(object): - 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 [] - -@@ -320,10 +329,25 @@ 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 +355,8 @@ class OvsInterfaceHandler(CookieHandler): - - 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) --- -1.8.3.1 - diff --git a/SOURCES/0001-ovn-nbctl-add-bfd-option-to-lr-route-add.patch b/SOURCES/0001-ovn-nbctl-add-bfd-option-to-lr-route-add.patch deleted file mode 100644 index 2502d9f..0000000 --- a/SOURCES/0001-ovn-nbctl-add-bfd-option-to-lr-route-add.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 97b58dde0f92fc83165a6db816456073f5ddf727 Mon Sep 17 00:00:00 2001 -Message-Id: <97b58dde0f92fc83165a6db816456073f5ddf727.1612349784.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Fri, 29 Jan 2021 23:45:19 +0100 -Subject: [PATCH] ovn-nbctl: add --bfd option to lr-route-add - -Introduce the --bfd option to lr-route-add command. -If the BFD session UUID is provided, it will be used for the OVN route -otherwise the next-hop will be used to perform a lookup in the OVN BFD -table. -If the lookup fails and outport is specified, a new entry in the BFD table -will be created using the nexthop as dst_ip and outport as logical_port. - -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - tests/ovn-northd.at | 17 ++++++++++---- - tests/system-ovn.at | 5 ++-- - utilities/ovn-nbctl.8.xml | 11 +++++++++ - utilities/ovn-nbctl.c | 49 ++++++++++++++++++++++++++++++++++++++- - 4 files changed, 73 insertions(+), 9 deletions(-) - ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2342,7 +2342,7 @@ AT_KEYWORDS([northd-bfd]) - ovn_start - - check ovn-nbctl --wait=sb lr-add r0 --for i in $(seq 1 4); do -+for i in $(seq 1 5); do - check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i 00:00:00:00:00:0$i 192.168.$i.1/24 - check ovn-nbctl --wait=sb ls-add sw$i - check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0 -@@ -2387,17 +2387,24 @@ check_column 1000 bfd min_tx logical_por - check_column 1000 bfd min_rx logical_port=r0-sw1 - check_column 100 bfd detect_mult logical_port=r0-sw1 - --check ovn-nbctl lr-route-add r0 100.0.0.0/8 192.168.10.2 --route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") --check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.10.2 - check_column down bfd status logical_port=r0-sw1 - AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0]) - -+check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.20.2 -+check_column down bfd status logical_port=r0-sw2 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.20.2 | grep -q bfd],[0]) -+ -+check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw5 -+check_column down bfd status logical_port=r0-sw5 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.50.2 | grep -q bfd],[0]) -+ -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") - check ovn-nbctl clear logical_router_static_route $route_uuid bfd - check_column admin_down bfd status logical_port=r0-sw1 - - ovn-nbctl destroy bfd $uuid --check_row_count bfd 2 -+check_row_count bfd 3 - - AT_CLEANUP - ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -5606,10 +5606,9 @@ NS_CHECK_EXEC([server], [bfdd-control al - Allowing connections from 172.16.1.1 - ]) - --uuid=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=172.16.1.50 min_tx=250 min_rx=250 detect_mult=10) --check ovn-nbctl lr-route-add R1 100.0.0.0/8 172.16.1.50 -+check ovn-nbctl --bfd lr-route-add R1 100.0.0.0/8 172.16.1.50 rp-public -+uuid=$(fetch_column nb:bfd _uuid logical_port="rp-public") - route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") --check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid - check ovn-nbctl --wait=hv sync - - wait_column "up" nb:bfd status logical_port=rp-public ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -659,6 +659,7 @@ -
-
[--may-exist] [--policy=POLICY] - [--ecmp] [--ecmp-symmetric-reply] -+ [--bfd[=UUID]] - lr-route-add router - prefix nexthop [port]
-
-@@ -696,6 +697,16 @@ -

- -

-+ --bfd option is used to link a BFD session to the -+ OVN route. If the BFD session UUID is provided, it will be used -+ for the OVN route otherwise the next-hop will be used to perform -+ a lookup in the OVN BFD table. -+ If the lookup fails and port is specified, a new entry -+ in the BFD table will be created using the nexthop as -+ dst_ip and port as logical_port. -+

-+ -+

- It is an error if a route with prefix and - POLICY already exists, unless --may-exist, - --ecmp, or --ecmp-symmetric-reply is ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3957,6 +3957,29 @@ nbctl_lr_route_add(struct ctl_context *c - goto cleanup; - } - -+ struct shash_node *bfd = shash_find(&ctx->options, "--bfd"); -+ const struct nbrec_bfd *nb_bt = NULL; -+ if (bfd) { -+ if (bfd->data) { -+ struct uuid bfd_uuid; -+ if (uuid_from_string(&bfd_uuid, bfd->data)) { -+ nb_bt = nbrec_bfd_get_for_uuid(ctx->idl, &bfd_uuid); -+ } -+ if (!nb_bt) { -+ ctl_error(ctx, "no entry found in the BFD table"); -+ goto cleanup; -+ } -+ } else { -+ const struct nbrec_bfd *iter; -+ NBREC_BFD_FOR_EACH (iter, ctx->idl) { -+ if (!strcmp(iter->dst_ip, next_hop)) { -+ nb_bt = iter; -+ break; -+ } -+ } -+ } -+ } -+ - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool ecmp_symmetric_reply = shash_find(&ctx->options, - "--ecmp-symmetric-reply") != NULL; -@@ -4011,6 +4034,18 @@ nbctl_lr_route_add(struct ctl_context *c - if (policy) { - nbrec_logical_router_static_route_set_policy(route, policy); - } -+ if (bfd) { -+ if (!nb_bt) { -+ if (ctx->argc != 5) { -+ ctl_error(ctx, "insert entry in the BFD table failed"); -+ goto cleanup; -+ } -+ nb_bt = nbrec_bfd_insert(ctx->txn); -+ nbrec_bfd_set_dst_ip(nb_bt, next_hop); -+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ } -+ nbrec_logical_router_static_route_set_bfd(route, nb_bt); -+ } - free(rt_prefix); - goto cleanup; - } -@@ -4035,6 +4070,18 @@ nbctl_lr_route_add(struct ctl_context *c - } - - nbrec_logical_router_update_static_routes_addvalue(lr, route); -+ if (bfd) { -+ if (!nb_bt) { -+ if (ctx->argc != 5) { -+ ctl_error(ctx, "insert entry in the BFD table failed"); -+ goto cleanup; -+ } -+ nb_bt = nbrec_bfd_insert(ctx->txn); -+ nbrec_bfd_set_dst_ip(nb_bt, next_hop); -+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ } -+ nbrec_logical_router_static_route_set_bfd(route, nb_bt); -+ } - - cleanup: - free(next_hop); -@@ -6548,7 +6595,7 @@ static const struct ctl_command_syntax n - /* logical router route commands. */ - { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL, - nbctl_lr_route_add, NULL, "--may-exist,--ecmp,--ecmp-symmetric-reply," -- "--policy=", RW }, -+ "--policy=,--bfd?", RW }, - { "lr-route-del", 1, 4, "ROUTER [PREFIX [NEXTHOP [PORT]]]", NULL, - nbctl_lr_route_del, NULL, "--if-exists,--policy=", RW }, - { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL, diff --git a/SOURCES/0001-ovn-nbctl-add-bfd-report-to-lr-route-list-command.patch b/SOURCES/0001-ovn-nbctl-add-bfd-report-to-lr-route-list-command.patch deleted file mode 100644 index c46782d..0000000 --- a/SOURCES/0001-ovn-nbctl-add-bfd-report-to-lr-route-list-command.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 8770192b3b4732e02679f723ea5903a515c6bd8a Mon Sep 17 00:00:00 2001 -Message-Id: <8770192b3b4732e02679f723ea5903a515c6bd8a.1611833004.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Fri, 15 Jan 2021 00:00:24 +0100 -Subject: [PATCH 1/2] ovn-nbctl: add bfd report to lr-route-list command - -Introduce bfd info to lr-route-list command - -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - tests/ovn-northd.at | 1 + - utilities/ovn-nbctl.c | 5 +++++ - 2 files changed, 6 insertions(+) - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index d22cad863..8597ca1b9 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2391,6 +2391,7 @@ check ovn-nbctl lr-route-add r0 100.0.0.0/8 192.168.10.2 - route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") - check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid - check_column down bfd status logical_port=r0-sw1 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0]) - - check ovn-nbctl clear logical_router_static_route $route_uuid bfd - check_column admin_down bfd status logical_port=r0-sw1 -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 94e7eedeb..788b1972e 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -5502,6 +5502,11 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) - if (smap_get(&route->external_ids, "ic-learned-route")) { - ds_put_format(s, " (learned)"); - } -+ -+ if (route->bfd) { -+ ds_put_cstr(s, " bfd"); -+ } -+ - ds_put_char(s, '\n'); - } - --- -2.29.2 - diff --git a/SOURCES/0001-ovn-nbctl-add-may-exist-if-exists-options-for-policy.patch b/SOURCES/0001-ovn-nbctl-add-may-exist-if-exists-options-for-policy.patch deleted file mode 100644 index 76d59a0..0000000 --- a/SOURCES/0001-ovn-nbctl-add-may-exist-if-exists-options-for-policy.patch +++ /dev/null @@ -1,157 +0,0 @@ -From d811e1027f74de0f1eee1af9af8dd3338eadb61d Mon Sep 17 00:00:00 2001 -From: Lorenzo Bianconi -Date: Fri, 25 Sep 2020 13:21:58 +0200 -Subject: [PATCH] ovn-nbctl: add --may-exist/--if-exists options for policy - routing - -Introduce the following options to avoid error reporting for policy -routing: -1) --may-exist: the lr-policy-add does not result in an error if a policy - with the same priority and match string is already present -2) --if-exists: the lr-policy-del does not result in an error if a policy - with the specified uuid is not present in the db - -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Han Zhou ---- - tests/ovn-nbctl.at | 7 ++++++- - utilities/ovn-nbctl.8.xml | 20 +++++++++++++++----- - utilities/ovn-nbctl.c | 16 ++++++++++------ - 3 files changed, 31 insertions(+), 12 deletions(-) - -diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index baf7a87f5..3dbedc843 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1651,6 +1651,8 @@ AT_CHECK([ovn-nbctl lr-policy-add lr0 100 "ip4.src == 1.1.1.0/24" drop], [1], [] - [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 +1677,13 @@ Routing Policies - - - 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]) - -diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml -index fcc4312dd..59302296b 100644 ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -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 --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index c54e63937..caf99dfeb 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3648,12 +3648,15 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - - /* Check if same routing policy already exists. - * A policy is uniquely identified by priority and match */ -+ bool may_exist = !!shash_find(&ctx->options, "--may-exist"); - 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; - } - } -@@ -3733,7 +3736,6 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - ctx->error = error; - return; - } -- - } - /* If uuid was specified, delete routing policy with the - * specified uuid. */ -@@ -3751,7 +3753,9 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - } - } - 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."); -+ } - return; - } - -@@ -6529,9 +6533,9 @@ static const struct ctl_command_syntax nbctl_commands[] = { - /* 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 }, - --- -2.26.2 - diff --git a/SOURCES/0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch b/SOURCES/0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch deleted file mode 100644 index 9641180..0000000 --- a/SOURCES/0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch +++ /dev/null @@ -1,93 +0,0 @@ -From a6e21d42de7c195fa50d46a068462301959bda26 Mon Sep 17 00:00:00 2001 -From: Ben Pfaff -Date: Thu, 2 Apr 2020 10:26:15 -0700 -Subject: [PATCH 01/10] ovn-nbctl, ovn-sbctl: Add convenient names for more - tables. - -Signed-off-by: Ben Pfaff -Acked-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 21 +++++++++++++++++++++ - utilities/ovn-sbctl.c | 27 +++++++++++++++++++++++++++ - 2 files changed, 48 insertions(+) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index caf99dfeb..dfcf67cfd 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -6107,8 +6107,29 @@ static const struct ctl_table_class tables[NBREC_N_TABLES] = { - - [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 * -diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c -index d3eec9133..f93384940 100644 ---- a/utilities/ovn-sbctl.c -+++ b/utilities/ovn-sbctl.c -@@ -1410,11 +1410,38 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = { - [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}, - }; - - --- -2.28.0 - diff --git a/SOURCES/0001-ovn-northd-Add-localnet-ports-to-Multicast_Groups-cr.patch b/SOURCES/0001-ovn-northd-Add-localnet-ports-to-Multicast_Groups-cr.patch deleted file mode 100644 index 28fe93c..0000000 --- a/SOURCES/0001-ovn-northd-Add-localnet-ports-to-Multicast_Groups-cr.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 89502a3b33b1bf407debe5edc6578b2a72afdd5b Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Thu, 8 Oct 2020 15:48:07 +0200 -Subject: [PATCH] ovn-northd: Add localnet ports to Multicast_Groups created by - IGMP_Group. - -In case a logical switch is a "bridged logical switch", i.e., it has a -localnet port, add the localnet port to all multicast groups generated -from IGMP_Groups. This is needed because multicast packets are never -tunneled between hypervisors. - -Note: There still exists an issue regarding IP Multicast Relay (routing) -when traffic is received on bridged logical switches connected to -logical routers with mcast_relay=true. That issue is not addressed -yet but an item is added to TODO.rst to track it. - -Reported-at: https://bugzilla.redhat.com/1886103 -Fixes: 5d1527b11e94 ("ovn-northd: Add IGMP Relay support") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 97778ab3e422ac071faa67f9f477fd54977e9c04) - -(cherry picked from upstream commit a2563f623f2c88f78134928a4f40bdd06cc89b02) - -Change-Id: I3f251a19317bfb8a3334357604605ccbb054a0f9 ---- - TODO.rst | 8 ++++++++ - northd/ovn-northd.c | 7 +++++++ - tests/ovn.at | 40 ++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 55 insertions(+) - -diff --git a/TODO.rst b/TODO.rst -index 4321630..c158155 100644 ---- a/TODO.rst -+++ b/TODO.rst -@@ -152,3 +152,11 @@ OVN To-do List - . 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 --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 3a00c8a..c7ca0f8 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -4095,6 +4095,13 @@ ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group, - 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 -diff --git a/tests/ovn.at b/tests/ovn.at -index 7769b69..4c6adf2 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -16650,6 +16650,7 @@ ovn_start - # - 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 -@@ -16776,6 +16777,9 @@ 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 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 -@@ -16820,6 +16824,7 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \ - 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 -@@ -16845,6 +16850,7 @@ ovs-vsctl -- add-port br-int hv2-vif4 -- \ - 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 - -@@ -17128,6 +17134,18 @@ store_ip_multicast_pkt \ - $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ - e518e518000a3b3a0000 expected_switched - -+# 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 -+ - 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]) -@@ -17275,6 +17293,7 @@ ovn_start - # - 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 -@@ -17400,6 +17419,9 @@ 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 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::fe/64 -@@ -17459,6 +17481,7 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \ - 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 -@@ -17484,6 +17507,7 @@ ovs-vsctl -- add-port br-int hv2-vif4 -- \ - 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 - -@@ -17794,6 +17818,22 @@ store_ip_multicast_pkt \ - 93407a69000e1b5e61736461640a \ - expected_switched - -+# 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 -+ - 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]) --- -1.8.3.1 - diff --git a/SOURCES/0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch b/SOURCES/0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch deleted file mode 100644 index f2d3d26..0000000 --- a/SOURCES/0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch +++ /dev/null @@ -1,212 +0,0 @@ -From df2c57d43d758f26204c3048b278fa546a4858f8 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Tue, 20 Oct 2020 16:54:32 +0200 -Subject: [PATCH] ovn-northd: Handle IPv6 addresses with prefixes for port - security. - -Reported-by: Rodolfo Alonso -Reported-at: https://bugzilla.redhat.com/1856898 -CC: Numan Siddique -Fixes: f631376bf75d ("ovn-northd: Handle IPv4 addresses with prefixes in lport port security") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry picked from upstream commit 45ed5a3cd500bf94706507d3d10f63ea133cbc29) - -Change-Id: Icaa1b299a0a2a7006532e37575f20d5a1f25787c ---- - lib/ovn-l7.h | 11 +++++++++++ - northd/ovn-northd.c | 35 ++++++++++++++++++++++++++++------- - tests/ovn.at | 40 +++++++++++++++++++++++++++++++--------- - 3 files changed, 70 insertions(+), 16 deletions(-) - -diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h -index 30a7955..9b729db 100644 ---- a/lib/ovn-l7.h -+++ b/lib/ovn-l7.h -@@ -428,6 +428,17 @@ ipv6_addr_is_routable_multicast(const struct in6_addr *ip) { - } - } - -+static inline bool -+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); -+ -+ tmp = ipv6_addr_bitand(&tmp, prefix); -+ return ipv6_is_zero(&tmp); -+} -+ - #define IPV6_EXT_HEADER_LEN 8 - struct ipv6_ext_header { - uint8_t ip6_nxt_proto; -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 3a71d0e..7cd2f9f 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -4285,10 +4285,20 @@ build_port_security_ipv6_nd_flow( - 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, ")))"); -@@ -4314,9 +4324,20 @@ build_port_security_ipv6_flow( - 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, ' '); -diff --git a/tests/ovn.at b/tests/ovn.at -index 337ab4e..2c6f7cc 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -4133,10 +4133,10 @@ for i in 1 2 3; do - 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 -@@ -4352,7 +4352,7 @@ for i in 1 2 3; do - 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` -@@ -4366,20 +4366,24 @@ sip=ee800000000000000000000000000000 - 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 - -@@ -4387,6 +4391,8 @@ tip=ee800000000000000000000000000000 - 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 -@@ -4404,9 +4410,7 @@ for i in 1 2 3; do - 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` -@@ -4419,12 +4423,24 @@ tip=`ip_to_hex 192 168 0 23` - # 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 -@@ -4437,6 +4453,12 @@ tip=`ip_to_hex 20 0 0 5` - # 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 --- -1.8.3.1 - diff --git a/SOURCES/0001-ovn-northd-Move-lswitch-ARP-ND-Responder-to-function.patch b/SOURCES/0001-ovn-northd-Move-lswitch-ARP-ND-Responder-to-function.patch deleted file mode 100644 index 667ff72..0000000 --- a/SOURCES/0001-ovn-northd-Move-lswitch-ARP-ND-Responder-to-function.patch +++ /dev/null @@ -1,575 +0,0 @@ -From f21c1b7a467a691847b5552d4570af706fcc5bb0 Mon Sep 17 00:00:00 2001 -Message-Id: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:28 +0000 -Subject: [PATCH 01/16] ovn-northd: Move lswitch ARP/ND Responder to functions. - -Move arp/nd responder lflow processing to per-iterable functions. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 496 +++++++++++++++++++++++--------------------- - 1 file changed, 260 insertions(+), 236 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index b377dffa1..d17cc55ac 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6770,7 +6770,7 @@ is_vlan_transparent(const struct ovn_datapath *od) - static void - build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct hmap *lflows, struct hmap *mcgroups, -- struct hmap *igmp_groups, struct hmap *lbs) -+ struct hmap *igmp_groups) - { - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ -@@ -6778,240 +6778,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - struct ovn_datapath *od; -- -- /* 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; -- } -- -- if ((!strcmp(op->nbsp->type, "localnet")) || -- (!strcmp(op->nbsp->type, "vtep"))) { -- ds_clear(&match); -- ds_put_format(&match, "inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, -- 100, ds_cstr(&match), "next;", -- &op->nbsp->header_); -- } -- } -- -- /* Ingress table 13: ARP/ND responder, reply for known IPs. -- * (priority 50). */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp) { -- continue; -- } -- -- if (!strcmp(op->nbsp->type, "virtual")) { -- /* Handle -- * - GARPs for virtual ip which belongs to a logical port -- * of type 'virtual' and bind that port. -- * -- * - ARP reply from the virtual ip which belongs to a logical -- * port of type 'virtual' and bind that port. -- * */ -- ovs_be32 ip; -- const char *virtual_ip = smap_get(&op->nbsp->options, -- "virtual-ip"); -- const char *virtual_parents = smap_get(&op->nbsp->options, -- "virtual-parents"); -- if (!virtual_ip || !virtual_parents || -- !ip_parse(virtual_ip, &ip)) { -- continue; -- } -- -- char *tokstr = xstrdup(virtual_parents); -- char *save_ptr = NULL; -- char *vparent; -- for (vparent = strtok_r(tokstr, ",", &save_ptr); vparent != NULL; -- vparent = strtok_r(NULL, ",", &save_ptr)) { -- struct ovn_port *vp = ovn_port_find(ports, vparent); -- if (!vp || vp->od != op->od) { -- /* vparent name should be valid and it should belong -- * to the same logical switch. */ -- continue; -- } -- -- ds_clear(&match); -- ds_put_format(&match, "inport == \"%s\" && " -- "((arp.op == 1 && arp.spa == %s && " -- "arp.tpa == %s) || (arp.op == 2 && " -- "arp.spa == %s))", -- vparent, virtual_ip, virtual_ip, -- virtual_ip); -- ds_clear(&actions); -- ds_put_format(&actions, -- "bind_vport(%s, inport); " -- "next;", -- op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &vp->nbsp->header_); -- } -- -- free(tokstr); -- } else { -- /* -- * Add ARP/ND reply flows if either the -- * - port is up and it doesn't have 'unknown' address defined or -- * - port type is router or -- * - port type is localport -- */ -- if (check_lsp_is_up && -- !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) && -- strcmp(op->nbsp->type, "localport")) { -- continue; -- } -- -- if (lsp_is_external(op->nbsp) || op->has_unknown) { -- continue; -- } -- -- for (size_t i = 0; i < op->n_lsp_addrs; i++) { -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -- ds_clear(&match); -- ds_put_format(&match, "arp.tpa == %s && arp.op == 1", -- op->lsp_addrs[i].ipv4_addrs[j].addr_s); -- ds_clear(&actions); -- 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;", -- op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s, -- op->lsp_addrs[i].ipv4_addrs[j].addr_s); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_ARP_ND_RSP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -- &op->nbsp->header_); -- -- /* Do not reply to an ARP request from the port that owns -- * the address (otherwise a DHCP client that ARPs to check -- * for a duplicate address will fail). Instead, forward -- * it the usual way. -- * -- * (Another alternative would be to simply drop the packet. -- * If everything is working as it is configured, then this -- * would produce equivalent results, since no one should -- * reply to the request. But ARPing for one's own IP -- * address is intended to detect situations where the -- * network is not working as configured, so dropping the -- * request would frustrate that intent.) */ -- ds_put_format(&match, " && inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), "next;", -- &op->nbsp->header_); -- } -- -- /* For ND solicitations, we need to listen for both the -- * unicast IPv6 address and its all-nodes multicast address, -- * but always respond with the unicast IPv6 address. */ -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -- ds_clear(&match); -- ds_put_format(&match, -- "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", -- op->lsp_addrs[i].ipv6_addrs[j].addr_s, -- op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, -- op->lsp_addrs[i].ipv6_addrs[j].addr_s); -- -- ds_clear(&actions); -- ds_put_format(&actions, -- "%s { " -- "eth.src = %s; " -- "ip6.src = %s; " -- "nd.target = %s; " -- "nd.tll = %s; " -- "outport = inport; " -- "flags.loopback = 1; " -- "output; " -- "};", -- 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, -- op->lsp_addrs[i].ea_s); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_ARP_ND_RSP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -- &op->nbsp->header_); -- -- /* Do not reply to a solicitation from the port that owns -- * the address (otherwise DAD detection will fail). */ -- ds_put_format(&match, " && inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), "next;", -- &op->nbsp->header_); -- } -- } -- } -- } -- -- /* Ingress table 13: ARP/ND responder, by default goto next. -- * (priority 0)*/ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); -- } -- -- /* Ingress table 13: ARP/ND responder for service monitor source ip. -- * (priority 110)*/ -- struct ovn_northd_lb *lb; -- HMAP_FOR_EACH (lb, hmap_node, lbs) { -- for (size_t i = 0; i < lb->n_vips; i++) { -- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; -- if (!lb_vip_nb->lb_health_check) { -- continue; -- } -- -- for (size_t j = 0; j < lb_vip_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", -- backend_nb->svc_mon_src_ip); -- ds_clear(&actions); -- 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;", -- svc_monitor_mac, svc_monitor_mac, -- backend_nb->svc_mon_src_ip); -- ovn_lflow_add_with_hint(lflows, -- backend_nb->op->od, -- S_SWITCH_IN_ARP_ND_RSP, 110, -- ds_cstr(&match), ds_cstr(&actions), -- &lb->nlb->header_); -- } -- } -- } - - - /* Logical switch ingress table 14 and 15: DHCP options and response -@@ -7471,6 +7238,251 @@ build_lswitch_lflows_admission_control(struct ovn_datapath *od, - } - } - -+/* Ingress table 13: ARP/ND responder, skip requests coming from localnet -+ * and vtep ports. (priority 100); see ovn-northd.8.xml for the -+ * rationale. */ -+ -+static void -+build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match) -+{ -+ if (op->nbsp) { -+ if ((!strcmp(op->nbsp->type, "localnet")) || -+ (!strcmp(op->nbsp->type, "vtep"))) { -+ ds_clear(match); -+ ds_put_format(match, "inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, -+ 100, ds_cstr(match), "next;", -+ &op->nbsp->header_); -+ } -+ } -+} -+ -+/* Ingress table 13: ARP/ND responder, reply for known IPs. -+ * (priority 50). */ -+static void -+build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op, -+ struct hmap *lflows, -+ struct hmap *ports, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (op->nbsp) { -+ if (!strcmp(op->nbsp->type, "virtual")) { -+ /* Handle -+ * - GARPs for virtual ip which belongs to a logical port -+ * of type 'virtual' and bind that port. -+ * -+ * - ARP reply from the virtual ip which belongs to a logical -+ * port of type 'virtual' and bind that port. -+ * */ -+ ovs_be32 ip; -+ const char *virtual_ip = smap_get(&op->nbsp->options, -+ "virtual-ip"); -+ const char *virtual_parents = smap_get(&op->nbsp->options, -+ "virtual-parents"); -+ if (!virtual_ip || !virtual_parents || -+ !ip_parse(virtual_ip, &ip)) { -+ return; -+ } -+ -+ char *tokstr = xstrdup(virtual_parents); -+ char *save_ptr = NULL; -+ char *vparent; -+ for (vparent = strtok_r(tokstr, ",", &save_ptr); vparent != NULL; -+ vparent = strtok_r(NULL, ",", &save_ptr)) { -+ struct ovn_port *vp = ovn_port_find(ports, vparent); -+ if (!vp || vp->od != op->od) { -+ /* vparent name should be valid and it should belong -+ * to the same logical switch. */ -+ continue; -+ } -+ -+ ds_clear(match); -+ ds_put_format(match, "inport == \"%s\" && " -+ "((arp.op == 1 && arp.spa == %s && " -+ "arp.tpa == %s) || (arp.op == 2 && " -+ "arp.spa == %s))", -+ vparent, virtual_ip, virtual_ip, -+ virtual_ip); -+ ds_clear(actions); -+ ds_put_format(actions, -+ "bind_vport(%s, inport); " -+ "next;", -+ op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &vp->nbsp->header_); -+ } -+ -+ free(tokstr); -+ } else { -+ /* -+ * Add ARP/ND reply flows if either the -+ * - port is up and it doesn't have 'unknown' address defined or -+ * - port type is router or -+ * - port type is localport -+ */ -+ if (check_lsp_is_up && -+ !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) && -+ strcmp(op->nbsp->type, "localport")) { -+ return; -+ } -+ -+ if (lsp_is_external(op->nbsp) || op->has_unknown) { -+ return; -+ } -+ -+ for (size_t i = 0; i < op->n_lsp_addrs; i++) { -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -+ ds_clear(match); -+ ds_put_format(match, "arp.tpa == %s && arp.op == 1", -+ op->lsp_addrs[i].ipv4_addrs[j].addr_s); -+ ds_clear(actions); -+ 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;", -+ op->lsp_addrs[i].ea_s, op->lsp_addrs[i].ea_s, -+ op->lsp_addrs[i].ipv4_addrs[j].addr_s); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 50, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ -+ /* Do not reply to an ARP request from the port that owns -+ * the address (otherwise a DHCP client that ARPs to check -+ * for a duplicate address will fail). Instead, forward -+ * it the usual way. -+ * -+ * (Another alternative would be to simply drop the packet. -+ * If everything is working as it is configured, then this -+ * would produce equivalent results, since no one should -+ * reply to the request. But ARPing for one's own IP -+ * address is intended to detect situations where the -+ * network is not working as configured, so dropping the -+ * request would frustrate that intent.) */ -+ ds_put_format(match, " && inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 100, -+ ds_cstr(match), "next;", -+ &op->nbsp->header_); -+ } -+ -+ /* For ND solicitations, we need to listen for both the -+ * unicast IPv6 address and its all-nodes multicast address, -+ * but always respond with the unicast IPv6 address. */ -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -+ ds_clear(match); -+ ds_put_format(match, -+ "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", -+ op->lsp_addrs[i].ipv6_addrs[j].addr_s, -+ op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, -+ op->lsp_addrs[i].ipv6_addrs[j].addr_s); -+ -+ ds_clear(actions); -+ ds_put_format(actions, -+ "%s { " -+ "eth.src = %s; " -+ "ip6.src = %s; " -+ "nd.target = %s; " -+ "nd.tll = %s; " -+ "outport = inport; " -+ "flags.loopback = 1; " -+ "output; " -+ "};", -+ 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, -+ op->lsp_addrs[i].ea_s); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 50, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ -+ /* Do not reply to a solicitation from the port that owns -+ * the address (otherwise DAD detection will fail). */ -+ ds_put_format(match, " && inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 100, -+ ds_cstr(match), "next;", -+ &op->nbsp->header_); -+ } -+ } -+ } -+ } -+} -+ -+/* Ingress table 13: ARP/ND responder, by default goto next. -+ * (priority 0)*/ -+static void -+build_lswitch_arp_nd_responder_default(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); -+ } -+} -+ -+/* Ingress table 13: ARP/ND responder for service monitor source ip. -+ * (priority 110)*/ -+static void -+build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb, -+ struct hmap *lflows, -+ struct ds *actions, -+ struct ds *match) -+{ -+ for (size_t i = 0; i < lb->n_vips; i++) { -+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; -+ if (!lb_vip_nb->lb_health_check) { -+ continue; -+ } -+ -+ for (size_t j = 0; j < lb_vip_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", -+ backend_nb->svc_mon_src_ip); -+ ds_clear(actions); -+ 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;", -+ svc_monitor_mac, svc_monitor_mac, -+ backend_nb->svc_mon_src_ip); -+ ovn_lflow_add_with_hint(lflows, -+ backend_nb->op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 110, -+ ds_cstr(match), ds_cstr(actions), -+ &lb->nlb->header_); -+ } -+ } -+} -+ - - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. -@@ -11322,6 +11334,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_fwd_group_lflows(od, lsi->lflows); - build_lswitch_lflows_admission_control(od, lsi->lflows); - build_lswitch_input_port_sec_od(od, lsi->lflows); -+ build_lswitch_arp_nd_responder_default(od, lsi->lflows); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); -@@ -11352,7 +11365,12 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - /* Build Logical Switch Flows. */ - build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions, - &lsi->match); -- -+ build_lswitch_arp_nd_responder_skip_local(op, lsi->lflows, -+ &lsi->match); -+ build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows, -+ lsi->ports, -+ &lsi->actions, -+ &lsi->match); - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, - &lsi->actions); -@@ -11379,6 +11397,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - { - struct ovn_datapath *od; - struct ovn_port *op; -+ struct ovn_northd_lb *lb; - - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - -@@ -11405,6 +11424,11 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - HMAP_FOR_EACH (op, key_node, ports) { - build_lswitch_and_lrouter_iterate_by_op(op, &lsi); - } -+ HMAP_FOR_EACH (lb, hmap_node, lbs) { -+ build_lswitch_arp_nd_service_monitor(lb, lsi.lflows, -+ &lsi.actions, -+ &lsi.match); -+ } - free(svc_check_match); - - ds_destroy(&lsi.match); -@@ -11412,7 +11436,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - - /* Legacy lswitch build - to be migrated. */ - build_lswitch_flows(datapaths, ports, lflows, mcgroups, -- igmp_groups, lbs); -+ igmp_groups); - - /* Legacy lrouter build - to be migrated. */ - build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs); --- -2.29.2 - diff --git a/SOURCES/0001-ovn-trace-fix-trigger_event-warning.patch b/SOURCES/0001-ovn-trace-fix-trigger_event-warning.patch deleted file mode 100644 index 73b57f5..0000000 --- a/SOURCES/0001-ovn-trace-fix-trigger_event-warning.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 19fc17eee44cb899e62292a8f6ff4cfe98815ea6 Mon Sep 17 00:00:00 2001 -Message-Id: <19fc17eee44cb899e62292a8f6ff4cfe98815ea6.1610114400.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Tue, 5 Jan 2021 15:37:37 +0100 -Subject: [PATCH] ovn-trace: fix trigger_event warning. - -Fix the following ovn-trace warning triggered by controller_event: - -00001|ovntrace|WARN|trigger_event(event = "empty_lb_backends", meter = "", - vip = "192.168.0.100:80", protocol = "tcp", - load_balancer = "2c5462a7-b6ca-4b02-86c9-b9aa98a570e8"); -parsing actions failed (Syntax error a t `vip' expecting empty_lb_backends option name.) - -The issue can be triggered running the following reproducer in ovn-sanbox: - -$./ovn-setup.sh -$ovn-nbctl lb-add lb0 192.168.0.100:80 "" -$ovn-nbctl ls-lb-add sw0 lb0 -$ovn-nbctl --wait=hv set NB_Global . options:controller_event=true -$ovn-trace sw0 'inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src==192.168.0.2 && eth.dst == 00:00:00:00:ff:01 && ip4.dst==192.168.0.100 && tcp && tcp.dst==80' - -Acked-by: Dumitru Ceara -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - tests/ovn.at | 2 ++ - utilities/ovn-trace.c | 4 ++++ - 2 files changed, 6 insertions(+) - ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -16889,6 +16889,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl get control - "$uuid_lb2" - ]) - -+AT_CHECK_UNQUOTED([ovn-trace sw0 'inport == "sw0-p11" && eth.src == 00:00:00:00:00:11 && ip4.dst == 192.168.1.100 && tcp && tcp.dst == 80' | grep -q 'event = "empty_lb_backends"'], [0]) -+ - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP - ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -478,6 +478,7 @@ static struct shash port_groups; - static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */ - static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */ - static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */ -+static struct controller_event_options event_opts; - - static struct ovntrace_datapath * - ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) -@@ -901,6 +902,7 @@ parse_lflow_for_datapath(const struct sb - .dhcp_opts = &dhcp_opts, - .dhcpv6_opts = &dhcpv6_opts, - .nd_ra_opts = &nd_ra_opts, -+ .controller_event_opts = &event_opts, - .pipeline = (!strcmp(sblf->pipeline, "ingress") - ? OVNACT_P_INGRESS - : OVNACT_P_EGRESS), -@@ -1006,6 +1008,8 @@ read_gen_opts(void) - - hmap_init(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); -+ -+ controller_event_opts_init(&event_opts); - } - - static void diff --git a/SOURCES/0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch b/SOURCES/0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch deleted file mode 100644 index bc2f79b..0000000 --- a/SOURCES/0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch +++ /dev/null @@ -1,215 +0,0 @@ -From 4027dae96c587d68a7c15b798a7016cffbe9fd48 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Tue, 3 Nov 2020 16:51:04 +0100 -Subject: [PATCH 1/2] pinctrl: Directly update MAC_Bindings created by self - originated GARPs. - -OVN uses GARPs to announce changes to locally owned NAT addresses. This is -OK when updating upstream router caches but is unnecessary for updating OVN -logical router MAC_Bindings. - -ovn-controller already has the information required for directly -updating/inserting the MAC_Bindings that would be created by neighbor -routers. - -This also has the advantage that GARPs don't necessarily need to be flooded -in the complete L2 domain of the switch and that router patch ports can be -skipped. An upcoming commit will take advantage of this. - -Suggested-by: Lorenzo Bianconi -Fixes: 81e928526b8a ("ovn-controller: Inject GARPs to logical switch pipeline to update neighbors") -Acked-by: Mark Michelson -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit a2b88dc5136507e727e4bcdc4bf6fde559f519a9) - -Change-Id: I2209707359d268e7d80ed114e187e7ff13e7176c ---- - controller/pinctrl.c | 105 +++++++++++++++++++++++++++++++++++++++++---------- - 1 file changed, 85 insertions(+), 20 deletions(-) - -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index f15afc5..dd9fff9 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -200,8 +200,10 @@ static void init_send_garps_rarps(void); - 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, -@@ -3145,8 +3147,9 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - 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, -@@ -3837,6 +3840,64 @@ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - return retval; - } - -+/* Update or add an IP-MAC binding for 'logical_port'. */ -+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) -+{ -+ /* 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) { -+ 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) -+{ -+ 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; -+ } -+ -+ 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)); -+ 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, -@@ -3863,20 +3924,8 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn, - - 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)); - ds_destroy(&ip_s); - } - -@@ -4018,7 +4067,10 @@ add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip, - - /* 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; -@@ -4044,6 +4096,11 @@ send_garp_rarp_update(const struct sbrec_port_binding *binding_rec, - 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); - } -@@ -4079,6 +4136,10 @@ send_garp_rarp_update(const struct sbrec_port_binding *binding_rec, - 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; -@@ -5355,8 +5416,10 @@ send_garp_rarp_run(struct rconn *swconn, long long int *send_garp_rarp_time) - /* 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, -@@ -5395,7 +5458,8 @@ send_garp_rarp_prepare(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - 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); - } - } - -@@ -5405,7 +5469,8 @@ send_garp_rarp_prepare(struct ovsdb_idl_index *sbrec_port_binding_by_datapath, - 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); - } - } - --- -1.8.3.1 - diff --git a/SOURCES/0002-binding-Always-delete-child-port-bindings-first.patch b/SOURCES/0002-binding-Always-delete-child-port-bindings-first.patch deleted file mode 100644 index b46be2e..0000000 --- a/SOURCES/0002-binding-Always-delete-child-port-bindings-first.patch +++ /dev/null @@ -1,154 +0,0 @@ -From 47afa0664d6a41d0a75a65f0ba927974d957cb62 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Mon, 18 Jan 2021 17:50:33 +0100 -Subject: [PATCH 2/2] binding: Always delete child port bindings first. - -When incrementally processing changes, child Port Bindings (container -and virtual ports) must be deleted first, before their parents, because -they need to be removed from their parent's children hash to avoid -parents with children with NULL port bindings. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from master commit de8030e6abc7b51dd2ee48bbe2c76592ef8b064c) - -Change-Id: I382f463b757df1ff0f3b5b0e6ec2050d4e7811ed ---- - controller/binding.c | 75 +++++++++++++++++++++++++++++++++++++++++++++------- - tests/ovn.at | 18 +++++++++++++ - 2 files changed, 83 insertions(+), 10 deletions(-) - -diff --git a/controller/binding.c b/controller/binding.c -index 3512a1d..c8e8591 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -2147,13 +2147,26 @@ bool - binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out) - { -- bool handled = true; -+ /* Run the tracked port binding loop twice to ensure correctness: -+ * 1. First to handle deleted changes. This is split in four sub-parts -+ * because child local bindings must be cleaned up first: -+ * a. Container ports first. -+ * b. Then virtual ports. -+ * c. Then regular VIFs. -+ * d. Last other ports. -+ * 2. Second to handle add/update changes. -+ */ -+ struct shash deleted_container_pbs = -+ SHASH_INITIALIZER(&deleted_container_pbs); -+ struct shash deleted_virtual_pbs = -+ SHASH_INITIALIZER(&deleted_virtual_pbs); -+ struct shash deleted_vif_pbs = -+ SHASH_INITIALIZER(&deleted_vif_pbs); -+ struct shash deleted_other_pbs = -+ SHASH_INITIALIZER(&deleted_other_pbs); - const struct sbrec_port_binding *pb; -+ bool handled = true; - -- /* Run the tracked port binding loop twice. One to handle deleted -- * changes. And another to handle add/update changes. -- * This will ensure correctness. -- */ - SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, - b_ctx_in->port_binding_table) { - if (!sbrec_port_binding_is_deleted(pb)) { -@@ -2161,18 +2174,60 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - } - - enum en_lport_type lport_type = get_lport_type(pb); -- if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) { -- handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in, -- b_ctx_out); -+ -+ if (lport_type == LP_VIF) { -+ if (is_lport_container(pb)) { -+ shash_add(&deleted_container_pbs, pb->logical_port, pb); -+ } else { -+ shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ } -+ } else if (lport_type == LP_VIRTUAL) { -+ shash_add(&deleted_virtual_pbs, pb->logical_port, pb); - } else { -- handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -+ shash_add(&deleted_other_pbs, pb->logical_port, pb); -+ } -+ } -+ -+ struct shash_node *node; -+ struct shash_node *node_next; -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_container_pbs, node); -+ if (!handled) { -+ goto delete_done; - } -+ } - -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_virtual_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIRTUAL, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_virtual_pbs, node); - if (!handled) { -- break; -+ goto delete_done; -+ } -+ } -+ -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_vif_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_vif_pbs, node); -+ if (!handled) { -+ goto delete_done; - } - } - -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_other_pbs) { -+ handle_deleted_lport(node->data, b_ctx_in, b_ctx_out); -+ shash_delete(&deleted_other_pbs, node); -+ } -+ -+delete_done: -+ shash_destroy(&deleted_container_pbs); -+ shash_destroy(&deleted_virtual_pbs); -+ shash_destroy(&deleted_vif_pbs); -+ shash_destroy(&deleted_other_pbs); -+ - if (!handled) { - return false; - } -diff --git a/tests/ovn.at b/tests/ovn.at -index 2cdc036..e2d2d8a 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -22194,6 +22194,24 @@ check ovn-nbctl lsp-add ls2 lsp-cont1 lsp1 1 - check ovn-nbctl --wait=hv sync - check_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch - -+AS_BOX([delete both OVN VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl lsp-del lsp1 \ -+ -- lsp-del lsp-cont1 -+check ovn-nbctl --wait=sb sync -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+AS_BOX([readd both OVN VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl lsp-add ls1 lsp1 \ -+ -- lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=sb sync -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch -+wait_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP - --- -1.8.3.1 - diff --git a/SOURCES/0002-binding-Set-Port_Binding.up-only-if-supported.patch b/SOURCES/0002-binding-Set-Port_Binding.up-only-if-supported.patch deleted file mode 100644 index d55e65a..0000000 --- a/SOURCES/0002-binding-Set-Port_Binding.up-only-if-supported.patch +++ /dev/null @@ -1,218 +0,0 @@ -From 3de7959b9018f53abd06320bc7f1a43ec216db7e Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 3 Feb 2021 20:36:41 +0100 -Subject: [PATCH 2/4] binding: Set Port_Binding.up only if supported. - -The supported upgrade procedure is to always upgrade ovn-controllers -first and OVN DBs and ovn-northd later. This leads however to the -situation when ovn-controller might try to set the Port_Binding.up field -while the Southbound DB isn't yet aware of this field which would -trigger transaction failures and control plane/data plane outages. - -To avoid such situations ovn-controller only sets the Port_Binding.up -field if it was explicitly set to 'false'. This ensures that the SB -database was already upgraded. - -On the ovn-northd side, as soon as ovn-northd is upgraded it will update -all existing Port_Bindings and explicitly set 'Port_Binding.up' to -false, implicitly notifying ovn-controller that it is safe to write to -the field. - -Reported-by: Numan Siddique -Fixes: 4d3cb42b076b ("binding: Set Logical_Switch_Port.up when all OVS flows are installed.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit 8b45fc9b2269df68566e47eee9cdd5f043b595d3) - -Change-Id: I6fb8b59419466bbe43f0d993966d8316320c6327 ---- - controller-vtep/binding.c | 6 ++++-- - controller/binding.c | 33 ++++++++++++++++++++++---------- - northd/ovn-northd.c | 8 ++++++++ - tests/ovn.at | 48 ++++++++++++++++++++++++++++++++++++++++++++++- - 4 files changed, 82 insertions(+), 13 deletions(-) - -diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c -index d28a598..01d5a16 100644 ---- a/controller-vtep/binding.c -+++ b/controller-vtep/binding.c -@@ -110,9 +110,11 @@ update_pb_chassis(const struct sbrec_port_binding *port_binding_rec, - chassis_rec->name); - } - -- bool up = true; - sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec); -- sbrec_port_binding_set_up(port_binding_rec, &up, 1); -+ 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 353debe..efaa109 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -870,6 +870,11 @@ get_lport_type(const struct sbrec_port_binding *pb) - * container and virtual ports). - * Otherwise request a notification to be sent when the OVS flows - * corresponding to 'pb' have been installed. -+ * -+ * Note: -+ * Updates (directly or through a notification) the 'pb->up' field only if -+ * it's explicitly set to 'false'. -+ * This is to ensure compatibility with older versions of ovn-northd. - */ - static void - claimed_lport_set_up(const struct sbrec_port_binding *pb, -@@ -885,7 +890,7 @@ claimed_lport_set_up(const struct sbrec_port_binding *pb, - return; - } - -- if (pb->chassis != chassis_rec) { -+ if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) { - binding_iface_bound_add(pb->logical_port); - } - } -@@ -973,7 +978,10 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - sbrec_port_binding_set_virtual_parent(pb, NULL); - } - -- sbrec_port_binding_set_up(pb, NULL, 0); -+ if (pb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(pb, &up, 1); -+ } - update_lport_tracking(pb, tracked_datapaths); - binding_iface_released_add(pb->logical_port); - VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); -@@ -2503,7 +2511,10 @@ binding_seqno_run(struct shash *local_bindings) - ovsrec_interface_update_external_ids_delkey( - lb->iface, OVN_INSTALLED_EXT_ID); - } -- sbrec_port_binding_set_up(lb->pb, NULL, 0); -+ if (lb->pb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(lb->pb, &up, 1); -+ } - simap_put(&binding_iface_seqno_map, lb->name, new_seqno); - } - sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id)); -@@ -2536,7 +2547,6 @@ binding_seqno_install(struct shash *local_bindings) - - SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { - struct shash_node *lb_node = shash_find(local_bindings, node->name); -- bool up = true; - - if (!lb_node) { - goto del_seqno; -@@ -2554,12 +2564,15 @@ binding_seqno_install(struct shash *local_bindings) - ovsrec_interface_update_external_ids_setkey(lb->iface, - OVN_INSTALLED_EXT_ID, - "true"); -- sbrec_port_binding_set_up(lb->pb, &up, 1); -- -- struct shash_node *child_node; -- SHASH_FOR_EACH (child_node, &lb->children) { -- struct local_binding *lb_child = child_node->data; -- sbrec_port_binding_set_up(lb_child->pb, &up, 1); -+ if (lb->pb->n_up) { -+ bool up = true; -+ -+ sbrec_port_binding_set_up(lb->pb, &up, 1); -+ struct shash_node *child_node; -+ SHASH_FOR_EACH (child_node, &lb->children) { -+ struct local_binding *lb_child = child_node->data; -+ sbrec_port_binding_set_up(lb_child->pb, &up, 1); -+ } - } - - del_seqno: -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 307ee9c..0dc920b 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -3329,6 +3329,14 @@ ovn_port_update_sbrec(struct northd_context *ctx, - if (op->tunnel_key != op->sb->tunnel_key) { - sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); - } -+ -+ /* ovn-controller will update 'Port_Binding.up' only if it was explicitly -+ * set to 'false'. -+ */ -+ if (!op->sb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(op->sb, &up, 1); -+ } - } - - /* Remove mac_binding entries that refer to logical_ports which are -diff --git a/tests/ovn.at b/tests/ovn.at -index 1956f5c..2ef056b 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -23697,7 +23697,7 @@ check ovn-nbctl ls-add ls - AS_BOX([add OVS port for existing LSP]) - check ovn-nbctl lsp-add ls lsp1 - check ovn-nbctl --wait=hv sync --check_column "[]" Port_Binding up logical_port=lsp1 -+check_column "false" Port_Binding up logical_port=lsp1 - - check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 - check_column "true" Port_Binding up logical_port=lsp1 -@@ -23712,5 +23712,51 @@ check_column "true" Port_Binding up logical_port=lsp2 - wait_column "true" nb:Logical_Switch_Port up name=lsp2 - OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp2 external_ids:ovn-installed` = '"true"']) - -+AS_BOX([ovn-controller should not reset Port_Binding.up without northd]) -+# Pause northd and clear the "up" field to simulate older ovn-northd -+# versions writing to the Southbound DB. -+as northd ovn-appctl -t ovn-northd pause -+as northd-backup ovn-appctl -t ovn-northd pause -+ -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl clear Port_Binding lsp1 up -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+# Forcefully release the Port_Binding so ovn-controller reclaims it. -+# Make sure the Port_Binding.up field is not updated though. -+check ovn-sbctl clear Port_Binding lsp1 chassis -+hv1_uuid=$(fetch_column Chassis _uuid name=hv1) -+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp1 -+check_column "" Port_Binding up logical_port=lsp1 -+ -+# Once northd should explicitly set the Port_Binding.up field to 'false' and -+# ovn-controller sets it to 'true' as soon as the update is processed. -+as northd ovn-appctl -t ovn-northd resume -+as northd-backup ovn-appctl -t ovn-northd resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ -+AS_BOX([ovn-controller should reset Port_Binding.up - from NULL]) -+# If Port_Binding.up is cleared externally, ovn-northd resets it to 'false' -+# and ovn-controller finally sets it to 'true' once the update is processed. -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl clear Port_Binding lsp1 up -+check ovn-nbctl --wait=sb sync -+wait_column "false" nb:Logical_Switch_Port up name=lsp1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ -+AS_BOX([ovn-controller should reset Port_Binding.up - from false]) -+# If Port_Binding.up is externally set to 'false', ovn-controller should sets -+# it to 'true' once the update is processed. -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl set Port_Binding lsp1 up=false -+check ovn-nbctl --wait=sb sync -+wait_column "false" nb:Logical_Switch_Port up name=lsp1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP --- -1.8.3.1 - diff --git a/SOURCES/0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch b/SOURCES/0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch deleted file mode 100644 index 4d0da67..0000000 --- a/SOURCES/0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch +++ /dev/null @@ -1,219 +0,0 @@ -From 561f1c3ee471e3b3212111abeb8239122eb8efd4 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Sun, 22 Nov 2020 18:47:25 +0530 -Subject: [PATCH 02/16] controller: Allow pinctrl thread to handle packet-ins - when version mismatch with northd. - -The commit 1dd27ea7aea4 added the support to pin ovn-controller to a -specific version of ovn-northd. This patch did not handle the -scenario the packet-in scenario when ovn-controller is started and it -detects version mismatch with ovn-northd. In this case, pinctrl -thread is not configured with the proper integration bridge name, -because of which it cannot handle any packet-ins. - -This patch addresses this scenario. This is required so that any -existing VMs do not loose DHCP address during renewal. - -Fixes: 1dd27ea7aea4("Provide the option to pin ovn-controller and ovn-northd to a specific version") -Acked-by: Flavio Fernandes -Tested-by: Flavio Fernandes -Signed-off-by: Numan Siddique ---- - controller/ovn-controller.c | 27 ++++++++++++----- - controller/pinctrl.c | 34 ++++++++++++++------- - controller/pinctrl.h | 2 +- - tests/ovn.at | 59 ++++++++++++++++++++++++++++++++++++- - 4 files changed, 102 insertions(+), 20 deletions(-) - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index faae787f3..3b41cc390 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -2552,25 +2552,29 @@ main(int argc, char *argv[]) - - 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)) { -+ 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; -@@ -2751,6 +2755,13 @@ main(int argc, char *argv[]) - } - } - -+ 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); -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index dd9fff959..728fb3063 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -3113,6 +3113,29 @@ pinctrl_handler(void *arg_) - return NULL; - } - -+static void -+pinctrl_set_br_int_name_(char *br_int_name) -+{ -+ if (br_int_name && (!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(); -+ } -+} -+ -+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, -@@ -3132,16 +3155,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - 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); -diff --git a/controller/pinctrl.h b/controller/pinctrl.h -index 8fa4baae9..4b101ec92 100644 ---- a/controller/pinctrl.h -+++ b/controller/pinctrl.h -@@ -49,5 +49,5 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - 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 --git a/tests/ovn.at b/tests/ovn.at -index f771b7563..f56f8a696 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -6093,7 +6093,64 @@ AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) - cat 1.expected | cut -c 53- > expout - AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) - --OVN_CLEANUP([hv1]) -+# 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 - --- -2.28.0 - diff --git a/SOURCES/0002-controller-Implement-a-generic-barrier-based-on-ofct.patch b/SOURCES/0002-controller-Implement-a-generic-barrier-based-on-ofct.patch deleted file mode 100644 index c7a8387..0000000 --- a/SOURCES/0002-controller-Implement-a-generic-barrier-based-on-ofct.patch +++ /dev/null @@ -1,960 +0,0 @@ -From 2bafeec1b98cfa813fa75dfafa74fdacae8e32c4 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 13 Jan 2021 10:23:19 +0100 -Subject: [PATCH 2/3] controller: Implement a generic barrier based on ofctrl - cur_cfg sync. - -A new module, 'ofctrl-seqno', is added to implement this generic -barrier. Other modules can register their own types of seqno update -requests. The barrier implementation ensures that the a seqno update -request is acked (returned by ofctrl_acked_seqnos_get()) only if the -OVS flow operations that have been requested when the seqno update -request was queued have been processed by OVS. - -For now, the only user of this barrier is the main ovn-controller -module but a future commit will use it too in order to mark -Port_Bindings and OVS interfaces as "fully installed". - -This commit also adds unit tests for the new 'ofctrl-seqno' module. -The unit test structure is inspired by Mark Michelson's patch: -http://patchwork.ozlabs.org/project/ovn/patch/20201216182421.234772-3-mmichels@redhat.com/ - -Signed-off-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique -(cherry picked from upstream master commit c93c626248c120eeaffafd323aef323d3b2507ab) - -Change-Id: I500750d4756267e3f62746da33275a22cb1af26f ---- - controller/automake.mk | 2 + - controller/ofctrl-seqno.c | 254 +++++++++++++++++++++++++++++++++++++++++ - controller/ofctrl-seqno.h | 49 ++++++++ - controller/ovn-controller.c | 41 +++++-- - controller/test-ofctrl-seqno.c | 194 +++++++++++++++++++++++++++++++ - tests/automake.mk | 8 +- - tests/ovn-ofctrl-seqno.at | 226 ++++++++++++++++++++++++++++++++++++ - tests/testsuite.at | 1 + - 8 files changed, 765 insertions(+), 10 deletions(-) - create mode 100644 controller/ofctrl-seqno.c - create mode 100644 controller/ofctrl-seqno.h - create mode 100644 controller/test-ofctrl-seqno.c - create mode 100644 tests/ovn-ofctrl-seqno.at - -diff --git a/controller/automake.mk b/controller/automake.mk -index 45e1bdd..480578e 100644 ---- a/controller/automake.mk -+++ b/controller/automake.mk -@@ -18,6 +18,8 @@ controller_ovn_controller_SOURCES = \ - controller/lport.h \ - controller/ofctrl.c \ - controller/ofctrl.h \ -+ controller/ofctrl-seqno.c \ -+ controller/ofctrl-seqno.h \ - controller/pinctrl.c \ - controller/pinctrl.h \ - controller/patch.c \ -diff --git a/controller/ofctrl-seqno.c b/controller/ofctrl-seqno.c -new file mode 100644 -index 0000000..c9334b0 ---- /dev/null -+++ b/controller/ofctrl-seqno.c -@@ -0,0 +1,254 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "hash.h" -+#include "ofctrl-seqno.h" -+#include "openvswitch/list.h" -+#include "util.h" -+ -+/* A sequence number update request, i.e., when the barrier corresponding to -+ * the 'flow_cfg' sequence number is replied to by OVS then it is safe -+ * to inform the application that the 'req_cfg' seqno has been processed. -+ */ -+struct ofctrl_seqno_update { -+ struct ovs_list list_node; /* In 'ofctrl_seqno_updates'. */ -+ size_t seqno_type; /* Application specific seqno type. -+ * Relevant only for 'req_cfg'. -+ */ -+ uint64_t flow_cfg; /* The seqno that needs to be acked by OVS -+ * before 'req_cfg' can be acked for the -+ * application. -+ */ -+ uint64_t req_cfg; /* Application specific seqno. */ -+}; -+ -+/* List of in flight sequence number updates. */ -+static struct ovs_list ofctrl_seqno_updates; -+ -+/* Last sequence number request sent to OVS. */ -+static uint64_t ofctrl_req_seqno; -+ -+/* State of seqno requests for a given application seqno type. */ -+struct ofctrl_seqno_state { -+ struct ovs_list acked_cfgs; /* Acked requests since the last time the -+ * application consumed acked requests. -+ */ -+ uint64_t cur_cfg; /* Last acked application seqno. */ -+ uint64_t req_cfg; /* Last requested application seqno. */ -+}; -+ -+/* Per application seqno type states. */ -+static size_t n_ofctrl_seqno_states; -+static struct ofctrl_seqno_state *ofctrl_seqno_states; -+ -+/* ofctrl_acked_seqnos related static function prototypes. */ -+static void ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos, -+ uint64_t last_acked); -+static void ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val); -+ -+/* ofctrl_seqno_update related static function prototypes. */ -+static void ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg); -+static void ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list); -+static void ofctrl_seqno_cfg_run(size_t seqno_type, -+ struct ofctrl_seqno_update *update); -+ -+/* Returns the collection of acked ofctrl_seqno_update requests of type -+ * 'seqno_type'. It's the responsibility of the caller to free memory by -+ * calling ofctrl_acked_seqnos_destroy(). -+ */ -+struct ofctrl_acked_seqnos * -+ofctrl_acked_seqnos_get(size_t seqno_type) -+{ -+ struct ofctrl_acked_seqnos *acked_seqnos = xmalloc(sizeof *acked_seqnos); -+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type]; -+ struct ofctrl_seqno_update *update; -+ -+ ofctrl_acked_seqnos_init(acked_seqnos, state->cur_cfg); -+ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ LIST_FOR_EACH_POP (update, list_node, &state->acked_cfgs) { -+ ofctrl_acked_seqnos_add(acked_seqnos, update->req_cfg); -+ free(update); -+ } -+ return acked_seqnos; -+} -+ -+void -+ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos) -+{ -+ if (!seqnos) { -+ return; -+ } -+ -+ struct ofctrl_ack_seqno *seqno_node; -+ HMAP_FOR_EACH_POP (seqno_node, node, &seqnos->acked) { -+ free(seqno_node); -+ } -+ hmap_destroy(&seqnos->acked); -+ free(seqnos); -+} -+ -+/* Returns true if 'val' is one of the acked sequence numbers in 'seqnos'. */ -+bool -+ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val) -+{ -+ struct ofctrl_ack_seqno *sn; -+ -+ HMAP_FOR_EACH_WITH_HASH (sn, node, hash_int(val, 0), &seqnos->acked) { -+ if (sn->seqno == val) { -+ return true; -+ } -+ } -+ return false; -+} -+ -+void -+ofctrl_seqno_init(void) -+{ -+ ovs_list_init(&ofctrl_seqno_updates); -+} -+ -+/* Adds a new type of application specific seqno updates. */ -+size_t -+ofctrl_seqno_add_type(void) -+{ -+ size_t new_type = n_ofctrl_seqno_states; -+ n_ofctrl_seqno_states++; -+ -+ struct ofctrl_seqno_state *new_states = -+ xzalloc(n_ofctrl_seqno_states * sizeof *new_states); -+ -+ for (size_t i = 0; i < n_ofctrl_seqno_states - 1; i++) { -+ ovs_list_move(&new_states[i].acked_cfgs, -+ &ofctrl_seqno_states[i].acked_cfgs); -+ } -+ ovs_list_init(&new_states[new_type].acked_cfgs); -+ -+ free(ofctrl_seqno_states); -+ ofctrl_seqno_states = new_states; -+ return new_type; -+} -+ -+/* Creates a new seqno update request for an application specific -+ * 'seqno_type'. -+ */ -+void -+ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg) -+{ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ -+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type]; -+ -+ /* If new_cfg didn't change since the last request there should already -+ * be an update pending. -+ */ -+ if (new_cfg == state->req_cfg) { -+ return; -+ } -+ -+ state->req_cfg = new_cfg; -+ ofctrl_seqno_update_create__(seqno_type, new_cfg); -+} -+ -+/* Should be called when the application is certain that all OVS flow updates -+ * corresponding to 'flow_cfg' were processed. Populates the application -+ * specific lists of acked requests in 'ofctrl_seqno_states'. -+ */ -+void -+ofctrl_seqno_run(uint64_t flow_cfg) -+{ -+ struct ofctrl_seqno_update *update, *prev; -+ LIST_FOR_EACH_SAFE (update, prev, list_node, &ofctrl_seqno_updates) { -+ if (flow_cfg < update->flow_cfg) { -+ break; -+ } -+ -+ ovs_list_remove(&update->list_node); -+ ofctrl_seqno_cfg_run(update->seqno_type, update); -+ } -+} -+ -+/* Returns the seqno to be used when sending a barrier request to OVS. */ -+uint64_t -+ofctrl_seqno_get_req_cfg(void) -+{ -+ return ofctrl_req_seqno; -+} -+ -+/* Should be called whenever the openflow connection to OVS is lost. Flushes -+ * all pending 'ofctrl_seqno_updates'. -+ */ -+void -+ofctrl_seqno_flush(void) -+{ -+ for (size_t i = 0; i < n_ofctrl_seqno_states; i++) { -+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_states[i].acked_cfgs); -+ } -+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_updates); -+ ofctrl_req_seqno = 0; -+} -+ -+static void -+ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos, -+ uint64_t last_acked) -+{ -+ hmap_init(&seqnos->acked); -+ seqnos->last_acked = last_acked; -+} -+ -+static void -+ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos, uint32_t val) -+{ -+ seqnos->last_acked = val; -+ -+ struct ofctrl_ack_seqno *sn = xmalloc(sizeof *sn); -+ hmap_insert(&seqnos->acked, &sn->node, hash_int(val, 0)); -+ sn->seqno = val; -+} -+ -+static void -+ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg) -+{ -+ struct ofctrl_seqno_update *update = xmalloc(sizeof *update); -+ -+ ofctrl_req_seqno++; -+ ovs_list_push_back(&ofctrl_seqno_updates, &update->list_node); -+ update->seqno_type = seqno_type; -+ update->flow_cfg = ofctrl_req_seqno; -+ update->req_cfg = req_cfg; -+} -+ -+static void -+ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list) -+{ -+ struct ofctrl_seqno_update *update; -+ -+ LIST_FOR_EACH_POP (update, list_node, seqno_list) { -+ free(update); -+ } -+} -+ -+static void -+ofctrl_seqno_cfg_run(size_t seqno_type, struct ofctrl_seqno_update *update) -+{ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ ovs_list_push_back(&ofctrl_seqno_states[seqno_type].acked_cfgs, -+ &update->list_node); -+ ofctrl_seqno_states[seqno_type].cur_cfg = update->req_cfg; -+} -diff --git a/controller/ofctrl-seqno.h b/controller/ofctrl-seqno.h -new file mode 100644 -index 0000000..876947c ---- /dev/null -+++ b/controller/ofctrl-seqno.h -@@ -0,0 +1,49 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#ifndef OFCTRL_SEQNO_H -+#define OFCTRL_SEQNO_H 1 -+ -+#include -+ -+#include -+ -+/* Collection of acked ofctrl_seqno_update requests and the most recent -+ * 'last_acked' value. -+ */ -+struct ofctrl_acked_seqnos { -+ struct hmap acked; -+ uint64_t last_acked; -+}; -+ -+/* Acked application specific seqno. Stored in ofctrl_acked_seqnos.acked. */ -+struct ofctrl_ack_seqno { -+ struct hmap_node node; -+ uint64_t seqno; -+}; -+ -+struct ofctrl_acked_seqnos *ofctrl_acked_seqnos_get(size_t seqno_type); -+void ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos); -+bool ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val); -+ -+void ofctrl_seqno_init(void); -+size_t ofctrl_seqno_add_type(void); -+void ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg); -+void ofctrl_seqno_run(uint64_t flow_cfg); -+uint64_t ofctrl_seqno_get_req_cfg(void); -+void ofctrl_seqno_flush(void); -+ -+#endif /* controller/ofctrl-seqno.h */ -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 42883b4..bb1c659 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -39,6 +39,7 @@ - #include "lib/vswitch-idl.h" - #include "lport.h" - #include "ofctrl.h" -+#include "ofctrl-seqno.h" - #include "openvswitch/vconn.h" - #include "openvswitch/vlog.h" - #include "ovn/actions.h" -@@ -98,6 +99,9 @@ struct pending_pkt { - char *flow_s; - }; - -+/* Registered ofctrl seqno type for nb_cfg propagation. */ -+static size_t ofctrl_seq_type_nb_cfg; -+ - struct local_datapath * - get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key) - { -@@ -825,11 +829,14 @@ static void - store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, - const struct sbrec_chassis_private *chassis, - const struct ovsrec_bridge *br_int, -- unsigned int delay_nb_cfg_report, -- uint64_t cur_cfg) -+ unsigned int delay_nb_cfg_report) - { -+ struct ofctrl_acked_seqnos *acked_nb_cfg_seqnos = -+ ofctrl_acked_seqnos_get(ofctrl_seq_type_nb_cfg); -+ uint64_t cur_cfg = acked_nb_cfg_seqnos->last_acked; -+ - if (!cur_cfg) { -- return; -+ goto done; - } - - if (sb_txn && chassis && cur_cfg != chassis->nb_cfg) { -@@ -850,6 +857,9 @@ store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, - cur_cfg_str); - free(cur_cfg_str); - } -+ -+done: -+ ofctrl_acked_seqnos_destroy(acked_nb_cfg_seqnos); - } - - static const char * -@@ -967,6 +977,11 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data) - struct ed_type_ofctrl_is_connected *of_data = data; - if (of_data->connected != ofctrl_is_connected()) { - of_data->connected = !of_data->connected; -+ -+ /* Flush ofctrl seqno requests when the ofctrl connection goes down. */ -+ if (!of_data->connected) { -+ ofctrl_seqno_flush(); -+ } - engine_set_node_state(node, EN_UPDATED); - return; - } -@@ -2393,6 +2408,9 @@ main(int argc, char *argv[]) - pinctrl_init(); - lflow_init(); - -+ /* Register ofctrl seqno types. */ -+ ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type(); -+ - /* Connect to OVS OVSDB instance. */ - struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true)); -@@ -2624,6 +2642,7 @@ main(int argc, char *argv[]) - ofctrl_init(&flow_output_data->group_table, - &flow_output_data->meter_table, - get_ofctrl_probe_interval(ovs_idl_loop.idl)); -+ ofctrl_seqno_init(); - - unixctl_command_register("group-table-list", "", 0, 0, - extend_table_list, -@@ -2853,17 +2872,23 @@ main(int argc, char *argv[]) - sb_monitor_all); - } - } -+ -+ ofctrl_seqno_update_create( -+ ofctrl_seq_type_nb_cfg, -+ get_nb_cfg(sbrec_sb_global_table_get( -+ ovnsb_idl_loop.idl), -+ ovnsb_cond_seqno, -+ ovnsb_expected_cond_seqno)); -+ - 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), -+ ofctrl_seqno_get_req_cfg(), - engine_node_changed(&en_flow_output)); - } -+ ofctrl_seqno_run(ofctrl_get_cur_cfg()); - } - - } -@@ -2889,7 +2914,7 @@ main(int argc, char *argv[]) - } - - store_nb_cfg(ovnsb_idl_txn, ovs_idl_txn, chassis_private, -- br_int, delay_nb_cfg_report, ofctrl_get_cur_cfg()); -+ br_int, delay_nb_cfg_report); - - if (pending_pkt.conn) { - struct ed_type_addr_sets *as_data = -diff --git a/controller/test-ofctrl-seqno.c b/controller/test-ofctrl-seqno.c -new file mode 100644 -index 0000000..fce88d4 ---- /dev/null -+++ b/controller/test-ofctrl-seqno.c -@@ -0,0 +1,194 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "tests/ovstest.h" -+#include "sort.h" -+#include "util.h" -+ -+#include "ofctrl-seqno.h" -+ -+static void -+test_init(void) -+{ -+ ofctrl_seqno_init(); -+} -+ -+static bool -+test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index, -+ const char *descr, unsigned int *result) -+{ -+ if (index >= ctx->argc) { -+ fprintf(stderr, "Missing %s argument\n", descr); -+ return false; -+ } -+ -+ const char *arg = ctx->argv[index]; -+ if (!str_to_uint(arg, 10, result)) { -+ fprintf(stderr, "Invalid %s: %s\n", descr, arg); -+ return false; -+ } -+ return true; -+} -+ -+static int -+test_seqno_compare(size_t a, size_t b, void *values_) -+{ -+ uint64_t *values = values_; -+ -+ return values[a] == values[b] ? 0 : (values[a] < values[b] ? -1 : 1); -+} -+ -+static void -+test_seqno_swap(size_t a, size_t b, void *values_) -+{ -+ uint64_t *values = values_; -+ uint64_t tmp = values[a]; -+ -+ values[a] = values[b]; -+ values[b] = tmp; -+} -+ -+static void -+test_dump_acked_seqnos(size_t seqno_type) -+{ -+ struct ofctrl_acked_seqnos * acked_seqnos = -+ ofctrl_acked_seqnos_get(seqno_type); -+ -+ printf("ofctrl-seqno-type: %"PRIuSIZE"\n", seqno_type); -+ printf(" last-acked %"PRIu64"\n", acked_seqnos->last_acked); -+ -+ size_t n_acked = hmap_count(&acked_seqnos->acked); -+ uint64_t *acked = xmalloc(n_acked * sizeof *acked); -+ struct ofctrl_ack_seqno *ack_seqno; -+ size_t i = 0; -+ -+ /* A bit hacky but ignoring overflows the "total of all seqno + 1" should -+ * be a number that is not part of the acked seqnos. -+ */ -+ uint64_t total_seqno = 1; -+ HMAP_FOR_EACH (ack_seqno, node, &acked_seqnos->acked) { -+ ovs_assert(ofctrl_acked_seqnos_contains(acked_seqnos, -+ ack_seqno->seqno)); -+ total_seqno += ack_seqno->seqno; -+ acked[i++] = ack_seqno->seqno; -+ } -+ ovs_assert(!ofctrl_acked_seqnos_contains(acked_seqnos, total_seqno)); -+ -+ sort(n_acked, test_seqno_compare, test_seqno_swap, acked); -+ -+ for (i = 0; i < n_acked; i++) { -+ printf(" %"PRIu64"\n", acked[i]); -+ } -+ -+ free(acked); -+ ofctrl_acked_seqnos_destroy(acked_seqnos); -+} -+ -+static void -+test_ofctrl_seqno_add_type(struct ovs_cmdl_context *ctx) -+{ -+ unsigned int n_types; -+ -+ test_init(); -+ -+ if (!test_read_uint_value(ctx, 1, "n_types", &n_types)) { -+ return; -+ } -+ for (unsigned int i = 0; i < n_types; i++) { -+ printf("%"PRIuSIZE"\n", ofctrl_seqno_add_type()); -+ } -+} -+ -+static void -+test_ofctrl_seqno_ack_seqnos(struct ovs_cmdl_context *ctx) -+{ -+ unsigned int n_reqs = 0; -+ unsigned int shift = 2; -+ unsigned int n_types; -+ unsigned int n_acks; -+ -+ test_init(); -+ bool batch_acks = !strcmp(ctx->argv[1], "true"); -+ -+ if (!test_read_uint_value(ctx, shift++, "n_types", &n_types)) { -+ return; -+ } -+ -+ for (unsigned int i = 0; i < n_types; i++) { -+ ovs_assert(ofctrl_seqno_add_type() == i); -+ -+ /* Read number of app specific seqnos. */ -+ unsigned int n_app_seqnos; -+ -+ if (!test_read_uint_value(ctx, shift++, "n_app_seqnos", -+ &n_app_seqnos)) { -+ return; -+ } -+ -+ for (unsigned int j = 0; j < n_app_seqnos; j++, n_reqs++) { -+ unsigned int app_seqno; -+ -+ if (!test_read_uint_value(ctx, shift++, "app_seqno", &app_seqno)) { -+ return; -+ } -+ ofctrl_seqno_update_create(i, app_seqno); -+ } -+ } -+ printf("ofctrl-seqno-req-cfg: %u\n", n_reqs); -+ -+ if (!test_read_uint_value(ctx, shift++, "n_acks", &n_acks)) { -+ return; -+ } -+ for (unsigned int i = 0; i < n_acks; i++) { -+ unsigned int ack_seqno; -+ -+ if (!test_read_uint_value(ctx, shift++, "ack_seqno", &ack_seqno)) { -+ return; -+ } -+ ofctrl_seqno_run(ack_seqno); -+ -+ if (!batch_acks) { -+ for (unsigned int st = 0; st < n_types; st++) { -+ test_dump_acked_seqnos(st); -+ } -+ } -+ } -+ if (batch_acks) { -+ for (unsigned int st = 0; st < n_types; st++) { -+ test_dump_acked_seqnos(st); -+ } -+ } -+} -+ -+static void -+test_ofctrl_seqno_main(int argc, char *argv[]) -+{ -+ set_program_name(argv[0]); -+ static const struct ovs_cmdl_command commands[] = { -+ {"ofctrl_seqno_add_type", NULL, 1, 1, -+ test_ofctrl_seqno_add_type, OVS_RO}, -+ {"ofctrl_seqno_ack_seqnos", NULL, 2, INT_MAX, -+ test_ofctrl_seqno_ack_seqnos, OVS_RO}, -+ {NULL, NULL, 0, 0, NULL, OVS_RO}, -+ }; -+ struct ovs_cmdl_context ctx; -+ ctx.argc = argc - 1; -+ ctx.argv = argv + 1; -+ ovs_cmdl_run_command(&ctx, commands); -+} -+ -+OVSTEST_REGISTER("test-ofctrl-seqno", test_ofctrl_seqno_main); -diff --git a/tests/automake.mk b/tests/automake.mk -index c5c286e..c09f615 100644 ---- a/tests/automake.mk -+++ b/tests/automake.mk -@@ -31,7 +31,8 @@ TESTSUITE_AT = \ - tests/ovn-controller-vtep.at \ - tests/ovn-ic.at \ - tests/ovn-macros.at \ -- tests/ovn-performance.at -+ tests/ovn-performance.at \ -+ tests/ovn-ofctrl-seqno.at - - SYSTEM_KMOD_TESTSUITE_AT = \ - tests/system-common-macros.at \ -@@ -202,7 +203,10 @@ noinst_PROGRAMS += tests/ovstest - tests_ovstest_SOURCES = \ - tests/ovstest.c \ - tests/ovstest.h \ -- tests/test-ovn.c -+ tests/test-ovn.c \ -+ controller/test-ofctrl-seqno.c \ -+ controller/ofctrl-seqno.c \ -+ controller/ofctrl-seqno.h - - tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ - $(OVS_LIBDIR)/libopenvswitch.la lib/libovn.la -diff --git a/tests/ovn-ofctrl-seqno.at b/tests/ovn-ofctrl-seqno.at -new file mode 100644 -index 0000000..59dfea9 ---- /dev/null -+++ b/tests/ovn-ofctrl-seqno.at -@@ -0,0 +1,226 @@ -+# -+# Unit tests for the controller/ofctrl-seqno.c module. -+# -+AT_BANNER([OVN unit tests - ofctrl-seqno]) -+ -+AT_SETUP([ovn -- unit test -- ofctrl-seqno add-type]) -+ -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 1], [0], [dnl -+0 -+]) -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 2], [0], [dnl -+0 -+1 -+]) -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 3], [0], [dnl -+0 -+1 -+2 -+]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- unit test -- ofctrl-seqno ack-seqnos]) -+ -+AS_BOX([No Ack Batching, 1 seqno type]) -+n_types=1 -+n_app_seqnos=3 -+app_seqnos="40 41 42" -+ -+n_acks=1 -+acks="1" -+echo "ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+]) -+ -+n_acks=2 -+acks="1 2" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 42 -+]) -+ -+AS_BOX([Ack Batching, 1 seqno type]) -+n_types=1 -+n_app_seqnos=3 -+app_seqnos="40 41 42" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+]) -+ -+n_acks=2 -+acks="1 2" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 40 -+ 41 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+]) -+ -+AS_BOX([No Ack Batching, 2 seqno types]) -+n_types=2 -+n_app_seqnos=3 -+app_seqnos1="40 41 42" -+app_seqnos2="50 51 52" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="4 5 6" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 50 -+ 50 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ofctrl-seqno-type: 1 -+ last-acked 51 -+ 51 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ofctrl-seqno-type: 1 -+ last-acked 52 -+ 52 -+]) -+ -+AS_BOX([Ack Batching, 2 seqno types]) -+n_types=2 -+n_app_seqnos=3 -+app_seqnos1="40 41 42" -+app_seqnos2="50 51 52" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="4 5 6" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 52 -+ 50 -+ 51 -+ 52 -+]) -+AT_CLEANUP -diff --git a/tests/testsuite.at b/tests/testsuite.at -index 960227d..3eba785 100644 ---- a/tests/testsuite.at -+++ b/tests/testsuite.at -@@ -26,6 +26,7 @@ m4_include([tests/ovn.at]) - m4_include([tests/ovn-performance.at]) - m4_include([tests/ovn-northd.at]) - m4_include([tests/ovn-nbctl.at]) -+m4_include([tests/ovn-ofctrl-seqno.at]) - m4_include([tests/ovn-sbctl.at]) - m4_include([tests/ovn-ic-nbctl.at]) - m4_include([tests/ovn-ic-sbctl.at]) --- -1.8.3.1 - diff --git a/SOURCES/0002-lflow-Use-learn-action-to-generate-LB-hairpin-reply-.patch b/SOURCES/0002-lflow-Use-learn-action-to-generate-LB-hairpin-reply-.patch deleted file mode 100644 index 415198d..0000000 --- a/SOURCES/0002-lflow-Use-learn-action-to-generate-LB-hairpin-reply-.patch +++ /dev/null @@ -1,1126 +0,0 @@ -From 0ea619c80146bffd4fef7ac1a8a2aa07f9003eb0 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Fri, 5 Feb 2021 23:29:29 +0100 -Subject: [PATCH 2/2] lflow: Use learn() action to generate LB hairpin reply - flows. - -The main trait of load balancer hairpin traffic is that it never leaves -the local hypervisor. Essentially this means that only hairpin -openflows installed for logical switches that have at least one logical -switch port bound locally can ever be hit. - -Until now, if a load balancer was applied on multiple logical switches -that are connected through a distributed router, ovn-controller would -install flows to detect hairpin replies for all logical switches. In -practice this leads to a very high number of openflows out of which -most will never be used. - -Instead we now use an additional action, learn(), on flows that match on -packets that create the hairpin session. The learn() action will then -generate the necessary flows to handle hairpin replies, but only for -the local datapaths which actually generate hairpin traffic. - -For example, simulating how ovn-k8s uses load balancer for services, -in a "switch per node" scenario, the script below would generate -10K (n_nodes * n_vips * n_backends) openflows on every node in table=69 -(hairpin reply). With this patch the maximum number of openflows that -can be created for hairpin replies is 200 (n_vips * n_backends). - -In general, for deployments that leverage switch-per-node topologies, -the number of openflows is reduced by a factor of N, where N is the -number of nodes. - - $ cat lbs.sh - NODES=50 - VIPS=20 - BACKENDS=10 - ovn-nbctl lr-add rtr - for ((i = 1; i <= $NODES; i++)); do - ovn-nbctl \ - -- ls-add ls$i \ - -- lsp-add ls$i vm$i \ - -- lsp-add ls$i ls$i-rtr \ - -- lsp-set-type ls$i-rtr router \ - -- lsp-set-options ls$i-rtr router-port=rtr-ls$i \ - -- lrp-add rtr rtr-ls$i 00:00:00:00:01:00 42.42.42.$i/24 - done - - for ((i = 1; i <= $VIPS; i++)); do - lb=lb$i - vip=10.10.10.$i:1 - bip=20.20.20.1:2 - for ((j = 2; j <= $BACKENDS; j++)); do - bip="$bip,20.20.20.$j:2" - done - ovn-nbctl lb-add $lb $vip $backends - done - - for ((i = 1; i <= $NODES; i++)); do - for ((j = 1; j <= $VIPS; j++)); do - ovn-nbctl ls-lb-add ls$i lb$j - done - done - - ovs-vsctl add-port br-int vm1 \ - -- set interface vm1 type=internal \ - -- set interface vm1 external-ids:iface-id=vm1 - -Suggested-by: Ilya Maximets -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit 022ea339c8e22824ba6f6f1257da0d1b6c66d401) - -Change-Id: Ic0c20047538d6881e8cb79e00c96da8afde67a72 ---- - controller/lflow.c | 204 ++++++++++++++++--- - tests/ofproto-macros.at | 5 +- - tests/ovn.at | 516 +++++++++++++++++++++++++----------------------- - 3 files changed, 455 insertions(+), 270 deletions(-) - -diff --git a/controller/lflow.c b/controller/lflow.c -index 946c1e0..2b7d356 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -1171,6 +1171,178 @@ add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name, - } - } - -+/* Builds the "learn()" action to be triggered by packets initiating a -+ * hairpin session. -+ * -+ * This will generate flows in table OFTABLE_CHK_LB_HAIRPIN_REPLY of the form: -+ * - match: -+ * metadata=,ip/ipv6,ip.src=,ip.dst= -+ * nw_proto='lb_proto',tp_src_port= -+ * - action: -+ * set MLF_LOOKUP_LB_HAIRPIN_BIT=1 -+ */ -+static void -+add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip, -+ uint8_t lb_proto, bool has_l4_port, -+ uint64_t cookie, struct ofpbuf *ofpacts) -+{ -+ struct match match = MATCH_CATCHALL_INITIALIZER; -+ struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts); -+ struct ofpact_learn_spec *ol_spec; -+ unsigned int imm_bytes; -+ uint8_t *src_imm; -+ -+ /* Once learned, hairpin reply flows are permanent until the VIP/backend -+ * is removed. -+ */ -+ ol->flags = NX_LEARN_F_DELETE_LEARNED; -+ ol->idle_timeout = OFP_FLOW_PERMANENT; -+ ol->hard_timeout = OFP_FLOW_PERMANENT; -+ ol->priority = OFP_DEFAULT_PRIORITY; -+ ol->table_id = OFTABLE_CHK_LB_HAIRPIN_REPLY; -+ ol->cookie = htonll(cookie); -+ -+ /* Match on metadata of the packet that created the hairpin session. */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ -+ ol_spec->dst.field = mf_from_id(MFF_METADATA); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ ol_spec->src.field = mf_from_id(MFF_METADATA); -+ -+ /* Match on the same ETH type as the packet that created the hairpin -+ * session. -+ */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ union mf_value imm_eth_type = { -+ .be16 = !vip6 ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6) -+ }; -+ mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec'. */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_eth_type, imm_bytes); -+ -+ /* Hairpin replies have ip.src == . */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ if (!vip6) { -+ ol_spec->dst.field = mf_from_id(MFF_IPV4_SRC); -+ ol_spec->src.field = mf_from_id(MFF_IPV4_SRC); -+ } else { -+ ol_spec->dst.field = mf_from_id(MFF_IPV6_SRC); -+ ol_spec->src.field = mf_from_id(MFF_IPV6_SRC); -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ -+ /* Hairpin replies have ip.dst == . */ -+ union mf_value imm_ip; -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ if (!vip6) { -+ ol_spec->dst.field = mf_from_id(MFF_IPV4_DST); -+ imm_ip = (union mf_value) { -+ .be32 = vip -+ }; -+ } else { -+ ol_spec->dst.field = mf_from_id(MFF_IPV6_DST); -+ imm_ip = (union mf_value) { -+ .ipv6 = *vip6 -+ }; -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ mf_write_subfield_value(&ol_spec->dst, &imm_ip, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_ip, imm_bytes); -+ -+ /* Hairpin replies have the same nw_proto as packets that created the -+ * session. -+ */ -+ union mf_value imm_proto = { -+ .u8 = lb_proto, -+ }; -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_IP_PROTO); -+ ol_spec->src.field = mf_from_id(MFF_IP_PROTO); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_proto, imm_bytes); -+ -+ /* Hairpin replies have source port == . */ -+ if (has_l4_port) { -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ switch (lb_proto) { -+ case IPPROTO_TCP: -+ ol_spec->dst.field = mf_from_id(MFF_TCP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_TCP_DST); -+ break; -+ case IPPROTO_UDP: -+ ol_spec->dst.field = mf_from_id(MFF_UDP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_UDP_DST); -+ break; -+ case IPPROTO_SCTP: -+ ol_spec->dst.field = mf_from_id(MFF_SCTP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_SCTP_DST); -+ break; -+ default: -+ OVS_NOT_REACHED(); -+ break; -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ } -+ -+ /* Set MLF_LOOKUP_LB_HAIRPIN_BIT for hairpin replies. */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS); -+ ol_spec->dst.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT; -+ ol_spec->dst.n_bits = 1; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_LOAD; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ union mf_value imm_reg_value = { -+ .u8 = 1 -+ }; -+ mf_write_subfield_value(&ol_spec->dst, &imm_reg_value, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_reg_value, imm_bytes); -+ -+ ofpact_finish_LEARN(ofpacts, &ol); -+} -+ - static void - add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - struct ovn_lb_vip *lb_vip, -@@ -1180,14 +1352,12 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - { - uint64_t stub[1024 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); -+ struct match hairpin_match = MATCH_CATCHALL_INITIALIZER; - - 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 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip); - ovs_be32 vip4 = lb->hairpin_snat_ips.n_ipv4_addrs -@@ -1198,9 +1368,10 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - match_set_nw_src(&hairpin_match, bip4); - match_set_nw_dst(&hairpin_match, bip4); - -- match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IP)); -- match_set_nw_src(&hairpin_reply_match, bip4); -- match_set_nw_dst(&hairpin_reply_match, vip4); -+ add_lb_vip_hairpin_reply_action(NULL, vip4, lb_proto, -+ lb_backend->port, -+ lb->slb->header_.uuid.parts[0], -+ &ofpacts); - } else { - struct in6_addr *bip6 = &lb_backend->ip; - struct in6_addr *vip6 = lb->hairpin_snat_ips.n_ipv6_addrs -@@ -1210,17 +1381,15 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - match_set_ipv6_src(&hairpin_match, bip6); - match_set_ipv6_dst(&hairpin_match, bip6); - -- match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IPV6)); -- match_set_ipv6_src(&hairpin_reply_match, bip6); -- match_set_ipv6_dst(&hairpin_reply_match, vip6); -+ add_lb_vip_hairpin_reply_action(vip6, 0, lb_proto, -+ lb_backend->port, -+ lb->slb->header_.uuid.parts[0], -+ &ofpacts); - } - - 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 -@@ -1241,17 +1410,6 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - 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); - } - -diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at -index dd5d384..ff65d60 100644 ---- a/tests/ofproto-macros.at -+++ b/tests/ofproto-macros.at -@@ -12,7 +12,10 @@ strip_n_bytes () { - - # Strips 'cookie=...' from ovs-ofctl output. - strip_cookie () { -- sed 's/ cookie=0x[0-9a-fA-F]*,//' -+ sed ' -+s/ cookie=0x[0-9a-fA-F]*,// -+s/cookie=0x[0-9a-fA-F]*,// -+' - } - - # Strips out uninteresting parts of ovs-ofctl output, as well as parts -diff --git a/tests/ovn.at b/tests/ovn.at -index 14072ec..6c9bda0 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -20822,6 +20822,8 @@ ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([sbflows]) - > expected - -+AS_BOX([IPv4 TCP Hairpin]) -+ - # Inject IPv4 TCP packet from lsp. - tcp_payload=$(build_tcp_syn 84d0 1f90 05a7) - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e) -@@ -20835,9 +20837,15 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ - # Change LB Hairpin SNAT IP. - # Also flush conntrack to avoid reusing an existing entry. - as hv1 ovs-appctl dpctl/flush-conntrack -+ - ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp options:hairpin_snat_ip="88.88.88.87" - # Inject IPv4 TCP packet from lsp. - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f) -@@ -20851,6 +20859,13 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv4 UDP Hairpin]) -+ - # Inject IPv4 UDP packet from lsp. - udp_payload=$(build_udp 84d0 0fc8 6666) - hp_udp_payload=$(build_udp 84d0 07e5 6e49) -@@ -20864,6 +20879,12 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, 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]] -+]) -+ - # Change LB Hairpin SNAT IP. - # Also flush conntrack to avoid reusing an existing entry. - as hv1 ovs-appctl dpctl/flush-conntrack -@@ -20880,6 +20901,14 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv6 TCP Hairpin]) -+ - # Inject IPv6 TCP packet from lsp. - tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) -@@ -20893,6 +20922,13 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ - # Change LB Hairpin SNAT IP. - # Also flush conntrack to avoid reusing an existing entry. - as hv1 ovs-appctl dpctl/flush-conntrack -@@ -20910,6 +20946,15 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv6 UDP Hairpin]) -+ - # Inject IPv6 UDP packet from lsp. - udp_payload=$(build_udp 84d0 0fc8 a0b8) - hp_udp_payload=$(build_udp 84d0 07e5 a89b) -@@ -20920,9 +20965,17 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - 88000000000000000000000000000088 ${hp_udp_payload} \ - expected - --Check that traffic is hairpinned. -+# Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ - # Change LB Hairpin SNAT IP. - # Also flush conntrack to avoid reusing an existing entry. - as hv1 ovs-appctl dpctl/flush-conntrack -@@ -20937,6 +20990,41 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - 88000000000000000000000000000087 ${hp_udp_payload} \ - expected - -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([Delete VIP]) -+check ovn-nbctl --wait=hv set Load_Balancer lb-ipv4-tcp vips='"88.88.88.88:8080"=""' -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([Delete LB]) -+check ovn-nbctl --wait=hv \ -+ -- lb-del lb-ipv4-tcp \ -+ -- lb-del lb-ipv4-udp -+ -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+check ovn-nbctl --wait=hv lb-del lb-ipv6-tcp -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+check ovn-nbctl --wait=hv lb-del lb-ipv6-udp -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [1], [dnl -+]) -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP - -@@ -23188,93 +23276,79 @@ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp - check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp - check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp - --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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70], [0], [dnl --NXST_FLOW reply (xid=0x8): -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl - ]) - --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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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 hv2 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 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl --priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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 hv2 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 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl --priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - check ovn-nbctl lsp-add sw0 sw0-p2 -@@ -23282,192 +23356,159 @@ check ovn-nbctl lsp-add sw0 sw0-p2 - OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup]) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 4] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 5] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23475,67 +23516,53 @@ 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) --priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) --priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,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.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,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,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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 -@@ -23545,76 +23572,73 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) - as hv2 ovn-appctl -t ovn-controller recompute - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, 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]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 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 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl --priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) --priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,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,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST | sort], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVN_CLEANUP([hv1], [hv2]) --- -1.8.3.1 - diff --git a/SOURCES/0002-nbctl-Cache-to-which-switch-or-router-particular-por.patch b/SOURCES/0002-nbctl-Cache-to-which-switch-or-router-particular-por.patch deleted file mode 100644 index 2e1a12e..0000000 --- a/SOURCES/0002-nbctl-Cache-to-which-switch-or-router-particular-por.patch +++ /dev/null @@ -1,442 +0,0 @@ -From 6ab1ea8fcd83712ae3e79b96fe9494db92a1d836 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 11 Dec 2020 11:59:15 +0100 -Subject: [PATCH 2/7] nbctl: Cache to which switch or router particular port - belongs. - -nbctl always iterates over all ports in all logical switches or routers -to find to which logical router/switch current port belongs. This -could be optimized by iterating only once and caching the current -state. This should improve a little bit performance of this utility -in case where many updates are passed in a single nbctl command. - -However, this change alone will slightly reduce performance of -standalone commands, since we're iterating twice over ports on port -deletion. - -Cache is required for the upcoming change that will make nbctl to use -partial set updates. It will allow us to drop redundant iterations -over ports, i.e. to not duplicate work. - -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 209 +++++++++++++++++++++++++++++------------- - 1 file changed, 146 insertions(+), 63 deletions(-) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 3a95f6b1f..cbf2cb1f1 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -125,6 +125,61 @@ static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args, - const struct timer *); - static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]); - -+/* A context for keeping track of which switch/router certain ports are -+ * connected to. */ -+struct nbctl_context { -+ struct ctl_context base; -+ struct shash lsp_to_ls_map; -+ struct shash lrp_to_lr_map; -+ bool context_valid; -+}; -+ -+static void -+nbctl_context_init(struct nbctl_context *nbctx) -+{ -+ nbctx->context_valid = false; -+ shash_init(&nbctx->lsp_to_ls_map); -+ shash_init(&nbctx->lrp_to_lr_map); -+} -+ -+static void -+nbctl_context_destroy(struct nbctl_context *nbctx) -+{ -+ nbctx->context_valid = false; -+ shash_destroy(&nbctx->lsp_to_ls_map); -+ shash_destroy(&nbctx->lrp_to_lr_map); -+} -+ -+/* Casts 'base' into 'struct nbctl_context' and initializes it if needed. */ -+static struct nbctl_context * -+nbctl_context_get(struct ctl_context *base) -+{ -+ struct nbctl_context *nbctx; -+ -+ nbctx = CONTAINER_OF(base, struct nbctl_context, base); -+ -+ if (nbctx->context_valid) { -+ return nbctx; -+ } -+ -+ const struct nbrec_logical_switch *ls; -+ NBREC_LOGICAL_SWITCH_FOR_EACH (ls, base->idl) { -+ for (size_t i = 0; i < ls->n_ports; i++) { -+ shash_add_once(&nbctx->lsp_to_ls_map, ls->ports[i]->name, ls); -+ } -+ } -+ -+ const struct nbrec_logical_router *lr; -+ NBREC_LOGICAL_ROUTER_FOR_EACH (lr, base->idl) { -+ for (size_t i = 0; i < lr->n_ports; i++) { -+ shash_add_once(&nbctx->lrp_to_lr_map, lr->ports[i]->name, lr); -+ } -+ } -+ -+ nbctx->context_valid = true; -+ return nbctx; -+} -+ - int - main(int argc, char *argv[]) - { -@@ -1249,6 +1304,7 @@ static void - nbctl_ls_del(struct ctl_context *ctx) - { - bool must_exist = !shash_find(&ctx->options, "--if-exists"); -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const char *id = ctx->argv[1]; - const struct nbrec_logical_switch *ls = NULL; - -@@ -1261,6 +1317,11 @@ nbctl_ls_del(struct ctl_context *ctx) - return; - } - -+ /* Updating runtime cache. */ -+ for (size_t i = 0; i < ls->n_ports; i++) { -+ shash_find_and_delete(&nbctx->lsp_to_ls_map, ls->ports[i]->name); -+ } -+ - nbrec_logical_switch_delete(ls); - } - -@@ -1317,22 +1378,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, - - /* Returns the logical switch that contains 'lsp'. */ - static char * OVS_WARN_UNUSED_RESULT --lsp_to_ls(const struct ovsdb_idl *idl, -+lsp_to_ls(struct ctl_context *ctx, - const struct nbrec_logical_switch_port *lsp, - const struct nbrec_logical_switch **ls_p) - { -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const struct nbrec_logical_switch *ls; - *ls_p = NULL; - -- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, idl) { -- for (size_t i = 0; i < ls->n_ports; i++) { -- if (ls->ports[i] == lsp) { -- *ls_p = ls; -- return NULL; -- } -- } -+ ls = shash_find_data(&nbctx->lsp_to_ls_map, lsp->name); -+ if (ls) { -+ *ls_p = ls; -+ return NULL; - } -- - /* Can't happen because of the database schema */ - return xasprintf("logical port %s is not part of any logical switch", - lsp->name); -@@ -1353,6 +1411,7 @@ static void - nbctl_lsp_add(struct ctl_context *ctx) - { - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - - const struct nbrec_logical_switch *ls = NULL; - char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls); -@@ -1395,7 +1454,7 @@ nbctl_lsp_add(struct ctl_context *ctx) - } - - const struct nbrec_logical_switch *lsw; -- error = lsp_to_ls(ctx->idl, lsp, &lsw); -+ error = lsp_to_ls(ctx, lsp, &lsw); - if (error) { - ctx->error = error; - return; -@@ -1456,13 +1515,21 @@ nbctl_lsp_add(struct ctl_context *ctx) - lsp); - nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1); - free(new_ports); -+ -+ /* Updating runtime cache. */ -+ shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls); - } - - /* Removes logical switch port 'ls->ports[idx]'. */ - static void --remove_lsp(const struct nbrec_logical_switch *ls, size_t idx) -+remove_lsp(struct ctl_context *ctx, size_t idx, -+ const struct nbrec_logical_switch *ls, -+ const struct nbrec_logical_switch_port *lsp) - { -- const struct nbrec_logical_switch_port *lsp = ls->ports[idx]; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); -+ -+ /* Updating runtime cache. */ -+ shash_find_and_delete(&nbctx->lsp_to_ls_map, lsp->name); - - /* First remove 'lsp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is -@@ -1498,18 +1565,18 @@ nbctl_lsp_del(struct ctl_context *ctx) - - /* Find the switch that contains 'lsp', then delete it. */ - const struct nbrec_logical_switch *ls; -- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) { -- for (size_t i = 0; i < ls->n_ports; i++) { -- if (ls->ports[i] == lsp) { -- remove_lsp(ls, i); -- return; -- } -+ -+ error = lsp_to_ls(ctx, lsp, &ls); -+ if (error) { -+ ctx->error = error; -+ return; -+ } -+ for (size_t i = 0; i < ls->n_ports; i++) { -+ if (ls->ports[i] == lsp) { -+ remove_lsp(ctx, i, ls, lsp); -+ break; - } - } -- -- /* Can't happen because of the database schema. */ -- ctl_error(ctx, "logical port %s is not part of any logical switch", -- ctx->argv[1]); - } - - static void -@@ -1658,7 +1725,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx) - } - - const struct nbrec_logical_switch *ls; -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return; -@@ -3383,6 +3450,7 @@ static void - nbctl_lr_del(struct ctl_context *ctx) - { - bool must_exist = !shash_find(&ctx->options, "--if-exists"); -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const char *id = ctx->argv[1]; - const struct nbrec_logical_router *lr = NULL; - -@@ -3395,6 +3463,11 @@ nbctl_lr_del(struct ctl_context *ctx) - return; - } - -+ /* Updating runtime cache. */ -+ for (size_t i = 0; i < lr->n_ports; i++) { -+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lr->ports[i]->name); -+ } -+ - nbrec_logical_router_delete(lr); - } - -@@ -4672,20 +4745,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, - - /* Returns the logical router that contains 'lrp'. */ - static char * OVS_WARN_UNUSED_RESULT --lrp_to_lr(const struct ovsdb_idl *idl, -+lrp_to_lr(struct ctl_context *ctx, - const struct nbrec_logical_router_port *lrp, - const struct nbrec_logical_router **lr_p) - { -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const struct nbrec_logical_router *lr; - *lr_p = NULL; - -- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, idl) { -- for (size_t i = 0; i < lr->n_ports; i++) { -- if (lr->ports[i] == lrp) { -- *lr_p = lr; -- return NULL; -- } -- } -+ lr = shash_find_data(&nbctx->lrp_to_lr_map, lrp->name); -+ if (lr) { -+ *lr_p = lr; -+ return NULL; - } - - /* Can't happen because of the database schema */ -@@ -4898,6 +4969,7 @@ static void - nbctl_lrp_add(struct ctl_context *ctx) - { - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - - const struct nbrec_logical_router *lr = NULL; - char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); -@@ -4947,7 +5019,7 @@ nbctl_lrp_add(struct ctl_context *ctx) - } - - const struct nbrec_logical_router *bound_lr; -- error = lrp_to_lr(ctx->idl, lrp, &bound_lr); -+ error = lrp_to_lr(ctx, lrp, &bound_lr); - if (error) { - ctx->error = error; - return; -@@ -5053,13 +5125,21 @@ nbctl_lrp_add(struct ctl_context *ctx) - lrp); - nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1); - free(new_ports); -+ -+ /* Updating runtime cache. */ -+ shash_add(&nbctx->lrp_to_lr_map, lrp->name, lr); - } - - /* Removes logical router port 'lr->ports[idx]'. */ - static void --remove_lrp(const struct nbrec_logical_router *lr, size_t idx) -+remove_lrp(struct ctl_context *ctx, size_t idx, -+ const struct nbrec_logical_router *lr, -+ const struct nbrec_logical_router_port *lrp) - { -- const struct nbrec_logical_router_port *lrp = lr->ports[idx]; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); -+ -+ /* Updating runtime cache. */ -+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lrp->name); - - /* First remove 'lrp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is -@@ -5095,18 +5175,18 @@ nbctl_lrp_del(struct ctl_context *ctx) - - /* Find the router that contains 'lrp', then delete it. */ - const struct nbrec_logical_router *lr; -- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) { -- for (size_t i = 0; i < lr->n_ports; i++) { -- if (lr->ports[i] == lrp) { -- remove_lrp(lr, i); -- return; -- } -+ -+ error = lrp_to_lr(ctx, lrp, &lr); -+ if (error) { -+ ctx->error = error; -+ return; -+ } -+ for (size_t i = 0; i < lr->n_ports; i++) { -+ if (lr->ports[i] == lrp) { -+ remove_lrp(ctx, i, lr, lrp); -+ break; - } - } -- -- /* Can't happen because of the database schema. */ -- ctl_error(ctx, "logical port %s is not part of any logical router", -- ctx->argv[1]); - } - - /* Print a list of logical router ports. */ -@@ -5280,7 +5360,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx, - } - - const struct nbrec_logical_switch *ls; -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return NULL; -@@ -5355,7 +5435,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) - return; - } - if (lsp) { -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return; -@@ -6236,7 +6316,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - struct ovsdb_idl_txn *txn; - enum ovsdb_idl_txn_status status; - struct ovsdb_symbol_table *symtab; -- struct ctl_context ctx; -+ struct nbctl_context ctx; - struct ctl_command *c; - struct shash_node *node; - int64_t next_cfg = 0; -@@ -6273,25 +6353,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - ds_init(&c->output); - c->table = NULL; - } -- ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL); -+ nbctl_context_init(&ctx); -+ ctl_context_init(&ctx.base, NULL, idl, txn, symtab, NULL); - for (c = commands; c < &commands[n_commands]; c++) { -- ctl_context_init_command(&ctx, c); -+ ctl_context_init_command(&ctx.base, c); - if (c->syntax->run) { -- (c->syntax->run)(&ctx); -+ (c->syntax->run)(&ctx.base); - } -- if (ctx.error) { -- error = xstrdup(ctx.error); -- ctl_context_done(&ctx, c); -+ if (ctx.base.error) { -+ error = xstrdup(ctx.base.error); -+ ctl_context_done(&ctx.base, c); - goto out_error; - } -- ctl_context_done_command(&ctx, c); -+ ctl_context_done_command(&ctx.base, c); - -- if (ctx.try_again) { -- ctl_context_done(&ctx, NULL); -+ if (ctx.base.try_again) { -+ ctl_context_done(&ctx.base, NULL); - goto try_again; - } - } -- ctl_context_done(&ctx, NULL); -+ ctl_context_done(&ctx.base, NULL); - - SHASH_FOR_EACH (node, &symtab->sh) { - struct ovsdb_symbol *symbol = node->data; -@@ -6322,14 +6403,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - if (status == TXN_UNCHANGED || status == TXN_SUCCESS) { - for (c = commands; c < &commands[n_commands]; c++) { - if (c->syntax->postprocess) { -- ctl_context_init(&ctx, c, idl, txn, symtab, NULL); -- (c->syntax->postprocess)(&ctx); -- if (ctx.error) { -- error = xstrdup(ctx.error); -- ctl_context_done(&ctx, c); -+ ctl_context_init(&ctx.base, c, idl, txn, symtab, NULL); -+ (c->syntax->postprocess)(&ctx.base); -+ if (ctx.base.error) { -+ error = xstrdup(ctx.base.error); -+ ctl_context_done(&ctx.base, c); - goto out_error; - } -- ctl_context_done(&ctx, c); -+ ctl_context_done(&ctx.base, c); - } - } - } -@@ -6417,6 +6498,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - done: ; - } - -+ nbctl_context_destroy(&ctx); - ovsdb_symbol_table_destroy(symtab); - ovsdb_idl_txn_destroy(txn); - the_idl_txn = NULL; -@@ -6434,6 +6516,7 @@ out_error: - ovsdb_idl_txn_destroy(txn); - the_idl_txn = NULL; - -+ nbctl_context_destroy(&ctx); - ovsdb_symbol_table_destroy(symtab); - return error; - } --- -2.28.0 - diff --git a/SOURCES/0002-northd-Move-functions-from-ovn-northd.c-into-ovn-uti.patch b/SOURCES/0002-northd-Move-functions-from-ovn-northd.c-into-ovn-uti.patch deleted file mode 100644 index 36077ec..0000000 --- a/SOURCES/0002-northd-Move-functions-from-ovn-northd.c-into-ovn-uti.patch +++ /dev/null @@ -1,275 +0,0 @@ -From 732b689c8ff0b4bb48f1e7b726bd113642440f8b Mon Sep 17 00:00:00 2001 -From: Justin Pettit -Date: Wed, 28 Nov 2018 12:33:31 -0800 -Subject: [PATCH 02/10] northd: Move functions from ovn-northd.c into ovn-util. - -The upcoming ddlog implementation of northd needs these functions too, -so they should be in a common library. - -Signed-off-by: Justin Pettit -Signed-off-by: Ben Pfaff -Acked-by: Numan Siddique ---- - lib/ovn-util.c | 79 +++++++++++++++++++++++++++++++++++++++------ - lib/ovn-util.h | 11 +++++++ - northd/ovn-northd.c | 50 ---------------------------- - 3 files changed, 80 insertions(+), 60 deletions(-) - -diff --git a/lib/ovn-util.c b/lib/ovn-util.c -index 47c7f57a0..8722f7a48 100644 ---- a/lib/ovn-util.c -+++ b/lib/ovn-util.c -@@ -13,17 +13,21 @@ - */ - - #include -+ -+#include "ovn-util.h" -+ -+#include - #include - - #include "daemon.h" --#include "ovn-util.h" --#include "ovn-dirs.h" --#include "openvswitch/vlog.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); - -@@ -240,27 +244,37 @@ extract_ip_addresses(const char *address, struct lport_addresses *laddrs) - bool - 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 +283,13 @@ extract_lrp_networks(const struct nbrec_logical_router_port *lrp, - } - 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); - } - } -@@ -327,6 +341,23 @@ destroy_lport_addresses(struct lport_addresses *laddrs) - 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'. - * -@@ -661,3 +692,31 @@ ovn_smap_get_uint(const struct smap *smap, const char *key, unsigned int def) - - 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; -+} -diff --git a/lib/ovn-util.h b/lib/ovn-util.h -index a597efb50..f72a801df 100644 ---- a/lib/ovn-util.h -+++ b/lib/ovn-util.h -@@ -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_lrp_networks(const struct nbrec_logical_router_port *, - 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); -@@ -220,4 +228,7 @@ char *str_tolower(const char *orig); - 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); -+ - #endif -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 13fa0ebef..38074e7f4 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -2494,10 +2494,6 @@ join_logical_ports(struct northd_context *ctx, - } - } - --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) -@@ -5061,34 +5057,6 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) - } - } - --/* 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. -@@ -11482,24 +11450,6 @@ sync_address_set(struct northd_context *ctx, const char *name, - 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. - * --- -2.28.0 - diff --git a/SOURCES/0002-ofctrl.c-Avoid-repeatedly-linking-an-installed-flow-.patch b/SOURCES/0002-ofctrl.c-Avoid-repeatedly-linking-an-installed-flow-.patch deleted file mode 100644 index e7cbec0..0000000 --- a/SOURCES/0002-ofctrl.c-Avoid-repeatedly-linking-an-installed-flow-.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 93f257e672f814b87ff30512aadc4afc4387c3eb Mon Sep 17 00:00:00 2001 -From: Han Zhou -Date: Fri, 2 Oct 2020 12:47:52 -0700 -Subject: [PATCH 2/7] ofctrl.c: Avoid repeatedly linking an installed flow and - a desired flow. - -In update_installed_flows_by_compare() there are two loops. The first -loop iterates the installed flows and find its peer in desired flows to: - -1. uninstall flows that are not needed anymore - -2. update flows if needed - -At the same time, it links the desired flow found for the installed flow -which also set the desired flow as the current active installed flow. - -The second loop iterates the desired flows and find its peer in installed -flows to install missing flows. At the same time it will detect if there -are conflict desired flows matching same installed flow then just link -them. - -However, currently in the second loop, it blindly link the desired flows to the -installed flows, without checking if it is already linked in the first loop. -Lucky enough, this won't cause any real problem so far, because when there are -conflict flows, the one found in the first loop will be set as active in the -installed_flow, and in the function link_installed_to_desired() checks if it is -already the active desired flow it just does nothing but return. However, the -check in the link_installed_to_desired() is confusing because a desired_flow -may be linked to the installed_flow already but not the active flow, and the -check is insufficient. It should be rather an assertion and let the caller -ensure that a pair of desired_flow and installed_flow is never linked twice. - -For the above reason, this patch does the following changes: - -1. Removes the check in link_installed_to_desired() and convert it to an assert. - -2. Before calling link_installed_to_desired() in the above mentioned loop, - check if the desired flow is already installed. - -Acked-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit 7cab7bd1268ba67429954da4f73de91090acf779) - -Change-Id: I84f8d7c771c30785dc8c30ce2d4c8c0b4735faae ---- - controller/ofctrl.c | 19 ++++++++++++++----- - 1 file changed, 14 insertions(+), 5 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index e725c00..4425d98 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -806,13 +806,18 @@ desired_flow_set_active(struct desired_flow *d) - d->installed_flow->desired_flow = d; - } - -+/* Adds the desired flow to the list of desired flows that have same match -+ * conditions as the installed flow. -+ * -+ * If the newly added desired flow is the first one in the list, it is also set -+ * as the active one. -+ * -+ * It is caller's responsibility to make sure the link between the pair didn't -+ * exist before. */ - static void - link_installed_to_desired(struct installed_flow *i, struct desired_flow *d) - { -- if (i->desired_flow == d) { -- return; -- } -- -+ ovs_assert(i->desired_flow != d); - if (ovs_list_is_empty(&i->desired_refs)) { - ovs_assert(!i->desired_flow); - i->desired_flow = d; -@@ -1705,8 +1710,12 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - /* 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. */ -+ link_installed_to_desired(i, d); - } -- link_installed_to_desired(i, d); - } - } - --- -1.8.3.1 - diff --git a/SOURCES/0002-ovn-detrace-Improve-DB-connection-error-messages.patch b/SOURCES/0002-ovn-detrace-Improve-DB-connection-error-messages.patch deleted file mode 100644 index a56ab5a..0000000 --- a/SOURCES/0002-ovn-detrace-Improve-DB-connection-error-messages.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 10e4468403b118abd5f67f574d9f4ae922657af6 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Fri, 23 Oct 2020 10:07:48 +0200 -Subject: [PATCH 2/2] ovn-detrace: Improve DB connection error messages. - -Fixes: b326503f8176 ("ovn-detrace: Support SSL remotes.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit 24302dc5cad03a3618a4db92a203236789494ffa) - -Change-Id: I941a158cdae3d5f4a5796423855eaea21e3f7441 ---- - utilities/ovn-detrace.in | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/utilities/ovn-detrace.in b/utilities/ovn-detrace.in -index 2344f52..1dd98df 100755 ---- a/utilities/ovn-detrace.in -+++ b/utilities/ovn-detrace.in -@@ -98,9 +98,12 @@ class OVSDB(object): - 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]) --- -1.8.3.1 - diff --git a/SOURCES/0002-ovn-nbctl-add-ecmp-ecmp-symmetric-reply-to-lr-route-.patch b/SOURCES/0002-ovn-nbctl-add-ecmp-ecmp-symmetric-reply-to-lr-route-.patch deleted file mode 100644 index b51020c..0000000 --- a/SOURCES/0002-ovn-nbctl-add-ecmp-ecmp-symmetric-reply-to-lr-route-.patch +++ /dev/null @@ -1,212 +0,0 @@ -From 5e1bb597df512510dc82ce47f9b65a02e2fb5c0b Mon Sep 17 00:00:00 2001 -Message-Id: <5e1bb597df512510dc82ce47f9b65a02e2fb5c0b.1611833004.git.lorenzo.bianconi@redhat.com> -In-Reply-To: <8770192b3b4732e02679f723ea5903a515c6bd8a.1611833004.git.lorenzo.bianconi@redhat.com> -References: <8770192b3b4732e02679f723ea5903a515c6bd8a.1611833004.git.lorenzo.bianconi@redhat.com> -From: Lorenzo Bianconi -Date: Mon, 25 Jan 2021 14:51:13 +0100 -Subject: [PATCH 2/2] ovn-nbctl: add ecmp/ecmp-symmetric-reply to lr-route-list - command - -Explicitly add ecmp/ecmp-symmetric-reply info to ovn-nbctl -lr-route-list command - -https://bugzilla.redhat.com/show_bug.cgi?id=1915958 - -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - tests/ovn-nbctl.at | 36 ++++++++++++++--------- - utilities/ovn-nbctl.c | 67 +++++++++++++++++++++++++++++++++++-------- - 2 files changed, 78 insertions(+), 25 deletions(-) - ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1544,27 +1544,27 @@ AT_CHECK([ovn-nbctl --ecmp lr-route-add - AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3 lp0]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.1 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.1 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip lp0 ecmp - ]) - - dnl Delete ecmp routes - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip lp0 ecmp - ]) - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.2]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip lp0 ecmp - ]) - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.3 lp0]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl -@@ -1605,7 +1605,12 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10. - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1]) - AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1]) - AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0]) --AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::2]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::3]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::4]) -+AT_CHECK([ovn-nbctl lr-route-add lr0 2002:0db8:1::/64 2001:0db8:0:f103::5]) -+AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6]) - - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -@@ -1615,7 +1620,12 @@ IPv4 Routes - - IPv6 Routes - 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 -- 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip -+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::2 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::3 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::4 dst-ip ecmp -+ 2002:db8:1::/64 2001:db8:0:f103::5 dst-ip -+ 2003:db8:1::/64 2001:db8:0:f103::6 dst-ip ecmp-symmetric-reply - ::/0 2001:db8:0:f101::1 dst-ip - ]) - ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -5443,16 +5443,26 @@ struct ipv4_route { - }; - - static int -+__ipv4_route_cmp(const struct ipv4_route *r1, const struct ipv4_route *r2) -+{ -+ if (r1->priority != r2->priority) { -+ return r1->priority > r2->priority ? -1 : 1; -+ } -+ if (r1->addr != r2->addr) { -+ return ntohl(r1->addr) < ntohl(r2->addr) ? -1 : 1; -+ } -+ return 0; -+} -+ -+static int - ipv4_route_cmp(const void *route1_, const void *route2_) - { - const struct ipv4_route *route1p = route1_; - const struct ipv4_route *route2p = route2_; - -- if (route1p->priority != route2p->priority) { -- return route1p->priority > route2p->priority ? -1 : 1; -- } -- if (route1p->addr != route2p->addr) { -- return ntohl(route1p->addr) < ntohl(route2p->addr) ? -1 : 1; -+ int ret = __ipv4_route_cmp(route1p, route2p); -+ if (ret) { -+ return ret; - } - return route_cmp_details(route1p->route, route2p->route); - } -@@ -5464,15 +5474,21 @@ struct ipv6_route { - }; - - static int -+__ipv6_route_cmp(const struct ipv6_route *r1, const struct ipv6_route *r2) -+{ -+ if (r1->priority != r2->priority) { -+ return r1->priority > r2->priority ? -1 : 1; -+ } -+ return memcmp(&r1->addr, &r2->addr, sizeof(r1->addr)); -+} -+ -+static int - ipv6_route_cmp(const void *route1_, const void *route2_) - { - const struct ipv6_route *route1p = route1_; - const struct ipv6_route *route2p = route2_; - -- if (route1p->priority != route2p->priority) { -- return route1p->priority > route2p->priority ? -1 : 1; -- } -- int ret = memcmp(&route1p->addr, &route2p->addr, sizeof(route1p->addr)); -+ int ret = __ipv6_route_cmp(route1p, route2p); - if (ret) { - return ret; - } -@@ -5480,7 +5496,8 @@ ipv6_route_cmp(const void *route1_, cons - } - - static void --print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) -+print_route(const struct nbrec_logical_router_static_route *route, -+ struct ds *s, bool ecmp) - { - - char *prefix = normalize_prefix_str(route->ip_prefix); -@@ -5503,6 +5520,14 @@ print_route(const struct nbrec_logical_r - ds_put_format(s, " (learned)"); - } - -+ if (ecmp) { -+ ds_put_cstr(s, " ecmp"); -+ } -+ -+ if (smap_get_bool(&route->options, "ecmp_symmetric_reply", false)) { -+ ds_put_cstr(s, " ecmp-symmetric-reply"); -+ } -+ - if (route->bfd) { - ds_put_cstr(s, " bfd"); - } -@@ -5572,7 +5597,16 @@ nbctl_lr_route_list(struct ctl_context * - ds_put_cstr(&ctx->output, "IPv4 Routes\n"); - } - for (int i = 0; i < n_ipv4_routes; i++) { -- print_route(ipv4_routes[i].route, &ctx->output); -+ bool ecmp = false; -+ if (i < n_ipv4_routes - 1 && -+ !__ipv4_route_cmp(&ipv4_routes[i], &ipv4_routes[i + 1])) { -+ ecmp = true; -+ } else if (i > 0 && -+ !__ipv4_route_cmp(&ipv4_routes[i], -+ &ipv4_routes[i - 1])) { -+ ecmp = true; -+ } -+ print_route(ipv4_routes[i].route, &ctx->output, ecmp); - } - - if (n_ipv6_routes) { -@@ -5580,7 +5614,16 @@ nbctl_lr_route_list(struct ctl_context * - n_ipv4_routes ? "\n" : ""); - } - for (int i = 0; i < n_ipv6_routes; i++) { -- print_route(ipv6_routes[i].route, &ctx->output); -+ bool ecmp = false; -+ if (i < n_ipv6_routes - 1 && -+ !__ipv6_route_cmp(&ipv6_routes[i], &ipv6_routes[i + 1])) { -+ ecmp = true; -+ } else if (i > 0 && -+ !__ipv6_route_cmp(&ipv6_routes[i], -+ &ipv6_routes[i - 1])) { -+ ecmp = true; -+ } -+ print_route(ipv6_routes[i].route, &ctx->output, ecmp); - } - - free(ipv4_routes); diff --git a/SOURCES/0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch b/SOURCES/0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch deleted file mode 100644 index 0a79e7c..0000000 --- a/SOURCES/0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch +++ /dev/null @@ -1,498 +0,0 @@ -From ab83b9905ec3531b3d8b975f8035ca32d9254091 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Tue, 3 Nov 2020 16:51:21 +0100 -Subject: [PATCH 2/2] ovn-northd: Limit self originated ARP/ND broadcast - domain. - -Considering the following large scale deployment: -external-network -- public-logical-switch -- router-1 -- sw1 -- VIF-1 - -- router-2 -- sw2 -- VIF-2 - ... - -- router-n -- swn -- VIF-n - -To avoid hitting the max number of OVS resubmits (4K currently) OVN already -restricted the broadcast domain for ARP/ND requests received from the -external network and targeting an OVN-owned IP (e.g., owned by router-x). -Such packets are not flooded in the broadcast domain of the public logical -switch and instead are forwarded only to the port connecting router-x. - -However, the max number of OVS resubmits can also be hit in the following -scenarios: -- router-x tries to resolve an IP owned by router-y (e.g., VIF-x trying to - reach VIF-y via floating IP). -- router-x tries to resolve an IP owned by the external network. - -Because ARP/ND requests in the above cases are originated by OVN router ports -they were being flooded in the complete broadcast domain of the public -logical switch. - -Instead, we now create a new Multicast_Group for each logical switch and add -all non-router ports to it. ARP/ND requests are now forwarded to the router -port that owns the IP (if any) and then flooded in this restricted MC_FLOOD_L2 -broadcast domain. - -Fixes: 32f5ebb06226 ("ovn-northd: Limit ARP/ND broadcast domain whenever possible.") -Acked-by: Mark Michelson -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit 8c6a5bc21847dab8ccbe18ab1e4b563ddca13379) - -Change-Id: Ic35a6e7531d09374c0b3ba9c8be3bd986adc7941 ---- - lib/mcast-group-index.h | 1 + - northd/ovn-northd.8.xml | 19 +++++------ - northd/ovn-northd.c | 84 ++++++++++++++++++++++++++++++------------------- - tests/ovn.at | 50 ++++++++++++++--------------- - 4 files changed, 86 insertions(+), 68 deletions(-) - -diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h -index ba995ba..72af117 100644 ---- a/lib/mcast-group-index.h -+++ b/lib/mcast-group-index.h -@@ -30,6 +30,7 @@ enum ovn_mcast_tunnel_keys { - 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 --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index f1c7c9b..8206982 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -1367,18 +1367,19 @@ output; - - -
  • -- 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. -
  • - -
  • -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index ecbe98e..ce291ec 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -1493,6 +1493,12 @@ lsp_is_external(const struct nbrec_logical_switch_port *nbsp) - } - - 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; -@@ -2424,7 +2430,7 @@ join_logical_ports(struct northd_context *ctx, - * 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; -@@ -3105,7 +3111,7 @@ ovn_port_update_sbrec(struct northd_context *ctx, - - 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); -@@ -3808,6 +3814,10 @@ static const struct multicast_group mc_static = - 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) -@@ -6372,12 +6382,11 @@ build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op, - 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; - -@@ -6393,7 +6402,7 @@ build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op, - ds_cstr(ð_src)); - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority, - ds_cstr(&match), -- "outport = \""MC_FLOOD"\"; output;"); -+ "outport = \""MC_FLOOD_L2"\"; output;"); - - sset_destroy(&all_eth_addrs); - ds_destroy(ð_src); -@@ -6439,14 +6448,16 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips, - 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); -+ } else { -+ ds_put_format(&actions, "outport = %s; output;", patch_op->json_key); - } -- 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); - -@@ -6476,14 +6487,9 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, - 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); -@@ -6558,17 +6564,28 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, - - 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 -@@ -6908,7 +6925,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - * - 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; - } -@@ -6983,8 +7000,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - "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, -@@ -7066,7 +7082,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - 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; -@@ -7326,7 +7342,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - * 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_); - } -@@ -10786,7 +10802,7 @@ build_arp_resolve_flows_for_lrouter_port( - &op->nbrp->header_); - } - } -- } 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 logical switch port that backs a VM or a container. - * Extract its addresses. For each of the address, go through all -@@ -10870,7 +10886,7 @@ build_arp_resolve_flows_for_lrouter_port( - } - } - } -- } 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. -@@ -10974,7 +10990,7 @@ build_arp_resolve_flows_for_lrouter_port( - } - } - } -- } 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 -@@ -11929,6 +11945,10 @@ build_mcast_groups(struct northd_context *ctx, - } 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. - */ -@@ -12372,7 +12392,7 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, - 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); - } -diff --git a/tests/ovn.at b/tests/ovn.at -index ea4a6da..180fb91 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -3626,7 +3626,7 @@ test_ip() { - 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 -@@ -3637,25 +3637,21 @@ test_ip() { - # 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 -+ 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 - - # 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 -@@ -3792,9 +3788,9 @@ for i in 1 2 3; do - 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 -@@ -3803,7 +3799,7 @@ for i in 1 2 3; do - 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 -@@ -19895,7 +19891,7 @@ match_r1_metadata="metadata=0x${r1_dp_key}" - 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" - -@@ -19919,7 +19915,7 @@ dst_ipv6=00100000000000000000000000000001 - 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([ -@@ -19951,7 +19947,7 @@ ovn-nbctl --wait=hv sync - 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" - -@@ -19975,7 +19971,7 @@ dst_ipv6=00100000000000000000000000000011 - 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([ -@@ -20015,7 +20011,7 @@ ovn-nbctl --wait=hv sync - # - 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 -@@ -20025,7 +20021,7 @@ arp_tpa=10.0.0.2 - 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 -@@ -20041,10 +20037,10 @@ nd_target=fe80::200:ff:fe00:200 - # 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 - ]) -@@ -20056,13 +20052,13 @@ nd_target=fe80::200:1ff:fe00:0 - # - 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 -@@ -20072,7 +20068,7 @@ dl_src=00:00:00:02:00:00 - # 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 - ]) - -@@ -20080,7 +20076,7 @@ dl_src=00:00:01:00:00:00 - 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" - -@@ -20144,7 +20140,7 @@ dst_ipv6=00100000000000000000000000000111 - 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([ --- -1.8.3.1 - diff --git a/SOURCES/0002-ovn-northd-Move-DHCP-Options-and-Response-to-a-funct.patch b/SOURCES/0002-ovn-northd-Move-DHCP-Options-and-Response-to-a-funct.patch deleted file mode 100644 index d4ba4c9..0000000 --- a/SOURCES/0002-ovn-northd-Move-DHCP-Options-and-Response-to-a-funct.patch +++ /dev/null @@ -1,144 +0,0 @@ -From e513bafe5718f42844f41d248ddf1777b71aaa50 Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:29 +0000 -Subject: [PATCH 02/16] ovn-northd: Move DHCP Options and Response to a - function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 100 ++++++++++++++++++++++---------------------- - 1 file changed, 51 insertions(+), 49 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index d17cc55ac..a5b28584f 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6780,55 +6780,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- -- /* Logical switch ingress table 14 and 15: DHCP options and response -- * priority 100 flows. */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp) { -- continue; -- } -- -- 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; -- } -- -- if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) { -- /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport. -- */ -- continue; -- } -- -- bool is_external = lsp_is_external(op->nbsp); -- if (is_external && (!op->od->n_localnet_ports || -- !op->nbsp->ha_chassis_group)) { -- /* If it's an external port and there are no localnet ports -- * and if it doesn't belong to an HA chassis group ignore it. */ -- continue; -- } -- -- for (size_t i = 0; i < op->n_lsp_addrs; i++) { -- if (is_external) { -- for (size_t j = 0; j < op->od->n_localnet_ports; j++) { -- build_dhcpv4_options_flows( -- op, &op->lsp_addrs[i], -- op->od->localnet_ports[j]->json_key, is_external, -- lflows); -- build_dhcpv6_options_flows( -- op, &op->lsp_addrs[i], -- op->od->localnet_ports[j]->json_key, is_external, -- lflows); -- } -- } else { -- build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op->json_key, -- is_external, lflows); -- build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op->json_key, -- is_external, lflows); -- } -- } -- } -- - /* Logical switch ingress table 17 and 18: DNS lookup and response - * priority 100 flows. - */ -@@ -7484,6 +7435,55 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb, - } - - -+/* Logical switch ingress table 14 and 15: DHCP options and response -+ * priority 100 flows. */ -+static void -+build_lswitch_dhcp_options_and_response(struct ovn_port *op, -+ struct hmap *lflows) -+{ -+ if (op->nbsp) { -+ 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. */ -+ return; -+ } -+ -+ if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) { -+ /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport. -+ */ -+ return; -+ } -+ -+ bool is_external = lsp_is_external(op->nbsp); -+ if (is_external && (!op->od->n_localnet_ports || -+ !op->nbsp->ha_chassis_group)) { -+ /* If it's an external port and there are no localnet ports -+ * and if it doesn't belong to an HA chassis group ignore it. */ -+ return; -+ } -+ -+ for (size_t i = 0; i < op->n_lsp_addrs; i++) { -+ if (is_external) { -+ for (size_t j = 0; j < op->od->n_localnet_ports; j++) { -+ build_dhcpv4_options_flows( -+ op, &op->lsp_addrs[i], -+ op->od->localnet_ports[j]->json_key, is_external, -+ lflows); -+ build_dhcpv6_options_flows( -+ op, &op->lsp_addrs[i], -+ op->od->localnet_ports[j]->json_key, is_external, -+ lflows); -+ } -+ } else { -+ build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op->json_key, -+ is_external, lflows); -+ build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op->json_key, -+ is_external, lflows); -+ } -+ } -+ } -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -11371,6 +11371,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - lsi->ports, - &lsi->actions, - &lsi->match); -+ build_lswitch_dhcp_options_and_response(op,lsi->lflows); -+ - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, - &lsi->actions); --- -2.29.2 - diff --git a/SOURCES/0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch b/SOURCES/0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch deleted file mode 100644 index 035f8f5..0000000 --- a/SOURCES/0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 4d84a20d68a7c3ceec726aefaa0eca234598fb97 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Mon, 5 Oct 2020 22:26:56 +0530 -Subject: [PATCH 2/5] ovn-trace: Don't assert for next(stage=ingress,..). - -The commit [1] allowed next action to advance from ingress to egress pipeline, but -ovn-trace was not modified to handle this condition. Also corrected the ovntrace node -format message as per the next stage. - -[1] - b4b68177eb2f("Fix conntrack entry leaks because of TCP RST packets not sent to conntrack.") - -Fixes: b4b68177eb2f("Fix conntrack entry leaks because of TCP RST packets not sent to conntrack.") -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from upstream branch-20.09 commit 3583f4312eb6ad5af86053a2b0c795ee9dbec81e) - -Change-Id: Ia6c11333a51a2dc85fbdc7829e327b412b6e88d1 ---- - utilities/ovn-trace.c | 13 ++++++++----- - 1 file changed, 8 insertions(+), 5 deletions(-) - -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index 33afc4f43..0920ae159 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -1888,12 +1888,15 @@ execute_next(const struct ovnact_next *next, - 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); --- -2.26.2 - diff --git a/SOURCES/0002-ovsdb-idl-Try-committing-the-pending-txn-in-ovsdb_id.patch b/SOURCES/0002-ovsdb-idl-Try-committing-the-pending-txn-in-ovsdb_id.patch deleted file mode 100644 index 7f4c1c4..0000000 --- a/SOURCES/0002-ovsdb-idl-Try-committing-the-pending-txn-in-ovsdb_id.patch +++ /dev/null @@ -1,217 +0,0 @@ -From a2271039d49a12390ca3118fb3a90a057577d360 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Fri, 5 Jun 2020 14:00:29 +0530 -Subject: [PATCH 2/4] ovsdb idl: Try committing the pending txn in - ovsdb_idl_loop_run. - -The function ovsdb_idl_loop_run(), after calling ovsdb_idl_run(), -returns a transaction object (of type 'struct ovsdb_idl_txn'). -The returned transaction object can be NULL if there is a pending -transaction (loop->committing_txn) in the idl loop object. - -Normally the clients of idl library, first call ovsdb_idl_loop_run(), -then do their own processing and create any idl transactions during -this processing and then finally call ovsdb_idl_loop_commit_and_wait(). - -If ovsdb_idl_loop_run() returns NULL transaction object, then much -of the processing done by the client gets wasted as in the case -of ovn-controller. - -The client (in this case ovn-controller), can skip the processing -and instead call ovsdb_idl_loop_commit_and_wait() if the transaction -oject is NULL. But ovn-controller uses IDL tracking and it may -loose the tracked changes in that run. - -This patch tries to improve this scenario, by checking if the -pending transaction can be committed in the ovsdb_idl_loop_run() -itself and if the pending transaction is cleared (because of the -response messages from ovsdb-server due to a transaction message -in the previous run), ovsdb_idl_loop_run() can return a valid -transaction object. - -CC: Han Zhou -Signed-off-by: Numan Siddique -Signed-off-by: Ben Pfaff ---- - openvswitch-2.13.0/lib/ovsdb-idl.c | 143 +++++++++++++++++++---------- - 1 file changed, 93 insertions(+), 50 deletions(-) - -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl.c b/openvswitch-2.13.0/lib/ovsdb-idl.c -index 1535ad7b5..2d351791f 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl.c -+++ b/openvswitch-2.13.0/lib/ovsdb-idl.c -@@ -385,6 +385,8 @@ static void ovsdb_idl_send_cond_change(struct ovsdb_idl *idl); - static void ovsdb_idl_destroy_indexes(struct ovsdb_idl_table *); - static void ovsdb_idl_add_to_indexes(const struct ovsdb_idl_row *); - static void ovsdb_idl_remove_from_indexes(const struct ovsdb_idl_row *); -+static int ovsdb_idl_try_commit_loop_txn(struct ovsdb_idl_loop *loop, -+ bool *may_need_wakeup); - - static void - ovsdb_idl_db_init(struct ovsdb_idl_db *db, const struct ovsdb_idl_class *class, -@@ -5329,6 +5331,12 @@ struct ovsdb_idl_txn * - ovsdb_idl_loop_run(struct ovsdb_idl_loop *loop) - { - ovsdb_idl_run(loop->idl); -+ -+ /* See if we can commit the loop->committing_txn. */ -+ if (loop->committing_txn) { -+ ovsdb_idl_try_commit_loop_txn(loop, NULL); -+ } -+ - loop->open_txn = (loop->committing_txn - || ovsdb_idl_get_seqno(loop->idl) == loop->skip_seqno - ? NULL -@@ -5336,6 +5344,87 @@ ovsdb_idl_loop_run(struct ovsdb_idl_loop *loop) - return loop->open_txn; - } - -+/* Attempts to commit the current transaction, if one is open. -+ * -+ * If a transaction was open, in this or a previous iteration of the main loop, -+ * and had not before finished committing (successfully or unsuccessfully), the -+ * return value is one of: -+ * -+ * 1: The transaction committed successfully (or it did not change anything in -+ * the database). -+ * 0: The transaction failed. -+ * -1: The commit is still in progress. -+ * -+ * Thus, the return value is -1 if the transaction is in progress and otherwise -+ * true for success, false for failure. -+ * -+ * (In the corner case where the IDL sends a transaction to the database and -+ * the database commits it, and the connection between the IDL and the database -+ * drops before the IDL receives the message confirming the commit, this -+ * function can return 0 even though the transaction succeeded.) -+ */ -+static int -+ovsdb_idl_try_commit_loop_txn(struct ovsdb_idl_loop *loop, -+ bool *may_need_wakeup) -+{ -+ if (!loop->committing_txn) { -+ /* Not a meaningful return value: no transaction was in progress. */ -+ return 1; -+ } -+ -+ int retval; -+ struct ovsdb_idl_txn *txn = loop->committing_txn; -+ -+ enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(txn); -+ if (status != TXN_INCOMPLETE) { -+ switch (status) { -+ case TXN_TRY_AGAIN: -+ /* We want to re-evaluate the database when it's changed from -+ * the contents that it had when we started the commit. (That -+ * might have already happened.) */ -+ loop->skip_seqno = loop->precommit_seqno; -+ if (ovsdb_idl_get_seqno(loop->idl) != loop->skip_seqno -+ && may_need_wakeup) { -+ *may_need_wakeup = true; -+ } -+ retval = 0; -+ break; -+ -+ case TXN_SUCCESS: -+ /* Possibly some work on the database was deferred because no -+ * further transaction could proceed. Wake up again. */ -+ retval = 1; -+ loop->cur_cfg = loop->next_cfg; -+ if (may_need_wakeup) { -+ *may_need_wakeup = true; -+ } -+ break; -+ -+ case TXN_UNCHANGED: -+ retval = 1; -+ loop->cur_cfg = loop->next_cfg; -+ break; -+ -+ case TXN_ABORTED: -+ case TXN_NOT_LOCKED: -+ case TXN_ERROR: -+ retval = 0; -+ break; -+ -+ case TXN_UNCOMMITTED: -+ case TXN_INCOMPLETE: -+ default: -+ OVS_NOT_REACHED(); -+ } -+ ovsdb_idl_txn_destroy(txn); -+ loop->committing_txn = NULL; -+ } else { -+ retval = -1; -+ } -+ -+ return retval; -+} -+ - /* Attempts to commit the current transaction, if one is open, and sets up the - * poll loop to wake up when some more work might be needed. - * -@@ -5366,57 +5455,11 @@ ovsdb_idl_loop_commit_and_wait(struct ovsdb_idl_loop *loop) - loop->precommit_seqno = ovsdb_idl_get_seqno(loop->idl); - } - -- struct ovsdb_idl_txn *txn = loop->committing_txn; -- int retval; -- if (txn) { -- enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(txn); -- if (status != TXN_INCOMPLETE) { -- switch (status) { -- case TXN_TRY_AGAIN: -- /* We want to re-evaluate the database when it's changed from -- * the contents that it had when we started the commit. (That -- * might have already happened.) */ -- loop->skip_seqno = loop->precommit_seqno; -- if (ovsdb_idl_get_seqno(loop->idl) != loop->skip_seqno) { -- poll_immediate_wake(); -- } -- retval = 0; -- break; -- -- case TXN_SUCCESS: -- /* Possibly some work on the database was deferred because no -- * further transaction could proceed. Wake up again. */ -- retval = 1; -- loop->cur_cfg = loop->next_cfg; -- poll_immediate_wake(); -- break; -- -- case TXN_UNCHANGED: -- retval = 1; -- loop->cur_cfg = loop->next_cfg; -- break; -- -- case TXN_ABORTED: -- case TXN_NOT_LOCKED: -- case TXN_ERROR: -- retval = 0; -- break; -- -- case TXN_UNCOMMITTED: -- case TXN_INCOMPLETE: -- default: -- OVS_NOT_REACHED(); -- } -- ovsdb_idl_txn_destroy(txn); -- loop->committing_txn = NULL; -- } else { -- retval = -1; -- } -- } else { -- /* Not a meaningful return value: no transaction was in progress. */ -- retval = 1; -+ bool may_need_wakeup = false; -+ int retval = ovsdb_idl_try_commit_loop_txn(loop, &may_need_wakeup); -+ if (may_need_wakeup) { -+ poll_immediate_wake(); - } -- - ovsdb_idl_wait(loop->idl); - - return retval; --- -2.26.2 - diff --git a/SOURCES/0003-actions-Add-a-new-OVN-action-reject.patch b/SOURCES/0003-actions-Add-a-new-OVN-action-reject.patch deleted file mode 100644 index 4bcb5aa..0000000 --- a/SOURCES/0003-actions-Add-a-new-OVN-action-reject.patch +++ /dev/null @@ -1,462 +0,0 @@ -From 6fc44a540fdb1bc9ac0d0cc1ef5786efadbee13c Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Tue, 29 Sep 2020 15:25:43 +0530 -Subject: [PATCH 3/5] actions: Add a new OVN action - reject {}. - -This action is similar to tcp_reset and icmp4/icmp6 action. If the matching -packet is an IPv4 of IPv6 TCP packet, it sends out TCP RST packet else it -sends out ICMPv4 or ICMPv6 Destination unreachable packet. - -While transforming the original packet to the reject packet, this action -implicitly swaps the eth source with eth destination and IP source with -IP destination. - -A future patch will make use of this action for reject ACL. - -Acked-by: Mark Michelson -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from upstream master commit 64f8c9e9ff1ce26d06831995514cf66319f16b34) - -Change-Id: Iacabf30344f21daf51d53989b4774fbdbafe00be ---- - controller/pinctrl.c | 64 +++++++++++++++++++++++-------- - include/ovn/actions.h | 9 ++++- - lib/actions.c | 22 +++++++++++ - ovn-sb.xml | 16 ++++++++ - tests/ovn.at | 8 ++++ - utilities/ovn-trace.c | 88 ++++++++++++++++++++++++++++++++----------- - 6 files changed, 168 insertions(+), 39 deletions(-) - -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 7099687eb..d85ba504d 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -1542,7 +1542,7 @@ static void - 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. */ -@@ -1563,8 +1563,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, - 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); -@@ -1586,8 +1586,9 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, - 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) { -@@ -1634,8 +1635,12 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, - 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); -@@ -1692,7 +1697,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, - 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. */ -@@ -1707,14 +1713,23 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, - - 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); - } - -@@ -1745,6 +1760,18 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, - 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) - { -@@ -2848,18 +2875,23 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - - 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: -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 636cb4bc1..b4e5acabb 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -95,7 +95,8 @@ struct ovn_extend_table; - 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) \ - - /* enum ovnact_type, with a member OVNACT_ for each action. */ - enum OVS_PACKED_ENUM ovnact_type { -@@ -605,6 +606,12 @@ enum action_opcode { - /* 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. */ -diff --git a/lib/actions.c b/lib/actions.c -index 5fe0a3897..1e1bdeff2 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -1386,6 +1386,12 @@ parse_CLONE(struct action_context *ctx) - parse_nested_action(ctx, OVNACT_CLONE, NULL, WR_DEFAULT); - } - -+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) -@@ -1479,6 +1485,12 @@ format_TRIGGER_EVENT(const struct ovnact_controller_event *event, - ds_put_cstr(s, ");"); - } - -+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, -@@ -1560,6 +1572,14 @@ encode_TCP_RESET(const struct ovnact_nest *on, - encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts); - } - -+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, -@@ -3567,6 +3587,8 @@ parse_action(struct action_context *ctx) - parse_fwd_group_action(ctx); - } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) { - ovnact_put_DHCP6_REPLY(ctx->ovnacts); -+ } else if (lexer_match_id(ctx->lexer, "reject")) { -+ parse_REJECT(ctx); - } else { - lexer_syntax_error(ctx->lexer, "expecting action"); - } -diff --git a/ovn-sb.xml b/ovn-sb.xml -index 59888a155..182ff0a8a 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -2191,6 +2191,22 @@ tcp.flags = RST; -

    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;
    -
    -

    -diff --git a/tests/ovn.at b/tests/ovn.at -index 53f5d4d96..337ab4e7d 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1593,6 +1593,14 @@ tcp_reset { }; - 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) -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index 0920ae159..ad33f8e36 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -1638,16 +1638,23 @@ execute_nd_ns(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, - 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 +1670,23 @@ execute_icmp4(const struct ovnact_nest *on, - 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 */ -@@ -1689,15 +1703,23 @@ 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) -+ 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.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; -@@ -1711,6 +1733,23 @@ execute_tcp_reset(const struct ovnact_nest *on, - table_id, pipeline, &node->subs); - } - -+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, -@@ -2315,23 +2354,23 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - 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: -@@ -2340,13 +2379,18 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - - 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_TRIGGER_EVENT: - break; - --- -2.26.2 - diff --git a/SOURCES/0003-binding-Set-Logical_Switch_Port.up-when-all-OVS-flow.patch b/SOURCES/0003-binding-Set-Logical_Switch_Port.up-when-all-OVS-flow.patch deleted file mode 100644 index c66c5be..0000000 --- a/SOURCES/0003-binding-Set-Logical_Switch_Port.up-when-all-OVS-flow.patch +++ /dev/null @@ -1,483 +0,0 @@ -From 3719a1add73b860c50d85fad0b270c1b69fb9147 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 13 Jan 2021 10:23:32 +0100 -Subject: [PATCH 3/3] binding: Set Logical_Switch_Port.up when all OVS flows - are installed. - -Using the ofctrl-seqno generic barrier, register a new type of -notifications for Port_Bindings. This allows us to delay setting -the Logical_Switch_Port.up field until all OVS flows corresponding -to the logical port and underlying OVS interface have been installed. - -This commit also marks the OVS interface as "installed by OVN" by -setting a new "ovn-installed" external-id in the OVS Interface record -when the port is fully wired by OVN. - -Signed-off-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique -(cherry picked from upstream master commit 4d3cb42b076bb58fd8f01ab8ad146ffd539f2152) - -Change-Id: Id8e4fc3b28110cab2571d4ff8bc5ef81ae9b88c4 ---- - NEWS | 6 ++ - controller/binding.c | 181 ++++++++++++++++++++++++++++++++++++++++++++ - controller/binding.h | 5 ++ - controller/ovn-controller.c | 14 +++- - northd/ovn-northd.c | 6 +- - ovn-sb.ovsschema | 5 +- - ovn-sb.xml | 8 ++ - tests/ovn.at | 32 ++++++++ - utilities/ovn-sbctl.c | 4 + - 9 files changed, 254 insertions(+), 7 deletions(-) - -diff --git a/NEWS b/NEWS -index 7d2ba56..e89c5f4 100644 ---- a/NEWS -+++ b/NEWS -@@ -4,6 +4,12 @@ Post-v20.12.0 - - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD - availability check for OVN static routes. - [0] https://tools.ietf.org/html/rfc5880) -+ - Change the semantic of the "Logical_Switch_Port.up" field such that it is -+ set to "true" only when all corresponding OVS openflow operations have -+ been processed. This also introduces a new "OVS.Interface.external-id", -+ "ovn-installed". This external-id is set by ovn-controller only after all -+ openflow operations corresponding to the OVS interface being added have -+ been processed. - - OVN v20.12.0 - 18 Dec 2020 - -------------------------- -diff --git a/controller/binding.c b/controller/binding.c -index c8e8591..d44f635 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -18,6 +18,7 @@ - #include "ha-chassis.h" - #include "lflow.h" - #include "lport.h" -+#include "ofctrl-seqno.h" - #include "patch.h" - - #include "lib/bitmap.h" -@@ -34,6 +35,38 @@ - - VLOG_DEFINE_THIS_MODULE(binding); - -+/* External ID to be set in the OVS.Interface record when the OVS interface -+ * is ready for use, i.e., is bound to an OVN port and its corresponding -+ * flows have been installed. -+ */ -+#define OVN_INSTALLED_EXT_ID "ovn-installed" -+ -+/* Set of OVS interface IDs that have been released in the most recent -+ * processing iterations. This gets updated in release_lport() and is -+ * periodically emptied in binding_seqno_run(). -+ */ -+static struct sset binding_iface_released_set = -+ SSET_INITIALIZER(&binding_iface_released_set); -+ -+/* Set of OVS interface IDs that have been bound in the most recent -+ * processing iterations. This gets updated in release_lport() and is -+ * periodically emptied in binding_seqno_run(). -+ */ -+static struct sset binding_iface_bound_set = -+ SSET_INITIALIZER(&binding_iface_bound_set); -+ -+static void -+binding_iface_released_add(const char *iface_id) -+{ -+ sset_add(&binding_iface_released_set, iface_id); -+} -+ -+static void -+binding_iface_bound_add(const char *iface_id) -+{ -+ sset_add(&binding_iface_bound_set, iface_id); -+} -+ - #define OVN_QOS_TYPE "linux-htb" - - struct qos_queue { -@@ -845,6 +878,7 @@ claim_lport(const struct sbrec_port_binding *pb, - return false; - } - -+ binding_iface_bound_add(pb->logical_port); - if (pb->chassis) { - VLOG_INFO("Changing chassis for lport %s from %s to %s.", - pb->logical_port, pb->chassis->name, -@@ -908,7 +942,9 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - sbrec_port_binding_set_virtual_parent(pb, NULL); - } - -+ sbrec_port_binding_set_up(pb, NULL, 0); - update_lport_tracking(pb, tracked_datapaths); -+ binding_iface_released_add(pb->logical_port); - VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); - return true; - } -@@ -2358,3 +2394,148 @@ delete_done: - destroy_qos_map(&qos_map); - return handled; - } -+ -+/* Registered ofctrl seqno type for port_binding flow installation. */ -+static size_t binding_seq_type_pb_cfg; -+ -+/* Binding specific seqno to be acked by ofctrl when flows for new interfaces -+ * have been installed. -+ */ -+static uint32_t binding_iface_seqno = 0; -+ -+/* Map indexed by iface-id containing the sequence numbers that when acked -+ * indicate that the OVS flows for the iface-id have been installed. -+ */ -+static struct simap binding_iface_seqno_map = -+ SIMAP_INITIALIZER(&binding_iface_seqno_map); -+ -+void -+binding_init(void) -+{ -+ binding_seq_type_pb_cfg = ofctrl_seqno_add_type(); -+} -+ -+/* Processes new release/bind operations OVN ports. For newly bound ports -+ * it creates ofctrl seqno update requests that will be acked when -+ * corresponding OVS flows have been installed. -+ * -+ * NOTE: Should be called only when valid SB and OVS transactions are -+ * available. -+ */ -+void -+binding_seqno_run(struct shash *local_bindings) -+{ -+ const char *iface_id; -+ const char *iface_id_next; -+ -+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) { -+ struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ -+ /* If the local binding still exists (i.e., the OVS interface is -+ * still configured locally) then remove the external id and remove -+ * it from the in-flight seqno map. -+ */ -+ if (lb_node) { -+ struct local_binding *lb = lb_node->data; -+ -+ if (lb->iface && smap_get(&lb->iface->external_ids, -+ OVN_INSTALLED_EXT_ID)) { -+ ovsrec_interface_update_external_ids_delkey( -+ lb->iface, OVN_INSTALLED_EXT_ID); -+ } -+ } -+ simap_find_and_delete(&binding_iface_seqno_map, iface_id); -+ sset_delete(&binding_iface_released_set, -+ SSET_NODE_FROM_NAME(iface_id)); -+ } -+ -+ bool new_ifaces = false; -+ uint32_t new_seqno = binding_iface_seqno + 1; -+ -+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_bound_set) { -+ struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ -+ if (lb_node) { -+ /* This is a newly bound interface, make sure we reset the -+ * Port_Binding 'up' field and the OVS Interface 'external-id'. -+ */ -+ struct local_binding *lb = lb_node->data; -+ -+ ovs_assert(lb->pb && lb->iface); -+ new_ifaces = true; -+ -+ if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) { -+ ovsrec_interface_update_external_ids_delkey( -+ lb->iface, OVN_INSTALLED_EXT_ID); -+ } -+ sbrec_port_binding_set_up(lb->pb, NULL, 0); -+ simap_put(&binding_iface_seqno_map, lb->name, new_seqno); -+ } -+ sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id)); -+ } -+ -+ /* Request a seqno update when the flows for new interfaces have been -+ * installed in OVS. -+ */ -+ if (new_ifaces) { -+ binding_iface_seqno = new_seqno; -+ ofctrl_seqno_update_create(binding_seq_type_pb_cfg, new_seqno); -+ } -+} -+ -+/* Processes ofctrl seqno ACKs for new bindings. Sets the -+ * 'OVN_INSTALLED_EXT_ID' external-id in the OVS interface and the -+ * Port_Binding.up field for all ports for which OVS flows have been -+ * installed. -+ * -+ * NOTE: Should be called only when valid SB and OVS transactions are -+ * available. -+ */ -+void -+binding_seqno_install(struct shash *local_bindings) -+{ -+ struct ofctrl_acked_seqnos *acked_seqnos = -+ ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg); -+ struct simap_node *node; -+ struct simap_node *node_next; -+ -+ SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { -+ struct shash_node *lb_node = shash_find(local_bindings, node->name); -+ bool up = true; -+ -+ if (!lb_node) { -+ goto del_seqno; -+ } -+ -+ struct local_binding *lb = lb_node->data; -+ if (!lb->pb || !lb->iface) { -+ goto del_seqno; -+ } -+ -+ if (!ofctrl_acked_seqnos_contains(acked_seqnos, node->data)) { -+ continue; -+ } -+ -+ ovsrec_interface_update_external_ids_setkey(lb->iface, -+ OVN_INSTALLED_EXT_ID, -+ "true"); -+ sbrec_port_binding_set_up(lb->pb, &up, 1); -+ -+ struct shash_node *child_node; -+ SHASH_FOR_EACH (child_node, &lb->children) { -+ struct local_binding *lb_child = child_node->data; -+ sbrec_port_binding_set_up(lb_child->pb, &up, 1); -+ } -+ -+del_seqno: -+ simap_delete(&binding_iface_seqno_map, node); -+ } -+ -+ ofctrl_acked_seqnos_destroy(acked_seqnos); -+} -+ -+void -+binding_seqno_flush(void) -+{ -+ simap_clear(&binding_iface_seqno_map); -+} -diff --git a/controller/binding.h b/controller/binding.h -index 2885971..c9ebef4 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -135,4 +135,9 @@ bool binding_handle_ovs_interface_changes(struct binding_ctx_in *, - bool binding_handle_port_binding_changes(struct binding_ctx_in *, - struct binding_ctx_out *); - void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); -+ -+void binding_init(void); -+void binding_seqno_run(struct shash *local_bindings); -+void binding_seqno_install(struct shash *local_bindings); -+void binding_seqno_flush(void); - #endif /* controller/binding.h */ -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index bb1c659..5599ea4 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -981,6 +981,7 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data) - /* Flush ofctrl seqno requests when the ofctrl connection goes down. */ - if (!of_data->connected) { - ofctrl_seqno_flush(); -+ binding_seqno_flush(); - } - engine_set_node_state(node, EN_UPDATED); - return; -@@ -2404,13 +2405,14 @@ main(int argc, char *argv[]) - - daemonize_complete(); - -+ /* Register ofctrl seqno types. */ -+ ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type(); -+ -+ binding_init(); - patch_init(); - pinctrl_init(); - lflow_init(); - -- /* Register ofctrl seqno types. */ -- ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type(); -- - /* Connect to OVS OVSDB instance. */ - struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER( - ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true)); -@@ -2879,6 +2881,9 @@ main(int argc, char *argv[]) - ovnsb_idl_loop.idl), - ovnsb_cond_seqno, - ovnsb_expected_cond_seqno)); -+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -+ binding_seqno_run(&runtime_data->local_bindings); -+ } - - flow_output_data = engine_get_data(&en_flow_output); - if (flow_output_data && ct_zones_data) { -@@ -2889,6 +2894,9 @@ main(int argc, char *argv[]) - engine_node_changed(&en_flow_output)); - } - ofctrl_seqno_run(ofctrl_get_cur_cfg()); -+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -+ binding_seqno_install(&runtime_data->local_bindings); -+ } - } - - } -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 9f8fb3b..307ee9c 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -13049,7 +13049,7 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, - continue; - } - -- bool up = (sb->chassis || lsp_is_router(op->nbsp)); -+ bool up = ((sb->up && (*sb->up)) || lsp_is_router(op->nbsp)); - if (!op->nbsp->up || *op->nbsp->up != up) { - nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); - } -@@ -13197,7 +13197,7 @@ static const char *rbac_encap_update[] = - static const char *rbac_port_binding_auth[] = - {""}; - static const char *rbac_port_binding_update[] = -- {"chassis"}; -+ {"chassis", "up"}; - - static const char *rbac_mac_binding_auth[] = - {""}; -@@ -13684,6 +13684,8 @@ main(int argc, char *argv[]) - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_virtual_parent); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, -+ &sbrec_port_binding_col_up); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, -diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema -index 97db6de..b418434 100644 ---- a/ovn-sb.ovsschema -+++ b/ovn-sb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Southbound", -- "version": "20.13.0", -- "cksum": "3035725595 25676", -+ "version": "20.14.0", -+ "cksum": "1412040198 25748", - "tables": { - "SB_Global": { - "columns": { -@@ -225,6 +225,7 @@ - "nat_addresses": {"type": {"key": "string", - "min": 0, - "max": "unlimited"}}, -+ "up": {"type": {"key": "boolean", "min": 0, "max": 1}}, - "external_ids": {"type": {"key": "string", - "value": "string", - "min": 0, -diff --git a/ovn-sb.xml b/ovn-sb.xml -index eb440e4..4c82d51 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -2771,6 +2771,14 @@ tcp.flags = RST; -

    - - -+ -+

    -+ This is set to true whenever all OVS flows -+ required by this Port_Binding have been installed. This is -+ populated by ovn-controller. -+

    -+
    -+ - -

    - A number that represents the logical port in the key (e.g. STT key or -diff --git a/tests/ovn.at b/tests/ovn.at -index a4fafa5..dfb94d2 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -23662,3 +23662,35 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - AT_CLEANUP -+ -+AT_SETUP([ovn -- propagate Port_Binding.up to NB and OVS]) -+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 -+ -+check ovn-nbctl ls-add ls -+ -+AS_BOX([add OVS port for existing LSP]) -+check ovn-nbctl lsp-add ls lsp1 -+check ovn-nbctl --wait=hv sync -+check_column "[]" Port_Binding up logical_port=lsp1 -+ -+check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 -+check_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp1 external_ids:ovn-installed` = '"true"']) -+ -+AS_BOX([add LSP for existing OVS port]) -+check ovs-vsctl add-port br-int lsp2 -- set Interface lsp2 external-ids:iface-id=lsp2 -+check ovn-nbctl lsp-add ls lsp2 -+check ovn-nbctl --wait=hv sync -+check_column "true" Port_Binding up logical_port=lsp2 -+wait_column "true" nb:Logical_Switch_Port up name=lsp2 -+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp2 external_ids:ovn-installed` = '"true"']) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c -index 0a1b9ff..c38e8ec 100644 ---- a/utilities/ovn-sbctl.c -+++ b/utilities/ovn-sbctl.c -@@ -526,6 +526,7 @@ pre_get_info(struct ctl_context *ctx) - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_tunnel_key); - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis); - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath); -+ ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up); - - 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); -@@ -665,6 +666,7 @@ cmd_lsp_bind(struct ctl_context *ctx) - struct sbctl_chassis *sbctl_ch; - struct sbctl_port_binding *sbctl_bd; - char *lport_name, *ch_name; -+ bool up = true; - - /* port_binding must exist, chassis must exist! */ - lport_name = ctx->argv[1]; -@@ -683,6 +685,7 @@ cmd_lsp_bind(struct ctl_context *ctx) - } - } - sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg); -+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, &up, 1); - sbctl_context_invalidate_cache(ctx); - } - -@@ -699,6 +702,7 @@ cmd_lsp_unbind(struct ctl_context *ctx) - sbctl_bd = find_port_binding(sbctl_ctx, lport_name, must_exist); - if (sbctl_bd) { - sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL); -+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, NULL, 0); - } - } - --- -1.8.3.1 - diff --git a/SOURCES/0003-nbctl-Use-partial-set-updates-instead-of-re-setting-.patch b/SOURCES/0003-nbctl-Use-partial-set-updates-instead-of-re-setting-.patch deleted file mode 100644 index caf0772..0000000 --- a/SOURCES/0003-nbctl-Use-partial-set-updates-instead-of-re-setting-.patch +++ /dev/null @@ -1,772 +0,0 @@ -From 5b1c8c21d5620bf3c7e8d3899fe7dbdd1c65b10e Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 11 Dec 2020 11:59:16 +0100 -Subject: [PATCH 3/7] nbctl: Use partial set updates instead of re-setting the - whole column. - -Northbound database has many tables that could contain columns with -very large sets. For example 'ports' column of the 'Logical_Switch' -table could contain thousands of ports in a set. - -Current strategy of nbctl while adding a new port to the set is to -copy all existing ports, add one and set the new set of ports. -Similar behavior is for deletion too. - -If we have 1000 ports and want to add one more, resulted transaction -in current code will look like this: - - {transact, - < wait operation > - < create new lsp in Logical_Switch_Port table > - - where: _uuid == 'logical switch row uuid' - op : update - rows : ports ['< list of current 1000 ports + 1 new >'] - } - -The code was written before support for partial updates for sets was -implemented. Now we have it and can replace the old strategy. -With this change resulted transaction will be: - - {transact, - < wait operation > - < create new lsp in Logical_Switch_Port table > - - where: _uuid == 'logical switch row uuid' - op : mutate - mutations : ports insert ['< 1 uuid of a new lsp >'] - } - -Unfortunately, for now, this only decreases transaction size in half, -because '', that is still in the transaction, contains -the full current list of ports: - - where: _uuid == 'logical switch row uuid' - op : wait - until: == - rows : ports ['< list of current 1000 ports >'] - -But anyway, beside the overall code cleanup, this reduces transaction -size in half and should reduce pressure on the ovsdb-server since it -will not need to parse and process all 1000 ports twice. - -This change doesn't affect 'append_request' messages within the raft -cluster, because ovsdb-server is not smart enough to use mutations -there, and this will also not affect 'update' messages from the -ovsdb-server to its clients, because it is smart enough to construct -'modify' updates regardless of the original transaction. - -One difference between full updates and partial is that partial -changes are not visible for the idl client until transaction is -committed and the update received from the server. New switch ports -are not visible while iterating over 'ports' of the logical switch and -removed ports are still there. For this reason, we have to maintain -runtime cache of the mapping between ports and routers/switches they -attached to. - -Acked-by: Mark Michelson -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 350 +++++++++++------------------------------- - 1 file changed, 87 insertions(+), 263 deletions(-) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index cbf2cb1f1..7c4dce12a 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -126,7 +126,11 @@ static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args, - static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]); - - /* A context for keeping track of which switch/router certain ports are -- * connected to. */ -+ * connected to. -+ * -+ * It is required to track changes that we did within current set of commands -+ * because partial updates of sets in database are not reflected in the idl -+ * until transaction is committed and updates received from the server. */ - struct nbctl_context { - struct ctl_context base; - struct shash lsp_to_ls_map; -@@ -1508,21 +1512,15 @@ nbctl_lsp_add(struct ctl_context *ctx) - - /* Insert the logical port into the logical switch. */ - nbrec_logical_switch_verify_ports(ls); -- struct nbrec_logical_switch_port **new_ports = xmalloc(sizeof *new_ports * -- (ls->n_ports + 1)); -- nullable_memcpy(new_ports, ls->ports, sizeof *new_ports * ls->n_ports); -- new_ports[ls->n_ports] = CONST_CAST(struct nbrec_logical_switch_port *, -- lsp); -- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1); -- free(new_ports); -+ nbrec_logical_switch_update_ports_addvalue(ls, lsp); - - /* Updating runtime cache. */ - shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls); - } - --/* Removes logical switch port 'ls->ports[idx]'. */ -+/* Removes logical switch port 'lsp' from the logical switch 'ls'. */ - static void --remove_lsp(struct ctl_context *ctx, size_t idx, -+remove_lsp(struct ctl_context *ctx, - const struct nbrec_logical_switch *ls, - const struct nbrec_logical_switch_port *lsp) - { -@@ -1534,12 +1532,8 @@ remove_lsp(struct ctl_context *ctx, size_t idx, - /* First remove 'lsp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- struct nbrec_logical_switch_port **new_ports -- = xmemdup(ls->ports, sizeof *new_ports * ls->n_ports); -- new_ports[idx] = new_ports[ls->n_ports - 1]; - nbrec_logical_switch_verify_ports(ls); -- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports - 1); -- free(new_ports); -+ nbrec_logical_switch_update_ports_delvalue(ls, lsp); - - /* Delete 'lsp' from the IDL. This won't have a real effect on the - * database server (the IDL will suppress it in fact) but it means that it -@@ -1571,12 +1565,7 @@ nbctl_lsp_del(struct ctl_context *ctx) - ctx->error = error; - return; - } -- for (size_t i = 0; i < ls->n_ports; i++) { -- if (ls->ports[i] == lsp) { -- remove_lsp(ctx, i, ls, lsp); -- break; -- } -- } -+ remove_lsp(ctx, ls, lsp); - } - - static void -@@ -2366,17 +2355,13 @@ nbctl_acl_add(struct ctl_context *ctx) - } - - /* Insert the acl into the logical switch/port group. */ -- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * (n_acls + 1)); -- nullable_memcpy(new_acls, acls, sizeof *new_acls * n_acls); -- new_acls[n_acls] = acl; - if (pg) { - nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, n_acls + 1); -+ nbrec_port_group_update_acls_addvalue(pg, acl); - } else { - nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, n_acls + 1); -+ nbrec_logical_switch_update_acls_addvalue(ls, acl); - } -- free(new_acls); - } - - static void -@@ -2416,23 +2401,21 @@ nbctl_acl_del(struct ctl_context *ctx) - /* If priority and match are not specified, delete all ACLs with the - * specified direction. */ - if (ctx->argc == 3) { -- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * n_acls); -- -- int n_new_acls = 0; -- for (size_t i = 0; i < n_acls; i++) { -- if (strcmp(direction, acls[i]->direction)) { -- new_acls[n_new_acls++] = acls[i]; -- } -- } -- - if (pg) { - nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, n_new_acls); - } else { - nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, n_new_acls); - } -- free(new_acls); -+ -+ for (size_t i = 0; i < n_acls; i++) { -+ if (!strcmp(direction, acls[i]->direction)) { -+ if (pg) { -+ nbrec_port_group_update_acls_delvalue(pg, acls[i]); -+ } else { -+ nbrec_logical_switch_update_acls_delvalue(ls, acls[i]); -+ } -+ } -+ } - return; - } - -@@ -2454,19 +2437,13 @@ nbctl_acl_del(struct ctl_context *ctx) - - if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) && - !strcmp(direction, acl->direction)) { -- struct nbrec_acl **new_acls -- = xmemdup(acls, sizeof *new_acls * n_acls); -- new_acls[i] = acls[n_acls - 1]; - if (pg) { - nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, -- n_acls - 1); -+ nbrec_port_group_update_acls_delvalue(pg, acl); - } else { - nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, -- n_acls - 1); -+ nbrec_logical_switch_update_acls_delvalue(ls, acl); - } -- free(new_acls); - return; - } - } -@@ -2620,14 +2597,7 @@ nbctl_qos_add(struct ctl_context *ctx) - - /* Insert the qos rule the logical switch. */ - nbrec_logical_switch_verify_qos_rules(ls); -- struct nbrec_qos **new_qos_rules -- = xmalloc(sizeof *new_qos_rules * (ls->n_qos_rules + 1)); -- nullable_memcpy(new_qos_rules, -- ls->qos_rules, sizeof *new_qos_rules * ls->n_qos_rules); -- new_qos_rules[ls->n_qos_rules] = qos; -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, -- ls->n_qos_rules + 1); -- free(new_qos_rules); -+ nbrec_logical_switch_update_qos_rules_addvalue(ls, qos); - } - - static void -@@ -2664,34 +2634,32 @@ nbctl_qos_del(struct ctl_context *ctx) - /* If uuid was specified, delete qos_rule with the - * specified uuid. */ - if (ctx->argc == 3) { -- struct nbrec_qos **new_qos_rules -- = xmalloc(sizeof *new_qos_rules * ls->n_qos_rules); -+ size_t i; - -- int n_qos_rules = 0; -+ nbrec_logical_switch_verify_qos_rules(ls); - if (qos_rule_uuid) { -- for (size_t i = 0; i < ls->n_qos_rules; i++) { -- if (!uuid_equals(qos_rule_uuid, -- &(ls->qos_rules[i]->header_.uuid))) { -- new_qos_rules[n_qos_rules++] = ls->qos_rules[i]; -+ for (i = 0; i < ls->n_qos_rules; i++) { -+ if (uuid_equals(qos_rule_uuid, -+ &(ls->qos_rules[i]->header_.uuid))) { -+ nbrec_logical_switch_update_qos_rules_delvalue( -+ ls, ls->qos_rules[i]); -+ break; - } - } -- if (n_qos_rules == ls->n_qos_rules) { -+ if (i == ls->n_qos_rules) { - ctl_error(ctx, "uuid is not found"); - } - - /* If priority and match are not specified, delete all qos_rules - * with the specified direction. */ - } else { -- for (size_t i = 0; i < ls->n_qos_rules; i++) { -- if (strcmp(direction, ls->qos_rules[i]->direction)) { -- new_qos_rules[n_qos_rules++] = ls->qos_rules[i]; -+ for (i = 0; i < ls->n_qos_rules; i++) { -+ if (!strcmp(direction, ls->qos_rules[i]->direction)) { -+ nbrec_logical_switch_update_qos_rules_delvalue( -+ ls, ls->qos_rules[i]); - } - } - } -- -- nbrec_logical_switch_verify_qos_rules(ls); -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, n_qos_rules); -- free(new_qos_rules); - return; - } - -@@ -2718,14 +2686,8 @@ nbctl_qos_del(struct ctl_context *ctx) - - if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) && - !strcmp(direction, qos->direction)) { -- struct nbrec_qos **new_qos_rules -- = xmemdup(ls->qos_rules, -- sizeof *new_qos_rules * ls->n_qos_rules); -- new_qos_rules[i] = ls->qos_rules[ls->n_qos_rules - 1]; - nbrec_logical_switch_verify_qos_rules(ls); -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, -- ls->n_qos_rules - 1); -- free(new_qos_rules); -+ nbrec_logical_switch_update_qos_rules_delvalue(ls, qos); - return; - } - } -@@ -3188,16 +3150,7 @@ nbctl_lr_lb_add(struct ctl_context *ctx) - - /* Insert the load balancer into the logical router. */ - nbrec_logical_router_verify_load_balancer(lr); -- struct nbrec_load_balancer **new_lbs -- = xmalloc(sizeof *new_lbs * (lr->n_load_balancer + 1)); -- -- nullable_memcpy(new_lbs, lr->load_balancer, -- sizeof *new_lbs * lr->n_load_balancer); -- new_lbs[lr->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *, -- new_lb); -- nbrec_logical_router_set_load_balancer(lr, new_lbs, -- lr->n_load_balancer + 1); -- free(new_lbs); -+ nbrec_logical_router_update_load_balancer_addvalue(lr, new_lb); - } - - static void -@@ -3231,14 +3184,7 @@ nbctl_lr_lb_del(struct ctl_context *ctx) - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ - nbrec_logical_router_verify_load_balancer(lr); -- -- struct nbrec_load_balancer **new_lbs -- = xmemdup(lr->load_balancer, -- sizeof *new_lbs * lr->n_load_balancer); -- new_lbs[i] = lr->load_balancer[lr->n_load_balancer - 1]; -- nbrec_logical_router_set_load_balancer(lr, new_lbs, -- lr->n_load_balancer - 1); -- free(new_lbs); -+ nbrec_logical_router_update_load_balancer_delvalue(lr, lb); - return; - } - } -@@ -3313,16 +3259,7 @@ nbctl_ls_lb_add(struct ctl_context *ctx) - - /* Insert the load balancer into the logical switch. */ - nbrec_logical_switch_verify_load_balancer(ls); -- struct nbrec_load_balancer **new_lbs -- = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1)); -- -- nullable_memcpy(new_lbs, ls->load_balancer, -- sizeof *new_lbs * ls->n_load_balancer); -- new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *, -- new_lb); -- nbrec_logical_switch_set_load_balancer(ls, new_lbs, -- ls->n_load_balancer + 1); -- free(new_lbs); -+ nbrec_logical_switch_update_load_balancer_addvalue(ls, new_lb); - } - - static void -@@ -3356,14 +3293,7 @@ nbctl_ls_lb_del(struct ctl_context *ctx) - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ - nbrec_logical_switch_verify_load_balancer(ls); -- -- struct nbrec_load_balancer **new_lbs -- = xmemdup(ls->load_balancer, -- sizeof *new_lbs * ls->n_load_balancer); -- new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1]; -- nbrec_logical_switch_set_load_balancer(ls, new_lbs, -- ls->n_load_balancer - 1); -- free(new_lbs); -+ nbrec_logical_switch_update_load_balancer_delvalue(ls, lb); - return; - } - } -@@ -3792,14 +3722,7 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - smap_destroy(&options); - - nbrec_logical_router_verify_policies(lr); -- struct nbrec_logical_router_policy **new_policies -- = xmalloc(sizeof *new_policies * (lr->n_policies + 1)); -- memcpy(new_policies, lr->policies, -- sizeof *new_policies * lr->n_policies); -- new_policies[lr->n_policies] = policy; -- nbrec_logical_router_set_policies(lr, new_policies, -- lr->n_policies + 1); -- free(new_policies); -+ nbrec_logical_router_update_policies_addvalue(lr, policy); - if (next_hop != NULL) { - free(next_hop); - } -@@ -3836,38 +3759,35 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - /* If uuid was specified, delete routing policy with the - * specified uuid. */ - if (ctx->argc == 3) { -- struct nbrec_logical_router_policy **new_policies -- = xmemdup(lr->policies, -- sizeof *new_policies * lr->n_policies); -- int n_policies = 0; -+ size_t i; - -+ nbrec_logical_router_verify_policies(lr); - if (lr_policy_uuid) { -- for (size_t i = 0; i < lr->n_policies; i++) { -- if (!uuid_equals(lr_policy_uuid, -- &(lr->policies[i]->header_.uuid))) { -- new_policies[n_policies++] = lr->policies[i]; -+ for (i = 0; i < lr->n_policies; i++) { -+ if (uuid_equals(lr_policy_uuid, -+ &(lr->policies[i]->header_.uuid))) { -+ nbrec_logical_router_update_policies_delvalue( -+ lr, lr->policies[i]); -+ break; - } - } -- if (n_policies == lr->n_policies) { -+ if (i == lr->n_policies) { - if (!shash_find(&ctx->options, "--if-exists")) { - ctl_error(ctx, "Logical router policy uuid is not found."); - } -- free(new_policies); - return; - } - -- /* If match is not specified, delete all routing policies with the -- * specified priority. */ -+ /* If match is not specified, delete all routing policies with the -+ * specified priority. */ - } else { -- for (int i = 0; i < lr->n_policies; i++) { -- if (priority != lr->policies[i]->priority) { -- new_policies[n_policies++] = lr->policies[i]; -+ for (i = 0; i < lr->n_policies; i++) { -+ if (priority == lr->policies[i]->priority) { -+ nbrec_logical_router_update_policies_delvalue( -+ lr, lr->policies[i]); - } - } - } -- nbrec_logical_router_verify_policies(lr); -- nbrec_logical_router_set_policies(lr, new_policies, n_policies); -- free(new_policies); - return; - } - -@@ -3876,14 +3796,8 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - struct nbrec_logical_router_policy *routing_policy = lr->policies[i]; - if (priority == routing_policy->priority && - !strcmp(ctx->argv[3], routing_policy->match)) { -- struct nbrec_logical_router_policy **new_policies -- = xmemdup(lr->policies, -- sizeof *new_policies * lr->n_policies); -- new_policies[i] = lr->policies[lr->n_policies - 1]; - nbrec_logical_router_verify_policies(lr); -- nbrec_logical_router_set_policies(lr, new_policies, -- lr->n_policies - 1); -- free(new_policies); -+ nbrec_logical_router_update_policies_delvalue(lr, routing_policy); - return; - } - } -@@ -4083,14 +3997,7 @@ nbctl_lr_route_add(struct ctl_context *ctx) - } - - nbrec_logical_router_verify_static_routes(lr); -- struct nbrec_logical_router_static_route **new_routes -- = xmalloc(sizeof *new_routes * (lr->n_static_routes + 1)); -- nullable_memcpy(new_routes, lr->static_routes, -- sizeof *new_routes * lr->n_static_routes); -- new_routes[lr->n_static_routes] = route; -- nbrec_logical_router_set_static_routes(lr, new_routes, -- lr->n_static_routes + 1); -- free(new_routes); -+ nbrec_logical_router_update_static_routes_addvalue(lr, route); - - cleanup: - free(next_hop); -@@ -4147,11 +4054,9 @@ nbctl_lr_route_del(struct ctl_context *ctx) - output_port = ctx->argv[4]; - } - -- struct nbrec_logical_router_static_route **new_routes -- = xmemdup(lr->static_routes, -- sizeof *new_routes * lr->n_static_routes); -- size_t n_new = 0; -- for (int i = 0; i < lr->n_static_routes; i++) { -+ size_t n_removed = 0; -+ nbrec_logical_router_verify_static_routes(lr); -+ for (size_t i = 0; i < lr->n_static_routes; i++) { - /* Compare route policy, if specified. */ - if (policy) { - char *nb_policy = lr->static_routes[i]->policy; -@@ -4160,7 +4065,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) - nb_is_src_route = true; - } - if (is_src_route != nb_is_src_route) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4171,14 +4075,12 @@ nbctl_lr_route_del(struct ctl_context *ctx) - normalize_prefix_str(lr->static_routes[i]->ip_prefix); - if (!rt_prefix) { - /* Ignore existing prefix we couldn't parse. */ -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - - int ret = strcmp(prefix, rt_prefix); - free(rt_prefix); - if (ret) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4189,13 +4091,11 @@ nbctl_lr_route_del(struct ctl_context *ctx) - normalize_prefix_str(lr->static_routes[i]->nexthop); - if (!rt_nexthop) { - /* Ignore existing nexthop we couldn't parse. */ -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - int ret = strcmp(nexthop, rt_nexthop); - free(rt_nexthop); - if (ret) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4204,18 +4104,17 @@ nbctl_lr_route_del(struct ctl_context *ctx) - if (output_port) { - char *rt_output_port = lr->static_routes[i]->output_port; - if (!rt_output_port || strcmp(output_port, rt_output_port)) { -- new_routes[n_new++] = lr->static_routes[i]; -+ continue; - } - } -- } - -- if (n_new < lr->n_static_routes) { -- nbrec_logical_router_verify_static_routes(lr); -- nbrec_logical_router_set_static_routes(lr, new_routes, n_new); -- goto out; -+ /* Everything matched. Removing. */ -+ nbrec_logical_router_update_static_routes_delvalue( -+ lr, lr->static_routes[i]); -+ n_removed++; - } - -- if (!shash_find(&ctx->options, "--if-exists")) { -+ if (!n_removed && !shash_find(&ctx->options, "--if-exists")) { - ctl_error(ctx, "no matching route: policy '%s', prefix '%s', nexthop " - "'%s', output_port '%s'.", - policy ? policy : "any", -@@ -4224,8 +4123,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) - output_port ? output_port : "any"); - } - --out: -- free(new_routes); - free(prefix); - free(nexthop); - } -@@ -4497,11 +4394,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx) - - /* Insert the NAT into the logical router. */ - nbrec_logical_router_verify_nat(lr); -- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * (lr->n_nat + 1)); -- nullable_memcpy(new_nats, lr->nat, sizeof *new_nats * lr->n_nat); -- new_nats[lr->n_nat] = nat; -- nbrec_logical_router_set_nat(lr, new_nats, lr->n_nat + 1); -- free(new_nats); -+ nbrec_logical_router_update_nat_addvalue(lr, nat); - - cleanup: - free(new_logical_ip); -@@ -4537,17 +4430,12 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - - if (ctx->argc == 3) { - /*Deletes all NATs with the specified type. */ -- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * lr->n_nat); -- int n_nat = 0; -+ nbrec_logical_router_verify_nat(lr); - for (size_t i = 0; i < lr->n_nat; i++) { -- if (strcmp(nat_type, lr->nat[i]->type)) { -- new_nats[n_nat++] = lr->nat[i]; -+ if (!strcmp(nat_type, lr->nat[i]->type)) { -+ nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]); - } - } -- -- nbrec_logical_router_verify_nat(lr); -- nbrec_logical_router_set_nat(lr, new_nats, n_nat); -- free(new_nats); - return; - } - -@@ -4569,13 +4457,8 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - continue; - } - if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) { -- struct nbrec_nat **new_nats -- = xmemdup(lr->nat, sizeof *new_nats * lr->n_nat); -- new_nats[i] = lr->nat[lr->n_nat - 1]; - nbrec_logical_router_verify_nat(lr); -- nbrec_logical_router_set_nat(lr, new_nats, -- lr->n_nat - 1); -- free(new_nats); -+ nbrec_logical_router_update_nat_delvalue(lr, nat); - should_return = true; - } - free(old_ip); -@@ -4854,14 +4737,7 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx) - - /* Insert the logical gateway chassis into the logical router port. */ - nbrec_logical_router_port_verify_gateway_chassis(lrp); -- struct nbrec_gateway_chassis **new_gc = xmalloc( -- sizeof *new_gc * (lrp->n_gateway_chassis + 1)); -- nullable_memcpy(new_gc, lrp->gateway_chassis, -- sizeof *new_gc * lrp->n_gateway_chassis); -- new_gc[lrp->n_gateway_chassis] = gc; -- nbrec_logical_router_port_set_gateway_chassis( -- lrp, new_gc, lrp->n_gateway_chassis + 1); -- free(new_gc); -+ nbrec_logical_router_port_update_gateway_chassis_addvalue(lrp, gc); - free(gc_name); - } - -@@ -4878,14 +4754,8 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) - * will actually cause the gateway chassis to be deleted when the - * transaction is sent to the database server (due to garbage - * collection). */ -- struct nbrec_gateway_chassis **new_gc -- = xmemdup(lrp->gateway_chassis, -- sizeof *new_gc * lrp->n_gateway_chassis); -- new_gc[idx] = new_gc[lrp->n_gateway_chassis - 1]; - nbrec_logical_router_port_verify_gateway_chassis(lrp); -- nbrec_logical_router_port_set_gateway_chassis( -- lrp, new_gc, lrp->n_gateway_chassis - 1); -- free(new_gc); -+ nbrec_logical_router_port_update_gateway_chassis_delvalue(lrp, gc); - } - - /* Delete 'gc' from the IDL. This won't have a real effect on -@@ -5118,21 +4988,15 @@ nbctl_lrp_add(struct ctl_context *ctx) - - /* Insert the logical port into the logical router. */ - nbrec_logical_router_verify_ports(lr); -- struct nbrec_logical_router_port **new_ports = xmalloc(sizeof *new_ports * -- (lr->n_ports + 1)); -- nullable_memcpy(new_ports, lr->ports, sizeof *new_ports * lr->n_ports); -- new_ports[lr->n_ports] = CONST_CAST(struct nbrec_logical_router_port *, -- lrp); -- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1); -- free(new_ports); -+ nbrec_logical_router_update_ports_addvalue(lr, lrp); - - /* Updating runtime cache. */ - shash_add(&nbctx->lrp_to_lr_map, lrp->name, lr); - } - --/* Removes logical router port 'lr->ports[idx]'. */ -+/* Removes logical router port 'lrp' from logical router 'lr'. */ - static void --remove_lrp(struct ctl_context *ctx, size_t idx, -+remove_lrp(struct ctl_context *ctx, - const struct nbrec_logical_router *lr, - const struct nbrec_logical_router_port *lrp) - { -@@ -5144,12 +5008,8 @@ remove_lrp(struct ctl_context *ctx, size_t idx, - /* First remove 'lrp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- struct nbrec_logical_router_port **new_ports -- = xmemdup(lr->ports, sizeof *new_ports * lr->n_ports); -- new_ports[idx] = new_ports[lr->n_ports - 1]; - nbrec_logical_router_verify_ports(lr); -- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports - 1); -- free(new_ports); -+ nbrec_logical_router_update_ports_delvalue(lr, lrp); - - /* Delete 'lrp' from the IDL. This won't have a real effect on - * the database server (the IDL will suppress it in fact) but it -@@ -5181,12 +5041,7 @@ nbctl_lrp_del(struct ctl_context *ctx) - ctx->error = error; - return; - } -- for (size_t i = 0; i < lr->n_ports; i++) { -- if (lr->ports[i] == lrp) { -- remove_lrp(ctx, i, lr, lrp); -- break; -- } -- } -+ remove_lrp(ctx, lr, lrp); - } - - /* Print a list of logical router ports. */ -@@ -5458,15 +5313,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) - nbrec_forwarding_group_set_liveness(fwd_group, true); - } - -- struct nbrec_forwarding_group **new_fwd_groups = -- xmalloc(sizeof(*new_fwd_groups) * (ls->n_forwarding_groups + 1)); -- memcpy(new_fwd_groups, ls->forwarding_groups, -- sizeof *new_fwd_groups * ls->n_forwarding_groups); -- new_fwd_groups[ls->n_forwarding_groups] = fwd_group; -- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups, -- (ls->n_forwarding_groups + 1)); -- free(new_fwd_groups); -- -+ nbrec_logical_switch_update_forwarding_groups_addvalue(ls, fwd_group); - } - - static void -@@ -5488,14 +5335,8 @@ nbctl_fwd_group_del(struct ctl_context *ctx) - - for (int i = 0; i < ls->n_forwarding_groups; ++i) { - if (!strcmp(ls->forwarding_groups[i]->name, fwd_group->name)) { -- struct nbrec_forwarding_group **new_fwd_groups = -- xmemdup(ls->forwarding_groups, -- sizeof *new_fwd_groups * ls->n_forwarding_groups); -- new_fwd_groups[i] = -- ls->forwarding_groups[ls->n_forwarding_groups - 1]; -- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups, -- (ls->n_forwarding_groups - 1)); -- free(new_fwd_groups); -+ nbrec_logical_switch_update_forwarding_groups_delvalue( -+ ls, ls->forwarding_groups[i]); - nbrec_forwarding_group_delete(fwd_group); - return; - } -@@ -6093,16 +5934,7 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx) - nbrec_ha_chassis_set_priority(ha_chassis, priority); - - nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); -- -- struct nbrec_ha_chassis **new_ha_chs = -- xmalloc(sizeof *new_ha_chs * (ha_ch_grp->n_ha_chassis + 1)); -- nullable_memcpy(new_ha_chs, ha_ch_grp->ha_chassis, -- sizeof *new_ha_chs * ha_ch_grp->n_ha_chassis); -- new_ha_chs[ha_ch_grp->n_ha_chassis] = -- CONST_CAST(struct nbrec_ha_chassis *, ha_chassis); -- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_chs, -- ha_ch_grp->n_ha_chassis + 1); -- free(new_ha_chs); -+ nbrec_ha_chassis_group_update_ha_chassis_addvalue(ha_ch_grp, ha_chassis); - } - - static void -@@ -6117,11 +5949,9 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) - - const char *chassis_name = ctx->argv[2]; - struct nbrec_ha_chassis *ha_chassis = NULL; -- size_t idx = 0; - for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) { - if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) { - ha_chassis = ha_ch_grp->ha_chassis[i]; -- idx = i; - break; - } - } -@@ -6132,14 +5962,8 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) - return; - } - -- struct nbrec_ha_chassis **new_ha_ch -- = xmemdup(ha_ch_grp->ha_chassis, -- sizeof *new_ha_ch * ha_ch_grp->n_ha_chassis); -- new_ha_ch[idx] = new_ha_ch[ha_ch_grp->n_ha_chassis - 1]; - nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); -- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_ch, -- ha_ch_grp->n_ha_chassis - 1); -- free(new_ha_ch); -+ nbrec_ha_chassis_group_update_ha_chassis_delvalue(ha_ch_grp, ha_chassis); - nbrec_ha_chassis_delete(ha_chassis); - } - --- -2.28.0 - diff --git a/SOURCES/0003-northd-Allow-backwards-compatibility-for-Logical_Swi.patch b/SOURCES/0003-northd-Allow-backwards-compatibility-for-Logical_Swi.patch deleted file mode 100644 index 93143b4..0000000 --- a/SOURCES/0003-northd-Allow-backwards-compatibility-for-Logical_Swi.patch +++ /dev/null @@ -1,222 +0,0 @@ -From 07b0f0468faeeb1e149dcc3e4926a54cbb9bb367 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 3 Feb 2021 20:36:52 +0100 -Subject: [PATCH 3/4] northd: Allow backwards compatibility for - Logical_Switch_Port.up. - -In general, ovn-northd expects ovn-controller to set Port_Binding.up -before it declares the logical switch port as being up. - -Even though the recommended upgrade procedure for OVN states that -ovn-controllers should be upgraded before ovn-northd, there are cases -when CMSs don't follow this guideline. - -This would cause all existing and bound Logical_Switch_Ports to be -declared "down" until ovn-controllers are upgraded. - -To avoid this situation, ovn-controllers now explicitly set -Chassis.other_config:port-up-notif in their own chassis record. Based -on this value, ovn-northd can determine if it needs to use the old type -of logic or the new one (Port_Binding.up) when setting LSP.up. - -Note: -In case of downgrading ovn-controller before ovn-northd, if -ovn-controller is forcefully stopped it will not clear its chassis -record from the SB. Older versions will not have the capability to -clear the other_config:port-up-notif value so LSPs will be declared -"down" until ovn-northd is downgraded as well. As this -upgrade/downgrade procedure is not the recommended one, we don't try -to deal with this scenario. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit a99af0367acc744321747bad33bf598d06a612de) - -Change-Id: Iaec681d05abec490b7e7cb330f1ca8f00149cefb ---- - controller/chassis.c | 7 +++++++ - include/ovn/automake.mk | 1 + - include/ovn/features.h | 22 ++++++++++++++++++++++ - northd/ovn-northd.c | 13 ++++++++++++- - ovn-sb.xml | 5 +++++ - tests/ovn-controller.at | 17 +++++++++++++++++ - tests/ovn-northd.at | 22 ++++++++++++++++++++++ - 7 files changed, 86 insertions(+), 1 deletion(-) - create mode 100644 include/ovn/features.h - -diff --git a/controller/chassis.c b/controller/chassis.c -index b4d4b0e..0937e33 100644 ---- a/controller/chassis.c -+++ b/controller/chassis.c -@@ -28,6 +28,7 @@ - #include "lib/ovn-sb-idl.h" - #include "ovn-controller.h" - #include "lib/util.h" -+#include "ovn/features.h" - - VLOG_DEFINE_THIS_MODULE(chassis); - -@@ -293,6 +294,7 @@ chassis_build_other_config(struct smap *config, const char *bridge_mappings, - smap_replace(config, "iface-types", iface_types); - smap_replace(config, "ovn-chassis-mac-mappings", chassis_macs); - smap_replace(config, "is-interconn", is_interconn ? "true" : "false"); -+ smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true"); - } - - /* -@@ -363,6 +365,11 @@ chassis_other_config_changed(const char *bridge_mappings, - return true; - } - -+ if (!smap_get_bool(&chassis_rec->other_config, OVN_FEATURE_PORT_UP_NOTIF, -+ false)) { -+ return true; -+ } -+ - return false; - } - -diff --git a/include/ovn/automake.mk b/include/ovn/automake.mk -index 54b0e2c..582241a 100644 ---- a/include/ovn/automake.mk -+++ b/include/ovn/automake.mk -@@ -2,5 +2,6 @@ ovnincludedir = $(includedir)/ovn - ovninclude_HEADERS = \ - include/ovn/actions.h \ - include/ovn/expr.h \ -+ include/ovn/features.h \ - include/ovn/lex.h \ - include/ovn/logical-fields.h -diff --git a/include/ovn/features.h b/include/ovn/features.h -new file mode 100644 -index 0000000..10ee46f ---- /dev/null -+++ b/include/ovn/features.h -@@ -0,0 +1,22 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#ifndef OVN_FEATURES_H -+#define OVN_FEATURES_H 1 -+ -+/* ovn-controller supported feature names. */ -+#define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif" -+ -+#endif -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 0dc920b..62d45f9 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -38,6 +38,7 @@ - #include "lib/ovn-util.h" - #include "lib/lb.h" - #include "ovn/actions.h" -+#include "ovn/features.h" - #include "ovn/logical-fields.h" - #include "packets.h" - #include "openvswitch/poll-loop.h" -@@ -13057,7 +13058,17 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, - continue; - } - -- bool up = ((sb->up && (*sb->up)) || lsp_is_router(op->nbsp)); -+ bool up = false; -+ -+ if (lsp_is_router(op->nbsp)) { -+ up = true; -+ } else if (sb->chassis) { -+ up = smap_get_bool(&sb->chassis->other_config, -+ OVN_FEATURE_PORT_UP_NOTIF, false) -+ ? sb->n_up && sb->up[0] -+ : true; -+ } -+ - if (!op->nbsp->up || *op->nbsp->up != up) { - nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); - } -diff --git a/ovn-sb.xml b/ovn-sb.xml -index 4c82d51..980a096 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -322,6 +322,11 @@ - table. See ovn-controller(8) for more information. - - -+ -+ ovn-controller populates this key with true -+ when it supports Port_Binding.up. -+ -+ - - The overall purpose of these columns is described under Common - Columns at the beginning of this document. -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 1b46799..f818f9c 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -414,3 +414,20 @@ OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1]) - - OVN_CLEANUP([hv1]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- features]) -+AT_KEYWORDS([features]) -+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. -+OVS_WAIT_UNTIL([ -+ test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"' -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index c00225e..d52aeed 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2452,3 +2452,25 @@ check ovn-nbctl --wait=sb sync - AT_CHECK([grep -qE 'duplicate logical.*port p1' northd/ovn-northd.log], [0]) - - AT_CLEANUP -+ -+AT_SETUP([ovn -- Port_Binding.up backwards compatibility]) -+ovn_start -+ -+ovn-nbctl ls-add ls1 -+ovn-nbctl --wait=sb lsp-add ls1 lsp1 -+ -+# Simulate the fact that lsp1 had been previously bound on hv1 by an -+# ovn-controller running an older version. -+ovn-sbctl \ -+ --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ -+ -- --id=@c create chassis name=hv1 encaps=@e \ -+ -- set Port_Binding lsp1 chassis=@c -+ -+wait_for_ports_up lsp1 -+ -+# Simulate the fact that hv1 is aware of Port_Binding.up, ovn-northd -+# should transition the port state to down. -+check ovn-sbctl set chassis hv1 other_config:port-up-notif=true -+wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1 -+ -+AT_CLEANUP --- -1.8.3.1 - diff --git a/SOURCES/0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch b/SOURCES/0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch deleted file mode 100644 index ef0d190..0000000 --- a/SOURCES/0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch +++ /dev/null @@ -1,43 +0,0 @@ -From b8a33e87955d39b7e354fad6b8317bdcd9568c02 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:11 +0100 -Subject: [PATCH 03/16] northd: Fix leaks of strings while formatting ecmp - flows. - -Result of 'normalize_v46_prefix()' should be freed and all dynamic -strings should be destroyed. - -Fixes: 4fdca656857d ("Add ECMP symmetric replies.") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 3884e08eb..0acff2322 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -7800,6 +7800,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - route->prefix.family == AF_INET ? "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_); -@@ -7851,6 +7852,10 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, - 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 --- -2.28.0 - diff --git a/SOURCES/0003-ofctrl.c-Only-merge-actions-for-conjunctive-flows.patch b/SOURCES/0003-ofctrl.c-Only-merge-actions-for-conjunctive-flows.patch deleted file mode 100644 index 7cdc0a9..0000000 --- a/SOURCES/0003-ofctrl.c-Only-merge-actions-for-conjunctive-flows.patch +++ /dev/null @@ -1,218 +0,0 @@ -From 9d095799aa11c555bf7d5f8e86fee1be1a16c67b Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Sun, 11 Oct 2020 14:05:31 +0200 -Subject: [PATCH 3/7] ofctrl.c: Only merge actions for conjunctive flows. - -In ofctrl_add_or_append_flow() when merging flow actions make sure we only -do that for conjunctive flows. All other actions can not be merged with -action "conjunction". - -CC: Mark Michelson -Fixes: e659bab31a91 ("Combine conjunctions with identical matches into one flow.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit dadae4f800ccb1f2759378f0bd804dd002e31605) - -Change-Id: I6fc4091b3110ae595615c7f9006c3f5e53ebc39a ---- - controller/ofctrl.c | 124 +++++++++++++++++++++++++++++++++++++++++----------- - 1 file changed, 99 insertions(+), 25 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 4425d98..24b55fc 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -206,6 +206,9 @@ struct installed_flow { - 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,8 +217,14 @@ static struct desired_flow *desired_flow_alloc( - 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( -@@ -806,6 +815,19 @@ desired_flow_set_active(struct desired_flow *d) - d->installed_flow->desired_flow = d; - } - -+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; -+} -+ - /* Adds the desired flow to the list of desired flows that have same match - * conditions as the installed flow. - * -@@ -962,7 +984,7 @@ ofctrl_check_and_add_flow(struct ovn_desired_flow_table *flow_table, - 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)) { -@@ -1002,14 +1024,15 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows, - 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; -@@ -1248,15 +1271,11 @@ installed_flow_dup(struct desired_flow *src) - 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) -+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, -@@ -1265,20 +1284,76 @@ desired_flow_lookup(struct ovn_desired_flow_table *flow_table, - 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 * -@@ -1676,8 +1751,7 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - 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. */ --- -1.8.3.1 - diff --git a/SOURCES/0003-ovn-northd-Move-lswitch-DNS-lookup-and-response-to-a.patch b/SOURCES/0003-ovn-northd-Move-lswitch-DNS-lookup-and-response-to-a.patch deleted file mode 100644 index 80b31c3..0000000 --- a/SOURCES/0003-ovn-northd-Move-lswitch-DNS-lookup-and-response-to-a.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 685d26ba45965b2268fbbc36d167115419321f25 Mon Sep 17 00:00:00 2001 -Message-Id: <685d26ba45965b2268fbbc36d167115419321f25.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:30 +0000 -Subject: [PATCH 03/16] ovn-northd: Move lswitch DNS lookup and response to a - function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 50 ++++++++++++++++++++++++--------------------- - 1 file changed, 27 insertions(+), 23 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index a5b28584f..be98a6013 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6780,29 +6780,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- /* Logical switch ingress table 17 and 18: DNS lookup and response -- * priority 100 flows. -- */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs || !ls_has_dns_records(od->nbs)) { -- continue; -- } -- -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, -- "udp.dst == 53", -- REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;"); -- const char *dns_action = "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " -- "udp.dst = udp.src; udp.src = 53; outport = inport; " -- "flags.loopback = 1; output;"; -- const char *dns_match = "udp.dst == 53 && "REGBIT_DNS_LOOKUP_RESULT; -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, -- dns_match, dns_action); -- dns_action = "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " -- "udp.dst = udp.src; udp.src = 53; outport = inport; " -- "flags.loopback = 1; output;"; -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, -- dns_match, dns_action); -- } - - /* Ingress table 14 and 15: DHCP options and response, by default goto - * next. (priority 0). -@@ -7484,6 +7461,32 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op, - } - } - -+/* Logical switch ingress table 17 and 18: DNS lookup and response -+* priority 100 flows. -+*/ -+static void -+build_lswitch_dns_lookup_and_response(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs && ls_has_dns_records(od->nbs)) { -+ -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, -+ "udp.dst == 53", -+ REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;"); -+ const char *dns_action = "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " -+ "udp.dst = udp.src; udp.src = 53; outport = inport; " -+ "flags.loopback = 1; output;"; -+ const char *dns_match = "udp.dst == 53 && "REGBIT_DNS_LOOKUP_RESULT; -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, -+ dns_match, dns_action); -+ dns_action = "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " -+ "udp.dst = udp.src; udp.src = 53; outport = inport; " -+ "flags.loopback = 1; output;"; -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, -+ dns_match, dns_action); -+ } -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -11335,6 +11338,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_lswitch_lflows_admission_control(od, lsi->lflows); - build_lswitch_input_port_sec_od(od, lsi->lflows); - build_lswitch_arp_nd_responder_default(od, lsi->lflows); -+ build_lswitch_dns_lookup_and_response(od, lsi->lflows); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); --- -2.29.2 - diff --git a/SOURCES/0003-ovsdb-idl-Avoid-inconsistent-IDL-state-with-OVSDB_MO.patch b/SOURCES/0003-ovsdb-idl-Avoid-inconsistent-IDL-state-with-OVSDB_MO.patch deleted file mode 100644 index c2856ef..0000000 --- a/SOURCES/0003-ovsdb-idl-Avoid-inconsistent-IDL-state-with-OVSDB_MO.patch +++ /dev/null @@ -1,460 +0,0 @@ -From 2b29d852dc13843c72c3820fb82c1b123f3f77d7 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Thu, 28 May 2020 14:32:31 +0200 -Subject: [PATCH 3/4] ovsdb-idl: Avoid inconsistent IDL state with - OVSDB_MONITOR_V3. - -Assuming an ovsdb client connected to a database using OVSDB_MONITOR_V3 -(i.e., "monitor_cond_since" method) with the initial monitor condition -MC1. - -Assuming the following two transactions are executed on the -ovsdb-server: -TXN1: "insert record R1 in table T1" -TXN2: "insert record R2 in table T2" - -If the client's monitor condition MC1 for table T2 matches R2 then the -client will receive the following update3 message: -method="update3", "insert record R2 in table T2", last-txn-id=TXN2 - -At this point, if the presence of the new record R2 in the IDL triggers -the client to update its monitor condition to MC2 and add a clause for -table T1 which matches R1, a monitor_cond_change message is sent to the -server: -method="monitor_cond_change", "clauses from MC2" - -In normal operation the ovsdb-server will reply with a new update3 -message of the form: -method="update3", "insert record R1 in table T1", last-txn-id=TXN2 - -However, if the connection drops in the meantime, this last update might -get lost. - -It might happen that during the reconnect a new transaction happens -that modifies the original record R1: -TXN3: "modify record R1 in table T1" - -When the client reconnects, it will try to perform a fast resync by -sending: -method="monitor_cond_since", "clauses from MC2", last-txn-id=TXN2 - -Because TXN2 is still in the ovsdb-server transaction history, the -server replies with the changes from the most recent transactions only, -i.e., TXN3: -result="true", last-txbb-id=TXN3, "modify record R1 in table T1" - -This causes the IDL on the client in to end up in an inconsistent -state because it has never seen the update that created R1. - -Such a scenario is described in: -https://bugzilla.redhat.com/show_bug.cgi?id=1808580#c22 - -To avoid this issue, the IDL will now maintain (up to) 3 different -types of conditions for each DB table: -- new_cond: condition that has been set by the IDL client but has - not yet been sent to the server through monitor_cond_change. -- req_cond: condition that has been sent to the server but the reply - acknowledging the change hasn't been received yet. -- ack_cond: condition that has been acknowledged by the server. - -Whenever the IDL FSM is restarted (e.g., voluntary or involuntary -disconnect): -- if there is a known last_id txn-id the code ensures that new_cond - will contain the most recent condition set by the IDL client - (either req_cond if there was a request in flight, or new_cond - if the IDL client set a condition while the IDL was disconnected) -- if there is no known last_id txn-id the code ensures that ack_cond will - contain the most recent conditions set by the IDL client regardless - whether they were acked by the server or not. - -When monitor_cond_since/monitor_cond requests are sent they will -always include ack_cond and if new_cond is not NULL a follow up -monitor_cond_change will be generated afterwards. - -On the other hand ovsdb_idl_db_set_condition() will always modify new_cond. - -This ensures that updates of type "insert" that happened before the last -transaction known by the IDL but didn't match old monitor conditions are -sent upon reconnect if the monitor condition has changed to include them -in the meantime. - -Fixes: 403a6a0cb003 ("ovsdb-idl: Fast resync from server when connection reset.") -Signed-off-by: Dumitru Ceara -Acked-by: Han Zhou -Signed-off-by: Ilya Maximets -(cherry picked from upstream OVS commit ae25f8c8fff80a58cd0a15e2d3ae7ab1b4994e48) - -Change-Id: I4f3cd43cf69dfe76eb65c9709b759e5062c29e89 ---- - openvswitch-2.13.0/lib/ovsdb-idl-provider.h | 8 +- - openvswitch-2.13.0/lib/ovsdb-idl.c | 167 +++++++++++++++++--- - openvswitch-2.13.0/tests/ovsdb-idl.at | 56 +++++++ - 3 files changed, 206 insertions(+), 25 deletions(-) - -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl-provider.h b/openvswitch-2.13.0/lib/ovsdb-idl-provider.h -index 30d1d08eb..00497d940 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl-provider.h -+++ b/openvswitch-2.13.0/lib/ovsdb-idl-provider.h -@@ -122,8 +122,12 @@ struct ovsdb_idl_table { - unsigned int change_seqno[OVSDB_IDL_CHANGE_MAX]; - struct ovs_list indexes; /* Contains "struct ovsdb_idl_index"s */ - struct ovs_list track_list; /* Tracked rows (ovsdb_idl_row.track_node). */ -- struct ovsdb_idl_condition condition; -- bool cond_changed; -+ struct ovsdb_idl_condition *ack_cond; /* Last condition acked by the -+ * server. */ -+ struct ovsdb_idl_condition *req_cond; /* Last condition requested to the -+ * server. */ -+ struct ovsdb_idl_condition *new_cond; /* Latest condition set by the IDL -+ * client. */ - }; - - struct ovsdb_idl_class { -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl.c b/openvswitch-2.13.0/lib/ovsdb-idl.c -index 2d351791f..8eb421366 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl.c -+++ b/openvswitch-2.13.0/lib/ovsdb-idl.c -@@ -240,6 +240,10 @@ static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *, - struct ovsdb_idl_db *, - enum ovsdb_idl_monitor_method); - static void ovsdb_idl_db_clear(struct ovsdb_idl_db *db); -+static void ovsdb_idl_db_ack_condition(struct ovsdb_idl_db *db); -+static void ovsdb_idl_db_sync_condition(struct ovsdb_idl_db *db); -+static void ovsdb_idl_condition_move(struct ovsdb_idl_condition **dst, -+ struct ovsdb_idl_condition **src); - - struct ovsdb_idl { - struct ovsdb_idl_db server; -@@ -424,9 +428,11 @@ ovsdb_idl_db_init(struct ovsdb_idl_db *db, const struct ovsdb_idl_class *class, - = table->change_seqno[OVSDB_IDL_CHANGE_MODIFY] - = table->change_seqno[OVSDB_IDL_CHANGE_DELETE] = 0; - table->db = db; -- ovsdb_idl_condition_init(&table->condition); -- ovsdb_idl_condition_add_clause_true(&table->condition); -- table->cond_changed = false; -+ table->ack_cond = NULL; -+ table->req_cond = NULL; -+ table->new_cond = xmalloc(sizeof *table->new_cond); -+ ovsdb_idl_condition_init(table->new_cond); -+ ovsdb_idl_condition_add_clause_true(table->new_cond); - } - db->monitor_id = json_array_create_2(json_string_create("monid"), - json_string_create(class->database)); -@@ -558,12 +564,15 @@ ovsdb_idl_set_shuffle_remotes(struct ovsdb_idl *idl, bool shuffle) - static void - ovsdb_idl_db_destroy(struct ovsdb_idl_db *db) - { -+ struct ovsdb_idl_condition *null_cond = NULL; - ovs_assert(!db->txn); - ovsdb_idl_db_txn_abort_all(db); - ovsdb_idl_db_clear(db); - for (size_t i = 0; i < db->class_->n_tables; i++) { - struct ovsdb_idl_table *table = &db->tables[i]; -- ovsdb_idl_condition_destroy(&table->condition); -+ ovsdb_idl_condition_move(&table->ack_cond, &null_cond); -+ ovsdb_idl_condition_move(&table->req_cond, &null_cond); -+ ovsdb_idl_condition_move(&table->new_cond, &null_cond); - ovsdb_idl_destroy_indexes(table); - shash_destroy(&table->columns); - hmap_destroy(&table->rows); -@@ -692,6 +701,12 @@ ovsdb_idl_send_request(struct ovsdb_idl *idl, struct jsonrpc_msg *request) - static void - ovsdb_idl_restart_fsm(struct ovsdb_idl *idl) - { -+ /* Resync data DB table conditions to avoid missing updates due to -+ * conditions that were in flight or changed locally while the connection -+ * was down. -+ */ -+ ovsdb_idl_db_sync_condition(&idl->data); -+ - ovsdb_idl_send_schema_request(idl, &idl->server); - ovsdb_idl_transition(idl, IDL_S_SERVER_SCHEMA_REQUESTED); - idl->data.monitoring = OVSDB_IDL_NOT_MONITORING; -@@ -799,7 +814,9 @@ ovsdb_idl_process_response(struct ovsdb_idl *idl, struct jsonrpc_msg *msg) - * do, it's a "monitor_cond_change", which means that the conditional - * monitor clauses were updated. - * -- * If further condition changes were pending, send them now. */ -+ * Mark the last requested conditions as acked and if further -+ * condition changes were pending, send them now. */ -+ ovsdb_idl_db_ack_condition(&idl->data); - ovsdb_idl_send_cond_change(idl); - idl->data.cond_seqno++; - break; -@@ -1495,30 +1512,60 @@ ovsdb_idl_condition_equals(const struct ovsdb_idl_condition *a, - } - - static void --ovsdb_idl_condition_clone(struct ovsdb_idl_condition *dst, -+ovsdb_idl_condition_clone(struct ovsdb_idl_condition **dst, - const struct ovsdb_idl_condition *src) - { -- ovsdb_idl_condition_init(dst); -+ if (*dst) { -+ ovsdb_idl_condition_destroy(*dst); -+ } else { -+ *dst = xmalloc(sizeof **dst); -+ } -+ ovsdb_idl_condition_init(*dst); - -- dst->is_true = src->is_true; -+ (*dst)->is_true = src->is_true; - - const struct ovsdb_idl_clause *clause; - HMAP_FOR_EACH (clause, hmap_node, &src->clauses) { -- ovsdb_idl_condition_add_clause__(dst, clause, clause->hmap_node.hash); -+ ovsdb_idl_condition_add_clause__(*dst, clause, clause->hmap_node.hash); - } - } - -+static void -+ovsdb_idl_condition_move(struct ovsdb_idl_condition **dst, -+ struct ovsdb_idl_condition **src) -+{ -+ if (*dst) { -+ ovsdb_idl_condition_destroy(*dst); -+ free(*dst); -+ } -+ *dst = *src; -+ *src = NULL; -+} -+ - static unsigned int - ovsdb_idl_db_set_condition(struct ovsdb_idl_db *db, - const struct ovsdb_idl_table_class *tc, - const struct ovsdb_idl_condition *condition) - { -+ struct ovsdb_idl_condition *table_cond; - struct ovsdb_idl_table *table = ovsdb_idl_db_table_from_class(db, tc); - unsigned int seqno = db->cond_seqno; -- if (!ovsdb_idl_condition_equals(condition, &table->condition)) { -- ovsdb_idl_condition_destroy(&table->condition); -- ovsdb_idl_condition_clone(&table->condition, condition); -- db->cond_changed = table->cond_changed = true; -+ -+ /* Compare the new condition to the last known condition which can be -+ * either "new" (not sent yet), "requested" or "acked", in this order. -+ */ -+ if (table->new_cond) { -+ table_cond = table->new_cond; -+ } else if (table->req_cond) { -+ table_cond = table->req_cond; -+ } else { -+ table_cond = table->ack_cond; -+ } -+ ovs_assert(table_cond); -+ -+ if (!ovsdb_idl_condition_equals(condition, table_cond)) { -+ ovsdb_idl_condition_clone(&table->new_cond, condition); -+ db->cond_changed = true; - poll_immediate_wake(); - return seqno + 1; - } -@@ -1563,9 +1610,8 @@ ovsdb_idl_condition_to_json(const struct ovsdb_idl_condition *cnd) - } - - static struct json * --ovsdb_idl_create_cond_change_req(struct ovsdb_idl_table *table) -+ovsdb_idl_create_cond_change_req(const struct ovsdb_idl_condition *cond) - { -- const struct ovsdb_idl_condition *cond = &table->condition; - struct json *monitor_cond_change_request = json_object_create(); - struct json *cond_json = ovsdb_idl_condition_to_json(cond); - -@@ -1585,8 +1631,12 @@ ovsdb_idl_db_compose_cond_change(struct ovsdb_idl_db *db) - for (size_t i = 0; i < db->class_->n_tables; i++) { - struct ovsdb_idl_table *table = &db->tables[i]; - -- if (table->cond_changed) { -- struct json *req = ovsdb_idl_create_cond_change_req(table); -+ /* Always use the most recent conditions set by the IDL client when -+ * requesting monitor_cond_change, i.e., table->new_cond. -+ */ -+ if (table->new_cond) { -+ struct json *req = -+ ovsdb_idl_create_cond_change_req(table->new_cond); - if (req) { - if (!monitor_cond_change_requests) { - monitor_cond_change_requests = json_object_create(); -@@ -1595,7 +1645,11 @@ ovsdb_idl_db_compose_cond_change(struct ovsdb_idl_db *db) - table->class_->name, - json_array_create_1(req)); - } -- table->cond_changed = false; -+ /* Mark the new condition as requested by moving it to req_cond. -+ * If there's already requested condition that's a bug. -+ */ -+ ovs_assert(table->req_cond == NULL); -+ ovsdb_idl_condition_move(&table->req_cond, &table->new_cond); - } - } - -@@ -1610,6 +1664,73 @@ ovsdb_idl_db_compose_cond_change(struct ovsdb_idl_db *db) - return jsonrpc_create_request("monitor_cond_change", params, NULL); - } - -+/* Marks all requested table conditions in 'db' as acked by the server. -+ * It should be called when the server replies to monitor_cond_change -+ * requests. -+ */ -+static void -+ovsdb_idl_db_ack_condition(struct ovsdb_idl_db *db) -+{ -+ for (size_t i = 0; i < db->class_->n_tables; i++) { -+ struct ovsdb_idl_table *table = &db->tables[i]; -+ -+ if (table->req_cond) { -+ ovsdb_idl_condition_move(&table->ack_cond, &table->req_cond); -+ } -+ } -+} -+ -+/* Should be called when the IDL fsm is restarted and resyncs table conditions -+ * based on the state the DB is in: -+ * - if a non-zero last_id is available for the DB then upon reconnect -+ * the IDL should first request acked conditions to avoid missing updates -+ * about records that were added before the transaction with -+ * txn-id == last_id. If there were requested condition changes in flight -+ * (i.e., req_cond not NULL) and the IDL client didn't set new conditions -+ * (i.e., new_cond is NULL) then move req_cond to new_cond to trigger a -+ * follow up monitor_cond_change request. -+ * - if there's no last_id available for the DB then it's safe to use the -+ * latest conditions set by the IDL client even if they weren't acked yet. -+ */ -+static void -+ovsdb_idl_db_sync_condition(struct ovsdb_idl_db *db) -+{ -+ bool ack_all = uuid_is_zero(&db->last_id); -+ -+ db->cond_changed = false; -+ for (size_t i = 0; i < db->class_->n_tables; i++) { -+ struct ovsdb_idl_table *table = &db->tables[i]; -+ -+ /* When monitor_cond_since requests will be issued, the -+ * table->ack_cond condition will be added to the "where" clause". -+ * Follow up monitor_cond_change requests will use table->new_cond. -+ */ -+ if (ack_all) { -+ if (table->new_cond) { -+ ovsdb_idl_condition_move(&table->req_cond, &table->new_cond); -+ } -+ -+ if (table->req_cond) { -+ ovsdb_idl_condition_move(&table->ack_cond, &table->req_cond); -+ } -+ } else { -+ /* If there was no "unsent" condition but instead a -+ * monitor_cond_change request was in flight, move table->req_cond -+ * to table->new_cond and set db->cond_changed to trigger a new -+ * monitor_cond_change request. -+ * -+ * However, if a new condition has been set by the IDL client, -+ * monitor_cond_change will be sent anyway and will use the most -+ * recent table->new_cond so there's no need to update it here. -+ */ -+ if (table->req_cond && !table->new_cond) { -+ ovsdb_idl_condition_move(&table->new_cond, &table->req_cond); -+ db->cond_changed = true; -+ } -+ } -+ } -+} -+ - static void - ovsdb_idl_send_cond_change(struct ovsdb_idl *idl) - { -@@ -2064,13 +2185,15 @@ ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl, struct ovsdb_idl_db *db, - monitor_request = json_object_create(); - json_object_put(monitor_request, "columns", columns); - -- const struct ovsdb_idl_condition *cond = &table->condition; -+ /* Always use acked conditions when requesting -+ * monitor_cond/monitor_cond_since. -+ */ -+ const struct ovsdb_idl_condition *cond = table->ack_cond; - if ((monitor_method == OVSDB_IDL_MM_MONITOR_COND || - monitor_method == OVSDB_IDL_MM_MONITOR_COND_SINCE) && -- !ovsdb_idl_condition_is_true(cond)) { -+ cond && !ovsdb_idl_condition_is_true(cond)) { - json_object_put(monitor_request, "where", - ovsdb_idl_condition_to_json(cond)); -- table->cond_changed = false; - } - json_object_put(monitor_requests, tc->name, - json_array_create_1(monitor_request)); -@@ -2078,8 +2201,6 @@ ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl, struct ovsdb_idl_db *db, - } - free_schema(schema); - -- db->cond_changed = false; -- - struct json *params = json_array_create_3( - json_string_create(db->class_->database), - json_clone(db->monitor_id), -diff --git a/openvswitch-2.13.0/tests/ovsdb-idl.at b/openvswitch-2.13.0/tests/ovsdb-idl.at -index cc38d69c1..a5ca96646 100644 ---- a/openvswitch-2.13.0/tests/ovsdb-idl.at -+++ b/openvswitch-2.13.0/tests/ovsdb-idl.at -@@ -1814,3 +1814,59 @@ m4_define([OVSDB_CHECK_IDL_LEADER_ONLY_PY], - - OVSDB_CHECK_IDL_LEADER_ONLY_PY([Check Python IDL connects to leader], 3, ['remote']) - OVSDB_CHECK_IDL_LEADER_ONLY_PY([Check Python IDL reconnects to leader], 3, ['remote' '+remotestop' 'remote']) -+ -+# same as OVSDB_CHECK_IDL but uses C IDL implementation with tcp -+# with multiple remotes. -+m4_define([OVSDB_CHECK_CLUSTER_IDL_C], -+ [AT_SETUP([$1 - C - tcp]) -+ AT_KEYWORDS([ovsdb server idl positive tcp socket $5]) -+ m4_define([LPBK],[127.0.0.1]) -+ AT_CHECK([ovsdb_cluster_start_idltest $2 "ptcp:0:"LPBK]) -+ PARSE_LISTENING_PORT([s1.log], [TCP_PORT_1]) -+ PARSE_LISTENING_PORT([s2.log], [TCP_PORT_2]) -+ PARSE_LISTENING_PORT([s3.log], [TCP_PORT_3]) -+ remotes=tcp:LPBK:$TCP_PORT_1,tcp:LPBK:$TCP_PORT_2,tcp:LPBK:$TCP_PORT_3 -+ -+ m4_if([$3], [], [], -+ [AT_CHECK([ovsdb-client transact $remotes $3], [0], [ignore], [ignore])]) -+ AT_CHECK([test-ovsdb '-vPATTERN:console:test-ovsdb|%c|%m' -vjsonrpc -t10 idl tcp:LPBK:$TCP_PORT_1 $4], -+ [0], [stdout], [ignore]) -+ AT_CHECK([sort stdout | uuidfilt]m4_if([$7],,, [[| $7]]), -+ [0], [$5]) -+ AT_CLEANUP]) -+ -+# Checks that monitor_cond_since works fine when disconnects happen -+# with cond_change requests in flight (i.e., IDL is properly updated). -+OVSDB_CHECK_CLUSTER_IDL_C([simple idl, monitor_cond_since, cluster disconnect], -+ 3, -+ [['["idltest", -+ {"op": "insert", -+ "table": "simple", -+ "row": {"i": 1, -+ "r": 1.0, -+ "b": true}}, -+ {"op": "insert", -+ "table": "simple", -+ "row": {"i": 2, -+ "r": 1.0, -+ "b": true}}]']], -+ [['condition simple []' \ -+ 'condition simple [["i","==",2]]' \ -+ 'condition simple [["i","==",1]]' \ -+ '+reconnect' \ -+ '["idltest", -+ {"op": "update", -+ "table": "simple", -+ "where": [["i", "==", 1]], -+ "row": {"r": 2.0 }}]']], -+ [[000: change conditions -+001: empty -+002: change conditions -+003: i=2 r=1 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> -+004: change conditions -+005: reconnect -+006: i=2 r=1 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> -+007: {"error":null,"result":[{"count":1}]} -+008: i=1 r=2 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<2> -+009: done -+]]) --- -2.26.2 - diff --git a/SOURCES/0003-tests-Introduce-new-testing-helpers.patch b/SOURCES/0003-tests-Introduce-new-testing-helpers.patch deleted file mode 100644 index b316d91..0000000 --- a/SOURCES/0003-tests-Introduce-new-testing-helpers.patch +++ /dev/null @@ -1,2233 +0,0 @@ -From 0f2bc62c05f039f3311aebf33f7c01f49caabc5f Mon Sep 17 00:00:00 2001 -From: Ben Pfaff -Date: Wed, 21 Oct 2020 15:01:35 -0700 -Subject: [PATCH 03/10] tests: Introduce new testing helpers. - -These simplify a lot of otherwise harder to understand checks within -the tests. This commit should show how valuable they are, although I'm -sure scope remains to use them in more places. - -Signed-off-by: Ben Pfaff -Acked-by: Numan Siddique - -(cherry-picked from master commit 4afe409e95c72187a8f7a755fa19b17237d14818) -Conflicts: - tests/ovn-northd.at ---- - tests/ovn-controller.at | 8 +- - tests/ovn-ic.at | 31 +-- - tests/ovn-macros.at | 121 ++++++++++ - tests/ovn-nbctl.at | 12 +- - tests/ovn-northd.at | 520 ++++++++++------------------------------ - tests/ovn.at | 495 +++++++++++++------------------------- - tests/ovs-macros.at | 41 +++- - 7 files changed, 466 insertions(+), 762 deletions(-) - -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index d8061345f..014a97760 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -201,9 +201,7 @@ OVS_WAIT_UNTIL([ - ]) - - # 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 - - # Simulate system-id changing while ovn-controller is disconnected from the - # SB. -@@ -227,9 +225,7 @@ OVS_WAIT_UNTIL([ - ]) - - # 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 - - # Gracefully terminate daemons - OVN_CLEANUP_SBOX([hv]) -diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at -index 6fb00319a..1d40ce958 100644 ---- a/tests/ovn-ic.at -+++ b/tests/ovn-ic.at -@@ -5,7 +5,7 @@ ovn_init_ic_db - 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,21 @@ AT_CHECK([ovn-ic-nbctl ts-add ts1]) - 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 --]) - - # 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]) - -diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index a6719be83..be596caf3 100644 ---- a/tests/ovn-macros.at -+++ b/tests/ovn-macros.at -@@ -286,4 +286,125 @@ ovn_populate_arp__() { - } - 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 -+ echo "Waiting until $count rows in $db $table${1+ with $*}..." -+ OVS_WAIT_UNTIL([test $count = $(count_rows $db:$table $a $b $c)],[ -+ echo "$db table $table has the following rows. $(count_rows $db:$table $a $b $c) 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 -+ -+ 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) -+ 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 --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 3dbedc843..79d580d3f 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -536,18 +536,12 @@ snat 30.0.0.1 192.168.1.0/24 - 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]) - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 94007fcac..49d74b08b 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -22,130 +22,47 @@ nb_gwc1_uuid=`ovn-nbctl --bare --columns _uuid find Gateway_Chassis name="alice_ - - # 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 -+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 - --# 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 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" = ""]) -+check_column '' Port_Binding gateway_chassis logical_port=cr-alice - --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'` -- --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 --]) -+check_row_count HA_Chassis 0 -+check_row_count Gateway_Chassis 0 -+check_row_count Ha_Chassis_Group 0 - - AT_CLEANUP - -@@ -202,11 +119,11 @@ ovn_start - - 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,8 +299,7 @@ as northd start_daemon ovn-northd --unixctl="$ovs_base"/northd/ovn-northd.ctl -- - 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 -@@ -392,15 +308,13 @@ OVS_APP_EXIT_AND_WAIT_BY_TARGET(["$ovs_base"/northd/ovn-northd.ctl], ["$ovs_base - # Check no port_binding entry for new port as ovn-northd is not running - 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 --]) -+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]) -@@ -419,28 +333,22 @@ 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 - - # 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 - --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 -@@ -449,44 +357,21 @@ ovn-nbctl 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 - --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 - - # 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 -@@ -495,94 +380,68 @@ 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 - - # 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 - - # 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. -@@ -601,35 +460,23 @@ ovn-nbctl lsp-set-addresses sw0-lr0 router - ovn-nbctl 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 - - 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 -@@ -643,14 +490,10 @@ ovn-nbctl lsp-set-options sw1-lr1 router-port=lr1-sw1 - # Bind sw1-p1 in comp1. - 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 -@@ -661,30 +504,14 @@ ovn-nbctl 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 -- --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"]) -+exp_ref_ch_list="$comp1_ch_uuid $comp2_ch_uuid" -+ -+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 -@@ -699,14 +526,10 @@ ovn-nbctl lsp-set-options sw2-lr2 router-port=lr2-sw2 - # Bind sw2-p1 to comp1 - 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 -@@ -717,53 +540,32 @@ ovn-nbctl 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 - 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 - --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 - --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`]) -- --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 - - # Delete the gateway chassis. HA chassis group should be created in SB DB - # for the redirect-chassis option. -@@ -809,8 +611,8 @@ 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 +621,50 @@ name=hagrp1` - # 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], []) -+wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]' - --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 - - # 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]) -@@ -969,17 +752,11 @@ ovn-nbctl --wait=sb 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 -- - 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]) -@@ -998,9 +775,6 @@ ovn-nbctl --wait=sb 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 -- - # IPV4 - ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 - -@@ -1359,37 +1133,32 @@ 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 - --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 \ - Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \ - health_check @hc - --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 -- --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 ls-lb-add sw0 lb1 - -@@ -1400,7 +1169,7 @@ AT_CHECK([cat lflows.txt], [0], [dnl - - # 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 -@@ -1412,8 +1181,7 @@ 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 -@@ -1427,9 +1195,7 @@ sm_sw1_p1=`ovn-sbctl --bare --columns _uuid find service_monitor logical_port=sw - # Set the service monitor for sw1-p1 to offline - ovn-sbctl set service_monitor $sm_sw1_p1 status=offline - --OVS_WAIT_UNTIL([ -- status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1` -- test "$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 -@@ -1439,9 +1205,7 @@ AT_CHECK([cat lflows.txt], [0], [dnl - # 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 -@@ -1457,9 +1221,7 @@ AT_CHECK([cat lflows.txt], [0], [dnl - 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 -@@ -1468,9 +1230,7 @@ AT_CHECK([cat lflows.txt], [0], [dnl - - # 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 -@@ -1492,16 +1252,9 @@ health_check @hc - # * 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`]) -+wait_row_count Service_Monitor 3 -+wait_row_count Service_Monitor 2 logical_port=sw0-p1 -+wait_row_count Service_Monitor 1 port=1000 - - ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt - AT_CHECK([cat lflows.txt], [0], [dnl -@@ -1512,9 +1265,7 @@ AT_CHECK([cat lflows.txt], [0], [dnl - # Set the service monitor for sw1-p1 to 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 -@@ -1542,26 +1293,23 @@ ovn-nbctl ls-lb-add sw0 lb2 - ovn-nbctl ls-lb-add sw1 lb2 - ovn-nbctl lr-lb-add lr0 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" - --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 - --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 - -@@ -1638,22 +1386,14 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0]) - - # 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 +1419,7 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0]) - 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 - -@@ -1745,20 +1485,14 @@ 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 - -diff --git a/tests/ovn.at b/tests/ovn.at -index 616af83bd..ba17246d4 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -2063,20 +2063,12 @@ get_lsp_uuid () { - # 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 -@@ -2092,26 +2084,14 @@ sleep 1 - # 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 -@@ -4149,7 +4129,7 @@ ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request - - 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 -@@ -4174,7 +4154,7 @@ ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request - - 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 -@@ -8131,18 +8111,18 @@ ovn-nbctl lsp-add ls0 lp0 - 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]) - -@@ -8209,61 +8189,62 @@ AT_CHECK([ovn-nbctl lsp-add ls0 parent1]) - 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 --]) --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 --]) -+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 - --dnl A different parent. -+AS_BOX([tag allocation 3 for parent1]) -+AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0]) -+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 -+ -+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 --]) -- --dnl Restart northd to see that the same allocation remains. -+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" -+ -+AS_BOX([restart northd and make sure tag allocation is stable]) - as northd - OVS_APP_EXIT_AND_WAIT([ovn-northd]) - start_daemon ovn-northd \ -@@ -8272,30 +8253,30 @@ start_daemon ovn-northd \ - - 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' -@@ -8702,8 +8683,7 @@ check_tos 0 - - # 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 -@@ -8737,8 +8717,7 @@ 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 --]) -+as hv check_row_count nb:QoS 0 - check_tos 0 - - # check at hv without qos meter -@@ -8747,8 +8726,7 @@ AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], - - # 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 --]) -+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 -@@ -10217,12 +10195,8 @@ AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore]) - # 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. -@@ -10635,13 +10609,11 @@ expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}00351 - 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 - --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]) - -@@ -11083,11 +11055,9 @@ as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2 - 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. -@@ -11440,7 +11410,7 @@ packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111 - # 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' -@@ -11671,31 +11641,13 @@ 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'` -+check_row_count HA_Chassis 2 'chassis!=[[]]' - --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'` -- --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 -@@ -11738,8 +11690,8 @@ as hv1 ovs-ofctl dump-flows br-int table=32 - 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 \ -@@ -11767,29 +11719,12 @@ OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.16 - ]]) - - # 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 - -@@ -11815,9 +11750,7 @@ grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ - ]) - - # 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 -@@ -11889,9 +11822,7 @@ grep 00:00:02:01:02:04 | wc -l], [0], [[0 - ]]) - - # 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 -@@ -11924,11 +11855,7 @@ done - # 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 -@@ -11937,19 +11864,14 @@ echo "Deleting hv2-vif1" - 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" -@@ -11967,16 +11889,13 @@ ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 20 - # 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 \ -@@ -12018,24 +11937,10 @@ for i in 1 2; do - 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 -@@ -12123,9 +12028,7 @@ grep 00:00:02:01:02:04 | wc -l], [0], [[0 - ]]) - - # 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]) - -@@ -12392,22 +12295,14 @@ gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2) - 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]) - -@@ -12520,11 +12415,9 @@ AT_CHECK([ovn-sbctl dump-flows lr0_ip6 | grep nd_na_router | \ - 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,\}$//' -@@ -12672,10 +12565,10 @@ ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1 - ovn-nbctl --wait=hv --timeout=3 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" -@@ -12683,7 +12576,7 @@ as hv2 - 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], []) -@@ -12695,7 +12588,7 @@ as hv1 - 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 -@@ -12708,7 +12601,7 @@ echo "verifying that lsp0 binding moves when requested-chassis is changed" - - 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]) -@@ -12735,23 +12628,23 @@ ovs-vsctl add-br br-phys - 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} - 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 - 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], []) -+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], []) - -@@ -13589,29 +13482,17 @@ ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p - 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 - -@@ -16098,8 +15979,8 @@ 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 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 -@@ -17307,10 +17188,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ - /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 - - # Send traffic and make sure it gets forwarded only on the port that joined. - as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -@@ -17335,10 +17213,7 @@ OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) - - # 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" --]) -+wait_row_count IGMP_Group 0 address=239.0.1.68 - - # Check that traffic for 224.0.0.X is flooded even if some hosts register for - # it. -@@ -17349,10 +17224,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ - /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 -@@ -17443,10 +17315,7 @@ send_igmp_v3_report hv2-vif3 hv2 \ - /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 - - # Send traffic from sw3 and make sure it is relayed by rtr. - # to ports that joined. -@@ -17933,10 +17802,7 @@ send_mld_v2_report hv2-vif1 hv2 \ - /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"' - - # Send traffic and make sure it gets forwarded only on the two ports that - # joined. -@@ -17969,10 +17835,7 @@ send_mld_v2_report hv1-vif1 hv1 \ - /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 -@@ -18001,10 +17864,7 @@ OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty]) - - # 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" --]) -+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. -@@ -18015,10 +17875,7 @@ send_mld_v2_report hv1-vif1 hv1 \ - /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 -@@ -18115,10 +17972,7 @@ send_mld_v2_report hv2-vif3 hv2 \ - /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. -@@ -18169,10 +18023,7 @@ send_mld_v2_report hv1-vif4 hv1 \ - /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"' - - # Send traffic from sw3 and make sure it is relayed by rtr - # to ports that joined. -@@ -18281,10 +18132,7 @@ send_mld_v2_report hv1-vif2 hv1 \ - 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"' - - # Send traffic from sw1-p21 - send_ip_multicast_pkt hv2-vif1 hv2 \ -@@ -19005,14 +18853,14 @@ ip_to_hex() { - # 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 -@@ -19052,7 +18900,7 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa - # 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 -@@ -19634,8 +19482,7 @@ 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`]) -+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 -@@ -19661,8 +19508,7 @@ OVS_WAIT_UNTIL( - 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`]) -+wait_row_count Service_Monitor 2 status=offline - - OVS_WAIT_UNTIL( - [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \ -@@ -19689,33 +19535,26 @@ AT_CHECK([cat lflows.txt], [0], [dnl - # Delete sw0-p1 - 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)]) -+wait_row_count Service_Monitor 2 status=offline - - 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)]) -+wait_row_count Service_Monitor 0 - - # Add back sw0-p1 but without any address set. - 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 -@@ -19826,9 +19665,7 @@ ovn-nbctl --wait=hv sync - - # 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 -@@ -22107,9 +21944,9 @@ as hv1 ovs-vsctl \ - -- 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 -@@ -22821,9 +22658,9 @@ as hv1 ovs-vsctl \ - -- 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 \ -@@ -22833,13 +22670,13 @@ as hv1 ovs-vsctl \ - 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 - -diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at -index 3dcf8f96d..856f5d2d7 100644 ---- a/tests/ovs-macros.at -+++ b/tests/ovs-macros.at -@@ -23,9 +23,11 @@ dnl that can ordinarily be run only within AT_SETUP...AT_CLEANUP. - 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]) -@@ -231,21 +233,52 @@ ovs_wait_failed () { - 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])]) --- -2.28.0 - diff --git a/SOURCES/0004-Add-new-table-Load_Balancer-in-Southbound-database.patch b/SOURCES/0004-Add-new-table-Load_Balancer-in-Southbound-database.patch deleted file mode 100644 index 8799f5e..0000000 --- a/SOURCES/0004-Add-new-table-Load_Balancer-in-Southbound-database.patch +++ /dev/null @@ -1,428 +0,0 @@ -From 8eee079880255ecb70051be177c78bdfa115b3bf Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Wed, 7 Oct 2020 17:49:17 +0530 -Subject: [PATCH 04/10] Add new table Load_Balancer in Southbound database. - -This patch adds a new table 'Load_Balancer' in SB DB and syncs the Load_Balancer table rows -from NB DB to SB DB. An upcoming patch will make use of this table for handling the -load balancer hairpin traffic. - -Co-authored-by: Dumitru Ceara -Signed-off-by: Dumitru Ceara -Signed-off-by: Numan Siddique -Acked-by: Mark Michelson ---- - northd/ovn-northd.c | 114 ++++++++++++++++++++++++++++++++++++++++-- - ovn-sb.ovsschema | 27 +++++++++- - ovn-sb.xml | 45 +++++++++++++++++ - tests/ovn-northd.at | 86 +++++++++++++++++++++++++++++++ - utilities/ovn-sbctl.c | 3 ++ - 5 files changed, 269 insertions(+), 6 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 38074e7f4..c32e25c3d 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -3333,9 +3333,14 @@ struct ovn_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 lb_vip *vips; - size_t n_vips; -+ -+ size_t n_dps; -+ size_t n_allocated_dps; -+ const struct sbrec_datapath_binding **dps; - }; - - struct lb_vip { -@@ -3362,7 +3367,7 @@ struct lb_vip_backend { - - - static inline struct ovn_lb * --ovn_lb_find(struct hmap *lbs, struct uuid *uuid) -+ovn_lb_find(struct hmap *lbs, const struct uuid *uuid) - { - struct ovn_lb *lb; - size_t hash = uuid_hash(uuid); -@@ -3375,6 +3380,14 @@ ovn_lb_find(struct hmap *lbs, struct uuid *uuid) - return NULL; - } - -+static void -+ovn_lb_add_datapath(struct ovn_lb *lb, struct ovn_datapath *od) -+{ -+ 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++] = od->sb; -+} - - struct service_monitor_info { - struct hmap_node hmap_node; -@@ -3603,6 +3616,7 @@ ovn_lb_destroy(struct ovn_lb *lb) - } - free(lb->vips); - free(lb->selection_fields); -+ free(lb->dps); - } - - static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip, -@@ -3648,8 +3662,8 @@ static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip, - } - - 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); -@@ -3670,6 +3684,88 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *ports, - ovn_lb_create(ctx, lbs, nbrec_lb, ports, &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; -+ struct ovn_lb *lb = ovn_lb_find(lbs, lb_uuid); -+ -+ ovn_lb_add_datapath(lb, od); -+ } -+ } -+ -+ struct ovn_lb *lb; -+ -+ /* 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_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_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; - HMAP_FOR_EACH_POP (mon_info, hmap_node, &monitor_map) { - if (!mon_info->required) { -@@ -12176,7 +12272,7 @@ ovnnb_db_run(struct northd_context *ctx, - - 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); -@@ -12951,6 +13047,8 @@ main(int argc, char *argv[]) - ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_datapath_binding); - 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); - -@@ -13118,6 +13216,14 @@ main(int argc, char *argv[]) - 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); - -diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema -index d1c506a22..a1ee8d8d1 100644 ---- a/ovn-sb.ovsschema -+++ b/ovn-sb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Southbound", -- "version": "2.10.0", -- "cksum": "2548342632 22615", -+ "version": "2.11.0", -+ "cksum": "455413803 23814", - "tables": { - "SB_Global": { - "columns": { -@@ -152,6 +152,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 +452,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 --git a/ovn-sb.xml b/ovn-sb.xml -index 182ff0a8a..7fa0496fe 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -2497,6 +2497,12 @@ tcp.flags = RST; - constructed for each supported encapsulation. - - -+ -+

    -+ Load balancers associated with the datapath. -+

    -+
    -+ - -

    - Each row in is associated with some -@@ -4104,4 +4110,43 @@ tcp.flags = RST; - - -

    -+ -+ -+

    -+ 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 --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 49d74b08b..961fb3712 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1845,3 +1845,89 @@ action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implici - ]) - - 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 -diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c -index f93384940..30236c9cc 100644 ---- a/utilities/ovn-sbctl.c -+++ b/utilities/ovn-sbctl.c -@@ -1442,6 +1442,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = { - - [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}, - }; - - --- -2.28.0 - diff --git a/SOURCES/0004-nbctl-Remove-column-verification-for-partial-updates.patch b/SOURCES/0004-nbctl-Remove-column-verification-for-partial-updates.patch deleted file mode 100644 index 1231213..0000000 --- a/SOURCES/0004-nbctl-Remove-column-verification-for-partial-updates.patch +++ /dev/null @@ -1,272 +0,0 @@ -From 63807420f208364d5143dfc9246f9777e6ae934f Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 11 Dec 2020 11:59:17 +0100 -Subject: [PATCH 4/7] nbctl: Remove column verification for partial updates. - -Since this is not a read-modify-write sequence, it's not strictly -necessary to verify that current value is exactly same while performing -mutations. Verification removal will allow to significantly reduce -size of ovsdb transaction, because it will no longer contain all the -data from the mutated column. - -Before this change, addition of 1 new switch port to a logical switch -with 1000 ports looked like this: - - {transact, - where: _uuid == 'logical switch row uuid' - op : wait - until: == - rows : ports ['< list of current 1000 ports >'] - - < create new lsp in Logical_Switch_Port table > - - where: _uuid == 'logical switch row uuid' - op : mutate - mutations : ports insert ['< 1 uuid of a new lsp >'] - } - -After: - - {transact, - < create new lsp in Logical_Switch_Port table > - - where: _uuid == 'logical switch row uuid' - op : mutate - mutations : ports insert ['< 1 uuid of a new lsp >'] - } - -This should relieve some pressure from the ovsdb-server since it will -not need to parse and execute this huge 'wait' operation. - -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 33 --------------------------------- - 1 file changed, 33 deletions(-) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 7c4dce12a..835161f25 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -1511,7 +1511,6 @@ nbctl_lsp_add(struct ctl_context *ctx) - } - - /* Insert the logical port into the logical switch. */ -- nbrec_logical_switch_verify_ports(ls); - nbrec_logical_switch_update_ports_addvalue(ls, lsp); - - /* Updating runtime cache. */ -@@ -1532,7 +1531,6 @@ remove_lsp(struct ctl_context *ctx, - /* First remove 'lsp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- nbrec_logical_switch_verify_ports(ls); - nbrec_logical_switch_update_ports_delvalue(ls, lsp); - - /* Delete 'lsp' from the IDL. This won't have a real effect on the -@@ -2356,10 +2354,8 @@ nbctl_acl_add(struct ctl_context *ctx) - - /* Insert the acl into the logical switch/port group. */ - if (pg) { -- nbrec_port_group_verify_acls(pg); - nbrec_port_group_update_acls_addvalue(pg, acl); - } else { -- nbrec_logical_switch_verify_acls(ls); - nbrec_logical_switch_update_acls_addvalue(ls, acl); - } - } -@@ -2401,12 +2397,6 @@ nbctl_acl_del(struct ctl_context *ctx) - /* If priority and match are not specified, delete all ACLs with the - * specified direction. */ - if (ctx->argc == 3) { -- if (pg) { -- nbrec_port_group_verify_acls(pg); -- } else { -- nbrec_logical_switch_verify_acls(ls); -- } -- - for (size_t i = 0; i < n_acls; i++) { - if (!strcmp(direction, acls[i]->direction)) { - if (pg) { -@@ -2438,10 +2428,8 @@ nbctl_acl_del(struct ctl_context *ctx) - if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) && - !strcmp(direction, acl->direction)) { - if (pg) { -- nbrec_port_group_verify_acls(pg); - nbrec_port_group_update_acls_delvalue(pg, acl); - } else { -- nbrec_logical_switch_verify_acls(ls); - nbrec_logical_switch_update_acls_delvalue(ls, acl); - } - return; -@@ -2596,7 +2584,6 @@ nbctl_qos_add(struct ctl_context *ctx) - } - - /* Insert the qos rule the logical switch. */ -- nbrec_logical_switch_verify_qos_rules(ls); - nbrec_logical_switch_update_qos_rules_addvalue(ls, qos); - } - -@@ -2636,7 +2623,6 @@ nbctl_qos_del(struct ctl_context *ctx) - if (ctx->argc == 3) { - size_t i; - -- nbrec_logical_switch_verify_qos_rules(ls); - if (qos_rule_uuid) { - for (i = 0; i < ls->n_qos_rules; i++) { - if (uuid_equals(qos_rule_uuid, -@@ -2686,7 +2672,6 @@ nbctl_qos_del(struct ctl_context *ctx) - - if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) && - !strcmp(direction, qos->direction)) { -- nbrec_logical_switch_verify_qos_rules(ls); - nbrec_logical_switch_update_qos_rules_delvalue(ls, qos); - return; - } -@@ -3149,7 +3134,6 @@ nbctl_lr_lb_add(struct ctl_context *ctx) - } - - /* Insert the load balancer into the logical router. */ -- nbrec_logical_router_verify_load_balancer(lr); - nbrec_logical_router_update_load_balancer_addvalue(lr, new_lb); - } - -@@ -3183,7 +3167,6 @@ nbctl_lr_lb_del(struct ctl_context *ctx) - - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ -- nbrec_logical_router_verify_load_balancer(lr); - nbrec_logical_router_update_load_balancer_delvalue(lr, lb); - return; - } -@@ -3258,7 +3241,6 @@ nbctl_ls_lb_add(struct ctl_context *ctx) - } - - /* Insert the load balancer into the logical switch. */ -- nbrec_logical_switch_verify_load_balancer(ls); - nbrec_logical_switch_update_load_balancer_addvalue(ls, new_lb); - } - -@@ -3292,7 +3274,6 @@ nbctl_ls_lb_del(struct ctl_context *ctx) - - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ -- nbrec_logical_switch_verify_load_balancer(ls); - nbrec_logical_switch_update_load_balancer_delvalue(ls, lb); - return; - } -@@ -3721,7 +3702,6 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - nbrec_logical_router_policy_set_options(policy, &options); - smap_destroy(&options); - -- nbrec_logical_router_verify_policies(lr); - nbrec_logical_router_update_policies_addvalue(lr, policy); - if (next_hop != NULL) { - free(next_hop); -@@ -3761,7 +3741,6 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - if (ctx->argc == 3) { - size_t i; - -- nbrec_logical_router_verify_policies(lr); - if (lr_policy_uuid) { - for (i = 0; i < lr->n_policies; i++) { - if (uuid_equals(lr_policy_uuid, -@@ -3796,7 +3775,6 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - struct nbrec_logical_router_policy *routing_policy = lr->policies[i]; - if (priority == routing_policy->priority && - !strcmp(ctx->argv[3], routing_policy->match)) { -- nbrec_logical_router_verify_policies(lr); - nbrec_logical_router_update_policies_delvalue(lr, routing_policy); - return; - } -@@ -3996,7 +3974,6 @@ nbctl_lr_route_add(struct ctl_context *ctx) - nbrec_logical_router_static_route_set_options(route, &options); - } - -- nbrec_logical_router_verify_static_routes(lr); - nbrec_logical_router_update_static_routes_addvalue(lr, route); - - cleanup: -@@ -4055,7 +4032,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) - } - - size_t n_removed = 0; -- nbrec_logical_router_verify_static_routes(lr); - for (size_t i = 0; i < lr->n_static_routes; i++) { - /* Compare route policy, if specified. */ - if (policy) { -@@ -4393,7 +4369,6 @@ nbctl_lr_nat_add(struct ctl_context *ctx) - smap_destroy(&nat_options); - - /* Insert the NAT into the logical router. */ -- nbrec_logical_router_verify_nat(lr); - nbrec_logical_router_update_nat_addvalue(lr, nat); - - cleanup: -@@ -4430,7 +4405,6 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - - if (ctx->argc == 3) { - /*Deletes all NATs with the specified type. */ -- nbrec_logical_router_verify_nat(lr); - for (size_t i = 0; i < lr->n_nat; i++) { - if (!strcmp(nat_type, lr->nat[i]->type)) { - nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]); -@@ -4457,7 +4431,6 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - continue; - } - if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) { -- nbrec_logical_router_verify_nat(lr); - nbrec_logical_router_update_nat_delvalue(lr, nat); - should_return = true; - } -@@ -4736,7 +4709,6 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx) - nbrec_gateway_chassis_set_priority(gc, priority); - - /* Insert the logical gateway chassis into the logical router port. */ -- nbrec_logical_router_port_verify_gateway_chassis(lrp); - nbrec_logical_router_port_update_gateway_chassis_addvalue(lrp, gc); - free(gc_name); - } -@@ -4754,7 +4726,6 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) - * will actually cause the gateway chassis to be deleted when the - * transaction is sent to the database server (due to garbage - * collection). */ -- nbrec_logical_router_port_verify_gateway_chassis(lrp); - nbrec_logical_router_port_update_gateway_chassis_delvalue(lrp, gc); - } - -@@ -4987,7 +4958,6 @@ nbctl_lrp_add(struct ctl_context *ctx) - } - - /* Insert the logical port into the logical router. */ -- nbrec_logical_router_verify_ports(lr); - nbrec_logical_router_update_ports_addvalue(lr, lrp); - - /* Updating runtime cache. */ -@@ -5008,7 +4978,6 @@ remove_lrp(struct ctl_context *ctx, - /* First remove 'lrp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- nbrec_logical_router_verify_ports(lr); - nbrec_logical_router_update_ports_delvalue(lr, lrp); - - /* Delete 'lrp' from the IDL. This won't have a real effect on -@@ -5933,7 +5902,6 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx) - nbrec_ha_chassis_set_chassis_name(ha_chassis, chassis_name); - nbrec_ha_chassis_set_priority(ha_chassis, priority); - -- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); - nbrec_ha_chassis_group_update_ha_chassis_addvalue(ha_ch_grp, ha_chassis); - } - -@@ -5962,7 +5930,6 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) - return; - } - -- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); - nbrec_ha_chassis_group_update_ha_chassis_delvalue(ha_ch_grp, ha_chassis); - nbrec_ha_chassis_delete(ha_chassis); - } --- -2.28.0 - diff --git a/SOURCES/0004-ofctrl.c-Do-not-change-flow-ordering-when-merging-op.patch b/SOURCES/0004-ofctrl.c-Do-not-change-flow-ordering-when-merging-op.patch deleted file mode 100644 index bbdf637..0000000 --- a/SOURCES/0004-ofctrl.c-Do-not-change-flow-ordering-when-merging-op.patch +++ /dev/null @@ -1,79 +0,0 @@ -From a0828524dcc90169dc1dde36f1306d6f1921ea85 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Sun, 11 Oct 2020 14:05:45 +0200 -Subject: [PATCH 4/7] ofctrl.c: Do not change flow ordering when merging - opposite changes. - -Instead of removing the old desired flow from the list and inserting the new -one at the end we now directly replace the old flow with the new one while -maintaining the same ordering. - -For now order of the flows is not relevant but further commits require -maintaining the order of desired flows. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit e49ce9a33f38f29c44e3c30afcc189b5f6a9ef8e) - -Change-Id: I83e64763ed63cbc44def69d9ea23259e2035f518 ---- - controller/ofctrl.c | 28 +++++++++++++++++++++------- - 1 file changed, 21 insertions(+), 7 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 24b55fc..20cf3ac 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -848,6 +848,26 @@ link_installed_to_desired(struct installed_flow *i, struct desired_flow *d) - d->installed_flow = i; - } - -+/* Replaces 'old_desired' with 'new_desired' in the list of desired flows -+ * that have same match conditions as the installed flow. -+ * -+ * If 'old_desired' was the active flow, 'new_desired' becomes the active one. -+ */ -+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; -+ if (i->desired_flow == old_desired) { -+ i->desired_flow = new_desired; -+ } -+} -+ - static void - unlink_installed_to_desired(struct installed_flow *i, struct desired_flow *d) - { -@@ -1842,21 +1862,15 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table) - * removed during track_flow_add_or_modify. */ - ovs_assert(del_f->installed_flow); - -- bool del_f_was_active = desired_flow_is_active(del_f); - 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. */ - ovs_assert(f->installed_flow == del_f->installed_flow); - unlink_installed_to_desired(del_f->installed_flow, del_f); - } -- if (del_f_was_active) { -- desired_flow_set_active(f); -- } - hmap_remove(&deleted_flows, &del_f->match_hmap_node); - ovs_list_remove(&del_f->track_list_node); - desired_flow_destroy(del_f); --- -1.8.3.1 - diff --git a/SOURCES/0004-ovn-northd-Move-DNS-and-DHCP-defaults-to-a-function.patch b/SOURCES/0004-ovn-northd-Move-DNS-and-DHCP-defaults-to-a-function.patch deleted file mode 100644 index 0668159..0000000 --- a/SOURCES/0004-ovn-northd-Move-DNS-and-DHCP-defaults-to-a-function.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 502d52712bca01f237aa15e5853bc3090e6034e5 Mon Sep 17 00:00:00 2001 -Message-Id: <502d52712bca01f237aa15e5853bc3090e6034e5.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:31 +0000 -Subject: [PATCH 04/16] ovn-northd: Move DNS and DHCP defaults to a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 40 ++++++++++++++++++++-------------------- - 1 file changed, 20 insertions(+), 20 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index be98a6013..45d6a6a2e 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6780,26 +6780,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- -- /* Ingress table 14 and 15: DHCP options and response, by default goto -- * next. (priority 0). -- * Ingress table 16 and 17: DNS lookup and response, by default goto next. -- * (priority 0). -- * Ingress table 18 - External port handling, by default goto next. -- * (priority 0). */ -- -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;"); -- } -- - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbsp || !lsp_is_external(op->nbsp)) { - continue; -@@ -7461,6 +7441,25 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op, - } - } - -+/* Ingress table 14 and 15: DHCP options and response, by default goto -+ * next. (priority 0). -+ * Ingress table 16 and 17: DNS lookup and response, by default goto next. -+ * (priority 0). -+ * Ingress table 18 - External port handling, by default goto next. -+ * (priority 0). */ -+static void -+build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;"); -+ } -+} -+ - /* Logical switch ingress table 17 and 18: DNS lookup and response - * priority 100 flows. - */ -@@ -11339,6 +11338,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_lswitch_input_port_sec_od(od, lsi->lflows); - build_lswitch_arp_nd_responder_default(od, lsi->lflows); - build_lswitch_dns_lookup_and_response(od, lsi->lflows); -+ build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); --- -2.29.2 - diff --git a/SOURCES/0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch b/SOURCES/0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch deleted file mode 100644 index a88d060..0000000 --- a/SOURCES/0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch +++ /dev/null @@ -1,444 +0,0 @@ -From 44d993b41f4a9e995b2ff76938f23413bfed4652 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Mon, 28 Sep 2020 17:31:32 +0530 -Subject: [PATCH 4/5] ovn-northd: Optimize logical flow generation for reject - ACLs. - -ovn-northd adds below lflows for a reject ACL with a match - M - -match = (ip4 && tcp && 'M') action = tcp_reject{} -match = (ip6 && tcp && 'M') action = tcp_reject{} -match = (ip4 && 'M') action = icmp4{} -match = (ip6 && 'M') action = icmp6{} - -This approach has a couple of problems: - - ovn-controller can reject the lflows if there are invalid matches. - Eg. If match 'M' is - 'ip4 && udp'. - - - In a large scale deployment, this could result in lot of invalid - logical flows and increase the size of the SB DB. - -This patch addresses this problem by using newly added reject OVN action. -With this patch, there will be just one lflow for each reject ACL. - -Acked-by: Mark Michelson -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from upstream master commit 4e19493b3d4eb549fc52d6fb3cf004847b64d2de) - -Change-Id: I540247929b4939bd5696a2edcb15a4bf7dc4af77 ---- - northd/ovn-northd.c | 50 +---------- - tests/ovn-northd.at | 214 +++++++------------------------------------- - tests/system-ovn.at | 46 +++++++++- - 3 files changed, 81 insertions(+), 229 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index b099f705b..3a71d0ee8 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -5385,61 +5385,19 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows, - ingress ? ovn_stage_get_table(S_SWITCH_OUT_QOS_MARK) - : ovn_stage_get_table(S_SWITCH_IN_L2_LKUP)); - -- /* 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 };", next_action); -- 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 };", next_action); -- ovn_lflow_add_with_hint(lflows, od, stage, -- acl->priority + OVN_ACL_PRI_OFFSET + 10, -- ds_cstr(&match), ds_cstr(&actions), stage_hint); -+ ds_put_cstr(&match, acl->match); - -- /* IP traffic */ -- 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, "ip4 && (%s)", 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 };", next_action); -- 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; " -+ "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, -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index a6c32c115..94007fcac 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2028,232 +2028,86 @@ ovn-nbctl --wait=hv sync - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_in_acl" | grep pg0 | sort], [0], [dnl - table=7 (ls_in_acl ), priority=2002 , dnl --match=(ip4 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2002 , dnl --match=(ip6 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2012 , dnl --match=(ip4 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2012 , dnl --match=(ip6 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+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([ovn-sbctl lflow-list sw1 | grep "ls_in_acl" | grep pg0 | sort], [0], [dnl - table=7 (ls_in_acl ), priority=2002 , dnl --match=(ip4 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2002 , dnl --match=(ip6 && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2012 , dnl --match=(ip4 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -- table=7 (ls_in_acl ), priority=2012 , dnl --match=(ip6 && tcp && (inport == @pg0 && ip4 && tcp && tcp.dst == 80)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=egress,table=6); };) -+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([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2002 , dnl --match=(ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=(ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2002 , dnl --match=(ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=(ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=(ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=(ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2001 , dnl --match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), dnl --action=(reg0[[1]] = 1; next;) -+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) && ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[10]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl - table=5 (ls_out_acl ), priority=2001 , dnl --match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), dnl --action=(reg0[[1]] = 1; next;) -+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) && ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2002 , dnl --match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[10]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[10]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[9]] == 1) && ip4 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp4 { eth.dst <-> eth.src; ip4.dst <-> ip4.src; outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - table=5 (ls_out_acl ), priority=2003 , dnl --match=((reg0[[9]] == 1) && ip6 && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; icmp6 { eth.dst <-> eth.src; ip6.dst <-> ip6.src; outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2012 , dnl --match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip4 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[10]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[10]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[9]] == 1) && ip4 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip4.dst <-> ip4.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -- table=5 (ls_out_acl ), priority=2013 , dnl --match=((reg0[[9]] == 1) && ip6 && tcp && (outport == @pg0 && ip6 && udp)), dnl --action=(reg0 = 0; eth.dst <-> eth.src; ip6.dst <-> ip6.src; tcp_reset { outport <-> inport; next(pipeline=ingress,table=20); };) -+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=20); };) - ]) - - AT_CLEANUP -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 60bd20fd4..091b61f91 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -4473,9 +4473,6 @@ ovn-nbctl lsp-add sw0 sw0-p2-rej - 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 @@ aef0::3 udp port objcall" | uniq | wc -l) - 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 --- -2.26.2 - diff --git a/SOURCES/0004-ovsdb-idl-Add-function-to-reset-min_index.patch b/SOURCES/0004-ovsdb-idl-Add-function-to-reset-min_index.patch deleted file mode 100644 index 656edca..0000000 --- a/SOURCES/0004-ovsdb-idl-Add-function-to-reset-min_index.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 1de31c3a531f5db6793819fa18f6e69304db929c Mon Sep 17 00:00:00 2001 -From: Mark Michelson -Date: Fri, 1 May 2020 15:13:08 -0400 -Subject: [PATCH 4/4] ovsdb-idl: Add function to reset min_index. - -If an administrator removes all of the databases in a cluster from -disk, then ovsdb IDL clients will have a problem. The databases will all -reset their stored indexes to 0, so The IDL client's min_index will be -higher than the indexes of all databases in the cluster. This results in -the client constantly connecting to databases, detecting the data as -"stale", and then attempting to connect to another. - -This function provides a way to reset the IDL to an initial state with -min_index of 0. This way, the client will not wrongly detect the -database data as stale and will recover properly. - -Notice that this function is not actually used anywhere in this patch. -This will be used by OVN, though, since OVN is the primary user of -clustered OVSDB. - -Signed-off-by: Mark Michelson -Acked-by: Han Zhou -Signed-off-by: Ilya Maximets - -(cherry-picked from upstream ovs commit 89b522aee379f7ebd21ec67ffb622118af7e9db1) - -Change-Id: I943ece9a07566a34b11248455cc1abbe7892d4e8 ---- - openvswitch-2.13.0/lib/ovsdb-idl.c | 10 ++++++++++ - openvswitch-2.13.0/lib/ovsdb-idl.h | 1 + - 2 files changed, 11 insertions(+) - -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl.c b/openvswitch-2.13.0/lib/ovsdb-idl.c -index 8eb421366..648c227d6 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl.c -+++ b/openvswitch-2.13.0/lib/ovsdb-idl.c -@@ -561,6 +561,16 @@ ovsdb_idl_set_shuffle_remotes(struct ovsdb_idl *idl, bool shuffle) - idl->shuffle_remotes = shuffle; - } - -+/* Reset min_index to 0. This prevents a situation where the client -+ * thinks all databases have stale data, when they actually have all -+ * been destroyed and rebuilt from scratch. -+ */ -+void -+ovsdb_idl_reset_min_index(struct ovsdb_idl *idl) -+{ -+ idl->min_index = 0; -+} -+ - static void - ovsdb_idl_db_destroy(struct ovsdb_idl_db *db) - { -diff --git a/openvswitch-2.13.0/lib/ovsdb-idl.h b/openvswitch-2.13.0/lib/ovsdb-idl.h -index 9f12ce320..c56cd19b1 100644 ---- a/openvswitch-2.13.0/lib/ovsdb-idl.h -+++ b/openvswitch-2.13.0/lib/ovsdb-idl.h -@@ -64,6 +64,7 @@ struct ovsdb_idl *ovsdb_idl_create_unconnected( - const struct ovsdb_idl_class *, bool monitor_everything_by_default); - void ovsdb_idl_set_remote(struct ovsdb_idl *, const char *, bool); - void ovsdb_idl_set_shuffle_remotes(struct ovsdb_idl *, bool); -+void ovsdb_idl_reset_min_index(struct ovsdb_idl *); - void ovsdb_idl_destroy(struct ovsdb_idl *); - - void ovsdb_idl_set_leader_only(struct ovsdb_idl *, bool leader_only); --- -2.26.2 - diff --git a/SOURCES/0004-test-ovn-Fix-expression-leak.patch b/SOURCES/0004-test-ovn-Fix-expression-leak.patch deleted file mode 100644 index b51722e..0000000 --- a/SOURCES/0004-test-ovn-Fix-expression-leak.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 06d9464335d81f4b78a4ff8c0daf890b947f571f Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:12 +0100 -Subject: [PATCH 04/16] test-ovn: Fix expression leak. - -No need to clone when assigning to the same variable. - -Fixes: 024500aeab9a ("expr: Evaluate the condition expression in a separate step.") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - tests/test-ovn.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tests/test-ovn.c b/tests/test-ovn.c -index 6662ced54..471126143 100644 ---- a/tests/test-ovn.c -+++ b/tests/test-ovn.c -@@ -918,7 +918,7 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab, - } 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)); - --- -2.28.0 - diff --git a/SOURCES/0004-tests-Fix-Port_Binding-up-test.patch b/SOURCES/0004-tests-Fix-Port_Binding-up-test.patch deleted file mode 100644 index 7741842..0000000 --- a/SOURCES/0004-tests-Fix-Port_Binding-up-test.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 4e143c1e58b18adf6914ec783ee4503a63dbf3a8 Mon Sep 17 00:00:00 2001 -From: Gongming Chen -Date: Sun, 7 Feb 2021 02:52:53 +0000 -Subject: [PATCH 4/4] tests: Fix Port_Binding up test. - -After setting the iface-id, immediately check the up status of the port -binding, it will occasionally fail, especially when the port binding -status is reported later. - -When it fails, the following will be output: -Checking values in sb Port_Binding with logical_port=lsp1 against false... found false -ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 -./ovn-macros.at:307: "$@" -Checking values in sb Port_Binding with logical_port=lsp1 against true... found false -_uuid : 15ebabb6-3dbb-4806-aa85-d1c03e3b39f6 -logical_port : lsp1 -up : true -./ovn-macros.at:393: hard failure - -Fixes: 4d3cb42b076b ("binding: Set Logical_Switch_Port.up when all OVS flows are installed.") -Signed-off-by: Gongming Chen -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique -(cherry picked from upstream commit 44ea2ec88136f83e7eab9790473025b6c95bdcc0) - -Change-Id: I53d1834cc6b59cc42b494661378d00bf722dc88a ---- - AUTHORS.rst | 1 + - tests/ovn.at | 2 +- - 2 files changed, 2 insertions(+), 1 deletion(-) - -diff --git a/AUTHORS.rst b/AUTHORS.rst -index 5d926c1..29c2c01 100644 ---- a/AUTHORS.rst -+++ b/AUTHORS.rst -@@ -155,6 +155,7 @@ Geoffrey Wossum gwossum@acm.org - Gianluca Merlo gianluca.merlo@gmail.com - Giuseppe Lettieri g.lettieri@iet.unipi.it - Glen Gibb grg@stanford.edu -+Gongming Chen gmingchen@tencent.com - Guoshuai Li ligs@dtdream.com - Guolin Yang gyang@vmware.com - Guru Chaitanya Perakam gperakam@Brocade.com -diff --git a/tests/ovn.at b/tests/ovn.at -index 2ef056b..9f2e152 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -23700,7 +23700,7 @@ check ovn-nbctl --wait=hv sync - check_column "false" Port_Binding up logical_port=lsp1 - - check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 --check_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" Port_Binding up logical_port=lsp1 - wait_column "true" nb:Logical_Switch_Port up name=lsp1 - OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp1 external_ids:ovn-installed` = '"true"']) - --- -1.8.3.1 - diff --git a/SOURCES/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch b/SOURCES/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch deleted file mode 100644 index ff8de74..0000000 --- a/SOURCES/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch +++ /dev/null @@ -1,59 +0,0 @@ -From d5eab772c52adcca1b02fe7c9cac36a40e74d29e Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:13 +0100 -Subject: [PATCH 05/16] actions: Fix leak of child ports in fwd group. - -'child_port_list' is an array of pointers that should be freed too. - - Direct leak of 30 byte(s) in 6 object(s) allocated from: - #0 0x501fff in malloc (/tests/ovstest+0x501fff) - #1 0x6227e6 in xmalloc /lib/util.c:138:15 - #2 0x6228b8 in xmemdup0 /lib/util.c:168:15 - #3 0x8183d6 in parse_fwd_group_action /lib/actions.c:3374:30 - #4 0x814b6e in parse_action /lib/actions.c:3610:9 - #5 0x8139ef in parse_actions /lib/actions.c:3637:14 - #6 0x8136a3 in ovnacts_parse /lib/actions.c:3672:9 - #7 0x813c80 in ovnacts_parse_string /lib/actions.c:3699:5 - #8 0x53a979 in test_parse_actions /tests/test-ovn.c:1372:21 - #9 0x54e7a8 in ovs_cmdl_run_command__ /lib/command-line.c:247:17 - #10 0x537c75 in test_ovn_main /tests/test-ovn.c:1630:5 - #11 0x54e7a8 in ovs_cmdl_run_command__ /lib/command-line.c:247:17 - #12 0x537359 in main /tests/ovstest.c:133:9 - #13 0x7f06978f21a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - -CC: Manoj Sharma -Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - lib/actions.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/lib/actions.c b/lib/actions.c -index 015bcbc4d..0464c51a8 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -3375,6 +3375,9 @@ parse_fwd_group_action(struct action_context *ctx) - 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; -@@ -3480,6 +3483,9 @@ encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group, - 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); - } - --- -2.28.0 - diff --git a/SOURCES/0005-northd-Add-ECMP-support-to-router-policies.patch b/SOURCES/0005-northd-Add-ECMP-support-to-router-policies.patch deleted file mode 100644 index 49bb49b..0000000 --- a/SOURCES/0005-northd-Add-ECMP-support-to-router-policies.patch +++ /dev/null @@ -1,808 +0,0 @@ -From c41499498db66fbe54155206a513e418d75f8d49 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Wed, 2 Dec 2020 23:57:56 +0530 -Subject: [PATCH 5/7] northd: Add ECMP support to router policies. - -A user can add a policy now like: - -ovn-nbctl lr-policy-add 100 "ip4.src == 10.0.0.4" reroute 172.0.0.5,172.0.0.6 - -We do have ECMP support for logical router static routes, but since -policies are applied after the routing stage, ECMP support for -policies is desired by ovn-kubernetes. - -A new column 'nexthops' is added to the Logical_Router_Policy table -instead of modifying the existing column 'nexthop' to preserve -backward compatibility and avoid any upgrade issues. - -Change-Id: Ib5723d1de30f0ad86ee740bb4e3b593f1cca98eb -Requested-by: Alexander Constantinescu -Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1881826 -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.8.xml | 80 +++++++++++++++++++-- - northd/ovn-northd.c | 148 ++++++++++++++++++++++++++++++++++---- - ovn-nb.ovsschema | 6 +- - ovn-nb.xml | 18 ++++- - tests/ovn-northd.at | 124 ++++++++++++++++++++++++++++++++ - tests/ovn.at | 16 ++--- - utilities/ovn-nbctl.8.xml | 12 ++-- - utilities/ovn-nbctl.c | 73 +++++++++++++++---- - 8 files changed, 429 insertions(+), 48 deletions(-) - -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index d86f36ea6..1f0f71f34 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -3041,14 +3041,36 @@ outport = P; - -
  • -

    -- If the policy action is reroute, then the logical -- flow is added with the following actions: -+ If the policy action is reroute with 2 or more nexthops -+ defined, then the logical flow is added with the following actions: -+

    -+ -+
    -+reg8[0..15] = GID;
    -+reg8[16..31] = select(1,..n);
    -+        
    -+ -+

    -+ where GID is the ECMP group id generated by -+ ovn-northd for this policy and n -+ is the number of nexthops. select action -+ selects one of the nexthop member id, stores it in the register -+ reg8[16..31] and advances the packet to the -+ next stage. -+

    -+
  • -+ -+
  • -+

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

    - -
    - [xx]reg0 = H;
    - eth.src = E;
    - outport = P;
    -+reg8[0..15] = 0;
    - flags.loopback = 1;
    - next;
    -         
    -@@ -3072,7 +3094,51 @@ next; -
  • - - --

    Ingress Table 13: ARP/ND Resolution

    -+

    Ingress Table 13: ECMP handling for router policies

    -+

    -+ This table handles the ECMP for the router policies configured -+ with multiple nexthops. -+

    -+ -+
      -+
    • -+

      -+ A priority-150 flow is added to advance the packet to the next stage -+ if the ECMP group id register reg8[0..15] is 0. -+

      -+
    • -+ -+
    • -+

      -+ For each ECMP reroute router policy with multiple nexthops, -+ a priority-100 flow is added for each nexthop H -+ with the match reg8[0..15] == GID && -+ reg8[16..31] == M where GID -+ is the router policy group id generated by ovn-northd -+ and M is the member id of the nexthop H -+ generated by ovn-northd. The following actions are added -+ to the flow: -+

      -+ -+
      -+[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. -+

      -+
    • -+
    -+ -+

    Ingress Table 14: ARP/ND Resolution

    - -

    - Any packet that reaches this table is an IP packet whose next-hop -@@ -3258,7 +3324,7 @@ next; - - - --

    Ingress Table 14: Check packet length

    -+

    Ingress Table 15: Check packet length

    - -

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

    - --

    Ingress Table 15: Handle larger packets

    -+

    Ingress Table 16: Handle larger packets

    - -

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

    - --

    Ingress Table 16: Gateway Redirect

    -+

    Ingress Table 17: Gateway Redirect

    - -

    - For distributed logical routers where one of the logical router -@@ -3389,7 +3455,7 @@ icmp6 { - - - --

    Ingress Table 17: ARP Request

    -+

    Ingress Table 18: ARP Request

    - -

    - In the common case where the Ethernet destination has been resolved, this -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 478f1a339..dfd7d69d0 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -188,11 +188,12 @@ enum ovn_stage { - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 10, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 11, "lr_in_ip_routing_ecmp") \ - PIPELINE_STAGE(ROUTER, IN, POLICY, 12, "lr_in_policy") \ -- PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 13, "lr_in_arp_resolve") \ -- PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 14, "lr_in_chk_pkt_len") \ -- PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 15,"lr_in_larger_pkts") \ -- PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 16, "lr_in_gw_redirect") \ -- PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 17, "lr_in_arp_request") \ -+ PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 13, "lr_in_policy_ecmp") \ -+ PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 14, "lr_in_arp_resolve") \ -+ PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 15, "lr_in_chk_pkt_len") \ -+ PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 16, "lr_in_larger_pkts") \ -+ PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 17, "lr_in_gw_redirect") \ -+ PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 18, "lr_in_arp_request") \ - \ - /* Logical router egress stages. */ \ - PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ -@@ -7562,33 +7563,39 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - struct ds actions = DS_EMPTY_INITIALIZER; - - if (!strcmp(rule->action, "reroute")) { -+ ovs_assert(rule->n_nexthops <= 1); -+ -+ char *nexthop = -+ (rule->n_nexthops == 1 ? rule->nexthops[0] : rule->nexthop); - struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( -- od, ports, rule->priority, rule->nexthop); -+ od, ports, rule->priority, nexthop); - if (!out_port) { - return; - } - -- const char *lrp_addr_s = find_lrp_member_ip(out_port, rule->nexthop); -+ const char *lrp_addr_s = find_lrp_member_ip(out_port, nexthop); - if (!lrp_addr_s) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy " - " priority %"PRId64" nexthop %s", -- rule->priority, rule->nexthop); -+ rule->priority, nexthop); - return; - } - uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0); - if (pkt_mark) { - ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); - } -- bool is_ipv4 = strchr(rule->nexthop, '.') ? true : false; -+ -+ bool is_ipv4 = strchr(nexthop, '.') ? true : false; - ds_put_format(&actions, "%s = %s; " - "%s = %s; " - "eth.src = %s; " - "outport = %s; " - "flags.loopback = 1; " -+ REG_ECMP_GROUP_ID" = 0; " - "next;", - is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6, -- rule->nexthop, -+ nexthop, - is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6, - lrp_addr_s, - out_port->lrp_networks.ea_s, -@@ -7601,7 +7608,7 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - if (pkt_mark) { - ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); - } -- ds_put_cstr(&actions, "next;"); -+ ds_put_cstr(&actions, REG_ECMP_GROUP_ID" = 0; next;"); - } - ds_put_format(&match, "%s", rule->match); - -@@ -7611,6 +7618,107 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - ds_destroy(&actions); - } - -+static void -+build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od, -+ struct hmap *ports, -+ const struct nbrec_logical_router_policy *rule, -+ uint16_t ecmp_group_id) -+{ -+ ovs_assert(rule->n_nexthops > 1); -+ -+ bool nexthops_is_ipv4 = true; -+ -+ /* Check that all the nexthops belong to the same addr family before -+ * adding logical flows. */ -+ for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false; -+ -+ if (i == 0) { -+ nexthops_is_ipv4 = is_ipv4; -+ } -+ -+ if (is_ipv4 != nexthops_is_ipv4) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "nexthop [%s] of the router policy with " -+ "the match [%s] do not belong to the same address " -+ "family as other next hops", -+ rule->nexthops[i], rule->match); -+ return; -+ } -+ } -+ -+ struct ds match = DS_EMPTY_INITIALIZER; -+ struct ds actions = DS_EMPTY_INITIALIZER; -+ -+ for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( -+ od, ports, rule->priority, rule->nexthops[i]); -+ if (!out_port) { -+ goto cleanup; -+ } -+ -+ const char *lrp_addr_s = -+ find_lrp_member_ip(out_port, rule->nexthops[i]); -+ if (!lrp_addr_s) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy " -+ " priority %"PRId64" nexthop %s", -+ rule->priority, rule->nexthops[i]); -+ goto cleanup; -+ } -+ -+ ds_clear(&actions); -+ uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0); -+ if (pkt_mark) { -+ ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); -+ } -+ -+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false; -+ -+ ds_put_format(&actions, "%s = %s; " -+ "%s = %s; " -+ "eth.src = %s; " -+ "outport = %s; " -+ "flags.loopback = 1; " -+ "next;", -+ is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6, -+ rule->nexthops[i], -+ is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6, -+ lrp_addr_s, -+ out_port->lrp_networks.ea_s, -+ out_port->json_key); -+ -+ ds_clear(&match); -+ ds_put_format(&match, REG_ECMP_GROUP_ID" == %"PRIu16" && " -+ REG_ECMP_MEMBER_ID" == %"PRIu16, -+ ecmp_group_id, i + 1); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY_ECMP, -+ 100, ds_cstr(&match), -+ ds_cstr(&actions), &rule->header_); -+ } -+ -+ ds_clear(&actions); -+ ds_put_format(&actions, "%s = %"PRIu16 -+ "; %s = select(", REG_ECMP_GROUP_ID, ecmp_group_id, -+ REG_ECMP_MEMBER_ID); -+ -+ for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ if (i > 0) { -+ ds_put_cstr(&actions, ", "); -+ } -+ -+ ds_put_format(&actions, "%"PRIu16, i + 1); -+ } -+ ds_put_cstr(&actions, ");"); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY, -+ rule->priority, rule->match, -+ ds_cstr(&actions), &rule->header_); -+ -+cleanup: -+ ds_destroy(&match); -+ ds_destroy(&actions); -+} -+ - struct parsed_route { - struct ovs_list list_node; - struct in6_addr prefix; -@@ -10300,13 +10408,27 @@ build_ingress_policy_flows_for_lrouter( - if (od->nbr) { - /* This is a catch-all rule. It has the lowest priority (0) - * does a match-all("1") and pass-through (next) */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", -+ REG_ECMP_GROUP_ID" = 0; next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - - /* Convert routing policies to flows. */ -+ uint16_t ecmp_group_id = 1; - for (int i = 0; i < od->nbr->n_policies; i++) { - const struct nbrec_logical_router_policy *rule - = od->nbr->policies[i]; -- build_routing_policy_flow(lflows, od, ports, rule, &rule->header_); -+ bool is_ecmp_reroute = -+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); -+ -+ if (is_ecmp_reroute) { -+ build_ecmp_routing_policy_flows(lflows, od, ports, rule, -+ ecmp_group_id); -+ ecmp_group_id++; -+ } else { -+ build_routing_policy_flow(lflows, od, ports, rule, -+ &rule->header_); -+ } - } - } - } -diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema -index af77dd138..b77a2308c 100644 ---- a/ovn-nb.ovsschema -+++ b/ovn-nb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Northbound", -- "version": "5.29.0", -- "cksum": "328602112 27064", -+ "version": "5.30.0", -+ "cksum": "3273824429 27172", - "tables": { - "NB_Global": { - "columns": { -@@ -391,6 +391,8 @@ - "key": {"type": "string", - "enum": ["set", ["allow", "drop", "reroute"]]}}}, - "nexthop": {"type": {"key": "string", "min": 0, "max": 1}}, -+ "nexthops": {"type": { -+ "key": "string", "min": 0, "max": "unlimited"}}, - "options": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}, -diff --git a/ovn-nb.xml b/ovn-nb.xml -index e7a8d6833..0cf043790 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -2723,18 +2723,34 @@ - - -

  • -- reroute: Reroute packet to . -+ reroute: Reroute packet to or -+ . -
  • - - - - -+

    -+ Note: This column is deprecated in favor of . -+

    -

    - Next-hop IP address for this route, which should be the IP - address of a connected router port or the IP address of a logical port. -

    -
    - -+ -+

    -+ Next-hop ECMP IP addresses for this route. Each IP in the list should -+ be the IP address of a connected router port or the IP address of a -+ logical port. -+

    -+ -+

    -+ One IP from the list is selected as next hop. -+

    -+
    -+ - -

    - Marks the packet with the value specified when the router policy -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 50a4cae76..ce6c44db4 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2198,3 +2198,127 @@ 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 -+ -+AT_SETUP([ovn -- Router policies - ECMP reroute]) -+AT_KEYWORDS([router policies ecmp reroute]) -+ovn_start -+ -+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" -+ -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl lsp-add sw1 sw1-port1 -+check ovn-nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3" -+ -+# Create a logical router and attach both logical switches -+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 1000::a/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 00:00:00:00:ff:01 -+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 2000::a/64 -+check ovn-nbctl lsp-add sw1 sw1-lr0 -+check ovn-nbctl lsp-set-type sw1-lr0 router -+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 -+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr-sw1 -+ -+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 -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102 -+ -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = 0; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.4" reroute 172.168.0.101,172.168.0.102,172.168.0.103 -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.5" reroute 172.168.0.110 -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.3" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.4" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb add logical_router_policy . nexthops "2000\:\:b" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+AT_CLEANUP -diff --git a/tests/ovn.at b/tests/ovn.at -index 2e0bc9c53..66088a7f5 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -21156,31 +21156,31 @@ AT_CHECK([ - - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(pkt.mark = 4294967295; next;) -+match=(ip6), action=(pkt.mark = 4294967295; reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=-1 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=2147483648 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(pkt.mark = 2147483648; next;) -+match=(ip6), action=(pkt.mark = 2147483648; reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=foo - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=4294967296 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - OVN_CLEANUP([hv1]) -@@ -21759,7 +21759,7 @@ AT_CHECK([ - grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) - ]) - AT_CHECK([ -- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ -+ test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=22 | \ - grep "priority=200" | \ - grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) - ]) -@@ -21770,7 +21770,7 @@ AT_CHECK([ - grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) - ]) - AT_CHECK([ -- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=21 | \ -+ test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=22 | \ - grep "priority=200" | \ - grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) - ]) -@@ -22208,7 +22208,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep - ]) - - # The packet should've been dropped in the lr_in_arp_resolve stage. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=22, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl - 1 - ]) - -diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml -index e5a35f307..e6fec9980 100644 ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -739,7 +739,7 @@ -

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

    -@@ -748,10 +748,12 @@ - are similar to OVN ACLs, but exist on the logical-router. Reroute - policies are needed for service-insertion and service-chaining. - nexthop is an optional parameter. It needs to be provided -- only when action is reroute. A policy is -- uniquely identified by priority and match. -- Multiple policies can have the same priority. -- options sets the router policy options as key-value pair. -+ only when action is reroute. Multiple -+ nexthops can be specified for ECMP routing. -+ A policy is uniquely identified by priority and -+ match. Multiple policies can have the same -+ priority. options sets the router policy -+ options as key-value pair. - The supported option is : pkt_mark. -

    - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 835161f25..94e7eedeb 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -766,7 +766,7 @@ Route commands:\n\ - lr-route-list ROUTER print routes for ROUTER\n\ - \n\ - Policy commands:\n\ -- lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP] \ -+ lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP,[NEXTHOP,...]] \ - [OPTIONS KEY=VALUE ...] \n\ - add a policy to router\n\ - lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\ -@@ -3634,7 +3634,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - return; - } - const char *action = ctx->argv[4]; -- char *next_hop = NULL; -+ size_t n_nexthops = 0; -+ char **nexthops = NULL; - - bool reroute = false; - /* Validate action. */ -@@ -3654,7 +3655,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - /* Check if same routing policy already exists. - * A policy is uniquely identified by priority and match */ - bool may_exist = !!shash_find(&ctx->options, "--may-exist"); -- for (int i = 0; i < lr->n_policies; i++) { -+ size_t i; -+ for (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])) { -@@ -3665,12 +3667,53 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - return; - } - } -+ - if (reroute) { -- next_hop = normalize_prefix_str(ctx->argv[5]); -- if (!next_hop) { -- ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]); -- return; -+ char *nexthops_arg = xstrdup(ctx->argv[5]); -+ char *save_ptr, *next_hop, *token; -+ -+ n_nexthops = 0; -+ size_t n_allocs = 0; -+ -+ bool nexthops_is_ipv4 = true; -+ for (token = strtok_r(nexthops_arg, ",", &save_ptr); -+ token != NULL; token = strtok_r(NULL, ",", &save_ptr)) { -+ next_hop = normalize_addr_str(token); -+ -+ if (!next_hop) { -+ ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]); -+ free(nexthops_arg); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); -+ return; -+ } -+ if (n_nexthops == n_allocs) { -+ nexthops = x2nrealloc(nexthops, &n_allocs, sizeof *nexthops); -+ } -+ -+ bool is_ipv4 = strchr(next_hop, '.') ? true : false; -+ if (n_nexthops == 0) { -+ nexthops_is_ipv4 = is_ipv4; -+ } -+ -+ if (is_ipv4 != nexthops_is_ipv4) { -+ ctl_error(ctx, "bad next hops argument, not in the same " -+ "addr family : %s", ctx->argv[5]); -+ free(nexthops_arg); -+ free(next_hop); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); -+ return; -+ } -+ nexthops[n_nexthops] = next_hop; -+ n_nexthops++; - } -+ -+ free(nexthops_arg); - } - - struct nbrec_logical_router_policy *policy; -@@ -3679,12 +3722,13 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - nbrec_logical_router_policy_set_match(policy, ctx->argv[3]); - nbrec_logical_router_policy_set_action(policy, action); - if (reroute) { -- nbrec_logical_router_policy_set_nexthop(policy, next_hop); -+ nbrec_logical_router_policy_set_nexthops( -+ policy, (const char **)nexthops, n_nexthops); - } - - /* Parse the options. */ - struct smap options = SMAP_INITIALIZER(&options); -- for (size_t i = reroute ? 6 : 5; i < ctx->argc; i++) { -+ for (i = reroute ? 6 : 5; i < ctx->argc; i++) { - char *key, *value; - value = xstrdup(ctx->argv[i]); - key = strsep(&value, "="); -@@ -3694,7 +3738,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - ctl_error(ctx, "No value specified for the option : %s", key); - smap_destroy(&options); - free(key); -- free(next_hop); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); - return; - } - free(key); -@@ -3703,9 +3750,11 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - smap_destroy(&options); - - nbrec_logical_router_update_policies_addvalue(lr, policy); -- if (next_hop != NULL) { -- free(next_hop); -+ -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); - } -+ free(nexthops); - } - - static void --- -2.28.0 - diff --git a/SOURCES/0005-northd-Refactor-load-balancer-vip-parsing.patch b/SOURCES/0005-northd-Refactor-load-balancer-vip-parsing.patch deleted file mode 100644 index 067279a..0000000 --- a/SOURCES/0005-northd-Refactor-load-balancer-vip-parsing.patch +++ /dev/null @@ -1,1179 +0,0 @@ -From 58f78bf44b9df97a45d7dbe08c8f95770a295a68 Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Mon, 12 Oct 2020 17:52:38 +0530 -Subject: [PATCH 05/10] northd: Refactor load balancer vip parsing. - -Parsing of the load balancer VIPs is moved to a separate file - lib/lb.c. -ovn-northd makes use of these functions. Upcoming patch will make use of these -util functions for parsing SB Load_Balancers. - -Co-authored-by: Numan Siddique -Signed-off-by: Numan Siddique -Signed-off-by: Dumitru Ceara -Acked-by: Mark Michelson - -(cherry-picked from master commit f1119c1257652702e0ee88cf634f2ee19cc92c44) -Conflicts: - northd/ovn-northd.c ---- - lib/automake.mk | 4 +- - lib/lb.c | 301 ++++++++++++++++++++++++++++++++ - lib/lb.h | 97 +++++++++++ - northd/ovn-northd.c | 416 +++++++++++++------------------------------- - 4 files changed, 518 insertions(+), 300 deletions(-) - create mode 100644 lib/lb.c - create mode 100644 lib/lb.h - -diff --git a/lib/automake.mk b/lib/automake.mk -index f3e9c8818..430cd11fc 100644 ---- a/lib/automake.mk -+++ b/lib/automake.mk -@@ -23,7 +23,9 @@ lib_libovn_la_SOURCES = \ - 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 --git a/lib/lb.c b/lib/lb.c -new file mode 100644 -index 000000000..a90042e58 ---- /dev/null -+++ b/lib/lb.c -@@ -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 --git a/lib/lb.h b/lib/lb.h -new file mode 100644 -index 000000000..6644ad0d8 ---- /dev/null -+++ b/lib/lb.h -@@ -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 --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index c32e25c3d..a7695bc63 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -35,6 +35,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" -@@ -3329,66 +3330,6 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx, - } - } - --struct ovn_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 lb_vip *vips; -- size_t n_vips; -- -- size_t n_dps; -- size_t n_allocated_dps; -- const struct sbrec_datapath_binding **dps; --}; -- --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, const 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; --} -- --static void --ovn_lb_add_datapath(struct ovn_lb *lb, struct ovn_datapath *od) --{ -- 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++] = od->sb; --} -- - struct service_monitor_info { - struct hmap_node hmap_node; - const struct sbrec_service_monitor *sbrec_mon; -@@ -3428,126 +3369,39 @@ create_or_get_service_mon(struct northd_context *ctx, - 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; -+ 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]; - -- 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; -- -- 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) || -@@ -3557,89 +3411,45 @@ ovn_lb_create(struct northd_context *ctx, struct hmap *lbs, - } - - 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) --{ -- 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); -- } -- -- free(lb->vips[i].backends); -- } -- free(lb->vips); -- free(lb->selection_fields); -- free(lb->dps); --} -- --static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip, -- struct ds *action, -- char *selection_fields) -+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) - { - bool skip_hash_fields = false; - -- 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) { -@@ -3651,7 +3461,7 @@ static void build_lb_vip_ct_lb_actions(struct lb_vip *lb_vip, - 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 (!skip_hash_fields && selection_fields && selection_fields[0]) { -@@ -3681,7 +3491,14 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - - 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; -@@ -3693,14 +3510,12 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - for (size_t i = 0; i < od->nbs->n_load_balancer; i++) { - const struct uuid *lb_uuid = - &od->nbs->load_balancer[i]->header_.uuid; -- struct ovn_lb *lb = ovn_lb_find(lbs, lb_uuid); -+ lb = ovn_northd_lb_find(lbs, lb_uuid); - -- ovn_lb_add_datapath(lb, od); -+ ovn_northd_lb_add_datapath(lb, od->sb); - } - } - -- struct ovn_lb *lb; -- - /* 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) { -@@ -3711,7 +3526,7 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - continue; - } - -- lb = ovn_lb_find(lbs, &lb_uuid); -+ lb = ovn_northd_lb_find(lbs, &lb_uuid); - if (lb && lb->n_dps) { - lb->slb = sbrec_lb; - } else { -@@ -3756,7 +3571,7 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - 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_lb_find(lbs, lb_uuid); -+ lb = ovn_northd_lb_find(lbs, lb_uuid); - sbrec_lbs[i] = lb->slb; - } - -@@ -3777,16 +3592,6 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - hmap_destroy(&monitor_map); - } - --static void --destroy_ovn_lbs(struct hmap *lbs) --{ -- struct ovn_lb *lb; -- HMAP_FOR_EACH_POP (lb, hmap_node, lbs) { -- ovn_lb_destroy(lb); -- free(lb); -- } --} -- - /* Updates the southbound Port_Binding table so that it contains the logical - * switch ports specified by the northbound database. - * -@@ -5171,7 +4976,7 @@ ls_has_dns_records(const struct nbrec_logical_switch *nbs) - - 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) - { -@@ -5179,7 +4984,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - 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; - -@@ -5188,13 +4993,13 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - } - - 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); - } - -@@ -5268,12 +5073,12 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, - 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); - -@@ -6008,7 +5813,8 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows) - - static void - build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, -- struct ovn_lb *lb, struct lb_vip *lb_vip, -+ struct ovn_northd_lb *lb, -+ struct ovn_lb_vip *lb_vip, - const char *ip_match, const char *proto) - { - if (lb_vip->n_backends == 0) { -@@ -6030,7 +5836,7 @@ build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - */ - 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]; -+ struct ovn_lb_backend *backend = &lb_vip->backends[i]; - - /* Packets that after load balancing have equal source and - * destination IPs should be hairpinned. -@@ -6040,7 +5846,7 @@ build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - proto, backend->port); - } - ds_put_format(&match_initiator, "(%s.src == %s && %s.dst == %s%s)", -- ip_match, backend->ip, ip_match, backend->ip, -+ ip_match, backend->ip_str, ip_match, backend->ip_str, - ds_cstr(&proto_match)); - - /* Replies to hairpinned traffic are originated by backend->ip:port. */ -@@ -6049,8 +5855,8 @@ build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - 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_put_format(&match_reply, "(%s.src == %s%s)", -+ ip_match, backend->ip_str, ds_cstr(&proto_match)); - ds_clear(&proto_match); - - if (i < lb_vip->n_backends - 1) { -@@ -6064,13 +5870,13 @@ build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - * also directed through OVN. - */ - ds_put_format(&action, REGBIT_HAIRPIN " = 1; ct_snat(%s);", -- lb_vip->vip); -+ lb_vip->vip_str); - 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); -+ ds_put_format(&match_reply, " && %s.dst == %s", ip_match, lb_vip->vip_str); - - /* UNSNAT replies for hairpinned traffic. */ - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 1, -@@ -6085,13 +5891,15 @@ build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, - } - - 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"; -@@ -6111,10 +5919,12 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, struct ovn_lb *lb) - - /* 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, -@@ -6174,8 +5984,8 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) - * 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); -@@ -7110,22 +6920,24 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - - /* 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[i]; -+ 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; " -@@ -7139,9 +6951,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - "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_); -@@ -8319,7 +8131,7 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, - 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) - { -@@ -8355,13 +8167,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - 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) -@@ -8375,7 +8187,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - * 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); -@@ -8399,8 +8211,9 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - 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) || ", -@@ -10095,18 +9908,19 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - - 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. -@@ -10116,12 +9930,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - * 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;", -@@ -10134,12 +9948,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - * 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; -@@ -12287,7 +12101,11 @@ ovnnb_db_run(struct northd_context *ctx, - sync_port_groups(ctx, &port_groups); - sync_meters(ctx); - 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; --- -2.28.0 - diff --git a/SOURCES/0005-ofctrl.c-Simplify-active-desired-flow-selection.patch b/SOURCES/0005-ofctrl.c-Simplify-active-desired-flow-selection.patch deleted file mode 100644 index 6fa4546..0000000 --- a/SOURCES/0005-ofctrl.c-Simplify-active-desired-flow-selection.patch +++ /dev/null @@ -1,217 +0,0 @@ -From c8cf2e5c7f859bc3c781948e8a9cdd832035a7ee Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Sun, 11 Oct 2020 14:05:59 +0200 -Subject: [PATCH 5/7] ofctrl.c: Simplify active desired flow selection. - -Always consider the first "desired flow" in the list of flows that refer an -"installed flow" as the active flow. This simplifies the logic of the flow -update code and is used in a further commit to implement a partial ordering -of desired flows within installed flows. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit 107bb25029350bd0f7dfeeb0ef3053adbd504e3e) - -Change-Id: I5e5aca6f18601a3cbd9decc913dad3507ee0a448 ---- - controller/ofctrl.c | 91 +++++++++++++++++++++-------------------------------- - 1 file changed, 36 insertions(+), 55 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 20cf3ac..ba0c61c 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -188,6 +188,8 @@ struct sb_flow_ref { - * 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. -+ * 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,11 +201,6 @@ struct installed_flow { - * 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 -@@ -231,6 +228,7 @@ 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 *); -@@ -796,24 +794,6 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type) - log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored"); - } - } -- --/* Returns true if a desired flow is active (the one currently installed -- * among the list of desired flows that are linked to the same installed -- * flow). */ --static inline bool --desired_flow_is_active(struct desired_flow *d) --{ -- return (d->installed_flow && d->installed_flow->desired_flow == d); --} -- --/* Set a desired flow as the active one among the list of desired flows -- * that are linked to the same installed flow. */ --static inline void --desired_flow_set_active(struct desired_flow *d) --{ -- ovs_assert(d->installed_flow); -- d->installed_flow->desired_flow = d; --} - - static bool - flow_action_has_conj(const struct ovn_flow *f) -@@ -831,27 +811,22 @@ flow_action_has_conj(const struct ovn_flow *f) - /* Adds the desired flow to the list of desired flows that have same match - * conditions as the installed flow. - * -- * If the newly added desired flow is the first one in the list, it is also set -- * as the active one. -- * - * It is caller's responsibility to make sure the link between the pair didn't -- * exist before. */ --static void -+ * 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) - { -- ovs_assert(i->desired_flow != d); -- if (ovs_list_is_empty(&i->desired_refs)) { -- ovs_assert(!i->desired_flow); -- i->desired_flow = d; -- } -- ovs_list_insert(&i->desired_refs, &d->installed_ref_list_node); - d->installed_flow = i; -+ ovs_list_push_back(&i->desired_refs, &d->installed_ref_list_node); -+ 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. -- * -- * If 'old_desired' was the active flow, 'new_desired' becomes the active one. - */ - static void - replace_installed_to_desired(struct installed_flow *i, -@@ -863,24 +838,22 @@ replace_installed_to_desired(struct installed_flow *i, - &old_desired->installed_ref_list_node); - old_desired->installed_flow = NULL; - new_desired->installed_flow = i; -- if (i->desired_flow == old_desired) { -- i->desired_flow = new_desired; -- } - } - --static void -+/* 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 -@@ -1280,7 +1253,6 @@ installed_flow_dup(struct desired_flow *src) - { - 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); -@@ -1292,6 +1264,17 @@ installed_flow_dup(struct desired_flow *src) - } - - static struct desired_flow * -+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, -@@ -1439,8 +1422,7 @@ static void - 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); - } -@@ -1898,10 +1880,10 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - /* The desired flow was deleted */ - if (f->installed_flow) { - struct installed_flow *i = f->installed_flow; -- bool was_active = desired_flow_is_active(f); -- 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)"); - -@@ -1912,7 +1894,6 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - * 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); - } -@@ -1931,7 +1912,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - hmap_insert(&installed_flows, &new_node->match_hmap_node, - new_node->flow.hash); - link_installed_to_desired(new_node, f); -- } else if (desired_flow_is_active(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)"); --- -1.8.3.1 - diff --git a/SOURCES/0005-ovn-northd-Move-ARP-response-for-external-ports-to-a.patch b/SOURCES/0005-ovn-northd-Move-ARP-response-for-external-ports-to-a.patch deleted file mode 100644 index 85c1abb..0000000 --- a/SOURCES/0005-ovn-northd-Move-ARP-response-for-external-ports-to-a.patch +++ /dev/null @@ -1,77 +0,0 @@ -From d63444b7fcdcc2c68b7af94090410bc3e40e574b Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:32 +0000 -Subject: [PATCH 05/16] ovn-northd: Move ARP response for external ports to a - function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 33 ++++++++++++++++++--------------- - 1 file changed, 18 insertions(+), 15 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 45d6a6a2e..09bfaae5e 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6780,21 +6780,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || !lsp_is_external(op->nbsp)) { -- continue; -- } -- -- /* Table 18: External port. Drop ARP request for router ips from -- * external ports on chassis not binding those ports. -- * This makes the router pipeline to be run only on the chassis -- * binding the external ports. */ -- for (size_t i = 0; i < op->od->n_localnet_ports; i++) { -- build_drop_arp_nd_flows_for_unbound_router_ports( -- op, op->od->localnet_ports[i], lflows); -- } -- } -- - /* Ingress table 19: Destination lookup, broadcast and multicast handling - * (priority 70 - 100). */ - HMAP_FOR_EACH (od, key_node, datapaths) { -@@ -7486,6 +7471,23 @@ build_lswitch_dns_lookup_and_response(struct ovn_datapath *od, - } - } - -+/* Table 18: External port. Drop ARP request for router ips from -+ * external ports on chassis not binding those ports. -+ * This makes the router pipeline to be run only on the chassis -+ * binding the external ports. */ -+static void -+build_lswitch_external_port(struct ovn_port *op, -+ struct hmap *lflows) -+{ -+ if (op->nbsp && lsp_is_external(op->nbsp)) { -+ -+ for (size_t i = 0; i < op->od->n_localnet_ports; i++) { -+ build_drop_arp_nd_flows_for_unbound_router_ports( -+ op, op->od->localnet_ports[i], lflows); -+ } -+ } -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -11376,6 +11378,7 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - &lsi->actions, - &lsi->match); - build_lswitch_dhcp_options_and_response(op,lsi->lflows); -+ build_lswitch_external_port(op, lsi->lflows); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, --- -2.29.2 - diff --git a/SOURCES/0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch b/SOURCES/0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch deleted file mode 100644 index 6c36a93..0000000 --- a/SOURCES/0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch +++ /dev/null @@ -1,98 +0,0 @@ -From b829ad716daf393925c5404953e0a2212c4d350a Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Fri, 16 Oct 2020 15:45:30 +0530 -Subject: [PATCH 5/5] ovn-trace: Handle IPv6 packets for tcp_reset action. - -tcp_reset action can be used for both IPv4 and IPv6 TCP packets, but ovn-trace -was not handling IPv6. - -Reported-by: Dumitru Ceara -Acked-by: Mark Michelson -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique - -(cherry-picked from upstream master commit 29b3fd650b99c0928951c4d537176c4924243cc4) - -Change-Id: I27b9a79b2091cc8980854387d18421fd6f6fcb78 ---- - utilities/ovn-trace.c | 57 +++++++++++++++++++++++++++++++++++++++---- - 1 file changed, 52 insertions(+), 5 deletions(-) - -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index ad33f8e36..5d54c0fd8 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -1700,11 +1700,11 @@ execute_icmp6(const struct ovnact_nest *on, - } - - 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) -+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; - -@@ -1733,6 +1733,53 @@ execute_tcp_reset(const struct ovnact_nest *on, - 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 lookback, enum ovnact_pipeline pipeline, -+ struct ovs_list *super) -+{ -+ struct flow tcp_flow = *uflow; -+ -+ /* Update fields for TCP segment. */ -+ if (lookback) { -+ 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; -+ 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_tcp_reset(const struct ovnact_nest *on, -+ const struct ovntrace_datapath *dp, -+ const struct flow *uflow, uint8_t table_id, -+ bool lookback, 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, lookback, pipeline, super); -+ } else { -+ execute_tcp6_reset(on, dp, uflow, table_id, lookback, pipeline, super); -+ } -+} - static void - execute_reject(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, --- -2.26.2 - diff --git a/SOURCES/0006-actions-Fix-leak-of-select-group-members.patch b/SOURCES/0006-actions-Fix-leak-of-select-group-members.patch deleted file mode 100644 index f5e38b5..0000000 --- a/SOURCES/0006-actions-Fix-leak-of-select-group-members.patch +++ /dev/null @@ -1,47 +0,0 @@ -From a614f338a280aa180b1b8d818c7571269164827b Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:14 +0100 -Subject: [PATCH 06/16] actions: Fix leak of select group members. - -'dsts' should be freed in case of any error. - - Direct leak of 4 byte(s) in 1 object(s) allocated from: - #0 0x502378 in realloc (/tests/ovstest+0x502378) - #1 0x622826 in xrealloc /lib/util.c:149:9 - #2 0x8194f4 in parse_select_action /lib/actions.c:1185:20 - #3 0x814f49 in parse_set_action /lib/actions.c:3499:13 - #4 0x814341 in parse_action /lib/actions.c:3554:9 - #5 0x8139ef in parse_actions /lib/actions.c:3643:14 - #6 0x8136a3 in ovnacts_parse /lib/actions.c:3678:9 - #7 0x813c80 in ovnacts_parse_string /lib/actions.c:3705:5 - #8 0x53a4e8 in test_parse_actions /tests/test-ovn.c:1321:17 - #9 0x54e7a8 in ovs_cmdl_run_command__ /lib/command-line.c:247:17 - #10 0x537c75 in test_ovn_main /tests/test-ovn.c:1630:5 - #11 0x54e7a8 in ovs_cmdl_run_command__ /lib/command-line.c:247:17 - #12 0x537359 in main /tests/ovstest.c:133:9 - #13 0x7f9ce05ba1a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - -CC: Han Zhou -Fixes: 85b3544aabb2 ("ovn-controller: A new action "select".") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - lib/actions.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/lib/actions.c b/lib/actions.c -index 0464c51a8..156ebb2fe 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -1188,6 +1188,7 @@ parse_select_action(struct action_context *ctx, struct expr_field *res_field) - } - if (n_dsts <= 1) { - lexer_syntax_error(ctx->lexer, "expecting at least 2 group members"); -+ free(dsts); - return; - } - --- -2.28.0 - diff --git a/SOURCES/0006-controller-Add-load-balancer-hairpin-OF-flows.patch b/SOURCES/0006-controller-Add-load-balancer-hairpin-OF-flows.patch deleted file mode 100644 index 311bad7..0000000 --- a/SOURCES/0006-controller-Add-load-balancer-hairpin-OF-flows.patch +++ /dev/null @@ -1,902 +0,0 @@ -From 4f81eab659b42aeb8b433783515b03b5772c68de Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Thu, 12 Nov 2020 17:26:17 +0530 -Subject: [PATCH 06/10] controller: Add load balancer hairpin OF flows. - -Presently to handle the load balancer hairpin traffic (the traffic destined to the -load balancer VIP is dnatted to the backend which originated the traffic), ovn-northd -adds a lot of logical flows to check this scenario. This patch attempts to reduce the -these logical flows. Each ovn-controller will read the load balancers from -the newly added southbound Load_Balancer table and adds the load balancer hairpin OF -flows in the table 68, 69 and 70. If suppose a below load balancer is configured - -10.0.0.10:80 = 10.0.0.4:8080, 10.0.0.5:8090, then the below flows are added - -table=68, ip.src = 10.0.0.4,ip.dst=10.0.0.4,tcp.dst=8080 actions=load:1->NXM_NX_REG10[7] -table=68, ip.src = 10.0.0.5,ip.dst=10.0.0.5,tcp.dst=8090 actions=load:1->NXM_NX_REG10[7] -table=69, ip.src = 10.0.0.4,ip.dst=10.0.0.10,tcp.src=8080 actions=load:1->NXM_NX_REG10[7] -table=69, ip.src = 10.0.0.5,ip.dst=10.0.0.10,tcp.src=8090 actions=load:1->NXM_NX_REG10[7] -table=70, ct.trk && ct.dnat && ct.nw_dst == 10.0.0.10. actions=ct(commit, zone=reg12, nat(src=10.0.0.5)) - -Upcoming patch will add OVN actions which does the lookup in these tables to handle the -hairpin traffic. - -Acked-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 7da8145d9d4743b3d15f0d7f51b201e7ba87fe63) -Conflicts: - tests/ovn.at ---- - controller/lflow.c | 230 +++++++++++++++++ - controller/lflow.h | 6 +- - controller/ovn-controller.c | 27 +- - include/ovn/logical-fields.h | 3 + - tests/ovn.at | 469 +++++++++++++++++++++++++++++++++++ - 5 files changed, 733 insertions(+), 2 deletions(-) - -diff --git a/controller/lflow.c b/controller/lflow.c -index 4d71dfddb..633fdfb7f 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -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" -@@ -1144,6 +1145,190 @@ add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name, - } - } - -+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)); -+ } -+ -+ for (size_t i = 0; i < lb->slb->n_datapaths; i++) { -+ match_set_metadata(&hairpin_match, -+ htonll(lb->slb->datapaths[i]->tunnel_key)); -+ match_set_metadata(&hairpin_reply_match, -+ htonll(lb->slb->datapaths[i]->tunnel_key)); -+ -+ ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN, 100, -+ lb->slb->header_.uuid.parts[0], &hairpin_match, -+ &ofpacts, &lb->slb->header_.uuid); -+ -+ 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 +1388,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) - 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 -@@ -1262,6 +1449,15 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp, - 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 +1475,37 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb, - 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 --git a/controller/lflow.h b/controller/lflow.h -index 1251fb0f4..1225131de 100644 ---- a/controller/lflow.h -+++ b/controller/lflow.h -@@ -68,6 +68,9 @@ struct uuid; - #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 -@@ -132,6 +135,7 @@ struct lflow_ctx_in { - const struct sbrec_logical_flow_table *logical_flow_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 +164,7 @@ void lflow_handle_changed_neighbors( - 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 --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 8d8c678e5..e5479cf3e 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -790,7 +790,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) - 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, -@@ -1682,6 +1683,10 @@ static void init_lflow_ctx(struct engine_node *node, - (struct sbrec_multicast_group_table *)EN_OVSDB_GET( - engine_get_input("SB_multicast_group", node)); - -+ struct sbrec_load_balancer_table *lb_table = -+ (struct sbrec_load_balancer_table *)EN_OVSDB_GET( -+ engine_get_input("SB_load_balancer", node)); -+ - const char *chassis_id = chassis_get_id(); - const struct sbrec_chassis *chassis = NULL; - struct ovsdb_idl_index *sbrec_chassis_by_name = -@@ -1713,6 +1718,7 @@ static void init_lflow_ctx(struct engine_node *node, - l_ctx_in->logical_flow_table = logical_flow_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; -@@ -2131,6 +2137,23 @@ flow_output_runtime_data_handler(struct engine_node *node, - 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; -@@ -2327,6 +2350,8 @@ main(int argc, char *argv[]) - 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); -diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h -index ac6f2f909..0fe5bc3bb 100644 ---- a/include/ovn/logical-fields.h -+++ b/include/ovn/logical-fields.h -@@ -57,6 +57,7 @@ enum mff_log_flags_bits { - 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 +89,8 @@ enum mff_log_flags { - - /* 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 -diff --git a/tests/ovn.at b/tests/ovn.at -index ba17246d4..5cb96bae6 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -22727,3 +22727,472 @@ 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,tcp,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,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-], [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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,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-], [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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x1,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x1,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 -+ -+OVS_WAIT_UNTIL( -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 7] -+) -+ -+OVS_WAIT_UNTIL( -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 7] -+) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x2,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,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x2,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 7] -+) -+ -+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 4] -+) -+ -+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,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+priority=100,udp6,metadata=0x2,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 --- -2.28.0 - diff --git a/SOURCES/0006-ofctrl.c-Always-log-the-most-recent-flow-changes.patch b/SOURCES/0006-ofctrl.c-Always-log-the-most-recent-flow-changes.patch deleted file mode 100644 index dd98c85..0000000 --- a/SOURCES/0006-ofctrl.c-Always-log-the-most-recent-flow-changes.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 814f6acbce556e3d580c9bd7d2f69be573375cfd Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Tue, 13 Oct 2020 11:04:03 +0200 -Subject: [PATCH 6/7] ofctrl.c: Always log the most recent flow changes. - -Fixes: 6f0b1e02d9ab ("ofctrl: Incremental processing for flow installation by tracking.") -Signed-off-by: Dumitru Ceara -Signed-off-by: Han Zhou -(cherry picked from upstream commit 33c15c145988daa6172928dc870f3a0225515f50) - -Change-Id: I17cd01e6a0924c1f52b2d9ae4b15f8e6438b10d5 ---- - controller/ofctrl.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index ba0c61c..f444cae 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -1894,8 +1894,8 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - * 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). */ -- 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); -@@ -1915,8 +1915,8 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - } 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. */ --- -1.8.3.1 - diff --git a/SOURCES/0006-osx-Fix-compilation-error.patch b/SOURCES/0006-osx-Fix-compilation-error.patch deleted file mode 100644 index 0de35f6..0000000 --- a/SOURCES/0006-osx-Fix-compilation-error.patch +++ /dev/null @@ -1,69 +0,0 @@ -From befea4cc873c25574a62ead84cc4413fcbf23619 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Tue, 15 Dec 2020 17:30:17 +0530 -Subject: [PATCH 6/7] osx: Fix compilation error. - -The commit "northd: Add ECMP support to router policies." introduced -compilaton error on osx platform. This patch fixes the issue. - -The errors are: - ----- -northd/ovn-northd.c:7697:38: error: format specifies type 'unsigned short' -but the argument has type 'int' [-Werror,-Wformat] - ecmp_group_id, i + 1); - ^~~~~ -northd/ovn-northd.c:7713:44: error: format specifies type 'unsigned short' -but the argument has type 'int' [-Werror,-Wformat] - ds_put_format(&actions, "%"PRIu16, i + 1); - ~~~~~~~~ ^~~~~ ----- - -Fixes: 35b00c7e7990("northd: Add ECMP support to router policies.") -Suggested-by: Dumitru Ceara -Acked-by: Dumitru Ceara -Signed-off-by: Numan Siddique ---- - northd/ovn-northd.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index dfd7d69d0..b377dffa1 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -7650,7 +7650,7 @@ build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od, - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - -- for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ for (size_t i = 0; i < rule->n_nexthops; i++) { - struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( - od, ports, rule->priority, rule->nexthops[i]); - if (!out_port) { -@@ -7690,7 +7690,7 @@ build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od, - - ds_clear(&match); - ds_put_format(&match, REG_ECMP_GROUP_ID" == %"PRIu16" && " -- REG_ECMP_MEMBER_ID" == %"PRIu16, -+ REG_ECMP_MEMBER_ID" == %"PRIuSIZE, - ecmp_group_id, i + 1); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY_ECMP, - 100, ds_cstr(&match), -@@ -7702,12 +7702,12 @@ build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od, - "; %s = select(", REG_ECMP_GROUP_ID, ecmp_group_id, - REG_ECMP_MEMBER_ID); - -- for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ for (size_t i = 0; i < rule->n_nexthops; i++) { - if (i > 0) { - ds_put_cstr(&actions, ", "); - } - -- ds_put_format(&actions, "%"PRIu16, i + 1); -+ ds_put_format(&actions, "%"PRIuSIZE, i + 1); - } - ds_put_cstr(&actions, ");"); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY, --- -2.28.0 - diff --git a/SOURCES/0006-ovn-northd-Move-broadcast-and-multicast-lookup-in-ls.patch b/SOURCES/0006-ovn-northd-Move-broadcast-and-multicast-lookup-in-ls.patch deleted file mode 100644 index d734a8c..0000000 --- a/SOURCES/0006-ovn-northd-Move-broadcast-and-multicast-lookup-in-ls.patch +++ /dev/null @@ -1,213 +0,0 @@ -From 9e60b5574786c0ef8f6403ac61567553c1a7717f Mon Sep 17 00:00:00 2001 -Message-Id: <9e60b5574786c0ef8f6403ac61567553c1a7717f.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:33 +0000 -Subject: [PATCH 06/16] ovn-northd: Move broadcast and multicast lookup in - lswitch to a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 169 +++++++++++++++++++++++--------------------- - 1 file changed, 87 insertions(+), 82 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 09bfaae5e..f4e248f55 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6780,88 +6780,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- /* Ingress table 19: Destination lookup, broadcast and multicast handling -- * (priority 70 - 100). */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -- 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; -- -- if (mcast_sw_info->enabled) { -- ds_clear(&actions); -- if (mcast_sw_info->flood_reports) { -- ds_put_cstr(&actions, -- "clone { " -- "outport = \""MC_MROUTER_STATIC"\"; " -- "output; " -- "};"); -- } -- ds_put_cstr(&actions, "igmp;"); -- /* Punt IGMP traffic to controller. */ -- 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_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_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_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. -- * If configured to flood unregistered traffic this will be -- * handled by the L2 multicast flow. -- */ -- if (!mcast_sw_info->flood_unregistered) { -- ds_clear(&actions); -- -- if (mcast_sw_info->flood_relay) { -- ds_put_cstr(&actions, -- "clone { " -- "outport = \""MC_MROUTER_FLOOD"\"; " -- "output; " -- "}; "); -- } -- -- if (mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;"); -- } -- -- /* Explicitly drop the traffic if relay or static flooding -- * is not configured. -- */ -- if (!mcast_sw_info->flood_relay && -- !mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, "drop;"); -- } -- -- ovn_lflow_add_unique(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, 70, "eth.mcast", -- "outport = \""MC_FLOOD"\"; output;"); -- } - - /* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD - * (priority 90). */ -@@ -7488,6 +7406,92 @@ build_lswitch_external_port(struct ovn_port *op, - } - } - -+/* Ingress table 19: Destination lookup, broadcast and multicast handling -+ * (priority 70 - 100). */ -+static void -+build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od, -+ struct hmap *lflows, -+ struct ds *actions) -+{ -+ if (od->nbs) { -+ -+ 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; -+ -+ if (mcast_sw_info->enabled) { -+ ds_clear(actions); -+ if (mcast_sw_info->flood_reports) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_MROUTER_STATIC"\"; " -+ "output; " -+ "};"); -+ } -+ ds_put_cstr(actions, "igmp;"); -+ /* Punt IGMP traffic to controller. */ -+ 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_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_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_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. -+ * If configured to flood unregistered traffic this will be -+ * handled by the L2 multicast flow. -+ */ -+ if (!mcast_sw_info->flood_unregistered) { -+ ds_clear(actions); -+ -+ if (mcast_sw_info->flood_relay) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_MROUTER_FLOOD"\"; " -+ "output; " -+ "}; "); -+ } -+ -+ if (mcast_sw_info->flood_static) { -+ ds_put_cstr(actions, "outport =\""MC_STATIC"\"; output;"); -+ } -+ -+ /* Explicitly drop the traffic if relay or static flooding -+ * is not configured. -+ */ -+ if (!mcast_sw_info->flood_relay && -+ !mcast_sw_info->flood_static) { -+ ds_put_cstr(actions, "drop;"); -+ } -+ -+ ovn_lflow_add_unique(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, 70, "eth.mcast", -+ "outport = \""MC_FLOOD"\"; output;"); -+ } -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -11341,6 +11345,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_lswitch_arp_nd_responder_default(od, lsi->lflows); - build_lswitch_dns_lookup_and_response(od, lsi->lflows); - build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows); -+ build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); --- -2.29.2 - diff --git a/SOURCES/0007-actions-Add-new-actions-chk_lb_hairpin-chk_lb_hairpi.patch b/SOURCES/0007-actions-Add-new-actions-chk_lb_hairpin-chk_lb_hairpi.patch deleted file mode 100644 index 840d3da..0000000 --- a/SOURCES/0007-actions-Add-new-actions-chk_lb_hairpin-chk_lb_hairpi.patch +++ /dev/null @@ -1,474 +0,0 @@ -From c55da56e570b50cfc33358341f17f2dc738e8b33 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Thu, 12 Nov 2020 17:26:57 +0530 -Subject: [PATCH 07/10] actions: Add new actions chk_lb_hairpin, - chk_lb_hairpin_reply and ct_snat_to_vip. - -The action - chk_lb_hairpin checks if the packet destined to a load balancer VIP -is to be hairpinned back to the same destination and if so, sets the destination register -bit to 1. - -The action - chk_lb_hairpin_reply checks if the packet is a reply of the hairpinned -packet. If so, it sets the destination register bit to 1. - -The action ct_snat_to_vip snats the source IP to the load balancer VIP if chk_lb_hairpin() -returned true. - -These actions will be used in the upcoming patch by ovn-northd in the hairpin logical flows. -This helps in reducing lots of hairpin logical flows. - -Acked-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique - -(cherry-picked from master commit fc219d84b667a48760c62e41dbc25fcb5748d41a) ---- - controller/lflow.c | 3 ++ - include/ovn/actions.h | 15 ++++-- - lib/actions.c | 116 ++++++++++++++++++++++++++++++++++++++---- - ovn-sb.xml | 37 ++++++++++++++ - tests/ovn.at | 39 ++++++++++++++ - tests/test-ovn.c | 3 ++ - utilities/ovn-trace.c | 65 ++++++++++++++++++++++- - 7 files changed, 265 insertions(+), 13 deletions(-) - -diff --git a/controller/lflow.c b/controller/lflow.c -index 633fdfb7f..7ab6a686b 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -698,6 +698,9 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - .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); - -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index b4e5acabb..630bbe79e 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -83,7 +83,7 @@ struct ovn_extend_table; - 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) \ -@@ -97,6 +97,9 @@ struct ovn_extend_table; - OVNACT(DHCP6_REPLY, ovnact_null) \ - 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 { -@@ -338,8 +341,8 @@ struct ovnact_set_queue { - 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. */ - }; -@@ -727,6 +730,12 @@ struct ovnact_encode_params { - 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 --git a/lib/actions.c b/lib/actions.c -index 23e54ef2a..015bcbc4d 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -2655,13 +2655,14 @@ ovnact_set_queue_free(struct ovnact_set_queue *a OVS_UNUSED) - } - - 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. */ -@@ -2671,19 +2672,29 @@ parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst, - free(error); - return; - } -- dl->dst = *dst; -- add_prerequisite(ctx, "udp"); -+ res->dst = *dst; -+ -+ if (prereq) { -+ add_prerequisite(ctx, prereq); -+ } - } - - static void --format_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, struct ds *s) -+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_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) - { -@@ -2700,7 +2711,7 @@ encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, - - - static void --ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) -+ovnact_result_free(struct ovnact_result *dl OVS_UNUSED) - { - } - -@@ -3472,6 +3483,83 @@ ovnact_fwd_group_free(struct ovnact_fwd_group *fwd_group) - 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) -@@ -3524,6 +3612,14 @@ parse_set_action(struct action_context *ctx) - && 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); - } -@@ -3610,6 +3706,8 @@ parse_action(struct action_context *ctx) - 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 --git a/ovn-sb.xml b/ovn-sb.xml -index 7fa0496fe..7d38c7e3f 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -2325,6 +2325,43 @@ tcp.flags = RST; - 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. -+

    -+
    -
    -
    - -diff --git a/tests/ovn.at b/tests/ovn.at -index 5cb96bae6..8dbb13d3a 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1716,6 +1716,45 @@ fwd_group(liveness="false", childports="eth0", "lsp1"); - 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 `;'. -diff --git a/tests/test-ovn.c b/tests/test-ovn.c -index 80d99b7a8..6662ced54 100644 ---- a/tests/test-ovn.c -+++ b/tests/test-ovn.c -@@ -1342,6 +1342,9 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) - .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 --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index 5d54c0fd8..cc1cd1b16 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -1990,7 +1990,7 @@ execute_next(const struct ovnact_next *next, - - - 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); -@@ -2222,6 +2222,57 @@ execute_ovnfield_load(const struct ovnact_load *load, - } - } - -+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, -@@ -2438,6 +2489,18 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - 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; - --- -2.28.0 - diff --git a/SOURCES/0007-ofctrl-Fix-leak-of-meter-mod-bands.patch b/SOURCES/0007-ofctrl-Fix-leak-of-meter-mod-bands.patch deleted file mode 100644 index 619ad4d..0000000 --- a/SOURCES/0007-ofctrl-Fix-leak-of-meter-mod-bands.patch +++ /dev/null @@ -1,42 +0,0 @@ -From b2758cd591c6b46426f9b21540c88b2c3ef9b1d5 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:15 +0100 -Subject: [PATCH 07/16] ofctrl: Fix leak of meter mod bands. - -'parse_ofp_meter_mod_str' allocates space for meter.bands that -should be freed. - - Direct leak of 448 byte(s) in 7 object(s) allocated from: - #0 0x52100f in malloc (/controller/ovn-controller+0x52100f) - #1 0x7523a6 in xmalloc /lib/util.c:138:15 - #2 0x6fd079 in ofpbuf_init /lib/ofpbuf.c:123:26 - #3 0x6cba27 in parse_ofp_meter_mod_str /lib/ofp-meter.c:779:5 - #4 0x5705b8 in add_meter_string /controller/ofctrl.c:1674:19 - #5 0x56f736 in ofctrl_put /controller/ofctrl.c:2105:13 - #6 0x59aebb in main /controller/ovn-controller.c:2627:25 - #7 0x7f07873251a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - -CC: Guoshuai Li -Fixes: c25094b3884d ("ovn: OVN Support QoS meter") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - controller/ofctrl.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index 79529d13c..c1bbc589e 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -1675,6 +1675,7 @@ add_meter_string(struct ovn_extend_table_info *m_desired, - &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); --- -2.28.0 - diff --git a/SOURCES/0007-ofctrl.c-Add-a-predictable-resolution-for-conflictin.patch b/SOURCES/0007-ofctrl.c-Add-a-predictable-resolution-for-conflictin.patch deleted file mode 100644 index 1e57565..0000000 --- a/SOURCES/0007-ofctrl.c-Add-a-predictable-resolution-for-conflictin.patch +++ /dev/null @@ -1,418 +0,0 @@ -From e552cd40bf8abb3eb5ff80bd25c13105e427119a Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Thu, 15 Oct 2020 11:12:30 +0200 -Subject: [PATCH 7/7] ofctrl.c: Add a predictable resolution for conflicting - flow actions. - -Until now, in case the ACL configuration generates openflows that have -the same match but different actions, ovn-controller was using the -following approach: -1. If the flow being added contains conjunctive actions, merge its - actions with the already existing flow. -2. Otherwise, if the flow is being added incrementally - (update_installed_flows_by_track), don't install the new flow but - instead keep the old one. -3. Otherwise, (update_installed_flows_by_compare), don't install the - new flow but instead keep the old one. - -Even though one can argue that having an ACL with a match that includes -the match of another ACL is a misconfiguration, it can happen that the -users provision OVN like this. Depending on the order of reading and -installing the logical flows, the above operations can yield -unpredictable results, e.g., allow specific traffic but then after -ovn-controller is restarted (or a recompute happens) that specific -traffic starts being dropped. - -A simple example of ACL configuration is: -ovn-nbctl acl-add ls 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 ls to-lport 3 'ip4.src==10.0.0.1' allow -ovn-nbctl acl-add ls to-lport 2 'arp' allow -ovn-nbctl acl-add ls to-lport 1 'ip4' drop - -This is a pattern used by most CMSs: -- define a default deny policy. -- punch holes in the default deny policy based on user specific - configurations. - -Without this commit the behavior for traffic from 10.0.0.1 to 10.0.0.5 -is unpredictable. Depending on the order of operations traffic might be -dropped or allowed. - -It's also quite hard to force the CMS to ensure that such match overlaps -never occur. - -To address this issue we now ensure that all desired flows refering the -same installed flow are partially sorted in the following way: -- first all flows with action "allow". -- then all flows with action "drop". -- then a single flow with action "conjunction" (resulting from merging - all flows with the same match and action conjunction). - -This ensures that "allow" flows have precedence over "drop" flows which -in turn have precedence over "conjunction" flows. Essentially less -restrictive flows are always preferred over more restrictive flows whenever a match -conflict happens. - -CC: Daniel Alvarez -Reported-at: https://bugzilla.redhat.com/1871931 -Signed-off-by: Dumitru Ceara -Acked-by: Mark Gray -Signed-off-by: Han Zhou -(cherry picked from upstream commit 986b3d5e4ad6f05245d021ba699c957246294a22) - -Change-Id: Ibf49b5103ea34e5f268782f81cdae9cc7c06cae0 ---- - controller/ofctrl.c | 74 ++++++++++++++++-- - tests/ovn.at | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 283 insertions(+), 5 deletions(-) - -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index f444cae..79529d1 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -188,6 +188,14 @@ struct sb_flow_ref { - * 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. - */ -@@ -796,6 +804,12 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type) - } - - static bool -+flow_action_has_drop(const struct ovn_flow *f) -+{ -+ return f->ofpacts_len == 0; -+} -+ -+static bool - flow_action_has_conj(const struct ovn_flow *f) - { - const struct ofpact *a = NULL; -@@ -808,6 +822,33 @@ flow_action_has_conj(const struct ovn_flow *f) - 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(); -+} -+ - /* Adds the desired flow to the list of desired flows that have same match - * conditions as the installed flow. - * -@@ -820,8 +861,18 @@ flow_action_has_conj(const struct ovn_flow *f) - 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(&f->installed_ref_list_node, &d->installed_ref_list_node); - d->installed_flow = i; -- ovs_list_push_back(&i->desired_refs, &d->installed_ref_list_node); - return installed_flow_get_active(i) == d; - } - -@@ -1789,8 +1840,14 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - 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. */ -- link_installed_to_desired(i, d); -+ * 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)"); -+ } - } - } - } -@@ -1919,8 +1976,15 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - 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. */ -diff --git a/tests/ovn.at b/tests/ovn.at -index 6f1ab59..53f5d4d 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -13727,6 +13727,220 @@ grep conjunction.*conjunction.*conjunction | wc -l`]) - OVN_CLEANUP([hv1]) - AT_CLEANUP - -+AT_SETUP([ovn -- Superseeding 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 -+} -+ -+ip_to_hex() { -+ printf "%02x%02x%02x%02x" "$@" -+} -+ -+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 | \ -+ grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl -+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction(3,2/2) -+]) -+ -+# 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 | \ -+ grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl -+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction(3,2/2) -+]) -+ -+# 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 | \ -+ grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl -+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(2,2/2),conjunction(3,2/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction(3,2/2) -+]) -+ -+# 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 | \ -+ grep "priority=1003" | awk '{print $7 " " $8}' | sort], [0], [dnl -+priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(2,1/2),conjunction(3,1/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(2,2/2) -+priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction(3,2/2) -+]) -+ -+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 --- -1.8.3.1 - diff --git a/SOURCES/0007-ovn-northd-Move-destination-handling-into-functions.patch b/SOURCES/0007-ovn-northd-Move-destination-handling-into-functions.patch deleted file mode 100644 index 68fd93e..0000000 --- a/SOURCES/0007-ovn-northd-Move-destination-handling-into-functions.patch +++ /dev/null @@ -1,506 +0,0 @@ -From 137b049777cfc301eadba8a2c3b55764bde6f451 Mon Sep 17 00:00:00 2001 -Message-Id: <137b049777cfc301eadba8a2c3b55764bde6f451.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:34 +0000 -Subject: [PATCH 07/16] ovn-northd: Move destination handling into functions. - -1. Move igmp/mld destination handling into a function. -2. Move unicast destination handling into a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 433 +++++++++++++++++++++++--------------------- - 1 file changed, 223 insertions(+), 210 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index f4e248f55..27a788095 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -6769,8 +6769,7 @@ is_vlan_transparent(const struct ovn_datapath *od) - - static void - build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, -- struct hmap *lflows, struct hmap *mcgroups, -- struct hmap *igmp_groups) -+ struct hmap *lflows) - { - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ -@@ -6778,212 +6777,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; - struct ovn_datapath *od; -- struct ovn_port *op; -- -- -- /* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD -- * (priority 90). */ -- struct ovn_igmp_group *igmp_group; -- -- HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) { -- if (!igmp_group->datapath) { -- continue; -- } -- -- ds_clear(&match); -- ds_clear(&actions); -- -- struct mcast_switch_info *mcast_sw_info = -- &igmp_group->datapath->mcast_info.sw; -- -- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -- /* RFC 4541, section 2.1.2, item 2: Skip groups in the 224.0.0.X -- * range. -- */ -- ovs_be32 group_address = -- in6_addr_get_mapped_ipv4(&igmp_group->address); -- if (ip_is_local_multicast(group_address)) { -- continue; -- } -- -- if (mcast_sw_info->active_v4_flows >= mcast_sw_info->table_size) { -- continue; -- } -- mcast_sw_info->active_v4_flows++; -- ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ", -- igmp_group->mcgroup.name); -- } else { -- /* RFC 4291, section 2.7.1: Skip groups that correspond to all -- * hosts. -- */ -- if (ipv6_is_all_hosts(&igmp_group->address)) { -- continue; -- } -- if (mcast_sw_info->active_v6_flows >= mcast_sw_info->table_size) { -- continue; -- } -- mcast_sw_info->active_v6_flows++; -- ds_put_format(&match, "eth.mcast && ip6 && ip6.dst == %s ", -- igmp_group->mcgroup.name); -- } -- -- /* Also flood traffic to all multicast routers with relay enabled. */ -- if (mcast_sw_info->flood_relay) { -- ds_put_cstr(&actions, -- "clone { " -- "outport = \""MC_MROUTER_FLOOD "\"; " -- "output; " -- "};"); -- } -- if (mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, -- "clone { " -- "outport =\""MC_STATIC"\"; " -- "output; " -- "};"); -- } -- ds_put_format(&actions, "outport = \"%s\"; output; ", -- igmp_group->mcgroup.name); -- -- 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), */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || lsp_is_external(op->nbsp)) { -- continue; -- } -- -- /* For ports connected to logical routers add flows to bypass the -- * broadcast flooding of ARP/ND requests in table 19. We direct the -- * requests only to the router port that owns the IP address. -- */ -- if (lsp_is_router(op->nbsp)) { -- build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows, -- &op->nbsp->header_); -- } -- -- for (size_t i = 0; i < op->nbsp->n_addresses; i++) { -- /* Addresses are owned by the logical port. -- * Ethernet address followed by zero or more IPv4 -- * or IPv6 addresses (or both). */ -- struct eth_addr mac; -- if (ovs_scan(op->nbsp->addresses[i], -- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -- ETH_ADDR_ARGS(mac)); -- -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -- 50, ds_cstr(&match), -- ds_cstr(&actions), -- &op->nbsp->header_); -- } else if (!strcmp(op->nbsp->addresses[i], "unknown")) { -- if (lsp_is_enabled(op->nbsp)) { -- ovn_multicast_add(mcgroups, &mc_unknown, op); -- op->od->has_unknown = true; -- } -- } else if (is_dynamic_lsp_address(op->nbsp->addresses[i])) { -- if (!op->nbsp->dynamic_addresses -- || !ovs_scan(op->nbsp->dynamic_addresses, -- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -- continue; -- } -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -- ETH_ADDR_ARGS(mac)); -- -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -- 50, ds_cstr(&match), -- ds_cstr(&actions), -- &op->nbsp->header_); -- } else if (!strcmp(op->nbsp->addresses[i], "router")) { -- if (!op->peer || !op->peer->nbrp -- || !ovs_scan(op->peer->nbrp->mac, -- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -- continue; -- } -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -- ETH_ADDR_ARGS(mac)); -- if (op->peer->od->l3dgw_port -- && op->peer->od->l3redirect_port -- && op->od->n_localnet_ports) { -- bool add_chassis_resident_check = false; -- if (op->peer == op->peer->od->l3dgw_port) { -- /* 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 gateway chassis. */ -- add_chassis_resident_check = true; -- } else { -- /* Check if the option 'reside-on-redirect-chassis' -- * is set to true on the peer port. If set to true -- * and if the logical switch has a localnet port, it -- * means the router pipeline for the packets from -- * this logical switch should be run on the chassis -- * hosting the gateway port. -- */ -- add_chassis_resident_check = smap_get_bool( -- &op->peer->nbrp->options, -- "reside-on-redirect-chassis", false); -- } -- -- if (add_chassis_resident_check) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- op->peer->od->l3redirect_port->json_key); -- } -- } -- -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_L2_LKUP, 50, -- ds_cstr(&match), ds_cstr(&actions), -- &op->nbsp->header_); -- -- /* Add ethernet addresses specified in NAT rules on -- * distributed logical routers. */ -- if (op->peer->od->l3dgw_port -- && op->peer == op->peer->od->l3dgw_port) { -- for (int j = 0; j < op->peer->od->nbr->n_nat; j++) { -- const struct nbrec_nat *nat -- = op->peer->od->nbr->nat[j]; -- if (!strcmp(nat->type, "dnat_and_snat") -- && nat->logical_port && nat->external_mac -- && eth_addr_from_string(nat->external_mac, &mac)) { -- -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT -- " && is_chassis_resident(\"%s\")", -- ETH_ADDR_ARGS(mac), -- nat->logical_port); -- -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", -- op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_SWITCH_IN_L2_LKUP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -- &op->nbsp->header_); -- } -- } -- } -- } else { -- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -- -- VLOG_INFO_RL(&rl, -- "%s: invalid syntax '%s' in addresses column", -- op->nbsp->name, op->nbsp->addresses[i]); -- } -- } -- } - - /* Ingress table 19: Destination lookup for unknown MACs (priority 0). */ - HMAP_FOR_EACH (od, key_node, datapaths) { -@@ -7492,6 +7285,218 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od, - } - } - -+ -+/* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD -+ * (priority 90). */ -+static void -+build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group, -+ struct hmap *lflows, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (igmp_group->datapath) { -+ -+ ds_clear(match); -+ ds_clear(actions); -+ -+ struct mcast_switch_info *mcast_sw_info = -+ &igmp_group->datapath->mcast_info.sw; -+ -+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -+ /* RFC 4541, section 2.1.2, item 2: Skip groups in the 224.0.0.X -+ * range. -+ */ -+ ovs_be32 group_address = -+ in6_addr_get_mapped_ipv4(&igmp_group->address); -+ if (ip_is_local_multicast(group_address)) { -+ return; -+ } -+ -+ if (mcast_sw_info->active_v4_flows >= mcast_sw_info->table_size) { -+ return; -+ } -+ mcast_sw_info->active_v4_flows++; -+ ds_put_format(match, "eth.mcast && ip4 && ip4.dst == %s ", -+ igmp_group->mcgroup.name); -+ } else { -+ /* RFC 4291, section 2.7.1: Skip groups that correspond to all -+ * hosts. -+ */ -+ if (ipv6_is_all_hosts(&igmp_group->address)) { -+ return; -+ } -+ if (mcast_sw_info->active_v6_flows >= mcast_sw_info->table_size) { -+ return; -+ } -+ mcast_sw_info->active_v6_flows++; -+ ds_put_format(match, "eth.mcast && ip6 && ip6.dst == %s ", -+ igmp_group->mcgroup.name); -+ } -+ -+ /* Also flood traffic to all multicast routers with relay enabled. */ -+ if (mcast_sw_info->flood_relay) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_MROUTER_FLOOD "\"; " -+ "output; " -+ "};"); -+ } -+ if (mcast_sw_info->flood_static) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport =\""MC_STATIC"\"; " -+ "output; " -+ "};"); -+ } -+ ds_put_format(actions, "outport = \"%s\"; output; ", -+ igmp_group->mcgroup.name); -+ -+ 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), */ -+static void -+build_lswitch_ip_unicast_lookup(struct ovn_port *op, -+ struct hmap *lflows, -+ struct hmap *mcgroups, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (op->nbsp && (!lsp_is_external(op->nbsp))) { -+ -+ /* For ports connected to logical routers add flows to bypass the -+ * broadcast flooding of ARP/ND requests in table 19. We direct the -+ * requests only to the router port that owns the IP address. -+ */ -+ if (lsp_is_router(op->nbsp)) { -+ build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows, -+ &op->nbsp->header_); -+ } -+ -+ for (size_t i = 0; i < op->nbsp->n_addresses; i++) { -+ /* Addresses are owned by the logical port. -+ * Ethernet address followed by zero or more IPv4 -+ * or IPv6 addresses (or both). */ -+ struct eth_addr mac; -+ if (ovs_scan(op->nbsp->addresses[i], -+ ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, -+ ETH_ADDR_ARGS(mac)); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -+ 50, ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ } else if (!strcmp(op->nbsp->addresses[i], "unknown")) { -+ if (lsp_is_enabled(op->nbsp)) { -+ ovn_multicast_add(mcgroups, &mc_unknown, op); -+ op->od->has_unknown = true; -+ } -+ } else if (is_dynamic_lsp_address(op->nbsp->addresses[i])) { -+ if (!op->nbsp->dynamic_addresses -+ || !ovs_scan(op->nbsp->dynamic_addresses, -+ ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -+ continue; -+ } -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, -+ ETH_ADDR_ARGS(mac)); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -+ 50, ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ } else if (!strcmp(op->nbsp->addresses[i], "router")) { -+ if (!op->peer || !op->peer->nbrp -+ || !ovs_scan(op->peer->nbrp->mac, -+ ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -+ continue; -+ } -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, -+ ETH_ADDR_ARGS(mac)); -+ if (op->peer->od->l3dgw_port -+ && op->peer->od->l3redirect_port -+ && op->od->n_localnet_ports) { -+ bool add_chassis_resident_check = false; -+ if (op->peer == op->peer->od->l3dgw_port) { -+ /* 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 gateway chassis. */ -+ add_chassis_resident_check = true; -+ } else { -+ /* Check if the option 'reside-on-redirect-chassis' -+ * is set to true on the peer port. If set to true -+ * and if the logical switch has a localnet port, it -+ * means the router pipeline for the packets from -+ * this logical switch should be run on the chassis -+ * hosting the gateway port. -+ */ -+ add_chassis_resident_check = smap_get_bool( -+ &op->peer->nbrp->options, -+ "reside-on-redirect-chassis", false); -+ } -+ -+ if (add_chassis_resident_check) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->peer->od->l3redirect_port->json_key); -+ } -+ } -+ -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_L2_LKUP, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); -+ -+ /* Add ethernet addresses specified in NAT rules on -+ * distributed logical routers. */ -+ if (op->peer->od->l3dgw_port -+ && op->peer == op->peer->od->l3dgw_port) { -+ for (int j = 0; j < op->peer->od->nbr->n_nat; j++) { -+ const struct nbrec_nat *nat -+ = op->peer->od->nbr->nat[j]; -+ if (!strcmp(nat->type, "dnat_and_snat") -+ && nat->logical_port && nat->external_mac -+ && eth_addr_from_string(nat->external_mac, &mac)) { -+ -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT -+ " && is_chassis_resident(\"%s\")", -+ ETH_ADDR_ARGS(mac), -+ nat->logical_port); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", -+ op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_SWITCH_IN_L2_LKUP, 50, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ } -+ } -+ } -+ } else { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -+ -+ VLOG_INFO_RL(&rl, -+ "%s: invalid syntax '%s' in addresses column", -+ op->nbsp->name, op->nbsp->addresses[i]); -+ } -+ } -+ } -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -11384,6 +11389,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - &lsi->match); - build_lswitch_dhcp_options_and_response(op,lsi->lflows); - build_lswitch_external_port(op, lsi->lflows); -+ build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups, -+ &lsi->actions, &lsi->match); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, -@@ -11412,6 +11419,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - struct ovn_northd_lb *lb; -+ struct ovn_igmp_group *igmp_group; - - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - -@@ -11443,14 +11451,19 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - &lsi.actions, - &lsi.match); - } -+ HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) { -+ build_lswitch_ip_mcast_igmp_mld(igmp_group, -+ lsi.lflows, -+ &lsi.actions, -+ &lsi.match); -+ } - free(svc_check_match); - - ds_destroy(&lsi.match); - ds_destroy(&lsi.actions); - - /* Legacy lswitch build - to be migrated. */ -- build_lswitch_flows(datapaths, ports, lflows, mcgroups, -- igmp_groups); -+ build_lswitch_flows(datapaths, ports, lflows); - - /* Legacy lrouter build - to be migrated. */ - build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs); --- -2.29.2 - diff --git a/SOURCES/0007-tests-Make-ovn-ovn-controller-incremental-processing.patch b/SOURCES/0007-tests-Make-ovn-ovn-controller-incremental-processing.patch deleted file mode 100644 index eccf032..0000000 --- a/SOURCES/0007-tests-Make-ovn-ovn-controller-incremental-processing.patch +++ /dev/null @@ -1,179 +0,0 @@ -From df617604c7b1f366601bcec6fd0c752f8a52977c Mon Sep 17 00:00:00 2001 -From: Dumitru Ceara -Date: Wed, 16 Dec 2020 13:59:02 +0100 -Subject: [PATCH 7/7] tests: Make "ovn -- ovn-controller incremental - processing" more reliable. - -Relax the full recompute checks as changes to tunnel interfaces, e.g. due -to BFD state changes, are not processed incrementally and cause full -recomputes. On slower systems (like in CI) this can happen more often. - -Signed-off-by: Dumitru Ceara -Signed-off-by: Mark Michelson -Acked-by: Ilya Maximets ---- - tests/ovn-performance.at | 111 +++++++++++++++++++-------------------- - 1 file changed, 53 insertions(+), 58 deletions(-) - -diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at -index 6cc5b2174..e510c6cef 100644 ---- a/tests/ovn-performance.at -+++ b/tests/ovn-performance.at -@@ -232,37 +232,32 @@ AT_SETUP([ovn -- ovn-controller incremental processing]) - - ovn_start - net_add n1 --for i in 1 2; do -+for i in `seq 1 5`; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i --done -- --for i in 1 2 3; do -- sim_add gw$i -- as gw$i -- ovs-vsctl add-br br-phys -- ovs-vsctl add-br br-ex -- ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex" -- j=$((i + 2)) -- ovn_attach n1 br-phys 192.168.0.$j -- ip link add vgw$i type dummy -- ovs-vsctl add-port br-ex vgw$i -+ if [[ $i -ge 3 ]] ; then -+ ovs-vsctl add-br br-ex -+ ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex" -+ ip link add vgw$i type dummy -+ ovs-vsctl add-port br-ex vgw$i -+ fi - done - - # Wait for the tunnel ports to be created and up. - # Otherwise this may affect the lflow_run count. -+for i in `seq 1 5`; do -+ for j in `seq 1 5`; do -+ if [[ $i -ne $j ]] ; then -+ OVS_WAIT_UNTIL([ -+ test $(as hv$i ovs-vsctl list interface ovn-hv$j-0 | \ -+ grep -c tunnel_egress_iface_carrier=up) -eq 1 -+ ]) -+ fi -+ done -+done - --OVS_WAIT_UNTIL([ -- test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \ --grep tunnel_egress_iface_carrier=up | wc -l) -eq 1 --]) -- --OVS_WAIT_UNTIL([ -- test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \ --grep tunnel_egress_iface_carrier=up | wc -l) -eq 1 --]) - - # Add router lr1 - OVN_CONTROLLER_EXPECT_NO_HIT( -@@ -463,63 +458,63 @@ OVN_CONTROLLER_EXPECT_NO_HIT( - ) - - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 >0 =0 =0], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw1 30 && ovn-nbctl --wait=hv sync] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [=0 =0 >0 =0 =0], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv3 30 && ovn-nbctl --wait=hv sync] - ) - --# After this, BFD should be enabled from hv1 and hv2 to gw1. --# So there should be lflow_run hits in hv1, hv2, gw1 and gw2 -+# After this, BFD should be enabled from hv1 and hv2 to hv3. -+# So there should be lflow_run hits in hv1, hv2, hv3 and hv4 - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 >0 =0], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 20 && ovn-nbctl --wait=hv sync] --) -- --OVN_CONTROLLER_EXPECT_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw3 10 && ovn-nbctl --wait=hv sync] --) -- --# create QoS rule --OVN_CONTROLLER_EXPECT_NO_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 >0 =0], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 20 && ovn-nbctl --wait=hv sync] - ) - - OVN_CONTROLLER_EXPECT_HIT( -- [gw1], [lflow_run], -- [as gw1 ovs-vsctl set interface vgw1 external-ids:ovn-egress-iface=true] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv5 10 && ovn-nbctl --wait=hv sync] - ) - --# Make gw2 master. There is remote possibility that full recompute --# triggers for gw2 after it becomes master. Most of the time --# there will be no recompute. --ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 40 --gw2_ch=$(ovn-sbctl --bare --columns _uuid list chassis gw2) --OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$gw2_ch]) -+# Make hv4 master. There is remote possibility that full recompute -+# triggers for hv1-hv5 after hv4 becomes master because of updates to the -+# ovn-hv$i-0 interfaces. Most of the time there will be no recompute. -+ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 40 -+hv4_ch=$(ovn-sbctl --bare --columns _uuid list chassis hv4) -+OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$hv4_ch]) - - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 =0 >=0 =0], -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>=0 >=0 >=0 >=0 >=0], - [ovn-nbctl --wait=hv sync] - ) - --# Delete gw2 from gateway chassis -+# Delete hv4 from gateway chassis - OVN_CONTROLLER_EXPECT_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw2 && ovn-nbctl --wait=hv sync] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv4 && ovn-nbctl --wait=hv sync] - ) - --# Delete gw1 from gateway chassis --# After this, the BFD should be disabled entirely as gw3 is the -+# Delete hv3 from gateway chassis -+# After this, the BFD should be disabled entirely as hv5 is the - # only gateway chassis. - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 =0 >0], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw1] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 =0 >0], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv3] - ) - --# Delete gw3 from gateway chassis. There should be no lflow_run. -+# Delete hv5 from gateway chassis. There should be no lflow_run. - OVN_CONTROLLER_EXPECT_NO_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw3] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv5] -+) -+ -+# create QoS rule -+OVN_CONTROLLER_EXPECT_NO_HIT( -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000] -+) -+ -+OVN_CONTROLLER_EXPECT_HIT( -+ [hv3], [lflow_run], -+ [as hv3 ovs-vsctl set interface vgw3 external-ids:ovn-egress-iface=true] - ) - - for i in 1 2; do --- -2.28.0 - diff --git a/SOURCES/0008-northd-Make-use-of-new-hairpin-actions.patch b/SOURCES/0008-northd-Make-use-of-new-hairpin-actions.patch deleted file mode 100644 index 0e9babd..0000000 --- a/SOURCES/0008-northd-Make-use-of-new-hairpin-actions.patch +++ /dev/null @@ -1,609 +0,0 @@ -From 13ceb66f0deb1c1c1384041d3146e0189e7328d8 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Thu, 12 Nov 2020 17:27:23 +0530 -Subject: [PATCH 08/10] northd: Make use of new hairpin actions. - -This patch makes use of the new hairpin OVN actions - chk_lb_hairpin, chk_lb_hairpin_reply -and ct_snat_to_vip. - -Suppose there are 'm' load balancers associated to a logical switch and each load balancer -has 'n' VIPs and each VIP has 'p' backends then ovn-northd adds (m * ((n * p) + n)) -hairpin logical flows. After this patch, ovn-northd adds just 5 hairpin logical flows. - -With this patch number of hairpin related OF flows on a chassis are almost the same as before, -but in a large scale deployment, this reduces memory consumption and load on ovn-northd and -SB DB ovsdb-servers. - -Acked-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 3357ab14076f0a7e91fe690538b4315c7219de60) -Conflicts: - northd/ovn-northd.c ---- - northd/ovn-northd.8.xml | 65 +++++++++++----- - northd/ovn-northd.c | 160 +++++++++++++--------------------------- - tests/ovn-northd.at | 28 +++---- - tests/ovn.at | 52 ++++++------- - 4 files changed, 141 insertions(+), 164 deletions(-) - -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index 820698228..27d996944 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -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. -
    • -+ -
    • -- 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;. -+ 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. -+
    • -+ -+
    • -+ 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 @@ output; - - - --

    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 @@ next; - - - --

    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 @@ output; - - - --

    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 @@ reg0[4] = dns_lookup(); next; - - - --

    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 @@ output; - - - --

    Ingress table 19 External ports

    -+

    Ingress table 20 External ports

    - -

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

    Ingress Table 20 Destination Lookup

    -+

    Ingress Table 21 Destination Lookup

    - -

    - This table implements switching behavior. It contains these logical -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index a7695bc63..bb31e04fa 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -150,14 +150,15 @@ enum ovn_stage { - 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") \ -@@ -5811,85 +5812,6 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows) - } - } - --static void --build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows, -- struct ovn_northd_lb *lb, -- struct ovn_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 ovn_lb_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_str, ip_match, backend->ip_str, -- 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_str, 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_str); -- 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_str); -- -- /* 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_northd_lb *lb) -@@ -5938,12 +5860,6 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, - - 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); - } - } - -@@ -5990,24 +5906,53 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) - 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;"); -+ } - } - - static void -@@ -6693,6 +6638,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - build_qos(od, lflows); - build_lb(od, lflows); - build_stateful(od, lflows, lbs); -+ build_lb_hairpin(od, lflows); - } - - /* Build logical flows for the forwarding groups */ -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 961fb3712..bf3a99a6c 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1775,13 +1775,13 @@ action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implici - AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | 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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | 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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject -@@ -1789,19 +1789,19 @@ ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject - AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | 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=20); };) -+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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | 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=20); };) -+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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related -@@ -1813,16 +1813,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) - 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=20); };) -+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=20); };) -+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=20); };) -+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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl -@@ -1832,16 +1832,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) - 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=20); };) -+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=20); };) -+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=20); };) -+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=20); };) -+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };) - ]) - - AT_CLEANUP -diff --git a/tests/ovn.at b/tests/ovn.at -index 8dbb13d3a..8f18ca9e5 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -14657,17 +14657,17 @@ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 - ]) -@@ -14698,17 +14698,17 @@ port_binding logical_port=ls1-lp_ext1` - - # 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 - ]) -@@ -14730,38 +14730,38 @@ logical_port=ls1-lp_ext1` - 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 - ]) -@@ -15013,21 +15013,21 @@ logical_port=ls1-lp_ext1` - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 table=24 | \ - 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 -@@ -15293,7 +15293,7 @@ logical_port=ls1-lp_ext1` - # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined - # to router mac. - AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \ --table=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 - ]) - -@@ -16564,9 +16564,9 @@ ovn-nbctl --wait=hv sync - 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-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;) -+ table=15(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=15(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=15(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" \ -@@ -16776,8 +16776,8 @@ 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;) -+ table=15(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=15(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 --- -2.28.0 - diff --git a/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch b/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch deleted file mode 100644 index 4982ad2..0000000 --- a/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch +++ /dev/null @@ -1,181 +0,0 @@ -From a6b4b14ac1b6523f85fb13a7f259d9698a70444f Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:35 +0000 -Subject: [PATCH 08/16] ovn-northd: split build_lswitch_output_port_sec into - iterators. - -Split build_lswitch_output_port_sec into a per port and per -datapath iterator. Migrate to the relevant per-port and -per-datapath loops. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 82 ++++++++++++++++++++------------------------- - 1 file changed, 37 insertions(+), 45 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 27a788095..92300e017 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -4917,51 +4917,47 @@ build_lswitch_input_port_sec_od( - } - } - -+/* Egress table 8: Egress port security - IP (priorities 90 and 80) -+ * if port security enabled. -+ * -+ * Egress table 9: Egress port security - L2 (priorities 50 and 150). -+ * -+ * Priority 50 rules implement port security for enabled logical port. -+ * -+ * Priority 150 rules drop packets to disabled logical ports, so that -+ * they don't even receive multicast or broadcast packets. -+ */ - static void --build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, -- struct hmap *lflows) -+build_lswitch_output_port_sec_op(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, -+ struct ds *actions) - { -- struct ds actions = DS_EMPTY_INITIALIZER; -- struct ds match = DS_EMPTY_INITIALIZER; -- struct ovn_port *op; - -- /* Egress table 8: Egress port security - IP (priorities 90 and 80) -- * if port security enabled. -- * -- * Egress table 9: Egress port security - L2 (priorities 50 and 150). -- * -- * Priority 50 rules implement port security for enabled logical port. -- * -- * Priority 150 rules drop packets to disabled logical ports, so that -- * they don't even receive multicast or broadcast packets. -- */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || lsp_is_external(op->nbsp)) { -- continue; -- } -+ if (op->nbsp && (!lsp_is_external(op->nbsp))) { - -- ds_clear(&actions); -- ds_clear(&match); -+ ds_clear(actions); -+ ds_clear(match); - -- ds_put_format(&match, "outport == %s", op->json_key); -+ ds_put_format(match, "outport == %s", op->json_key); - if (lsp_is_enabled(op->nbsp)) { - build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs, -- &match); -+ match); - - if (!strcmp(op->nbsp->type, "localnet")) { - 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_format(actions, "set_queue(%s); ", queue_id); - } - } -- ds_put_cstr(&actions, "output;"); -+ ds_put_cstr(actions, "output;"); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, -- 50, ds_cstr(&match), ds_cstr(&actions), -+ 50, ds_cstr(match), ds_cstr(actions), - &op->nbsp->header_); - } else { - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, -- 150, ds_cstr(&match), "drop;", -+ 150, ds_cstr(match), "drop;", - &op->nbsp->header_); - } - -@@ -4969,23 +4965,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, - build_port_security_ip(P_OUT, op, lflows, &op->nbsp->header_); - } - } -+} - -- /* Egress tables 8: Egress port security - IP (priority 0) -- * Egress table 9: Egress port security L2 - multicast/broadcast -- * (priority 100). */ -- struct ovn_datapath *od; -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -+/* Egress tables 8: Egress port security - IP (priority 0) -+ * Egress table 9: Egress port security L2 - multicast/broadcast -+ * (priority 100). */ -+static void -+build_lswitch_output_port_sec_od(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast", - "output;"); - } -- -- ds_destroy(&match); -- ds_destroy(&actions); - } - - static void -@@ -6768,8 +6761,7 @@ is_vlan_transparent(const struct ovn_datapath *od) - } - - static void --build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, -- struct hmap *lflows) -+build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows) - { - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ -@@ -6790,8 +6782,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - } - } - -- build_lswitch_output_port_sec(ports, datapaths, lflows); -- - ds_destroy(&match); - ds_destroy(&actions); - } -@@ -11351,6 +11341,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_lswitch_dns_lookup_and_response(od, lsi->lflows); - build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows); - build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions); -+ build_lswitch_output_port_sec_od(od, lsi->lflows); - - /* Build Logical Router Flows. */ - build_adm_ctrl_flows_for_lrouter(od, lsi->lflows); -@@ -11391,6 +11382,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - build_lswitch_external_port(op, lsi->lflows); - build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups, - &lsi->actions, &lsi->match); -+ build_lswitch_output_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, -@@ -11462,8 +11455,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - ds_destroy(&lsi.match); - ds_destroy(&lsi.actions); - -- /* Legacy lswitch build - to be migrated. */ -- build_lswitch_flows(datapaths, ports, lflows); -+ build_lswitch_flows(datapaths, lflows); - - /* Legacy lrouter build - to be migrated. */ - build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs); --- -2.29.2 - diff --git a/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch b/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch deleted file mode 100644 index 7f23de7..0000000 --- a/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 87e4b1c8533f5b42175366706daff9c706dd1ecf Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:16 +0100 -Subject: [PATCH 08/16] pinctrl: Fix leak of DNS cache records. - -'smap_clear()' doesn't free allocated memory, but 'smap_clone()' -re-initializes hash map clearing internal pointers and leaking -this memory. 'smap_destroy()' should be used instead. - -Also, all the records and array of datapaths should be freed on -destruction of a cache entry. - - Direct leak of 16 byte(s) in 2 object(s) allocated from: - #0 0x5211c7 in calloc (/controller/ovn-controller+0x5211c7) - #1 0x752364 in xcalloc /lib/util.c:121:31 - #2 0x576e76 in sync_dns_cache /controller/pinctrl.c:2517:25 - #3 0x5758fb in pinctrl_run /controller/pinctrl.c:3158:5 - #4 0x59b06c in main /controller/ovn-controller.c:2642:25 - #5 0x7fb570fc11a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - - Indirect leak of 26 byte(s) in 2 object(s) allocated from: - #0 0x52100f in malloc (/controller/ovn-controller+0x52100f) - #1 0x7523d6 in xmalloc /lib/util.c:138:15 - #2 0x7524a8 in xmemdup0 /lib/util.c:168:15 - #3 0x73d8fc in smap_clone /lib/smap.c:314:45 - #4 0x576e2f in sync_dns_cache /controller/pinctrl.c:2513:13 - #5 0x5758fb in pinctrl_run /controller/pinctrl.c:3158:5 - #6 0x59b06c in main /controller/ovn-controller.c:2642:25 - #7 0x7fb570fc11a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - -Fixes: 6b72068202f1 ("ovn-controller: Add a new thread in pinctrl module to handle packet-ins.") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 728fb3063..d445d235e 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -2508,7 +2508,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table) - 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); - } - -@@ -2524,6 +2524,8 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table) - struct dns_data *d = iter->data; - if (d->delete) { - shash_delete(&dns_cache, iter); -+ smap_destroy(&d->records); -+ free(d->dps); - free(d); - } - } -@@ -2536,6 +2538,8 @@ destroy_dns_cache(void) - 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); - } - } --- -2.28.0 - diff --git a/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch b/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch deleted file mode 100644 index 79bd340..0000000 --- a/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch +++ /dev/null @@ -1,42 +0,0 @@ -From deed21b21e9d9cb0a05c3af5024fd27fd34720c8 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:17 +0100 -Subject: [PATCH 09/16] ovn-controller: Fix leak of pending ct zones. - -shash contains pointers to the data that should be freed. - - Direct leak of 32 byte(s) in 2 object(s) allocated from: - #0 0x52100f in malloc (/controller/ovn-controller+0x52100f) - #1 0x752436 in xmalloc /lib/util.c:138:15 - #2 0x5a2f0b in add_pending_ct_zone_entry /controller/ovn-controller.c:548:45 - #3 0x5a2d1d in update_ct_zones /controller/ovn-controller.c:668:9 - #4 0x59d8c6 in en_ct_zones_run /controller/ovn-controller.c:1495:5 - #5 0x5dade4 in engine_run /lib/inc-proc-eng.c:377:9 - #6 0x59adf4 in main /controller/ovn-controller.c - #7 0x7f0799ef41a2 in __libc_start_main (/lib64/libc.so.6+0x271a2) - -CC: xu rong -Fixes: 252e1642fb59 ("ovn-controller: pending_ct_zones should be destroyed") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - controller/ovn-controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 3b41cc390..e3cf855e7 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -1418,7 +1418,7 @@ en_ct_zones_cleanup(void *data) - 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 --- -2.28.0 - diff --git a/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch b/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch deleted file mode 100644 index 57112ed..0000000 --- a/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 83a851eddce5ed189b6a145524c41ad817eb2ddf Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Thu, 12 Nov 2020 17:27:59 +0530 -Subject: [PATCH 09/10] ovn-detrace: Add SB Load Balancer cookier handler. - -Acked-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 287605267f64daed845828d5f11b473f0cc98f33) ---- - utilities/ovn-detrace.in | 11 ++++++++++- - 1 file changed, 10 insertions(+), 1 deletion(-) - -diff --git a/utilities/ovn-detrace.in b/utilities/ovn-detrace.in -index 1dd98df0a..af42b5fc4 100755 ---- a/utilities/ovn-detrace.in -+++ b/utilities/ovn-detrace.in -@@ -328,6 +328,14 @@ class ChassisHandler(CookieHandlerByUUUID): - 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') -@@ -452,7 +460,8 @@ def main(): - 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]+)') --- -2.28.0 - diff --git a/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch b/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch deleted file mode 100644 index 2c011c7..0000000 --- a/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 34c2afc7d49f735e825e0d01bf1b2b64bb277f76 Mon Sep 17 00:00:00 2001 -Message-Id: <34c2afc7d49f735e825e0d01bf1b2b64bb277f76.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:36 +0000 -Subject: [PATCH 09/16] ovn-northd: Move lrouter arp and nd datapath processing - to a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 96 +++++++++++++++++++++++---------------------- - 1 file changed, 50 insertions(+), 46 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 92300e017..7f7bb07be 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -8937,52 +8937,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - struct ovn_datapath *od; - struct ovn_port *op; - -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbr) { -- continue; -- } -- -- /* Priority-90-92 flows handle ARP requests and ND packets. Most are -- * per logical port but DNAT addresses can be handled per datapath -- * for non gateway router ports. -- * -- * 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. -- */ -- for (int i = 0; i < od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &od->nat_entries[i]; -- -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- 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); -- } -- -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; -- -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -- -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -- } -- } -- - /* Logical router ingress table 3: IP Input for IPv4. */ - HMAP_FOR_EACH (op, key_node, ports) { - if (!op->nbrp) { -@@ -11308,6 +11262,55 @@ build_ipv6_input_flows_for_lrouter_port( - - } - -+static void -+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbr) { -+ -+ /* 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. -+ */ -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &od->nat_entries[i]; -+ -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ 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); -+ } -+ -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; -+ -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; -+ } -+ -+ struct ovn_nat *nat_entry = -+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -+ struct ovn_nat, ext_addr_list_node); -+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -+ } -+ } -+} -+ -+ - struct lswitch_flow_build_info { - struct hmap *datapaths; - struct hmap *ports; -@@ -11360,6 +11363,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match, - &lsi->actions); - build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); -+ build_lrouter_arp_nd_for_datapath(od, lsi->lflows); - } - - /* Helper function to combine all lflow generation which is iterated by port. --- -2.29.2 - diff --git a/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch b/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch deleted file mode 100644 index f33d9a2..0000000 --- a/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 90deedbeeb26efb1e042f6c0cd0d7caad4fb1b90 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:18 +0100 -Subject: [PATCH 10/16] ovn-nbctl: Fix error leak on duplicated switch port. - -Error is allocated from heap and should be freed. - -Fixes: 738295605b44 ("ovn: Detect and prevent duplicate address assignments.") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index dfcf67cfd..f4c4f9385 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -1683,6 +1683,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx) - error = lsp_contains_duplicates(ls, lsp, ctx->argv[i]); - if (error) { - ctl_error(ctx, "%s", error); -+ free(error); - return; - } - } --- -2.28.0 - diff --git a/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch b/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch deleted file mode 100644 index d15e521..0000000 --- a/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch +++ /dev/null @@ -1,556 +0,0 @@ -From 761f760a42d97184c870e892d299587e657a2c52 Mon Sep 17 00:00:00 2001 -Message-Id: <761f760a42d97184c870e892d299587e657a2c52.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:37 +0000 -Subject: [PATCH 10/16] ovn-northd: Move ipv4 input to a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 499 ++++++++++++++++++++++---------------------- - 1 file changed, 249 insertions(+), 250 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 7f7bb07be..f9b8d588b 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -8924,7 +8924,7 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, - } - - static void --build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, -+build_lrouter_flows(struct hmap *datapaths, - struct hmap *lflows, struct shash *meter_groups, - struct hmap *lbs) - { -@@ -8935,254 +8935,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - struct ds actions = DS_EMPTY_INITIALIZER; - - struct ovn_datapath *od; -- struct ovn_port *op; -- -- /* Logical router ingress table 3: IP Input for IPv4. */ -- 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_ipv4_addrs) { -- /* L3 admission control: drop packets that originate from an -- * IPv4 address owned by the router or a broadcast address -- * known to the router (priority 100). */ -- ds_clear(&match); -- ds_put_cstr(&match, "ip4.src == "); -- op_put_v4_networks(&match, op, true); -- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -- ds_cstr(&match), "drop;", -- &op->nbrp->header_); -- -- /* ICMP echo reply. These flows reply to ICMP echo requests -- * received for the router's IP address. Since packets only -- * get here as part of the logical router datapath, the inport -- * (i.e. the incoming locally attached net) does not matter. -- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -- ds_clear(&match); -- ds_put_cstr(&match, "ip4.dst == "); -- op_put_v4_networks(&match, op, false); -- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); -- -- const char * icmp_actions = "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 0; " -- "flags.loopback = 1; " -- "next; "; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -- ds_cstr(&match), icmp_actions, -- &op->nbrp->header_); -- } -- -- /* ICMP time exceeded */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_clear(&actions); -- -- ds_put_format(&match, -- "inport == %s && ip4 && " -- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -- ds_put_format(&actions, -- "icmp4 {" -- "eth.dst <-> eth.src; " -- "icmp4.type = 11; /* Time exceeded */ " -- "icmp4.code = 0; /* TTL exceeded in transit */ " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "next; };", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -- ds_cstr(&match), ds_cstr(&actions), -- &op->nbrp->header_); -- } -- -- /* ARP reply. These flows reply to ARP requests for the router's own -- * IP address. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, "arp.spa == %s/%u", -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); -- -- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -- && op->peer->od->n_localnet_ports) { -- bool add_chassis_resident_check = false; -- if (op == op->od->l3dgw_port) { -- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -- * should only be sent from the gateway chassis, so that -- * upstream MAC learning points to the gateway chassis. -- * Also need to avoid generation of multiple ARP responses -- * from different chassis. */ -- add_chassis_resident_check = true; -- } else { -- /* Check if the option 'reside-on-redirect-chassis' -- * is set to true on the router port. If set to true -- * and if peer's logical switch has a localnet port, it -- * means the router pipeline for the packets from -- * peer's logical switch is be run on the chassis -- * hosting the gateway port and it should reply to the -- * ARP requests for the router port IPs. -- */ -- add_chassis_resident_check = smap_get_bool( -- &op->nbrp->options, -- "reside-on-redirect-chassis", false); -- } -- -- if (add_chassis_resident_check) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -- } -- -- build_lrouter_arp_flow(op->od, op, -- op->lrp_networks.ipv4_addrs[i].addr_s, -- REG_INPORT_ETH_ADDR, &match, false, 90, -- &op->nbrp->header_, lflows); -- } -- -- /* A set to hold all load-balancer vips that need ARP responses. */ -- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); -- -- const char *ip_address; -- SSET_FOR_EACH (ip_address, &all_ips_v4) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -- -- build_lrouter_arp_flow(op->od, op, -- ip_address, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } -- -- SSET_FOR_EACH (ip_address, &all_ips_v6) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -- -- build_lrouter_nd_flow(op->od, op, "nd_na", -- ip_address, NULL, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } -- -- sset_destroy(&all_ips_v4); -- sset_destroy(&all_ips_v6); -- -- if (!smap_get(&op->od->nbr->options, "chassis") -- && !op->od->l3dgw_port) { -- /* UDP/TCP port unreachable. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && udp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- const char *action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 3; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->header_); -- -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "tcp_reset {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->header_); -- -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 2; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 70, ds_cstr(&match), action, -- &op->nbrp->header_); -- } -- } -- -- /* 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 / 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; -- } -- -- for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &op->od->nat_entries[i]; -- -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } -- -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { -- continue; -- } -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } -- -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; -- -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -- -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } -- } - - /* NAT, Defrag and load balancing. */ - HMAP_FOR_EACH (od, key_node, datapaths) { -@@ -11310,6 +11062,251 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od, - } - } - -+/* Logical router ingress table 3: IP Input for IPv4. */ -+static void -+build_lrouter_ipv4_ip_input(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ /* No ingress packets are accepted on a chassisredirect -+ * port, so no need to program flows for that port. */ -+ if (op->nbrp && (!op->derived)) { -+ if (op->lrp_networks.n_ipv4_addrs) { -+ /* L3 admission control: drop packets that originate from an -+ * IPv4 address owned by the router or a broadcast address -+ * known to the router (priority 100). */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.src == "); -+ op_put_v4_networks(match, op, true); -+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -+ ds_cstr(match), "drop;", -+ &op->nbrp->header_); -+ -+ /* ICMP echo reply. These flows reply to ICMP echo requests -+ * received for the router's IP address. Since packets only -+ * get here as part of the logical router datapath, the inport -+ * (i.e. the incoming locally attached net) does not matter. -+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.dst == "); -+ op_put_v4_networks(match, op, false); -+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); -+ -+ const char * icmp_actions = "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 0; " -+ "flags.loopback = 1; " -+ "next; "; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -+ ds_cstr(match), icmp_actions, -+ &op->nbrp->header_); -+ } -+ -+ /* ICMP time exceeded */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_clear(actions); -+ -+ ds_put_format(match, -+ "inport == %s && ip4 && " -+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -+ ds_put_format(actions, -+ "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "icmp4.type = 11; /* Time exceeded */ " -+ "icmp4.code = 0; /* TTL exceeded in transit */ " -+ "ip4.dst = ip4.src; " -+ "ip4.src = %s; " -+ "ip.ttl = 255; " -+ "next; };", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+ -+ /* ARP reply. These flows reply to ARP requests for the router's own -+ * IP address. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, "arp.spa == %s/%u", -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); -+ -+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -+ && op->peer->od->n_localnet_ports) { -+ bool add_chassis_resident_check = false; -+ if (op == op->od->l3dgw_port) { -+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -+ * should only be sent from the gateway chassis, so that -+ * upstream MAC learning points to the gateway chassis. -+ * Also need to avoid generation of multiple ARP responses -+ * from different chassis. */ -+ add_chassis_resident_check = true; -+ } else { -+ /* Check if the option 'reside-on-redirect-chassis' -+ * is set to true on the router port. If set to true -+ * and if peer's logical switch has a localnet port, it -+ * means the router pipeline for the packets from -+ * peer's logical switch is be run on the chassis -+ * hosting the gateway port and it should reply to the -+ * ARP requests for the router port IPs. -+ */ -+ add_chassis_resident_check = smap_get_bool( -+ &op->nbrp->options, -+ "reside-on-redirect-chassis", false); -+ } -+ -+ if (add_chassis_resident_check) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ } -+ -+ build_lrouter_arp_flow(op->od, op, -+ op->lrp_networks.ipv4_addrs[i].addr_s, -+ REG_INPORT_ETH_ADDR, match, false, 90, -+ &op->nbrp->header_, lflows); -+ } -+ -+ /* A set to hold all load-balancer vips that need ARP responses. */ -+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); -+ -+ const char *ip_address; -+ SSET_FOR_EACH (ip_address, &all_ips_v4) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ -+ build_lrouter_arp_flow(op->od, op, -+ ip_address, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); -+ } -+ -+ SSET_FOR_EACH (ip_address, &all_ips_v6) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ -+ build_lrouter_nd_flow(op->od, op, "nd_na", -+ ip_address, NULL, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); -+ } -+ -+ sset_destroy(&all_ips_v4); -+ sset_destroy(&all_ips_v6); -+ -+ if (!smap_get(&op->od->nbr->options, "chassis") -+ && !op->od->l3dgw_port) { -+ /* UDP/TCP port unreachable. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && udp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ const char *action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 3; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); -+ -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "tcp_reset {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); -+ -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 2; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 70, ds_cstr(match), action, -+ &op->nbrp->header_); -+ } -+ } -+ -+ /* 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 / 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) { -+ return; -+ } -+ -+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &op->od->nat_entries[i]; -+ -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ continue; -+ } -+ -+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately -+ * below. -+ */ -+ if (!strcmp(nat_entry->nb->type, "snat")) { -+ continue; -+ } -+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ } -+ -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; -+ -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; -+ } -+ -+ struct ovn_nat *nat_entry = -+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -+ struct ovn_nat, ext_addr_list_node); -+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ } -+ } -+} -+ - - struct lswitch_flow_build_info { - struct hmap *datapaths; -@@ -11404,6 +11401,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); - build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows, - &lsi->match, &lsi->actions); -+ build_lrouter_ipv4_ip_input(op, lsi->lflows, -+ &lsi->match, &lsi->actions); - } - - static void -@@ -11462,7 +11461,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - build_lswitch_flows(datapaths, lflows); - - /* Legacy lrouter build - to be migrated. */ -- build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs); -+ build_lrouter_flows(datapaths, lflows, meter_groups, lbs); - } - - struct ovn_dp_group { --- -2.29.2 - diff --git a/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch b/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch deleted file mode 100644 index ff7ae66..0000000 --- a/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch +++ /dev/null @@ -1,96 +0,0 @@ -From a1cbb077f5907a3ad898e43478614d18ad7be294 Mon Sep 17 00:00:00 2001 -From: Numan Siddique -Date: Thu, 12 Nov 2020 17:28:17 +0530 -Subject: [PATCH 10/10] sbctl: Add Load Balancer support for vflows option. - -Acked-by: Dumitru Ceara -Acked-by: Mark Michelson -Signed-off-by: Numan Siddique ---- - utilities/ovn-sbctl.c | 56 +++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 56 insertions(+) - -diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c -index 30236c9cc..a524175c4 100644 ---- a/utilities/ovn-sbctl.c -+++ b/utilities/ovn-sbctl.c -@@ -542,6 +542,11 @@ pre_get_info(struct ctl_context *ctx) - 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[] = { -@@ -1010,6 +1015,56 @@ cmd_lflow_list_chassis(struct ctl_context *ctx, struct vconn *vconn, - } - } - -+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 void - cmd_lflow_list(struct ctl_context *ctx) - { -@@ -1119,6 +1174,7 @@ cmd_lflow_list(struct ctl_context *ctx) - 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); --- -2.28.0 - diff --git a/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch b/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch deleted file mode 100644 index 99655a3..0000000 --- a/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0006b3389d43d5f3d55b895a9f856106cd28085e Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:19 +0100 -Subject: [PATCH 11/16] northd: Fix leak of dynamic string for fwd group ports. - -'group_ports' never destroyed and re-created on each iteration. - -CC: Manoj Sharma -Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 44f41669812c633bc180074e6d91e0d0f3a781a1) ---- - northd/ovn-northd.c | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 0acff2322..23ad7ba7f 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -5960,6 +5960,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows) - { - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; -+ struct ds group_ports = DS_EMPTY_INITIALIZER; - - for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) { - const struct nbrec_forwarding_group *fwd_group = NULL; -@@ -5993,7 +5994,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows) - ds_put_format(&match, "eth.dst == %s", fwd_group->vmac); - - /* Create a comma separated string of child ports */ -- struct ds group_ports = DS_EMPTY_INITIALIZER; -+ ds_clear(&group_ports); - if (fwd_group->liveness) { - ds_put_cstr(&group_ports, "liveness=\"true\","); - } -@@ -6013,6 +6014,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows) - - ds_destroy(&match); - ds_destroy(&actions); -+ ds_destroy(&group_ports); - } - - static void --- -2.28.0 - diff --git a/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch b/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch deleted file mode 100644 index a27b30b..0000000 --- a/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch +++ /dev/null @@ -1,4489 +0,0 @@ -From 7699c1043a3fec9eb215fc430202ca01846c505e Mon Sep 17 00:00:00 2001 -Message-Id: <7699c1043a3fec9eb215fc430202ca01846c505e.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Anton Ivanov -Date: Tue, 5 Jan 2021 17:49:38 +0000 -Subject: [PATCH 11/16] ovn-northd: move NAT, Defrag and lb to a function. - -Signed-off-by: Anton Ivanov -Signed-off-by: Numan Siddique -Signed-off-by: Lorenzo Bianconi ---- - northd/ovn-northd.c | 4128 +++++++++++++++++++++---------------------- - 1 file changed, 2058 insertions(+), 2070 deletions(-) - -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index f9b8d588b..f588d8c32 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -8923,2391 +8923,2380 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, - ds_destroy(&actions); - } - -+/* Logical router ingress Table 0: L2 Admission Control -+ * Generic admission control flows (without inport check). -+ */ - static void --build_lrouter_flows(struct hmap *datapaths, -- struct hmap *lflows, struct shash *meter_groups, -- struct hmap *lbs) -+build_adm_ctrl_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows) - { -- /* 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; -+ 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;"); -+ } -+} - -- struct ovn_datapath *od; -+/* Logical router ingress Table 0: L2 Admission Control -+ * This table drops packets that the router shouldn’t see at all based -+ * on their Ethernet headers. -+ */ -+static void -+build_adm_ctrl_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 from disabled logical ports (since logical flow -+ * tables are default-drop). */ -+ return; -+ } - -- /* NAT, Defrag and load balancing. */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbr) { -- continue; -+ if (op->derived) { -+ /* No ingress packets should be received on a chassisredirect -+ * port. */ -+ return; - } - -- /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); -+ /* Store the ethernet address of the port receiving the packet. -+ * This will save us from having to match on inport further down in -+ * the pipeline. -+ */ -+ ds_clear(actions); -+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -+ op->lrp_networks.ea_s); - -- /* Send the IPv6 NS packets to next table. When ovn-controller -- * generates IPv6 NS (for the action - nd_ns{}), the injected -- * packet would go through conntrack - which is not required. */ -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); -+ ds_clear(match); -+ ds_put_format(match, "eth.mcast && inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); - -- /* NAT rules are only valid on Gateway routers and routers with -- * l3dgw_port (router has a port with gateway chassis -- * specified). */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- continue; -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == %s && inport == %s", -+ op->lrp_networks.ea_s, op->json_key); -+ 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 gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+} - -- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - -- 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); -+/* Logical router ingress Table 1 and 2: Neighbor lookup and learning -+ * lflows for logical routers. */ -+static void -+build_neigh_learning_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- for (int i = 0; i < od->nbr->n_nat; i++) { -- const struct nbrec_nat *nat; -+ /* Learn MAC bindings from ARP/IPv6 ND. -+ * -+ * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the -+ * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp' -+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit. -+ * If "always_learn_from_arp_request" is set to false, it will also -+ * lookup for the (arp.spa) in the mac binding table using the -+ * "lookup_arp_ip" action for ARP request packets, and stores the -+ * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit -+ * to "1" directly for ARP response packets. -+ * -+ * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup -+ * for the (nd.target, nd.tll) in the mac binding table using the -+ * 'lookup_nd' action and stores the result in -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -+ * "always_learn_from_arp_request" is set to false, -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set. -+ * -+ * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup -+ * for the (ip6.src, nd.sll) in the mac binding table using the -+ * 'lookup_nd' action and stores the result in -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -+ * "always_learn_from_arp_request" is set to false, it will also lookup -+ * for the (ip6.src) in the mac binding table using the "lookup_nd_ip" -+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ * bit. -+ * -+ * Table LEARN_NEIGHBOR learns the mac-binding using the action -+ * - 'put_arp/put_nd'. Learning mac-binding is skipped if -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set. -+ * -+ * */ - -- nat = od->nbr->nat[i]; -+ /* Flows for LOOKUP_NEIGHBOR. */ -+ bool learn_from_arp_request = smap_get_bool(&od->nbr->options, -+ "always_learn_from_arp_request", true); -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -+ "arp.op == 2", ds_cstr(actions)); - -- ovs_be32 ip, mask; -- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -- bool is_v6 = false; -- bool stateless = lrouter_nat_is_stateless(nat); -- struct nbrec_address_set *allowed_ext_ips = -- nat->allowed_ext_ips; -- struct nbrec_address_set *exempted_ext_ips = -- nat->exempted_ext_ips; -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_nd(inport, nd.target, nd.tll); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", -+ ds_cstr(actions)); - -- if (allowed_ext_ips && exempted_ext_ips) { -- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -- "both allowed and exempt external ips set", -- UUID_ARGS(&(nat->header_.uuid))); -- continue; -- } -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_nd(inport, ip6.src, nd.sll); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ " = lookup_nd_ip(inport, ip6.src); "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", -+ ds_cstr(actions)); - -- char *error = ip_parse_masked(nat->external_ip, &ip, &mask); -- if (error || mask != OVS_BE32_MAX) { -- free(error); -- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad external ip %s for nat", -- nat->external_ip); -- free(error); -- continue; -- } -- /* It was an invalid IPv4 address, but valid IPv6. -- * Treat the rest of the handling of this NAT rule -- * as IPv6. */ -- is_v6 = true; -- } -+ /* For other packet types, we can skip neighbor learning. -+ * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", -+ REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); - -- /* Check the validity of nat->logical_ip. 'logical_ip' can -- * be a subnet when the type is "snat". */ -- int cidr_bits; -- if (is_v6) { -- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -- cidr_bits = ipv6_count_cidr_bits(&mask_v6); -- } else { -- error = ip_parse_masked(nat->logical_ip, &ip, &mask); -- cidr_bits = ip_count_cidr_bits(mask); -- } -- if (!strcmp(nat->type, "snat")) { -- if (error) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -- "in router "UUID_FMT"", -- nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -- } -- } else { -- if (error || (!is_v6 && mask != OVS_BE32_MAX) -- || (is_v6 && memcmp(&mask_v6, &v6_exact, -- sizeof mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -- } -- } -+ /* Flows for LEARN_NEIGHBOR. */ -+ /* Skip Neighbor learning if not required. */ -+ ds_clear(match); -+ ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s", -+ learn_from_arp_request ? "" : -+ " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, -+ ds_cstr(match), "next;"); - -- /* For distributed router NAT, determine whether this NAT rule -- * satisfies the conditions for distributed NAT processing. */ -- bool distributed = false; -- struct eth_addr mac; -- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -- nat->logical_port && nat->external_mac) { -- if (eth_addr_from_string(nat->external_mac, &mac)) { -- distributed = true; -- } else { -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -- continue; -- } -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "arp", "put_arp(inport, arp.spa, arp.sha); next;"); - -- /* Ingress UNSNAT table: It is for already established connections' -- * reverse traffic. i.e., SNAT has already been done in egress -- * pipeline and now the packet has entered the ingress pipeline as -- * part of a reply. We undo the SNAT here. -- * -- * Undoing SNAT has to happen before DNAT processing. This is -- * because when the packet was DNATed in ingress pipeline, it did -- * not know about the possibility of eventual additional SNAT in -- * egress pipeline. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(&actions, "ct_snat;"); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 90, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); -+ } - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -+} - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(&actions, "ct_snat;"); -- } -+/* Logical router ingress Table 1: Neighbor lookup lflows -+ * for logical router ports. */ -+static void -+build_neigh_learning_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (op->nbrp) { - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -+ bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options, -+ "always_learn_from_arp_request", true); -+ -+ /* Check if we need to learn mac-binding from ARP requests. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ if (!learn_from_arp_request) { -+ /* ARP request to this address should always get learned, -+ * so add a priority-110 flow to set -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */ -+ ds_clear(match); -+ ds_put_format(match, -+ "inport == %s && arp.spa == %s/%u && " -+ "arp.tpa == %s && arp.op == 1", -+ op->json_key, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen, -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ if (op->od->l3dgw_port && op == op->od->l3dgw_port -+ && op->od->l3redirect_port) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } -+ const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); " -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;" -+ " next;"; -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, -+ ds_cstr(match), actions_s, -+ &op->nbrp->header_); -+ } -+ ds_clear(match); -+ ds_put_format(match, -+ "inport == %s && arp.spa == %s/%u && arp.op == 1", -+ op->json_key, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); -+ if (op->od->l3dgw_port && op == op->od->l3dgw_port -+ && op->od->l3redirect_port) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ " = lookup_arp_ip(inport, arp.spa); "); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+ } -+} - -- /* Ingress DNAT table: Packets enter the pipeline with destination -- * IP address that needs to be DNATted from a external IP address -- * to a logical IP address. */ -- if (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- /* Packet when it goes from the initiator to destination. -- * We need to set flags.loopback because the router can -- * send the packet back through the same interface. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- ds_clear(&actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, true, mask); -- } -+/* 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) -+{ -+ if (!op->nbrp || op->nbrp->peer || !op->peer) { -+ return; -+ } - -- if (dnat_force_snat_ip) { -- /* Indicate to the future tables that a DNAT has taken -- * place and a force SNAT needs to be done in the -- * Egress SNAT table. */ -- ds_put_format(&actions, -- "flags.force_snat_for_dnat = 1; "); -- } -+ if (!op->lrp_networks.n_ipv6_addrs) { -+ return; -+ } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "flags.loopback = 1; " -- "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(&actions, "flags.loopback = 1; " -- "ct_dnat(%s", nat->logical_ip); -+ struct smap options; -+ smap_clone(&options, &op->sb->options); - -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ /* 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; -+ } -+ smap_add(&options, "ipv6_prefix_delegation", -+ prefix_delegation ? "true" : "false"); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+ bool ipv6_prefix = smap_get_bool(&op->nbrp->options, -+ "prefix", false); -+ if (!lrport_is_enabled(op->nbrp)) { -+ ipv6_prefix = false; -+ } -+ smap_add(&options, "ipv6_prefix", -+ ipv6_prefix ? "true" : "false"); -+ sbrec_port_binding_set_options(op->sb, &options); - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, true, mask); -- } -+ smap_destroy(&options); - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(&actions, "ct_dnat(%s", nat->logical_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ const char *address_mode = smap_get( -+ &op->nbrp->ipv6_ra_configs, "address_mode"); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -- } -+ if (!address_mode) { -+ return; -+ } -+ 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); -+ return; -+ } - -- /* ARP resolve for NAT IPs. */ -- if (od->l3dgw_port) { -- if (!strcmp(nat->type, "snat")) { -- ds_clear(&match); -- ds_put_format( -- &match, "inport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -- 120, ds_cstr(&match), "next;", -- &nat->header_); -- } -+ if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", -+ false)) { -+ copy_ra_to_sb(op, address_mode); -+ } - -- if (!sset_contains(&nat_entries, nat->external_ip)) { -- ds_clear(&match); -- ds_put_format( -- &match, "outport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -- nat->external_ip); -- ds_clear(&actions); -- ds_put_format( -- &actions, "eth.dst = %s; next;", -- distributed ? nat->external_mac : -- od->l3dgw_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, od, -- S_ROUTER_IN_ARP_RESOLVE, -- 100, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- sset_add(&nat_entries, nat->external_ip); -- } -- } else { -- /* Add the NAT external_ip to the nat_entries even for -- * gateway routers. This is required for adding load balancer -- * flows.*/ -- sset_add(&nat_entries, nat->external_ip); -- } -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -+ op->json_key); -+ ds_clear(actions); - -- /* Egress UNDNAT table: It is for already established connections' -- * reverse traffic. i.e., DNAT has already been done in ingress -- * pipeline and now the packet has entered the egress pipeline as -- * part of a reply. We undo the DNAT here. -- * -- * Note that this only applies for NAT on a distributed router. -- * Undo DNAT on a gateway router is done in the ingress DNAT -- * pipeline stage. */ -- if (od->l3dgw_port && (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat"))) { -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -- if (distributed) { -- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -- } -+ const char *mtu_s = smap_get( -+ &op->nbrp->ipv6_ra_configs, "mtu"); - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_dnat;"); -- } -+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -+ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -+ "addr_mode = \"%s\", slla = %s", -+ address_mode, op->lrp_networks.ea_s); -+ if (mtu > 0) { -+ ds_put_format(actions, ", mtu = %u", mtu); -+ } - -- /* Egress SNAT table: Packets enter the egress pipeline with -- * source ip address that needs to be SNATted to a external ip -- * address. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip); -- ds_clear(&actions); -+ const char *prf = smap_get_def( -+ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -+ if (strcmp(prf, "MEDIUM")) { -+ ds_put_format(actions, ", router_preference = \"%s\"", prf); -+ } - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, false, mask); -- } -+ bool add_rs_response_flow = false; - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_snat(%s", -- nat->external_ip); -+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -+ continue; -+ } - -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ ds_put_format(actions, ", prefix = %s/%u", -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen); - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- cidr_bits + 1, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } else { -- uint16_t priority = cidr_bits + 1; -+ add_rs_response_flow = true; -+ } - -- /* Distributed router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- priority += 128; -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -+ if (add_rs_response_flow) { -+ ds_put_cstr(actions, "); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -+ 50, ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ ds_clear(actions); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, false, mask); -- } -+ char ip6_str[INET6_ADDRSTRLEN + 1]; -+ struct in6_addr lla; -+ in6_generate_lla(op->lrp_networks.ea, &lla); -+ memset(ip6_str, 0, sizeof(ip6_str)); -+ ipv6_string_mapped(ip6_str, &lla); -+ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -+ "ip6.dst = ip6.src; ip6.src = %s; " -+ "outport = inport; flags.loopback = 1; " -+ "output;", -+ op->lrp_networks.ea_s, ip6_str); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_ND_RA_RESPONSE, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+} - -- if (distributed) { -- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -- } -+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -+ * responder, by default goto next. (priority 0). */ -+static void -+build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -+ } -+} - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_snat(%s", -- nat->external_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+/* Logical router ingress table IP_ROUTING : IP Routing. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * routed to the address in 'ip[46].dst'. -+ * -+ * For regular routes without ECMP, table IP_ROUTING sets outport to the -+ * correct output port, eth.src to the output port's MAC address, and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table. -+ * -+ * For ECMP routes, i.e. multiple routes with same policy and prefix, table -+ * IP_ROUTING remembers ECMP group id and selects a member id, and advances -+ * to table IP_ROUTING_ECMP, which sets outport, eth.src and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -+ */ -+static void -+build_ip_routing_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows) -+{ -+ if (op->nbrp) { - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- priority, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- } -- } -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } - -- /* Logical router ingress table 0: -- * For NAT on a distributed router, add rules allowing -- * ingress traffic with eth.dst matching nat->external_mac -- * on the l3dgw_port instance where nat->logical_port is -- * resident. */ -- if (distributed) { -- /* Store the ethernet address of the port receiving the packet. -- * This will save us from having to match on inport further -- * down in the pipeline. -- */ -- ds_clear(&actions); -- ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;", -- od->l3dgw_port->lrp_networks.ea_s); -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } -+ } -+} - -- ds_clear(&match); -- ds_put_format(&match, -- "eth.dst == "ETH_ADDR_FMT" && inport == %s" -- " && is_chassis_resident(\"%s\")", -- ETH_ADDR_ARGS(mac), -- od->l3dgw_port->json_key, -- nat->logical_port); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -+static void -+build_static_route_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - -- /* Ingress Gateway Redirect Table: For NAT on a distributed -- * router, add flows that are specific to a NAT rule. These -- * flows indicate the presence of an applicable NAT rule that -- * can be applied in a distributed manner. -- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -- * NAT external IP and NAT external mac so the ARP request -- * generated in the following stage is sent out with proper IP/MAC -- * src addresses. -- */ -- if (distributed) { -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, -- "ip%s.src == %s && outport == %s && " -- "is_chassis_resident(\"%s\")", -- is_v6 ? "6" : "4", nat->logical_ip, -- od->l3dgw_port->json_key, nat->logical_port); -- ds_put_format(&actions, "eth.src = %s; %s = %s; next;", -- nat->external_mac, -- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -- nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -- 100, ds_cstr(&match), -- ds_cstr(&actions), &nat->header_); -+ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -+ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -+ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -+ struct ecmp_groups_node *group; -+ for (int i = 0; i < od->nbr->n_static_routes; i++) { -+ struct parsed_route *route = -+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); -+ if (!route) { -+ continue; - } -- -- /* Egress Loopback table: For NAT on a distributed router. -- * If packets in the egress pipeline on the distributed -- * gateway port have ip.dst matching a NAT external IP, then -- * loop a clone of the packet back to the beginning of the -- * ingress pipeline with inport = outport. */ -- if (od->l3dgw_port) { -- /* Distributed router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip%s.dst == %s && outport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } else { -- ds_put_format(&match, " && is_chassis_resident(\"%s\")", -- nat->logical_port); -- } -- -- ds_clear(&actions); -- ds_put_format(&actions, -- "clone { ct_clear; " -- "inport = outport; outport = \"\"; " -- "flags = 0; flags.loopback = 1; "); -- for (int j = 0; j < MFF_N_LOG_REGS; j++) { -- ds_put_format(&actions, "reg%d = 0; ", j); -+ group = ecmp_groups_find(&ecmp_groups, route); -+ if (group) { -+ ecmp_groups_add_route(group, route); -+ } else { -+ const struct parsed_route *existed_route = -+ unique_routes_remove(&unique_routes, route); -+ if (existed_route) { -+ group = ecmp_groups_add(&ecmp_groups, existed_route); -+ if (group) { -+ ecmp_groups_add_route(group, route); -+ } -+ } else { -+ unique_routes_add(&unique_routes, route); - } -- ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; " -- "next(pipeline=ingress, table=%d); };", -- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); - } - } -- -- /* Handle force SNAT options set in the gateway router. */ -- if (!od->l3dgw_port) { -- if (dnat_force_snat_ip) { -- if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -- "dnat"); -- } -- if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -- "dnat"); -- } -- } -- if (lb_force_snat_ip) { -- if (od->lb_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -- } -- if (od->lb_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); -- } -- } -- -- /* For gateway router, re-circulate every packet through -- * the DNAT zone. This helps with the following. -- * -- * Any packet that needs to be unDNATed in the reverse -- * direction gets unDNATed. Ideally this could be done in -- * the egress pipeline. But since the gateway router -- * does not have any feature that depends on the source -- * ip address being external IP address for IP routing, -- * we can do it here, saving a future re-circulation. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -- "ip", "flags.loopback = 1; ct_dnat;"); -+ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -+ /* add a flow in IP_ROUTING, and one flow for each member in -+ * IP_ROUTING_ECMP. */ -+ build_ecmp_route_flow(lflows, od, ports, group); - } -- -- /* Load balancing and packet defrag are only valid on -- * Gateway routers or router with gateway port. */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- sset_destroy(&nat_entries); -- continue; -+ const struct unique_routes_node *ur; -+ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -+ build_static_route_flow(lflows, od, ports, ur->route); - } -+ ecmp_groups_destroy(&ecmp_groups); -+ unique_routes_destroy(&unique_routes); -+ parsed_routes_destroy(&parsed_routes); -+ } -+} - -- /* A set to hold all ips that need defragmentation and tracking. */ -- struct sset all_ips = SSET_INITIALIZER(&all_ips); -+/* IP Multicast lookup. Here we set the output port, adjust TTL and -+ * advance to next table (priority 500). -+ */ -+static void -+build_mcast_lookup_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- for (int i = 0; i < od->nbr->n_load_balancer; i++) { -- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -- struct ovn_northd_lb *lb = -- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -- ovs_assert(lb); -+ /* Drop IPv6 multicast traffic that shouldn't be forwarded, -+ * i.e., router solicitation and router advertisement. -+ */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -+ "nd_rs || nd_ra", "drop;"); -+ if (!od->mcast_info.rtr.relay) { -+ return; -+ } - -- for (size_t j = 0; j < lb->n_vips; j++) { -- struct ovn_lb_vip *lb_vip = &lb->vips[j]; -- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -- ds_clear(&actions); -- build_lb_vip_actions(lb_vip, lb_vip_nb, &actions, -- lb->selection_fields, false); -+ struct ovn_igmp_group *igmp_group; - -- if (!sset_contains(&all_ips, lb_vip->vip_str)) { -- sset_add(&all_ips, lb_vip->vip_str); -- /* If there are any load balancing rules, we should send -- * the packet to conntrack for defragmentation and -- * tracking. This helps with two things. -- * -- * 1. With tracking, we can send only new connections to -- * pick a DNAT ip address from a group. -- * 2. If there are L4 ports in load balancing rules, we -- * need the defragmentation to match on L4 ports. */ -- ds_clear(&match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(&match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(&match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -- } -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -- 100, ds_cstr(&match), "ct_next;", -- &nb_lb->header_); -- } -+ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { -+ ds_clear(match); -+ ds_clear(actions); -+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -+ ds_put_format(match, "ip4 && ip4.dst == %s ", -+ igmp_group->mcgroup.name); -+ } else { -+ ds_put_format(match, "ip6 && ip6.dst == %s ", -+ igmp_group->mcgroup.name); -+ } -+ if (od->mcast_info.rtr.flood_static) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } -+ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -+ igmp_group->mcgroup.name); -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -+ ds_cstr(match), ds_cstr(actions)); -+ } - -- /* Higher priority rules are added for load-balancing in DNAT -- * table. For every match (on a VIP[:port]), we add two flows -- * via add_router_lb_flow(). One flow is for specific matching -- * on ct.new with an action of "ct_lb($targets);". The other -- * flow is for ct.est with an action of "ct_dnat;". */ -- ds_clear(&match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(&match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(&match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -- } -+ /* If needed, flood unregistered multicast on statically configured -+ * ports. Otherwise drop any multicast traffic. -+ */ -+ if (od->mcast_info.rtr.flood_static) { -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } else { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", "drop;"); -+ } -+ } -+} - -- int prio = 110; -- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -- bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -- "sctp"); -- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; -+/* Logical router ingress table POLICY: Policy. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * permitted/denied/rerouted to the address in the rule's nexthop. -+ * This table sets outport to the correct out_port, -+ * eth.src to the output port's MAC address, -+ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table for ARP/ND resolution. */ -+static void -+build_ingress_policy_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports) -+{ -+ if (od->nbr) { -+ /* This is a catch-all rule. It has the lowest priority (0) -+ * does a match-all("1") and pass-through (next) */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", -+ REG_ECMP_GROUP_ID" = 0; next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - -- if (lb_vip->vip_port) { -- ds_put_format(&match, " && %s && %s.dst == %d", proto, -- proto, lb_vip->vip_port); -- prio = 120; -- } -+ /* Convert routing policies to flows. */ -+ uint16_t ecmp_group_id = 1; -+ for (int i = 0; i < od->nbr->n_policies; i++) { -+ const struct nbrec_logical_router_policy *rule -+ = od->nbr->policies[i]; -+ bool is_ecmp_reroute = -+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); - -- if (od->l3redirect_port && -- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- add_router_lb_flow(lflows, od, &match, &actions, prio, -- lb_force_snat_ip, lb_vip, proto, -- nb_lb, meter_groups, &nat_entries); -+ if (is_ecmp_reroute) { -+ build_ecmp_routing_policy_flows(lflows, od, ports, rule, -+ ecmp_group_id); -+ ecmp_group_id++; -+ } else { -+ build_routing_policy_flow(lflows, od, ports, rule, -+ &rule->header_); - } - } -- sset_destroy(&all_ips); -- sset_destroy(&nat_entries); - } -- -- ds_destroy(&match); -- ds_destroy(&actions); - } - --/* Logical router ingress Table 0: L2 Admission Control -- * Generic admission control flows (without inport check). -- */ -+/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ - static void --build_adm_ctrl_flows_for_lrouter( -+build_arp_resolve_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;"); -+ /* Multicast packets already have the outport set so just advance to -+ * next table (priority 500). */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, -+ "ip4.mcast || ip6.mcast", "next;"); -+ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", -+ "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); -+ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", -+ "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); - } - } - --/* Logical router ingress Table 0: L2 Admission Control -- * This table drops packets that the router shouldn’t see at all based -- * on their Ethernet headers. -- */ --static void --build_adm_ctrl_flows_for_lrouter_port( -+/* Local router ingress table ARP_RESOLVE: ARP Resolution. -+ * -+ * Any unicast packet that reaches this table is an IP packet whose -+ * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 -+ * (ip4.dst/ipv6.dst is the final destination). -+ * This table resolves the IP address in -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and -+ * an Ethernet address in eth.dst. -+ */ -+static void -+build_arp_resolve_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, -+ struct hmap *ports, - struct ds *match, struct ds *actions) - { -- if (op->nbrp) { -- if (!lrport_is_enabled(op->nbrp)) { -- /* Drop packets from disabled logical ports (since logical flow -- * tables are default-drop). */ -- return; -- } -+ if (op->nbsp && !lsp_is_enabled(op->nbsp)) { -+ return; -+ } - -- if (op->derived) { -- /* No ingress packets should be received on a chassisredirect -- * port. */ -- return; -- } -+ if (op->nbrp) { -+ /* This is a logical router port. If next-hop IP address in -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this -+ * router port, then the packet is intended to eventually be sent -+ * to this logical port. Set the destination mac address using -+ * this port's mac address. -+ * -+ * The packet is still in peer's logical pipeline. So the match -+ * should be on peer's outport. */ -+ if (op->peer && op->nbrp->peer) { -+ if (op->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 "== ", -+ op->peer->json_key); -+ op_put_v4_networks(match, op, false); - -- /* Store the ethernet address of the port receiving the packet. -- * This will save us from having to match on inport further down in -- * the pipeline. -- */ -- ds_clear(actions); -- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -- op->lrp_networks.ea_s); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, op->peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } - -- ds_clear(match); -- ds_put_format(match, "eth.mcast && inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -+ if (op->lrp_networks.n_ipv6_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == ", -+ op->peer->json_key); -+ op_put_v6_networks(match, op); - -- ds_clear(match); -- ds_put_format(match, "eth.dst == %s && inport == %s", -- op->lrp_networks.ea_s, op->json_key); -- 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 gateway chassis. */ -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, op->peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } - } -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } --} - -+ if (!op->derived && op->od->l3redirect_port) { -+ const char *redirect_type = smap_get(&op->nbrp->options, -+ "redirect-type"); -+ if (redirect_type && !strcasecmp(redirect_type, "bridged")) { -+ /* Packet is on a non gateway chassis and -+ * has an unresolved ARP on a network behind gateway -+ * chassis attached router port. Since, redirect type -+ * is "bridged", instead of calling "get_arp" -+ * on this node, we will redirect the packet to gateway -+ * chassis, by setting destination mac router port mac.*/ -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ "!is_chassis_resident(%s)", op->json_key, -+ op->od->l3redirect_port->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); - --/* Logical router ingress Table 1 and 2: Neighbor lookup and learning -- * lflows for logical routers. */ --static void --build_neigh_learning_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- if (od->nbr) { -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_ARP_RESOLVE, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+ } - -- /* Learn MAC bindings from ARP/IPv6 ND. -- * -- * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the -- * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp' -- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit. -- * If "always_learn_from_arp_request" is set to false, it will also -- * lookup for the (arp.spa) in the mac binding table using the -- * "lookup_arp_ip" action for ARP request packets, and stores the -- * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit -- * to "1" directly for ARP response packets. -- * -- * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup -- * for the (nd.target, nd.tll) in the mac binding table using the -- * 'lookup_nd' action and stores the result in -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -- * "always_learn_from_arp_request" is set to false, -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set. -- * -- * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup -- * for the (ip6.src, nd.sll) in the mac binding table using the -- * 'lookup_nd' action and stores the result in -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -- * "always_learn_from_arp_request" is set to false, it will also lookup -- * for the (ip6.src) in the mac binding table using the "lookup_nd_ip" -- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- * bit. -- * -- * Table LEARN_NEIGHBOR learns the mac-binding using the action -- * - 'put_arp/put_nd'. Learning mac-binding is skipped if -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set. -+ /* 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. - * -- * */ -- -- /* Flows for LOOKUP_NEIGHBOR. */ -- bool learn_from_arp_request = smap_get_bool(&od->nbr->options, -- "always_learn_from_arp_request", true); -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -- "arp.op == 2", ds_cstr(actions)); -+ * Priority 1. -+ */ -+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, -+ lflows); -+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) -+ && strcmp(op->nbsp->type, "virtual")) { -+ /* This is a logical switch port that backs a VM or a container. -+ * Extract its addresses. For each of the address, go through all -+ * the router ports attached to the switch (to which this port -+ * connects) and if the address in question is reachable from the -+ * router port, add an ARP/ND entry in that router's pipeline. */ - -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_nd(inport, nd.target, nd.tll); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", -- ds_cstr(actions)); -+ for (size_t i = 0; i < op->n_lsp_addrs; i++) { -+ const char *ea_s = op->lsp_addrs[i].ea_s; -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; -+ for (size_t k = 0; k < op->od->n_router_ports; k++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_nd(inport, ip6.src, nd.sll); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- " = lookup_nd_ip(inport, ip6.src); "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", -- ds_cstr(actions)); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- /* For other packet types, we can skip neighbor learning. -- * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", -- REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; -+ } - -- /* Flows for LEARN_NEIGHBOR. */ -- /* Skip Neighbor learning if not required. */ -- ds_clear(match); -- ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s", -- learn_from_arp_request ? "" : -- " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, -- ds_cstr(match), "next;"); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, ip_s); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "arp", "put_arp(inport, arp.spa, arp.sha); next;"); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ } -+ } - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -+ const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; -+ for (size_t k = 0; k < op->od->n_router_ports; k++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); -- } -- --} -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - --/* Logical router ingress Table 1: Neighbor lookup lflows -- * for logical router ports. */ --static void --build_neigh_learning_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- if (op->nbrp) { -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; -+ } - -- bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options, -- "always_learn_from_arp_request", true); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == %s", -+ peer->json_key, ip_s); - -- /* Check if we need to learn mac-binding from ARP requests. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- if (!learn_from_arp_request) { -- /* ARP request to this address should always get learned, -- * so add a priority-110 flow to set -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */ -- ds_clear(match); -- ds_put_format(match, -- "inport == %s && arp.spa == %s/%u && " -- "arp.tpa == %s && arp.op == 1", -- op->json_key, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen, -- op->lrp_networks.ipv4_addrs[i].addr_s); -- if (op->od->l3dgw_port && op == op->od->l3dgw_port -- && op->od->l3redirect_port) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); - } -- const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); " -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;" -- " next;"; -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, -- ds_cstr(match), actions_s, -- &op->nbrp->header_); -- } -- ds_clear(match); -- ds_put_format(match, -- "inport == %s && arp.spa == %s/%u && arp.op == 1", -- op->json_key, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); -- if (op->od->l3dgw_port && op == op->od->l3dgw_port -- && op->od->l3redirect_port) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); - } -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- " = lookup_arp_ip(inport, arp.spa); "); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); - } -- } --} -- --/* 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) --{ -- if (!op->nbrp || op->nbrp->peer || !op->peer) { -- return; -- } -+ } 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. -+ * If the logical port doesn't have virtual parent set in -+ * Port_Binding table, then add the flow to set eth.dst to -+ * 00:00:00:00:00:00 and advance to next table so that ARP is -+ * resolved by router pipeline using the arp{} action. -+ * The MAC_Binding entry for the virtual ip might be invalid. */ -+ ovs_be32 ip; - -- if (!op->lrp_networks.n_ipv6_addrs) { -- return; -- } -+ const char *vip = smap_get(&op->nbsp->options, -+ "virtual-ip"); -+ const char *virtual_parents = smap_get(&op->nbsp->options, -+ "virtual-parents"); -+ if (!vip || !virtual_parents || -+ !ip_parse(vip, &ip) || !op->sb) { -+ return; -+ } - -- struct smap options; -- smap_clone(&options, &op->sb->options); -+ if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || -+ !op->sb->chassis) { -+ /* The virtual port is not claimed yet. */ -+ for (size_t i = 0; i < op->od->n_router_ports; i++) { -+ const char *peer_name = smap_get( -+ &op->od->router_ports[i]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- /* 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; -- } -- smap_add(&options, "ipv6_prefix_delegation", -- prefix_delegation ? "true" : "false"); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- bool ipv6_prefix = smap_get_bool(&op->nbrp->options, -- "prefix", false); -- if (!lrport_is_enabled(op->nbrp)) { -- ipv6_prefix = false; -- } -- smap_add(&options, "ipv6_prefix", -- ipv6_prefix ? "true" : "false"); -- sbrec_port_binding_set_options(op->sb, &options); -+ if (find_lrp_member_ip(peer, vip)) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, vip); - -- smap_destroy(&options); -+ const char *arp_actions = -+ "eth.dst = 00:00:00:00:00:00; next;"; -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ arp_actions, -+ &op->nbsp->header_); -+ break; -+ } -+ } -+ } else { -+ struct ovn_port *vp = -+ ovn_port_find(ports, op->sb->virtual_parent); -+ if (!vp || !vp->nbsp) { -+ return; -+ } - -- const char *address_mode = smap_get( -- &op->nbrp->ipv6_ra_configs, "address_mode"); -+ for (size_t i = 0; i < vp->n_lsp_addrs; i++) { -+ bool found_vip_network = false; -+ const char *ea_s = vp->lsp_addrs[i].ea_s; -+ for (size_t j = 0; j < vp->od->n_router_ports; j++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &vp->od->router_ports[j]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- if (!address_mode) { -- return; -- } -- 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); -- return; -- } -+ struct ovn_port *peer = -+ ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", -- false)) { -- copy_ra_to_sb(op, address_mode); -- } -+ if (!find_lrp_member_ip(peer, vip)) { -+ continue; -+ } - -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -- op->json_key); -- ds_clear(actions); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, vip); - -- const char *mtu_s = smap_get( -- &op->nbrp->ipv6_ra_configs, "mtu"); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ found_vip_network = true; -+ break; -+ } - -- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; -+ if (found_vip_network) { -+ break; -+ } -+ } -+ } -+ } else if (lsp_is_router(op->nbsp)) { -+ /* This is a logical switch port that connects to a router. */ - -- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -- "addr_mode = \"%s\", slla = %s", -- address_mode, op->lrp_networks.ea_s); -- if (mtu > 0) { -- ds_put_format(actions, ", mtu = %u", mtu); -- } -+ /* The peer of this switch port is the router port for which -+ * we need to add logical flows such that it can resolve -+ * ARP entries for all the other router ports connected to -+ * the switch in question. */ - -- const char *prf = smap_get_def( -- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -- if (strcmp(prf, "MEDIUM")) { -- ds_put_format(actions, ", router_preference = \"%s\"", prf); -- } -+ const char *peer_name = smap_get(&op->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ return; -+ } - -- bool add_rs_response_flow = false; -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ return; -+ } - -- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -- continue; -+ if (peer->od->nbr && -+ smap_get_bool(&peer->od->nbr->options, -+ "dynamic_neigh_routers", false)) { -+ return; - } - -- ds_put_format(actions, ", prefix = %s/%u", -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen); -+ for (size_t i = 0; i < op->od->n_router_ports; i++) { -+ const char *router_port_name = smap_get( -+ &op->od->router_ports[i]->nbsp->options, -+ "router-port"); -+ struct ovn_port *router_port = ovn_port_find(ports, -+ router_port_name); -+ if (!router_port || !router_port->nbrp) { -+ continue; -+ } - -- add_rs_response_flow = true; -- } -+ /* Skip the router port under consideration. */ -+ if (router_port == peer) { -+ continue; -+ } - -- if (add_rs_response_flow) { -- ds_put_cstr(actions, "); next;"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -- 50, ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- ds_clear(actions); -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); -+ if (router_port->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == ", -+ peer->json_key); -+ op_put_v4_networks(match, router_port, false); - -- char ip6_str[INET6_ADDRSTRLEN + 1]; -- struct in6_addr lla; -- in6_generate_lla(op->lrp_networks.ea, &lla); -- memset(ip6_str, 0, sizeof(ip6_str)); -- ipv6_string_mapped(ip6_str, &lla); -- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -- "ip6.dst = ip6.src; ip6.src = %s; " -- "outport = inport; flags.loopback = 1; " -- "output;", -- op->lrp_networks.ea_s, ip6_str); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ND_RA_RESPONSE, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } --} -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ router_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); -+ } - --/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -- * responder, by default goto next. (priority 0). */ --static void --build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) --{ -- if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -+ if (router_port->lrp_networks.n_ipv6_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == ", -+ peer->json_key); -+ op_put_v6_networks(match, router_port); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ router_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); -+ } -+ } - } -+ - } - --/* Logical router ingress table IP_ROUTING : IP Routing. -+/* Local router ingress table CHK_PKT_LEN: Check packet length. - * -- * A packet that arrives at this table is an IP packet that should be -- * routed to the address in 'ip[46].dst'. -+ * Any IPv4 packet with outport set to the distributed gateway -+ * router port, check the packet length and store the result in the -+ * 'REGBIT_PKT_LARGER' register bit. - * -- * For regular routes without ECMP, table IP_ROUTING sets outport to the -- * correct output port, eth.src to the output port's MAC address, and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table. -+ * Local router ingress table LARGER_PKTS: Handle larger packets. - * -- * For ECMP routes, i.e. multiple routes with same policy and prefix, table -- * IP_ROUTING remembers ECMP group id and selects a member id, and advances -- * to table IP_ROUTING_ECMP, which sets outport, eth.src and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -- */ -+ * Any IPv4 packet with outport set to the distributed gateway -+ * router port and the 'REGBIT_PKT_LARGER' register bit is set, -+ * generate ICMPv4 packet with type 3 (Destination Unreachable) and -+ * code 4 (Fragmentation needed). -+ * */ - static void --build_ip_routing_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows) -+build_check_pkt_len_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports, -+ struct ds *match, struct ds *actions) - { -- if (op->nbrp) { -+ if (od->nbr) { - -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -- &op->nbrp->header_); -- } -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", -+ "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", -+ "next;"); - -- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -- &op->nbrp->header_); -+ if (od->l3dgw_port && od->l3redirect_port) { -+ int gw_mtu = 0; -+ if (od->l3dgw_port->nbrp) { -+ gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options, -+ "gateway_mtu", 0); -+ } -+ /* Add the flows only if gateway_mtu is configured. */ -+ if (gw_mtu <= 0) { -+ return; -+ } -+ -+ ds_clear(match); -+ ds_put_format(match, "outport == %s", od->l3dgw_port->json_key); -+ -+ ds_clear(actions); -+ ds_put_format(actions, -+ REGBIT_PKT_LARGER" = check_pkt_larger(%d);" -+ " next;", gw_mtu + VLAN_ETH_HEADER_LEN); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &od->l3dgw_port->nbrp->header_); -+ -+ for (size_t i = 0; i < od->nbr->n_ports; i++) { -+ struct ovn_port *rp = ovn_port_find(ports, -+ od->nbr->ports[i]->name); -+ if (!rp || rp == od->l3dgw_port) { -+ continue; -+ } -+ -+ if (rp->lrp_networks.ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && outport == %s" -+ " && ip4 && "REGBIT_PKT_LARGER, -+ rp->json_key, od->l3dgw_port->json_key); -+ -+ ds_clear(actions); -+ /* Set icmp4.frag_mtu to gw_mtu */ -+ ds_put_format(actions, -+ "icmp4_error {" -+ REGBIT_EGRESS_LOOPBACK" = 1; " -+ "eth.dst = %s; " -+ "ip4.dst = ip4.src; " -+ "ip4.src = %s; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; /* Destination Unreachable. */ " -+ "icmp4.code = 4; /* Frag Needed and DF was Set. */ " -+ "icmp4.frag_mtu = %d; " -+ "next(pipeline=ingress, table=%d); };", -+ rp->lrp_networks.ea_s, -+ rp->lrp_networks.ipv4_addrs[0].addr_s, -+ 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), -+ &rp->nbrp->header_); -+ } -+ -+ if (rp->lrp_networks.ipv6_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && outport == %s" -+ " && ip6 && "REGBIT_PKT_LARGER, -+ rp->json_key, od->l3dgw_port->json_key); -+ -+ ds_clear(actions); -+ /* Set icmp6.frag_mtu to gw_mtu */ -+ ds_put_format(actions, -+ "icmp6_error {" -+ REGBIT_EGRESS_LOOPBACK" = 1; " -+ "eth.dst = %s; " -+ "ip6.dst = ip6.src; " -+ "ip6.src = %s; " -+ "ip.ttl = 255; " -+ "icmp6.type = 2; /* Packet Too Big. */ " -+ "icmp6.code = 0; " -+ "icmp6.frag_mtu = %d; " -+ "next(pipeline=ingress, table=%d); };", -+ rp->lrp_networks.ea_s, -+ rp->lrp_networks.ipv6_addrs[0].addr_s, -+ 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), -+ &rp->nbrp->header_); -+ } -+ } - } - } - } - -+/* 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_static_route_flows_for_lrouter( -+build_gateway_redirect_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) -+ struct ds *match, struct ds *actions) - { - if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -- REG_ECMP_GROUP_ID" == 0", "next;"); -+ if (od->l3dgw_port && od->l3redirect_port) { -+ const struct ovsdb_idl_row *stage_hint = NULL; - -- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -- struct ecmp_groups_node *group; -- for (int i = 0; i < od->nbr->n_static_routes; i++) { -- struct parsed_route *route = -- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); -- if (!route) { -- continue; -+ if (od->l3dgw_port->nbrp) { -+ stage_hint = &od->l3dgw_port->nbrp->header_; - } -- group = ecmp_groups_find(&ecmp_groups, route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } else { -- const struct parsed_route *existed_route = -- unique_routes_remove(&unique_routes, route); -- if (existed_route) { -- group = ecmp_groups_add(&ecmp_groups, existed_route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } -- } else { -- unique_routes_add(&unique_routes, route); -- } -- } -- } -- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -- /* add a flow in IP_ROUTING, and one flow for each member in -- * IP_ROUTING_ECMP. */ -- build_ecmp_route_flow(lflows, od, ports, group); -- } -- const struct unique_routes_node *ur; -- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -- build_static_route_flow(lflows, od, ports, ur->route); -+ -+ /* 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); - } -- ecmp_groups_destroy(&ecmp_groups); -- unique_routes_destroy(&unique_routes); -- parsed_routes_destroy(&parsed_routes); -+ -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); - } - } - --/* IP Multicast lookup. Here we set the output port, adjust TTL and -- * advance to next table (priority 500). -- */ -+/* Local router ingress table ARP_REQUEST: ARP request. -+ * -+ * In the common case where the Ethernet destination has been resolved, -+ * this table outputs the packet (priority 0). Otherwise, it composes -+ * and sends an ARP/IPv6 NA request (priority 100). */ - static void --build_mcast_lookup_flows_for_lrouter( -+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; - -- /* Drop IPv6 multicast traffic that shouldn't be forwarded, -- * i.e., router solicitation and router advertisement. -- */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -- "nd_rs || nd_ra", "drop;"); -- if (!od->mcast_info.rtr.relay) { -- return; -- } -- -- struct ovn_igmp_group *igmp_group; -+ route = od->nbr->static_routes[i]; -+ struct in6_addr gw_ip6; -+ unsigned int plen; -+ char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen); -+ if (error || plen != 128) { -+ free(error); -+ continue; -+ } - -- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { - ds_clear(match); -+ ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && " -+ "ip6 && " REG_NEXT_HOP_IPV6 " == %s", -+ route->nexthop); -+ 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); -- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -- ds_put_format(match, "ip4 && ip4.dst == %s ", -- igmp_group->mcgroup.name); -- } else { -- ds_put_format(match, "ip6 && ip6.dst == %s ", -- igmp_group->mcgroup.name); -- } -- if (od->mcast_info.rtr.flood_static) { -- ds_put_cstr(actions, -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); -- } -- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -- igmp_group->mcgroup.name); -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -- ds_cstr(match), ds_cstr(actions)); -- } -+ ds_put_format(actions, -+ "nd_ns { " -+ "eth.dst = "ETH_ADDR_FMT"; " -+ "ip6.dst = %s; " -+ "nd.target = %s; " -+ "output; " -+ "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s, -+ route->nexthop); - -- /* If needed, flood unregistered multicast on statically configured -- * ports. Otherwise drop any multicast traffic. -- */ -- if (od->mcast_info.rtr.flood_static) { -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); -- } else { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", "drop;"); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200, -+ ds_cstr(match), ds_cstr(actions), -+ &route->header_); - } -+ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, -+ "eth.dst == 00:00:00:00:00:00 && ip4", -+ "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 ingress table POLICY: Policy. -+/* Logical router egress table DELIVERY: Delivery (priority 100-110). - * -- * A packet that arrives at this table is an IP packet that should be -- * permitted/denied/rerouted to the address in the rule's nexthop. -- * This table sets outport to the correct out_port, -- * eth.src to the output port's MAC address, -- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table for ARP/ND resolution. */ -+ * Priority 100 rules deliver packets to enabled logical ports. -+ * Priority 110 rules match multicast packets and update the source -+ * mac before delivering to enabled logical ports. IP multicast traffic -+ * bypasses S_ROUTER_IN_IP_ROUTING route lookups. -+ */ - static void --build_ingress_policy_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) -+build_egress_delivery_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ struct ds *match, struct ds *actions) - { -- if (od->nbr) { -- /* This is a catch-all rule. It has the lowest priority (0) -- * does a match-all("1") and pass-through (next) */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", -- REG_ECMP_GROUP_ID" = 0; next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, -- REG_ECMP_GROUP_ID" == 0", "next;"); -+ if (op->nbrp) { -+ if (!lrport_is_enabled(op->nbrp)) { -+ /* Drop packets to disabled logical ports (since logical flow -+ * tables are default-drop). */ -+ return; -+ } - -- /* Convert routing policies to flows. */ -- uint16_t ecmp_group_id = 1; -- for (int i = 0; i < od->nbr->n_policies; i++) { -- const struct nbrec_logical_router_policy *rule -- = od->nbr->policies[i]; -- bool is_ecmp_reroute = -- (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); -+ 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 (is_ecmp_reroute) { -- build_ecmp_routing_policy_flows(lflows, od, ports, rule, -- ecmp_group_id); -- ecmp_group_id++; -- } else { -- build_routing_policy_flow(lflows, od, ports, rule, -- &rule->header_); -- } -+ /* 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;"); - } -+ - } - --/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ - static void --build_arp_resolve_flows_for_lrouter( -+build_misc_local_traffic_drop_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows) - { - if (od->nbr) { -- /* Multicast packets already have the outport set so just advance to -- * next table (priority 500). */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, -- "ip4.mcast || ip6.mcast", "next;"); -+ /* L3 admission control: drop multicast and broadcast source, localhost -+ * source or destination, and zero network source or destination -+ * (priority 100). */ -+ 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;"); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", -- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); -+ /* Drop ARP packets (priority 85). ARP request packets for router's own -+ * IPs are handled with priority-90 flows. -+ * Drop IPv6 ND packets (priority 85). ND NA packets for router's own -+ * IPs are handled with priority-90 flows. -+ */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, -+ "arp || nd", "drop;"); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", -- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); -- } --} -+ /* Allow IPv6 multicast traffic that's supposed to reach the -+ * router pipeline (e.g., router solicitations). -+ */ -+ 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;"); -+ } -+} - --/* Local router ingress table ARP_RESOLVE: ARP Resolution. -- * -- * Any unicast packet that reaches this table is an IP packet whose -- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 -- * (ip4.dst/ipv6.dst is the final destination). -- * This table resolves the IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and -- * an Ethernet address in eth.dst. -- */ - static void --build_arp_resolve_flows_for_lrouter_port( -+build_dhcpv6_reply_flows_for_lrouter_port( - struct ovn_port *op, struct hmap *lflows, -- struct hmap *ports, -- struct ds *match, struct ds *actions) -+ struct ds *match) - { -- if (op->nbsp && !lsp_is_enabled(op->nbsp)) { -- return; -+ if (op->nbrp && (!op->derived)) { -+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ ds_clear(match); -+ 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;"); -+ } - } - -- if (op->nbrp) { -- /* This is a logical router port. If next-hop IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this -- * router port, then the packet is intended to eventually be sent -- * to this logical port. Set the destination mac address using -- * this port's mac address. -- * -- * The packet is still in peer's logical pipeline. So the match -- * should be on peer's outport. */ -- if (op->peer && op->nbrp->peer) { -- if (op->lrp_networks.n_ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 "== ", -- op->peer->json_key); -- op_put_v4_networks(match, op, false); -+} - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } -+static void -+build_ipv6_input_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ 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"); - -- if (op->lrp_networks.n_ipv6_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == ", -- op->peer->json_key); -- op_put_v6_networks(match, op); -+ const char *lrp_actions = -+ "ip6.dst <-> ip6.src; " -+ "ip.ttl = 255; " -+ "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_); -+ } - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -+ /* ND reply. These flows reply to ND solicitations for the -+ * router's own IP address. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ 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); - } - -- if (!op->derived && op->od->l3redirect_port) { -- const char *redirect_type = smap_get(&op->nbrp->options, -- "redirect-type"); -- if (redirect_type && !strcasecmp(redirect_type, "bridged")) { -- /* Packet is on a non gateway chassis and -- * has an unresolved ARP on a network behind gateway -- * chassis attached router port. Since, redirect type -- * is "bridged", instead of calling "get_arp" -- * on this node, we will redirect the packet to gateway -- * chassis, by setting destination mac router port mac.*/ -+ /* UDP/TCP port unreachable */ -+ if (!smap_get(&op->od->nbr->options, "chassis") -+ && !op->od->l3dgw_port) { -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { - ds_clear(match); -- ds_put_format(match, "outport == %s && " -- "!is_chassis_resident(%s)", op->json_key, -- op->od->l3redirect_port->json_key); -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -+ 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_); - -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ARP_RESOLVE, 50, -- ds_cstr(match), ds_cstr(actions), -+ 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_); - } - } - -- /* Drop IP traffic destined to router owned IPs. Part of it is dropped -- * in stage "lr_in_ip_input" but traffic that could have been unSNATed -- * but didn't match any existing session might still end up here. -- * -- * Priority 1. -- */ -- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, -- lflows); -- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) -- && strcmp(op->nbsp->type, "virtual")) { -- /* This is a logical switch port that backs a VM or a container. -- * Extract its addresses. For each of the address, go through all -- * the router ports attached to the switch (to which this port -- * connects) and if the address in question is reachable from the -- * router port, add an ARP/ND entry in that router's pipeline. */ -+ /* 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; -+ } - -- for (size_t i = 0; i < op->n_lsp_addrs; i++) { -- const char *ea_s = op->lsp_addrs[i].ea_s; -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ ds_clear(match); -+ ds_clear(actions); - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ 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_); -+ } -+ } - -- if (!find_lrp_member_ip(peer, ip_s)) { -- continue; -- } -+} - -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, ip_s); -+static void -+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbr) { - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- } -+ /* 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. -+ */ -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &od->nat_entries[i]; -+ -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ continue; - } - -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ /* 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); -+ } - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; - -- if (!find_lrp_member_ip(peer, ip_s)) { -- continue; -- } -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; -+ } - -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == %s", -- peer->json_key, ip_s); -+ 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); -+ } -+ } -+} - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- } -- } -+/* Logical router ingress table 3: IP Input for IPv4. */ -+static void -+build_lrouter_ipv4_ip_input(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ /* No ingress packets are accepted on a chassisredirect -+ * port, so no need to program flows for that port. */ -+ if (op->nbrp && (!op->derived)) { -+ if (op->lrp_networks.n_ipv4_addrs) { -+ /* L3 admission control: drop packets that originate from an -+ * IPv4 address owned by the router or a broadcast address -+ * known to the router (priority 100). */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.src == "); -+ op_put_v4_networks(match, op, true); -+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -+ ds_cstr(match), "drop;", -+ &op->nbrp->header_); -+ -+ /* ICMP echo reply. These flows reply to ICMP echo requests -+ * received for the router's IP address. Since packets only -+ * get here as part of the logical router datapath, the inport -+ * (i.e. the incoming locally attached net) does not matter. -+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.dst == "); -+ op_put_v4_networks(match, op, false); -+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); -+ -+ const char * icmp_actions = "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 0; " -+ "flags.loopback = 1; " -+ "next; "; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -+ ds_cstr(match), icmp_actions, -+ &op->nbrp->header_); - } -- } 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. -- * If the logical port doesn't have virtual parent set in -- * Port_Binding table, then add the flow to set eth.dst to -- * 00:00:00:00:00:00 and advance to next table so that ARP is -- * resolved by router pipeline using the arp{} action. -- * The MAC_Binding entry for the virtual ip might be invalid. */ -- ovs_be32 ip; - -- const char *vip = smap_get(&op->nbsp->options, -- "virtual-ip"); -- const char *virtual_parents = smap_get(&op->nbsp->options, -- "virtual-parents"); -- if (!vip || !virtual_parents || -- !ip_parse(vip, &ip) || !op->sb) { -- return; -+ /* ICMP time exceeded */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_clear(actions); -+ -+ ds_put_format(match, -+ "inport == %s && ip4 && " -+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -+ ds_put_format(actions, -+ "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "icmp4.type = 11; /* Time exceeded */ " -+ "icmp4.code = 0; /* TTL exceeded in transit */ " -+ "ip4.dst = ip4.src; " -+ "ip4.src = %s; " -+ "ip.ttl = 255; " -+ "next; };", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); - } - -- if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || -- !op->sb->chassis) { -- /* The virtual port is not claimed yet. */ -- for (size_t i = 0; i < op->od->n_router_ports; i++) { -- const char *peer_name = smap_get( -- &op->od->router_ports[i]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ /* ARP reply. These flows reply to ARP requests for the router's own -+ * IP address. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, "arp.spa == %s/%u", -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -+ && op->peer->od->n_localnet_ports) { -+ bool add_chassis_resident_check = false; -+ if (op == op->od->l3dgw_port) { -+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -+ * should only be sent from the gateway chassis, so that -+ * upstream MAC learning points to the gateway chassis. -+ * Also need to avoid generation of multiple ARP responses -+ * from different chassis. */ -+ add_chassis_resident_check = true; -+ } else { -+ /* Check if the option 'reside-on-redirect-chassis' -+ * is set to true on the router port. If set to true -+ * and if peer's logical switch has a localnet port, it -+ * means the router pipeline for the packets from -+ * peer's logical switch is be run on the chassis -+ * hosting the gateway port and it should reply to the -+ * ARP requests for the router port IPs. -+ */ -+ add_chassis_resident_check = smap_get_bool( -+ &op->nbrp->options, -+ "reside-on-redirect-chassis", false); - } - -- if (find_lrp_member_ip(peer, vip)) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -- -- const char *arp_actions = -- "eth.dst = 00:00:00:00:00:00; next;"; -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- arp_actions, -- &op->nbsp->header_); -- break; -+ if (add_chassis_resident_check) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } - } -- } else { -- struct ovn_port *vp = -- ovn_port_find(ports, op->sb->virtual_parent); -- if (!vp || !vp->nbsp) { -- return; -- } - -- for (size_t i = 0; i < vp->n_lsp_addrs; i++) { -- bool found_vip_network = false; -- const char *ea_s = vp->lsp_addrs[i].ea_s; -- for (size_t j = 0; j < vp->od->n_router_ports; j++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &vp->od->router_ports[j]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ build_lrouter_arp_flow(op->od, op, -+ op->lrp_networks.ipv4_addrs[i].addr_s, -+ REG_INPORT_ETH_ADDR, match, false, 90, -+ &op->nbrp->header_, lflows); -+ } - -- struct ovn_port *peer = -- ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ /* A set to hold all load-balancer vips that need ARP responses. */ -+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); - -- if (!find_lrp_member_ip(peer, vip)) { -- continue; -- } -- -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -+ const char *ip_address; -+ SSET_FOR_EACH (ip_address, &all_ips_v4) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- found_vip_network = true; -- break; -- } -+ build_lrouter_arp_flow(op->od, op, -+ ip_address, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); -+ } - -- if (found_vip_network) { -- break; -- } -+ SSET_FOR_EACH (ip_address, &all_ips_v6) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } -+ -+ build_lrouter_nd_flow(op->od, op, "nd_na", -+ ip_address, NULL, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); - } -- } 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 -- * we need to add logical flows such that it can resolve -- * ARP entries for all the other router ports connected to -- * the switch in question. */ -+ sset_destroy(&all_ips_v4); -+ sset_destroy(&all_ips_v6); - -- const char *peer_name = smap_get(&op->nbsp->options, -- "router-port"); -- if (!peer_name) { -- return; -- } -+ if (!smap_get(&op->od->nbr->options, "chassis") -+ && !op->od->l3dgw_port) { -+ /* UDP/TCP port unreachable. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && udp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ const char *action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 3; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- return; -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "tcp_reset {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); -+ -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 2; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 70, ds_cstr(match), action, -+ &op->nbrp->header_); -+ } - } - -- if (peer->od->nbr && -- smap_get_bool(&peer->od->nbr->options, -- "dynamic_neigh_routers", false)) { -+ /* 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 / 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) { - return; - } - -- for (size_t i = 0; i < op->od->n_router_ports; i++) { -- const char *router_port_name = smap_get( -- &op->od->router_ports[i]->nbsp->options, -- "router-port"); -- struct ovn_port *router_port = ovn_port_find(ports, -- router_port_name); -- if (!router_port || !router_port->nbrp) { -+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &op->od->nat_entries[i]; -+ -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { - continue; - } - -- /* Skip the router port under consideration. */ -- if (router_port == peer) { -- continue; -+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately -+ * below. -+ */ -+ if (!strcmp(nat_entry->nb->type, "snat")) { -+ continue; - } -+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ } - -- if (router_port->lrp_networks.n_ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == ", -- peer->json_key); -- op_put_v4_networks(match, router_port, false); -+ /* 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; - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- router_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbsp->header_); -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; - } - -- if (router_port->lrp_networks.n_ipv6_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == ", -- peer->json_key); -- op_put_v6_networks(match, router_port); -- -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- router_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbsp->header_); -- } -+ 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); - } - } -- - } - --/* Local router ingress table CHK_PKT_LEN: Check packet length. -- * -- * Any IPv4 packet with outport set to the distributed gateway -- * router port, check the packet length and store the result in the -- * 'REGBIT_PKT_LARGER' register bit. -- * -- * Local router ingress table LARGER_PKTS: Handle larger packets. -- * -- * Any IPv4 packet with outport set to the distributed gateway -- * router port and the 'REGBIT_PKT_LARGER' register bit is set, -- * generate ICMPv4 packet with type 3 (Destination Unreachable) and -- * code 4 (Fragmentation needed). -- * */ -+/* NAT, Defrag and load balancing. */ - 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) -+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, -+ struct hmap *lflows, -+ struct shash *meter_groups, -+ struct hmap *lbs, -+ struct ds *match, struct ds *actions) - { - if (od->nbr) { - - /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", -- "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", -- "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); - -- if (od->l3dgw_port && od->l3redirect_port) { -- int gw_mtu = 0; -- if (od->l3dgw_port->nbrp) { -- gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options, -- "gateway_mtu", 0); -- } -- /* Add the flows only if gateway_mtu is configured. */ -- if (gw_mtu <= 0) { -- return; -- } -+ /* Send the IPv6 NS packets to next table. When ovn-controller -+ * generates IPv6 NS (for the action - nd_ns{}), the injected -+ * packet would go through conntrack - which is not required. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); - -- ds_clear(match); -- ds_put_format(match, "outport == %s", od->l3dgw_port->json_key); -+ /* NAT rules are only valid on Gateway routers and routers with -+ * l3dgw_port (router has a port with gateway chassis -+ * specified). */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -+ return; -+ } - -- ds_clear(actions); -- ds_put_format(actions, -- REGBIT_PKT_LARGER" = check_pkt_larger(%d);" -- " next;", gw_mtu + VLAN_ETH_HEADER_LEN); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50, -- ds_cstr(match), ds_cstr(actions), -- &od->l3dgw_port->nbrp->header_); -+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - -- for (size_t i = 0; i < od->nbr->n_ports; i++) { -- struct ovn_port *rp = ovn_port_find(ports, -- od->nbr->ports[i]->name); -- if (!rp || rp == od->l3dgw_port) { -- continue; -- } -+ 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); - -- if (rp->lrp_networks.ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "inport == %s && outport == %s" -- " && ip4 && "REGBIT_PKT_LARGER, -- rp->json_key, od->l3dgw_port->json_key); -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ const struct nbrec_nat *nat; - -- ds_clear(actions); -- /* Set icmp4.frag_mtu to gw_mtu */ -- ds_put_format(actions, -- "icmp4_error {" -- REGBIT_EGRESS_LOOPBACK" = 1; " -- "eth.dst = %s; " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "icmp4.type = 3; /* Destination Unreachable. */ " -- "icmp4.code = 4; /* Frag Needed and DF was Set. */ " -- "icmp4.frag_mtu = %d; " -- "next(pipeline=ingress, table=%d); };", -- rp->lrp_networks.ea_s, -- rp->lrp_networks.ipv4_addrs[0].addr_s, -- 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), -- &rp->nbrp->header_); -- } -+ nat = od->nbr->nat[i]; - -- if (rp->lrp_networks.ipv6_addrs) { -- ds_clear(match); -- ds_put_format(match, "inport == %s && outport == %s" -- " && ip6 && "REGBIT_PKT_LARGER, -- rp->json_key, od->l3dgw_port->json_key); -+ ovs_be32 ip, mask; -+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -+ bool is_v6 = false; -+ bool stateless = lrouter_nat_is_stateless(nat); -+ struct nbrec_address_set *allowed_ext_ips = -+ nat->allowed_ext_ips; -+ struct nbrec_address_set *exempted_ext_ips = -+ nat->exempted_ext_ips; - -- ds_clear(actions); -- /* Set icmp6.frag_mtu to gw_mtu */ -- ds_put_format(actions, -- "icmp6_error {" -- REGBIT_EGRESS_LOOPBACK" = 1; " -- "eth.dst = %s; " -- "ip6.dst = ip6.src; " -- "ip6.src = %s; " -- "ip.ttl = 255; " -- "icmp6.type = 2; /* Packet Too Big. */ " -- "icmp6.code = 0; " -- "icmp6.frag_mtu = %d; " -- "next(pipeline=ingress, table=%d); };", -- rp->lrp_networks.ea_s, -- rp->lrp_networks.ipv6_addrs[0].addr_s, -- 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), -- &rp->nbrp->header_); -- } -+ if (allowed_ext_ips && exempted_ext_ips) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -+ "both allowed and exempt external ips set", -+ UUID_ARGS(&(nat->header_.uuid))); -+ continue; - } -- } -- } --} - --/* 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; -+ char *error = ip_parse_masked(nat->external_ip, &ip, &mask); -+ if (error || mask != OVS_BE32_MAX) { -+ free(error); -+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad external ip %s for nat", -+ nat->external_ip); -+ free(error); -+ continue; -+ } -+ /* It was an invalid IPv4 address, but valid IPv6. -+ * Treat the rest of the handling of this NAT rule -+ * as IPv6. */ -+ is_v6 = true; -+ } - -- if (od->l3dgw_port->nbrp) { -- stage_hint = &od->l3dgw_port->nbrp->header_; -+ /* Check the validity of nat->logical_ip. 'logical_ip' can -+ * be a subnet when the type is "snat". */ -+ int cidr_bits; -+ if (is_v6) { -+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -+ cidr_bits = ipv6_count_cidr_bits(&mask_v6); -+ } else { -+ error = ip_parse_masked(nat->logical_ip, &ip, &mask); -+ cidr_bits = ip_count_cidr_bits(mask); -+ } -+ if (!strcmp(nat->type, "snat")) { -+ if (error) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -+ "in router "UUID_FMT"", -+ nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ continue; -+ } -+ } else { -+ if (error || (!is_v6 && mask != OVS_BE32_MAX) -+ || (is_v6 && memcmp(&mask_v6, &v6_exact, -+ sizeof mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ continue; -+ } - } - -- /* For 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); -- } -+ /* For distributed router NAT, determine whether this NAT rule -+ * satisfies the conditions for distributed NAT processing. */ -+ bool distributed = false; -+ struct eth_addr mac; -+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -+ nat->logical_port && nat->external_mac) { -+ if (eth_addr_from_string(nat->external_mac, &mac)) { -+ distributed = true; -+ } else { -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -+ continue; -+ } -+ } - -- /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); -- } --} -+ /* Ingress UNSNAT table: It is for already established connections' -+ * reverse traffic. i.e., SNAT has already been done in egress -+ * pipeline and now the packet has entered the ingress pipeline as -+ * part of a reply. We undo the SNAT here. -+ * -+ * Undoing SNAT has to happen before DNAT processing. This is -+ * because when the packet was DNATed in ingress pipeline, it did -+ * not know about the possibility of eventual additional SNAT in -+ * egress pipeline. */ -+ if (!strcmp(nat->type, "snat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); -+ } - --/* 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; -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 90, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ - -- 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; -- } -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s" -+ " && inport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } - -- ds_clear(match); -- ds_put_format(match, "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); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); -+ } - -- char sn_addr_s[INET6_ADDRSTRLEN + 1]; -- ipv6_string_mapped(sn_addr_s, &sn_addr); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } -+ } - -- 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;"); -+ /* Ingress DNAT table: Packets enter the pipeline with destination -+ * IP address that needs to be DNATted from a external IP address -+ * to a logical IP address. */ -+ if (!strcmp(nat->type, "dnat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ /* Packet when it goes from the initiator to destination. -+ * We need to set flags.loopback because the router can -+ * send the packet back through the same interface. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip); -+ ds_clear(actions); -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); -+ } - -- /* TTL discard */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, -- "ip4 && ip.ttl == {0, 1}", "drop;"); -+ if (dnat_force_snat_ip) { -+ /* Indicate to the future tables that a DNAT has taken -+ * place and a force SNAT needs to be done in the -+ * Egress SNAT table. */ -+ ds_put_format(actions, -+ "flags.force_snat_for_dnat = 1; "); -+ } - -- /* Pass other traffic not already handled to the next table for -- * routing. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); -- } --} -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "flags.loopback = 1; " -+ "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_format(actions, "flags.loopback = 1; " -+ "ct_dnat(%s", nat->logical_ip); - --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;"); -- } -- } -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - --} -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ - --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"); -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s" -+ " && inport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); -+ } - -- 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_); -- } -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- /* 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); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - } - -- 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_); -- } -- } -+ /* ARP resolve for NAT IPs. */ -+ if (od->l3dgw_port) { -+ if (!strcmp(nat->type, "snat")) { -+ ds_clear(match); -+ ds_put_format( -+ match, "inport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -+ 120, ds_cstr(match), "next;", -+ &nat->header_); -+ } - -- /* 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; -+ if (!sset_contains(&nat_entries, nat->external_ip)) { -+ ds_clear(match); -+ ds_put_format( -+ match, "outport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -+ nat->external_ip); -+ ds_clear(actions); -+ ds_put_format( -+ actions, "eth.dst = %s; next;", -+ distributed ? nat->external_mac : -+ od->l3dgw_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, od, -+ S_ROUTER_IN_ARP_RESOLVE, -+ 100, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ sset_add(&nat_entries, nat->external_ip); -+ } -+ } else { -+ /* Add the NAT external_ip to the nat_entries even for -+ * gateway routers. This is required for adding load balancer -+ * flows.*/ -+ sset_add(&nat_entries, nat->external_ip); - } - -- 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_); -- } -- } -+ /* Egress UNDNAT table: It is for already established connections' -+ * reverse traffic. i.e., DNAT has already been done in ingress -+ * pipeline and now the packet has entered the egress pipeline as -+ * part of a reply. We undo the DNAT here. -+ * -+ * Note that this only applies for NAT on a distributed router. -+ * Undo DNAT on a gateway router is done in the ingress DNAT -+ * pipeline stage. */ -+ if (od->l3dgw_port && (!strcmp(nat->type, "dnat") -+ || !strcmp(nat->type, "dnat_and_snat"))) { -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s" -+ " && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - --} -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat;"); -+ } - --static void --build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od, -- struct hmap *lflows) --{ -- if (od->nbr) { -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - -- /* 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. -- */ -- for (int i = 0; i < od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &od->nat_entries[i]; -+ /* Egress SNAT table: Packets enter the egress pipeline with -+ * source ip address that needs to be SNATted to a external ip -+ * address. */ -+ if (!strcmp(nat->type, "snat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip); -+ ds_clear(actions); - -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- /* 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); -- } -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", -+ nat->external_ip); - -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ cidr_bits + 1, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ uint16_t priority = cidr_bits + 1; - -- 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); -- } -- } --} -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s" -+ " && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ priority += 128; -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); - --/* Logical router ingress table 3: IP Input for IPv4. */ --static void --build_lrouter_ipv4_ip_input(struct ovn_port *op, -- struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- /* No ingress packets are accepted on a chassisredirect -- * port, so no need to program flows for that port. */ -- if (op->nbrp && (!op->derived)) { -- if (op->lrp_networks.n_ipv4_addrs) { -- /* L3 admission control: drop packets that originate from an -- * IPv4 address owned by the router or a broadcast address -- * known to the router (priority 100). */ -- ds_clear(match); -- ds_put_cstr(match, "ip4.src == "); -- op_put_v4_networks(match, op, true); -- ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -- ds_cstr(match), "drop;", -- &op->nbrp->header_); -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- /* ICMP echo reply. These flows reply to ICMP echo requests -- * received for the router's IP address. Since packets only -- * get here as part of the logical router datapath, the inport -- * (i.e. the incoming locally attached net) does not matter. -- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -- ds_clear(match); -- ds_put_cstr(match, "ip4.dst == "); -- op_put_v4_networks(match, op, false); -- ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - -- const char * icmp_actions = "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 0; " -- "flags.loopback = 1; " -- "next; "; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -- ds_cstr(match), icmp_actions, -- &op->nbrp->header_); -- } -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", -+ nat->external_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- /* ICMP time exceeded */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(match); -- ds_clear(actions); -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ priority, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ } -+ } - -- ds_put_format(match, -- "inport == %s && ip4 && " -- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -- ds_put_format(actions, -- "icmp4 {" -- "eth.dst <-> eth.src; " -- "icmp4.type = 11; /* Time exceeded */ " -- "icmp4.code = 0; /* TTL exceeded in transit */ " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "next; };", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } -+ /* Logical router ingress table 0: -+ * For NAT on a distributed router, add rules allowing -+ * ingress traffic with eth.dst matching nat->external_mac -+ * on the l3dgw_port instance where nat->logical_port is -+ * resident. */ -+ if (distributed) { -+ /* Store the ethernet address of the port receiving the packet. -+ * This will save us from having to match on inport further -+ * down in the pipeline. -+ */ -+ ds_clear(actions); -+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -+ od->l3dgw_port->lrp_networks.ea_s); - -- /* ARP reply. These flows reply to ARP requests for the router's own -- * IP address. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(match); -- ds_put_format(match, "arp.spa == %s/%u", -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); -+ ds_clear(match); -+ ds_put_format(match, -+ "eth.dst == "ETH_ADDR_FMT" && inport == %s" -+ " && is_chassis_resident(\"%s\")", -+ ETH_ADDR_ARGS(mac), -+ od->l3dgw_port->json_key, -+ nat->logical_port); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - -- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -- && op->peer->od->n_localnet_ports) { -- bool add_chassis_resident_check = false; -- if (op == op->od->l3dgw_port) { -- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -- * should only be sent from the gateway chassis, so that -- * upstream MAC learning points to the gateway chassis. -- * Also need to avoid generation of multiple ARP responses -- * from different chassis. */ -- add_chassis_resident_check = true; -+ /* Ingress Gateway Redirect Table: For NAT on a distributed -+ * router, add flows that are specific to a NAT rule. These -+ * flows indicate the presence of an applicable NAT rule that -+ * can be applied in a distributed manner. -+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -+ * NAT external IP and NAT external mac so the ARP request -+ * generated in the following stage is sent out with proper IP/MAC -+ * src addresses. -+ */ -+ if (distributed) { -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, -+ "ip%s.src == %s && outport == %s && " -+ "is_chassis_resident(\"%s\")", -+ is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key, nat->logical_port); -+ ds_put_format(actions, "eth.src = %s; %s = %s; next;", -+ nat->external_mac, -+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -+ nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -+ 100, ds_cstr(match), -+ ds_cstr(actions), &nat->header_); -+ } -+ -+ /* Egress Loopback table: For NAT on a distributed router. -+ * If packets in the egress pipeline on the distributed -+ * gateway port have ip.dst matching a NAT external IP, then -+ * loop a clone of the packet back to the beginning of the -+ * ingress pipeline with inport = outport. */ -+ if (od->l3dgw_port) { -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip%s.dst == %s && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); - } else { -- /* Check if the option 'reside-on-redirect-chassis' -- * is set to true on the router port. If set to true -- * and if peer's logical switch has a localnet port, it -- * means the router pipeline for the packets from -- * peer's logical switch is be run on the chassis -- * hosting the gateway port and it should reply to the -- * ARP requests for the router port IPs. -- */ -- add_chassis_resident_check = smap_get_bool( -- &op->nbrp->options, -- "reside-on-redirect-chassis", false); -+ ds_put_format(match, " && is_chassis_resident(\"%s\")", -+ nat->logical_port); - } - -- if (add_chassis_resident_check) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, -+ "clone { ct_clear; " -+ "inport = outport; outport = \"\"; " -+ "flags = 0; flags.loopback = 1; "); -+ for (int j = 0; j < MFF_N_LOG_REGS; j++) { -+ ds_put_format(actions, "reg%d = 0; ", j); - } -+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " -+ "next(pipeline=ingress, table=%d); };", -+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); - } -- -- build_lrouter_arp_flow(op->od, op, -- op->lrp_networks.ipv4_addrs[i].addr_s, -- REG_INPORT_ETH_ADDR, match, false, 90, -- &op->nbrp->header_, lflows); - } - -- /* A set to hold all load-balancer vips that need ARP responses. */ -- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); -- -- const char *ip_address; -- SSET_FOR_EACH (ip_address, &all_ips_v4) { -- ds_clear(match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ /* Handle force SNAT options set in the gateway router. */ -+ if (!od->l3dgw_port) { -+ if (dnat_force_snat_ip) { -+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -+ "dnat"); -+ } -+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -+ "dnat"); -+ } - } -- -- build_lrouter_arp_flow(op->od, op, -- ip_address, REG_INPORT_ETH_ADDR, -- match, false, 90, NULL, lflows); -- } -- -- SSET_FOR_EACH (ip_address, &all_ips_v6) { -- ds_clear(match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ if (lb_force_snat_ip) { -+ if (od->lb_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -+ } -+ if (od->lb_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); -+ } - } - -- build_lrouter_nd_flow(op->od, op, "nd_na", -- ip_address, NULL, REG_INPORT_ETH_ADDR, -- match, false, 90, NULL, lflows); -+ /* For gateway router, re-circulate every packet through -+ * the DNAT zone. This helps with the following. -+ * -+ * Any packet that needs to be unDNATed in the reverse -+ * direction gets unDNATed. Ideally this could be done in -+ * the egress pipeline. But since the gateway router -+ * does not have any feature that depends on the source -+ * ip address being external IP address for IP routing, -+ * we can do it here, saving a future re-circulation. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -+ "ip", "flags.loopback = 1; ct_dnat;"); - } - -- sset_destroy(&all_ips_v4); -- sset_destroy(&all_ips_v6); -- -- if (!smap_get(&op->od->nbr->options, "chassis") -- && !op->od->l3dgw_port) { -- /* UDP/TCP port unreachable. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(match); -- ds_put_format(match, -- "ip4 && ip4.dst == %s && !ip.later_frag && udp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- const char *action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 3; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(match), action, -- &op->nbrp->header_); -- -- ds_clear(match); -- ds_put_format(match, -- "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "tcp_reset {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(match), action, -- &op->nbrp->header_); -- -- ds_clear(match); -- ds_put_format(match, -- "ip4 && ip4.dst == %s && !ip.later_frag", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 2; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 70, ds_cstr(match), action, -- &op->nbrp->header_); -- } -+ /* Load balancing and packet defrag are only valid on -+ * Gateway routers or router with gateway port. */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -+ sset_destroy(&nat_entries); -+ return; - } - -- /* 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); -+ /* A set to hold all ips that need defragmentation and tracking. */ -+ struct sset all_ips = SSET_INITIALIZER(&all_ips); - -- /* 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) { -- return; -- } -+ for (int i = 0; i < od->nbr->n_load_balancer; i++) { -+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -+ struct ovn_northd_lb *lb = -+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -+ ovs_assert(lb); - -- for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &op->od->nat_entries[i]; -+ for (size_t j = 0; j < lb->n_vips; j++) { -+ struct ovn_lb_vip *lb_vip = &lb->vips[j]; -+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -+ ds_clear(actions); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions, -+ lb->selection_fields, false); - -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } -+ if (!sset_contains(&all_ips, lb_vip->vip_str)) { -+ sset_add(&all_ips, lb_vip->vip_str); -+ /* If there are any load balancing rules, we should send -+ * the packet to conntrack for defragmentation and -+ * tracking. This helps with two things. -+ * -+ * 1. With tracking, we can send only new connections to -+ * pick a DNAT ip address from a group. -+ * 2. If there are L4 ports in load balancing rules, we -+ * need the defragmentation to match on L4 ports. */ -+ ds_clear(match); -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -+ 100, ds_cstr(match), "ct_next;", -+ &nb_lb->header_); -+ } - -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { -- continue; -- } -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } -+ /* Higher priority rules are added for load-balancing in DNAT -+ * table. For every match (on a VIP[:port]), we add two flows -+ * via add_router_lb_flow(). One flow is for specific matching -+ * on ct.new with an action of "ct_lb($targets);". The other -+ * flow is for ct.est with an action of "ct_dnat;". */ -+ ds_clear(match); -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } - -- /* 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; -+ int prio = 110; -+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -+ "sctp"); -+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; - -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -+ if (lb_vip->vip_port) { -+ ds_put_format(match, " && %s && %s.dst == %d", proto, -+ proto, lb_vip->vip_port); -+ prio = 120; -+ } - -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ if (od->l3redirect_port && -+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ add_router_lb_flow(lflows, od, match, actions, prio, -+ lb_force_snat_ip, lb_vip, proto, -+ nb_lb, meter_groups, &nat_entries); -+ } - } -+ sset_destroy(&all_ips); -+ sset_destroy(&nat_entries); - } - } - - -+ - struct lswitch_flow_build_info { - struct hmap *datapaths; - struct hmap *ports; -@@ -11361,6 +11350,8 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - &lsi->actions); - build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); - build_lrouter_arp_nd_for_datapath(od, lsi->lflows); -+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups, -+ lsi->lbs, &lsi->match, &lsi->actions); - } - - /* Helper function to combine all lflow generation which is iterated by port. -@@ -11459,9 +11450,6 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - ds_destroy(&lsi.actions); - - build_lswitch_flows(datapaths, lflows); -- -- /* Legacy lrouter build - to be migrated. */ -- build_lrouter_flows(datapaths, lflows, meter_groups, lbs); - } - - struct ovn_dp_group { --- -2.29.2 - diff --git a/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch b/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch deleted file mode 100644 index f87080e..0000000 --- a/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 3211033ae3ed6a055d4bc296ee8ff847f571640c Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:20 +0100 -Subject: [PATCH 12/16] actions: Fix leak of dynamic string on fwd group - encoding failure. - -CC: Manoj Sharma -Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 61d209bbf2abacb71cdb63555fe1fe6b0020daf3) ---- - lib/actions.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/lib/actions.c b/lib/actions.c -index 156ebb2fe..3219ab3be 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -3448,6 +3448,7 @@ encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group, - - /* 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="); -@@ -3455,6 +3456,7 @@ encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group, - 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; - } - --- -2.28.0 - diff --git a/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch b/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch deleted file mode 100644 index 6a48950..0000000 --- a/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch +++ /dev/null @@ -1,967 +0,0 @@ -From 2473b80f778654f0204d1cf4671e543cb6467d5f Mon Sep 17 00:00:00 2001 -Message-Id: <2473b80f778654f0204d1cf4671e543cb6467d5f.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Lorenzo Bianconi -Date: Fri, 8 Jan 2021 17:36:20 +0100 -Subject: [PATCH 12/16] controller: introduce BFD tx path in ovn-controller. - -Introduce the capability to transmit BFD packets in ovn-controller. -Introduce BFD tables in nb/sb dbs in order to configure BFD parameters -(e.g. min_tx, min_rx, ..) for ovn-controller. - -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/ovn-controller.c | 1 + - controller/pinctrl.c | 298 +++++++++++++++++++++++++++++++++++- - controller/pinctrl.h | 2 + - lib/ovn-l7.h | 19 +++ - northd/ovn-northd.c | 202 ++++++++++++++++++++++++ - ovn-nb.ovsschema | 29 +++- - ovn-nb.xml | 67 ++++++++ - ovn-sb.ovsschema | 27 +++- - ovn-sb.xml | 78 ++++++++++ - 9 files changed, 718 insertions(+), 5 deletions(-) - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 366fc9c06..75512871b 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -2837,6 +2837,7 @@ main(int argc, char *argv[]) - ovnsb_idl_loop.idl), - sbrec_service_monitor_table_get( - ovnsb_idl_loop.idl), -+ sbrec_bfd_table_get(ovnsb_idl_loop.idl), - br_int, chassis, - &runtime_data->local_datapaths, - &runtime_data->active_tunnels); -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 7e3abf0a4..9df6533a1 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -323,6 +323,18 @@ put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, - static void notify_pinctrl_main(void); - static void notify_pinctrl_handler(void); - -+static bool bfd_monitor_should_inject(void); -+static void bfd_monitor_wait(long long int timeout); -+static void bfd_monitor_init(void); -+static void bfd_monitor_destroy(void); -+static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) -+ OVS_REQUIRES(pinctrl_mutex); -+static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, -+ struct ovsdb_idl_index *sbrec_port_binding_by_name, -+ const struct sbrec_chassis *chassis, -+ const struct sset *active_tunnels) -+ OVS_REQUIRES(pinctrl_mutex); -+ - COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); - COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map); - COVERAGE_DEFINE(pinctrl_drop_controller_event); -@@ -487,6 +499,7 @@ pinctrl_init(void) - ip_mcast_snoop_init(); - init_put_vport_bindings(); - init_svc_monitors(); -+ bfd_monitor_init(); - pinctrl.br_int_name = NULL; - pinctrl_handler_seq = seq_create(); - pinctrl_main_seq = seq_create(); -@@ -3053,6 +3066,8 @@ pinctrl_handler(void *arg_) - swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP15_VERSION); - - while (!latch_is_set(&pctrl->pinctrl_thread_exit)) { -+ long long int bfd_time = LLONG_MAX; -+ - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_rconn_setup(swconn, pctrl->br_int_name); - ip_mcast_snoop_run(); -@@ -3085,6 +3100,7 @@ pinctrl_handler(void *arg_) - send_ipv6_ras(swconn, &send_ipv6_ra_time); - send_ipv6_prefixd(swconn, &send_prefixd_time); - send_mac_binding_buffered_pkts(swconn); -+ bfd_monitor_send_msg(swconn, &bfd_time); - ovs_mutex_unlock(&pinctrl_mutex); - - ip_mcast_querier_run(swconn, &send_mcast_query_time); -@@ -3102,6 +3118,7 @@ pinctrl_handler(void *arg_) - ip_mcast_querier_wait(send_mcast_query_time); - svc_monitors_wait(svc_monitors_next_run_time); - ipv6_prefixd_wait(send_prefixd_time); -+ bfd_monitor_wait(bfd_time); - - new_seq = seq_read(pinctrl_handler_seq); - seq_wait(pinctrl_handler_seq, new_seq); -@@ -3149,6 +3166,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_dns_table *dns_table, - const struct sbrec_controller_event_table *ce_table, - const struct sbrec_service_monitor_table *svc_mon_table, -+ const struct sbrec_bfd_table *bfd_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, -@@ -3179,6 +3197,10 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - local_datapaths); - sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name, - chassis); -+ if (ovnsb_idl_txn) { -+ bfd_monitor_run(bfd_table, sbrec_port_binding_by_name, chassis, -+ active_tunnels); -+ } - ovs_mutex_unlock(&pinctrl_mutex); - } - -@@ -3722,6 +3744,7 @@ pinctrl_destroy(void) - destroy_dns_cache(); - ip_mcast_snoop_destroy(); - destroy_svc_monitors(); -+ bfd_monitor_destroy(); - seq_destroy(pinctrl_main_seq); - seq_destroy(pinctrl_handler_seq); - } -@@ -5525,7 +5548,8 @@ may_inject_pkts(void) - !shash_is_empty(&send_garp_rarp_data) || - ipv6_prefixd_should_inject() || - !ovs_list_is_empty(&mcast_query_list) || -- !ovs_list_is_empty(&buffered_mac_bindings)); -+ !ovs_list_is_empty(&buffered_mac_bindings) || -+ bfd_monitor_should_inject()); - } - - static void -@@ -6312,6 +6336,278 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn, - - } - -+static struct hmap bfd_monitor_map; -+ -+struct bfd_entry { -+ struct hmap_node node; -+ bool erase; -+ -+ /* L2 source address */ -+ struct eth_addr src_mac; -+ /* IPv4 source address */ -+ ovs_be32 ip_src; -+ /* IPv4 destination address */ -+ ovs_be32 ip_dst; -+ /* RFC 5881 section 4 -+ * The source port MUST be in the range 49152 through 65535. -+ * The same UDP source port number MUST be used for all BFD -+ * Control packets associated with a particular session. -+ * The source port number SHOULD be unique among all BFD -+ * sessions on the system -+ */ -+ uint16_t udp_src; -+ ovs_be32 disc; -+ -+ int64_t port_key; -+ int64_t metadata; -+ -+ long long int next_tx; -+}; -+ -+static void -+bfd_monitor_init(void) -+{ -+ hmap_init(&bfd_monitor_map); -+} -+ -+static void -+bfd_monitor_destroy(void) -+{ -+ struct bfd_entry *entry; -+ HMAP_FOR_EACH_POP (entry, node, &bfd_monitor_map) { -+ free(entry); -+ } -+ hmap_destroy(&bfd_monitor_map); -+} -+ -+static struct bfd_entry * -+pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port) -+{ -+ struct bfd_entry *entry; -+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0), -+ &bfd_monitor_map) { -+ if (entry->udp_src == port) { -+ return entry; -+ } -+ } -+ return NULL; -+} -+ -+static bool -+bfd_monitor_should_inject(void) -+{ -+ long long int cur_time = time_msec(); -+ struct bfd_entry *entry; -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ if (entry->next_tx < cur_time) { -+ return true; -+ } -+ } -+ return false; -+} -+ -+static void -+bfd_monitor_wait(long long int timeout) -+{ -+ if (!hmap_is_empty(&bfd_monitor_map)) { -+ poll_timer_wait_until(timeout); -+ } -+} -+ -+static void -+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet) -+{ -+ struct udp_header *udp; -+ struct bfd_msg *msg; -+ -+ /* Properly align after the ethernet header */ -+ dp_packet_reserve(packet, 2); -+ struct eth_header *eth = dp_packet_put_uninit(packet, sizeof *eth); -+ eth->eth_dst = eth_addr_broadcast; -+ eth->eth_src = entry->src_mac; -+ eth->eth_type = htons(ETH_TYPE_IP); -+ -+ struct ip_header *ip = dp_packet_put_zeros(packet, sizeof *ip); -+ ip->ip_ihl_ver = IP_IHL_VER(5, 4); -+ ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg); -+ ip->ip_ttl = MAXTTL; -+ ip->ip_tos = IPTOS_PREC_INTERNETCONTROL; -+ ip->ip_proto = IPPROTO_UDP; -+ put_16aligned_be32(&ip->ip_src, entry->ip_src); -+ put_16aligned_be32(&ip->ip_dst, entry->ip_dst); -+ /* Checksum has already been zeroed by put_zeros call. */ -+ ip->ip_csum = csum(ip, sizeof *ip); -+ -+ udp = dp_packet_put_zeros(packet, sizeof *udp); -+ udp->udp_src = htons(entry->udp_src); -+ udp->udp_dst = htons(BFD_DEST_PORT); -+ udp->udp_len = htons(sizeof *udp + sizeof *msg); -+ -+ msg = dp_packet_put_uninit(packet, sizeof *msg); -+ msg->vers_diag = (BFD_VERSION << 5); -+ msg->length = BFD_PACKET_LEN; -+} -+ -+static void -+bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ long long int cur_time = time_msec(); -+ struct bfd_entry *entry; -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ if (cur_time < entry->next_tx) { -+ goto next; -+ } -+ -+ uint64_t packet_stub[256 / 8]; -+ struct dp_packet packet; -+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -+ bfd_monitor_put_bfd_msg(entry, &packet); -+ -+ uint64_t ofpacts_stub[4096 / 8]; -+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); -+ -+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ -+ uint32_t dp_key = entry->metadata; -+ uint32_t port_key = entry->port_key; -+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); -+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); -+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts); -+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); -+ resubmit->in_port = OFPP_CONTROLLER; -+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; -+ -+ struct ofputil_packet_out po = { -+ .packet = dp_packet_data(&packet), -+ .packet_len = dp_packet_size(&packet), -+ .buffer_id = UINT32_MAX, -+ .ofpacts = ofpacts.data, -+ .ofpacts_len = ofpacts.size, -+ }; -+ -+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); -+ enum ofp_version version = rconn_get_version(swconn); -+ enum ofputil_protocol proto = -+ ofputil_protocol_from_ofp_version(version); -+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); -+ dp_packet_uninit(&packet); -+ ofpbuf_uninit(&ofpacts); -+ -+ entry->next_tx = cur_time + 5000; -+next: -+ if (*bfd_time > entry->next_tx) { -+ *bfd_time = entry->next_tx; -+ } -+ } -+} -+ -+static void -+bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, -+ struct ovsdb_idl_index *sbrec_port_binding_by_name, -+ const struct sbrec_chassis *chassis, -+ const struct sset *active_tunnels) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ struct bfd_entry *entry, *next_entry; -+ long long int cur_time = time_msec(); -+ bool changed = false; -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ entry->erase = true; -+ } -+ -+ const struct sbrec_bfd *bt; -+ SBREC_BFD_TABLE_FOR_EACH (bt, bfd_table) { -+ const struct sbrec_port_binding *pb -+ = lport_lookup_by_name(sbrec_port_binding_by_name, -+ bt->logical_port); -+ if (!pb) { -+ continue; -+ } -+ -+ const char *peer_s = smap_get(&pb->options, "peer"); -+ if (!peer_s) { -+ continue; -+ } -+ -+ const struct sbrec_port_binding *peer -+ = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s); -+ if (!peer) { -+ continue; -+ } -+ -+ char *redirect_name = xasprintf("cr-%s", pb->logical_port); -+ bool resident = lport_is_chassis_resident( -+ sbrec_port_binding_by_name, chassis, active_tunnels, -+ redirect_name); -+ free(redirect_name); -+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) && -+ !resident) { -+ continue; -+ } -+ -+ entry = pinctrl_find_bfd_monitor_entry_by_port( -+ bt->dst_ip, bt->src_port); -+ if (!entry) { -+ ovs_be32 ip_dst, ip_src = htonl(BFD_DEFAULT_SRC_IP); -+ struct eth_addr ea = eth_addr_zero; -+ int i; -+ -+ if (!ip_parse(bt->dst_ip, &ip_dst)) { -+ continue; -+ } -+ -+ for (i = 0; i < pb->n_mac; i++) { -+ struct lport_addresses laddrs; -+ -+ if (!extract_lsp_addresses(pb->mac[i], &laddrs)) { -+ continue; -+ } -+ -+ ea = laddrs.ea; -+ if (laddrs.n_ipv4_addrs > 0) { -+ ip_src = laddrs.ipv4_addrs[0].addr; -+ destroy_lport_addresses(&laddrs); -+ break; -+ } -+ destroy_lport_addresses(&laddrs); -+ } -+ -+ if (eth_addr_is_zero(ea)) { -+ continue; -+ } -+ -+ entry = xzalloc(sizeof *entry); -+ entry->src_mac = ea; -+ entry->ip_src = ip_src; -+ entry->ip_dst = ip_dst; -+ entry->udp_src = bt->src_port; -+ entry->disc = htonl(bt->disc); -+ entry->next_tx = cur_time; -+ entry->metadata = pb->datapath->tunnel_key; -+ entry->port_key = pb->tunnel_key; -+ -+ uint32_t hash = hash_string(bt->dst_ip, 0); -+ hmap_insert(&bfd_monitor_map, &entry->node, hash); -+ changed = true; -+ } -+ entry->erase = false; -+ } -+ -+ HMAP_FOR_EACH_SAFE (entry, next_entry, node, &bfd_monitor_map) { -+ if (entry->erase) { -+ hmap_remove(&bfd_monitor_map, &entry->node); -+ free(entry); -+ } -+ } -+ -+ if (changed) { -+ notify_pinctrl_handler(); -+ } -+} -+ - static uint16_t - get_random_src_port(void) - { -diff --git a/controller/pinctrl.h b/controller/pinctrl.h -index 4b101ec92..8555d983d 100644 ---- a/controller/pinctrl.h -+++ b/controller/pinctrl.h -@@ -31,6 +31,7 @@ struct sbrec_chassis; - struct sbrec_dns_table; - struct sbrec_controller_event_table; - struct sbrec_service_monitor_table; -+struct sbrec_bfd_table; - - void pinctrl_init(void); - void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -@@ -44,6 +45,7 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_dns_table *, - const struct sbrec_controller_event_table *, - const struct sbrec_service_monitor_table *, -+ const struct sbrec_bfd_table *, - const struct ovsrec_bridge *, const struct sbrec_chassis *, - const struct hmap *local_datapaths, - const struct sset *active_tunnels); -diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h -index c84a0e7a9..d00982449 100644 ---- a/lib/ovn-l7.h -+++ b/lib/ovn-l7.h -@@ -26,6 +26,25 @@ - #include "hash.h" - #include "ovn/logical-fields.h" - -+#define BFD_PACKET_LEN 24 -+#define BFD_DEST_PORT 3784 -+#define BFD_VERSION 1 -+#define BFD_DEFAULT_SRC_IP 0xA9FE0101 /* 169.254.1.1 */ -+#define BFD_DEFAULT_DST_IP 0xA9FE0100 /* 169.254.1.0 */ -+ -+struct bfd_msg { -+ uint8_t vers_diag; -+ uint8_t flags; -+ uint8_t mult; -+ uint8_t length; -+ ovs_be32 my_disc; -+ ovs_be32 your_disc; -+ ovs_be32 min_tx; -+ ovs_be32 min_rx; -+ ovs_be32 min_rx_echo; -+}; -+BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg)); -+ - /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */ - struct gen_opts_map { - struct hmap_node hmap_node; -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index f588d8c32..77ea2181c 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -7487,6 +7487,191 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op, - } - } - -+struct bfd_entry { -+ struct hmap_node hmap_node; -+ -+ const struct sbrec_bfd *sb_bt; -+ -+ bool ref; -+}; -+ -+static struct bfd_entry * -+bfd_port_lookup(struct hmap *bfd_map, const char *logical_port, -+ const char *dst_ip) -+{ -+ struct bfd_entry *bfd_e; -+ uint32_t hash; -+ -+ hash = hash_string(dst_ip, 0); -+ hash = hash_string(logical_port, hash); -+ HMAP_FOR_EACH_WITH_HASH (bfd_e, hmap_node, hash, bfd_map) { -+ if (!strcmp(bfd_e->sb_bt->logical_port, logical_port) && -+ !strcmp(bfd_e->sb_bt->dst_ip, dst_ip)) { -+ return bfd_e; -+ } -+ } -+ return NULL; -+} -+ -+static void -+bfd_cleanup_connections(struct northd_context *ctx, struct hmap *bfd_map) -+{ -+ const struct nbrec_bfd *nb_bt; -+ struct bfd_entry *bfd_e; -+ -+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) { -+ bfd_e = bfd_port_lookup(bfd_map, nb_bt->logical_port, nb_bt->dst_ip); -+ if (!bfd_e) { -+ continue; -+ } -+ -+ if (!bfd_e->ref && strcmp(nb_bt->status, "admin_down")) { -+ /* no user for this bfd connection */ -+ nbrec_bfd_set_status(nb_bt, "admin_down"); -+ } -+ } -+ -+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, bfd_map) { -+ free(bfd_e); -+ } -+} -+ -+#define BFD_DEF_MINTX 1000 /* 1s */ -+#define BFD_DEF_MINRX 1000 /* 1s */ -+#define BFD_DEF_DETECT_MULT 5 -+ -+static void -+build_bfd_update_sb_conf(const struct nbrec_bfd *nb_bt, -+ const struct sbrec_bfd *sb_bt) -+{ -+ if (strcmp(nb_bt->dst_ip, sb_bt->dst_ip)) { -+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip); -+ } -+ -+ if (strcmp(nb_bt->logical_port, sb_bt->logical_port)) { -+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port); -+ } -+ -+ if (strcmp(nb_bt->status, sb_bt->status)) { -+ sbrec_bfd_set_status(sb_bt, nb_bt->status); -+ } -+ -+ int detect_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0] -+ : BFD_DEF_DETECT_MULT; -+ if (detect_mult != sb_bt->detect_mult) { -+ sbrec_bfd_set_detect_mult(sb_bt, detect_mult); -+ } -+ -+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX; -+ if (min_tx != sb_bt->min_tx) { -+ sbrec_bfd_set_min_tx(sb_bt, min_tx); -+ } -+ -+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX; -+ if (min_rx != sb_bt->min_rx) { -+ sbrec_bfd_set_min_rx(sb_bt, min_rx); -+ } -+} -+ -+/* RFC 5881 section 4 -+ * The source port MUST be in the range 49152 through 65535. -+ * The same UDP source port number MUST be used for all BFD -+ * Control packets associated with a particular session. -+ * The source port number SHOULD be unique among all BFD -+ * sessions on the system -+ */ -+#define BFD_UDP_SRC_PORT_START 49152 -+#define BFD_UDP_SRC_PORT_LEN (65535 - BFD_UDP_SRC_PORT_START) -+ -+static int bfd_get_unused_port(unsigned long *bfd_src_ports) -+{ -+ int port; -+ -+ port = bitmap_scan(bfd_src_ports, 0, 0, BFD_UDP_SRC_PORT_LEN); -+ if (port == BFD_UDP_SRC_PORT_LEN) { -+ return -ENOSPC; -+ } -+ bitmap_set1(bfd_src_ports, port); -+ -+ return port + BFD_UDP_SRC_PORT_START; -+} -+ -+static void -+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections) -+{ -+ struct hmap sb_only = HMAP_INITIALIZER(&sb_only); -+ const struct sbrec_bfd *sb_bt; -+ unsigned long *bfd_src_ports; -+ struct bfd_entry *bfd_e; -+ uint32_t hash; -+ -+ bfd_src_ports = bitmap_allocate(BFD_UDP_SRC_PORT_LEN); -+ -+ SBREC_BFD_FOR_EACH (sb_bt, ctx->ovnsb_idl) { -+ bfd_e = xmalloc(sizeof *bfd_e); -+ bfd_e->sb_bt = sb_bt; -+ hash = hash_string(sb_bt->dst_ip, 0); -+ hash = hash_string(sb_bt->logical_port, hash); -+ hmap_insert(&sb_only, &bfd_e->hmap_node, hash); -+ bitmap_set1(bfd_src_ports, sb_bt->src_port - BFD_UDP_SRC_PORT_START); -+ } -+ -+ const struct nbrec_bfd *nb_bt; -+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) { -+ if (!nb_bt->status) { -+ /* default state is admin_down */ -+ nbrec_bfd_set_status(nb_bt, "admin_down"); -+ } -+ -+ bfd_e = bfd_port_lookup(&sb_only, nb_bt->logical_port, nb_bt->dst_ip); -+ if (!bfd_e) { -+ int udp_src = bfd_get_unused_port(bfd_src_ports); -+ if (udp_src < 0) { -+ continue; -+ } -+ -+ sb_bt = sbrec_bfd_insert(ctx->ovnsb_txn); -+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port); -+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip); -+ sbrec_bfd_set_disc(sb_bt, 1 + random_uint32()); -+ sbrec_bfd_set_src_port(sb_bt, udp_src); -+ sbrec_bfd_set_status(sb_bt, nb_bt->status); -+ -+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX; -+ sbrec_bfd_set_min_tx(sb_bt, min_tx); -+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX; -+ sbrec_bfd_set_min_rx(sb_bt, min_rx); -+ int d_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0] -+ : BFD_DEF_DETECT_MULT; -+ sbrec_bfd_set_detect_mult(sb_bt, d_mult); -+ } else if (strcmp(bfd_e->sb_bt->status, nb_bt->status)) { -+ if (!strcmp(nb_bt->status, "admin_down") || -+ !strcmp(bfd_e->sb_bt->status, "admin_down")) { -+ sbrec_bfd_set_status(bfd_e->sb_bt, nb_bt->status); -+ } else { -+ nbrec_bfd_set_status(nb_bt, bfd_e->sb_bt->status); -+ } -+ } -+ if (bfd_e) { -+ build_bfd_update_sb_conf(nb_bt, bfd_e->sb_bt); -+ -+ hmap_remove(&sb_only, &bfd_e->hmap_node); -+ bfd_e->ref = false; -+ hash = hash_string(bfd_e->sb_bt->dst_ip, 0); -+ hash = hash_string(bfd_e->sb_bt->logical_port, hash); -+ hmap_insert(bfd_connections, &bfd_e->hmap_node, hash); -+ } -+ } -+ -+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) { -+ sbrec_bfd_delete(bfd_e->sb_bt); -+ free(bfd_e); -+ } -+ hmap_destroy(&sb_only); -+ -+ bitmap_free(bfd_src_ports); -+} -+ - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. - * -@@ -12444,6 +12629,7 @@ ovnnb_db_run(struct northd_context *ctx, - struct hmap igmp_groups; - struct shash meter_groups = SHASH_INITIALIZER(&meter_groups); - struct hmap lbs; -+ struct hmap bfd_connections = HMAP_INITIALIZER(&bfd_connections); - - /* Sync ipsec configuration. - * Copy nb_cfg from northbound to southbound database. -@@ -12538,6 +12724,7 @@ ovnnb_db_run(struct northd_context *ctx, - build_ip_mcast(ctx, datapaths); - build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups); - build_meter_groups(ctx, &meter_groups); -+ build_bfd_table(ctx, &bfd_connections); - build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups, - &igmp_groups, &meter_groups, &lbs); - ovn_update_ipv6_prefix(ports); -@@ -12563,9 +12750,13 @@ ovnnb_db_run(struct northd_context *ctx, - HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) { - ovn_port_group_destroy(&port_groups, pg); - } -+ -+ bfd_cleanup_connections(ctx, &bfd_connections); -+ - hmap_destroy(&igmp_groups); - hmap_destroy(&mcast_groups); - hmap_destroy(&port_groups); -+ hmap_destroy(&bfd_connections); - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &meter_groups) { -@@ -13497,6 +13688,16 @@ main(int argc, char *argv[]) - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_load_balancer_col_external_ids); - -+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_bfd); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_logical_port); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_dst_ip); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_status); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_tx); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_rx); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_detect_mult); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_disc); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_src_port); -+ - struct ovsdb_idl_index *sbrec_chassis_by_name - = chassis_index_create(ovnsb_idl_loop.idl); - -@@ -13619,6 +13820,7 @@ main(int argc, char *argv[]) - } - } - -+ - free(ovn_internal_version); - unixctl_server_destroy(unixctl); - ovsdb_idl_loop_destroy(&ovnnb_idl_loop); -diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema -index b77a2308c..aea932f55 100644 ---- a/ovn-nb.ovsschema -+++ b/ovn-nb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Northbound", -- "version": "5.30.0", -- "cksum": "3273824429 27172", -+ "version": "5.31.0", -+ "cksum": "1511492848 28473", - "tables": { - "NB_Global": { - "columns": { -@@ -526,5 +526,30 @@ - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, - "indexes": [["name"]], -+ "isRoot": true}, -+ "BFD": { -+ "columns": { -+ "logical_port": {"type": "string"}, -+ "dst_ip": {"type": "string"}, -+ "min_tx": {"type": {"key": {"type": "integer", -+ "minInteger": 1}, -+ "min": 0, "max": 1}}, -+ "min_rx": {"type": {"key": {"type": "integer"}, -+ "min": 0, "max": 1}}, -+ "detect_mult": {"type": {"key": {"type": "integer", -+ "minInteger": 1}, -+ "min": 0, "max": 1}}, -+ "status": { -+ "type": {"key": {"type": "string", -+ "enum": ["set", ["down", "init", "up", -+ "admin_down"]]}, -+ "min": 0, "max": 1}}, -+ "external_ids": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}}, -+ "indexes": [["logical_port", "dst_ip"]], - "isRoot": true}} - } -diff --git a/ovn-nb.xml b/ovn-nb.xml -index 0cf043790..cdc5e0f3a 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -3728,4 +3728,71 @@ - - - -+ -+ -+

    -+ Contains BFD parameter for ovn-controller bfd configuration. -+

    -+ -+ -+ -+ OVN logical port when BFD engine is running. -+ -+ -+ -+ BFD peer IP address. -+ -+ -+ -+ This is the minimum interval, in milliseconds, that the local -+ system would like to use when transmitting BFD Control packets, -+ less any jitter applied. The value zero is reserved. Default -+ value is 1000 ms. -+ -+ -+ -+ This is the minimum interval, in milliseconds, between received -+ BFD Control packets that this system is capable of supporting, -+ less any jitter applied by the sender. If this value is zero, -+ the transmitting system does not want the remote system to send -+ any periodic BFD Control packets. -+ -+ -+ -+ Detection time multiplier. The negotiated transmit interval, -+ multiplied by this value, provides the Detection Time for the -+ receiving system in Asynchronous mode. Default value is 5. -+ -+ -+ -+ Reserved for future use. -+ -+ -+ -+ See External IDs at the beginning of this document. -+ -+ -+ -+ -+ -+

    -+ BFD port logical states. Possible values are: -+

      -+
    • -+ admin_down -+
    • -+
    • -+ down -+
    • -+
    • -+ init -+
    • -+
    • -+ up -+
    • -+
    -+

    -+
    -+
    -+
    - -diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema -index 5228839b8..97db6de39 100644 ---- a/ovn-sb.ovsschema -+++ b/ovn-sb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Southbound", -- "version": "20.12.0", -- "cksum": "3969471120 24441", -+ "version": "20.13.0", -+ "cksum": "3035725595 25676", - "tables": { - "SB_Global": { - "columns": { -@@ -484,6 +484,29 @@ - "external_ids": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, -+ "isRoot": true}, -+ "BFD": { -+ "columns": { -+ "src_port": {"type": {"key": {"type": "integer", -+ "minInteger": 49152, -+ "maxInteger": 65535}}}, -+ "disc": {"type": {"key": {"type": "integer"}}}, -+ "logical_port": {"type": "string"}, -+ "dst_ip": {"type": "string"}, -+ "min_tx": {"type": {"key": {"type": "integer"}}}, -+ "min_rx": {"type": {"key": {"type": "integer"}}}, -+ "detect_mult": {"type": {"key": {"type": "integer"}}}, -+ "status": { -+ "type": {"key": {"type": "string", -+ "enum": ["set", ["down", "init", "up", -+ "admin_down"]]}}}, -+ "external_ids": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}}, -+ "indexes": [["logical_port", "dst_ip", "src_port", "disc"]], - "isRoot": true} - } - } -diff --git a/ovn-sb.xml b/ovn-sb.xml -index c13994848..eb440e492 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -4231,4 +4231,82 @@ tcp.flags = RST; - - - -+ -+ -+

    -+ Contains BFD parameter for ovn-controller bfd configuration. -+

    -+ -+ -+ -+ udp source port used in bfd control packets. -+ The source port MUST be in the range 49152 through 65535 -+ (RFC5881 section 4). -+ -+ -+ -+ A unique, nonzero discriminator value generated by the transmitting -+ system, used to demultiplex multiple BFD sessions between the same pair -+ of systems. -+ -+ -+ -+ OVN logical port when BFD engine is running. -+ -+ -+ -+ BFD peer IP address. -+ -+ -+ -+ This is the minimum interval, in milliseconds, that the local -+ system would like to use when transmitting BFD Control packets, -+ less any jitter applied. The value zero is reserved. -+ -+ -+ -+ This is the minimum interval, in milliseconds, between received -+ BFD Control packets that this system is capable of supporting, -+ less any jitter applied by the sender. If this value is zero, -+ the transmitting system does not want the remote system to send -+ any periodic BFD Control packets. -+ -+ -+ -+ Detection time multiplier. The negotiated transmit interval, -+ multiplied by this value, provides the Detection Time for the -+ receiving system in Asynchronous mode. -+ -+ -+ -+ Reserved for future use. -+ -+ -+ -+ See External IDs at the beginning of this document. -+ -+ -+ -+ -+ -+

    -+ BFD port logical states. Possible values are: -+

      -+
    • -+ admin_down -+
    • -+
    • -+ down -+
    • -+
    • -+ init -+
    • -+
    • -+ up -+
    • -+
    -+

    -+
    -+
    -+
    - --- -2.29.2 - diff --git a/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch b/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch deleted file mode 100644 index bf54a19..0000000 --- a/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 2d71cf47fdb194287719a97ee81dbb0dd9fab9d8 Mon Sep 17 00:00:00 2001 -Message-Id: <2d71cf47fdb194287719a97ee81dbb0dd9fab9d8.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Lorenzo Bianconi -Date: Fri, 8 Jan 2021 17:36:21 +0100 -Subject: [PATCH 13/16] action: introduce handle_bfd_msg() action. - -Add handle_bfd_msg() action to parse BFD packets received by the -controller. handle_bfd_msg() logic is currently empty and it will be -implemented adding BFD state machine in the following patches. - -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 15 +++++++++++++++ - include/ovn/actions.h | 7 +++++++ - lib/actions.c | 27 +++++++++++++++++++++++++++ - tests/ovn.at | 4 ++++ - utilities/ovn-trace.c | 2 ++ - 5 files changed, 55 insertions(+) - -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 9df6533a1..deeae7479 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -329,6 +329,9 @@ static void bfd_monitor_init(void); - static void bfd_monitor_destroy(void); - static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - OVS_REQUIRES(pinctrl_mutex); -+static void -+pinctrl_handle_bfd_msg(void) -+ OVS_REQUIRES(pinctrl_mutex); - static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_chassis *chassis, -@@ -2975,6 +2978,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - ovs_mutex_unlock(&pinctrl_mutex); - break; - -+ case ACTION_OPCODE_BFD_MSG: -+ ovs_mutex_lock(&pinctrl_mutex); -+ pinctrl_handle_bfd_msg(); -+ ovs_mutex_unlock(&pinctrl_mutex); -+ break; -+ - default: - VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, - ntohl(ah->opcode)); -@@ -6503,6 +6512,12 @@ next: - } - } - -+static void -+pinctrl_handle_bfd_msg(void) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+} -+ - static void - bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, - struct ovsdb_idl_index *sbrec_port_binding_by_name, -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 9c1ebf4aa..d104d4d64 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -105,6 +105,7 @@ struct ovn_extend_table; - OVNACT(CHK_LB_HAIRPIN, ovnact_result) \ - OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \ - OVNACT(CT_SNAT_TO_VIP, ovnact_null) \ -+ OVNACT(BFD_MSG, ovnact_null) \ - - /* enum ovnact_type, with a member OVNACT_ for each action. */ - enum OVS_PACKED_ENUM ovnact_type { -@@ -627,6 +628,12 @@ enum action_opcode { - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_REJECT, -+ -+ /* handle_bfd_msg { ...actions ...}." -+ * -+ * The actions, in OpenFlow 1.3 format, follow the action_header. -+ */ -+ ACTION_OPCODE_BFD_MSG, - }; - - /* Header. */ -diff --git a/lib/actions.c b/lib/actions.c -index fbaeb34bc..86be97f44 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -2742,6 +2742,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED, - encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts); - } - -+static void -+format_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, struct ds *s) -+{ -+ ds_put_cstr(s, "handle_bfd_msg();"); -+} -+ -+static void -+encode_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, -+ const struct ovnact_encode_params *ep OVS_UNUSED, -+ struct ofpbuf *ofpacts) -+{ -+ encode_controller_op(ACTION_OPCODE_BFD_MSG, ofpacts); -+} -+ -+static void -+parse_handle_bfd_msg(struct action_context *ctx OVS_UNUSED) -+{ -+ if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) { -+ return; -+ } -+ -+ ovnact_put_BFD_MSG(ctx->ovnacts); -+ lexer_force_match(ctx->lexer, LEX_T_RPAREN); -+} -+ - static void - parse_SET_QUEUE(struct action_context *ctx) - { -@@ -3842,6 +3867,8 @@ parse_action(struct action_context *ctx) - parse_fwd_group_action(ctx); - } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) { - ovnact_put_DHCP6_REPLY(ctx->ovnacts); -+ } else if (lexer_match_id(ctx->lexer, "handle_bfd_msg")) { -+ parse_handle_bfd_msg(ctx); - } else if (lexer_match_id(ctx->lexer, "reject")) { - parse_REJECT(ctx); - } else if (lexer_match_id(ctx->lexer, "ct_snat_to_vip")) { -diff --git a/tests/ovn.at b/tests/ovn.at -index ce6db8677..27cb2e410 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1807,6 +1807,10 @@ ct_snat_to_vip; - ct_snat_to_vip(foo); - Syntax error at `(' expecting `;'. - -+# bfd packets -+handle_bfd_msg(); -+ encodes as controller(userdata=00.00.00.17.00.00.00.00) -+ - # Miscellaneous negative tests. - ; - Syntax error at `;'. -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index 465049d34..e3aa73fb7 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -2544,6 +2544,8 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - break; - case OVNACT_DHCP6_REPLY: - break; -+ case OVNACT_BFD_MSG: -+ break; - } - } - ds_destroy(&s); --- -2.29.2 - diff --git a/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch b/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch deleted file mode 100644 index 17d12df..0000000 --- a/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 35864ac03e30fb69bafce90b49ada2f9da6eec86 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:21 +0100 -Subject: [PATCH 13/16] ovn-nbctl: Fix leak of IPs while configuring NAT. - -CC: Ankur Sharma -Fixes: 20bc58a67f39 ("External IP based NAT: Add Columns and CLI") -Signed-off-by: Ilya Maximets -Acked-by: Dumitru Ceara -Acked-by: Ankur Sharma -Signed-off-by: Numan Siddique - -(cherry-picked from master commit f9e449fce78b2e0682cef53ba09cade492b4d260) ---- - utilities/ovn-nbctl.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index f4c4f9385..6f5117876 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -4601,8 +4601,11 @@ nbctl_lr_nat_set_ext_ips(struct ctl_context *ctx) - } else { - nbrec_nat_set_allowed_ext_ips(nat, addr_set); - } -+ free(nat_ip); -+ free(old_ip); - return; - } -+ free(old_ip); - } - - if (!nat_found) { --- -2.28.0 - diff --git a/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch b/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch deleted file mode 100644 index 3a932e7..0000000 --- a/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch +++ /dev/null @@ -1,751 +0,0 @@ -From e75d53c69261a0b104c75d8f6f7dc7175a690833 Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: -From: Lorenzo Bianconi -Date: Fri, 8 Jan 2021 17:36:22 +0100 -Subject: [PATCH 14/16] controller: bfd: introduce BFD state machine. - -Introduce BFD state machine for BFD packet parsing -according to RFC880 https://tools.ietf.org/html/rfc5880. -Introduce BFD logical flows in ovn-northd. - -Change-Id: I1ea057ad45393360fa917eb6e3a576dd37cfbc0d -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - NEWS | 6 + - controller/pinctrl.c | 342 ++++++++++++++++++++++++++++++++++++++-- - northd/ovn-northd.8.xml | 21 +++ - northd/ovn-northd.c | 85 +++++++++- - tests/ovn-northd.at | 55 +++++++ - 5 files changed, 488 insertions(+), 21 deletions(-) - -diff --git a/NEWS b/NEWS -index f71ec329c..85f63503e 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,9 @@ -+Post-v20.12.0 -+------------------------- -+ - Support ECMP multiple nexthops for reroute router policies. -+ - BFD protocol support according to RFC880 [0]. IPv6 is not suported yet. -+ [0] https://tools.ietf.org/html/rfc5880) -+ - OVN v20.12.0 - 18 Dec 2020 - -------------------------- - - The "datapath" argument to ovn-trace is now optional, since the -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index deeae7479..6e363a0f9 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -330,9 +330,10 @@ static void bfd_monitor_destroy(void); - static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - OVS_REQUIRES(pinctrl_mutex); - static void --pinctrl_handle_bfd_msg(void) -+pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) - OVS_REQUIRES(pinctrl_mutex); --static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, -+static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ const struct sbrec_bfd_table *bfd_table, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_chassis *chassis, - const struct sset *active_tunnels) -@@ -2980,7 +2981,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - - case ACTION_OPCODE_BFD_MSG: - ovs_mutex_lock(&pinctrl_mutex); -- pinctrl_handle_bfd_msg(); -+ pinctrl_handle_bfd_msg(&headers, &packet); - ovs_mutex_unlock(&pinctrl_mutex); - break; - -@@ -3206,10 +3207,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - local_datapaths); - sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name, - chassis); -- if (ovnsb_idl_txn) { -- bfd_monitor_run(bfd_table, sbrec_port_binding_by_name, chassis, -- active_tunnels); -- } -+ bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name, -+ chassis, active_tunnels); - ovs_mutex_unlock(&pinctrl_mutex); - } - -@@ -6345,8 +6344,48 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn, - - } - -+enum bfd_state { -+ BFD_STATE_ADMIN_DOWN, -+ BFD_STATE_DOWN, -+ BFD_STATE_INIT, -+ BFD_STATE_UP, -+}; -+ -+enum bfd_flags { -+ BFD_FLAG_MULTIPOINT = 1 << 0, -+ BFD_FLAG_DEMAND = 1 << 1, -+ BFD_FLAG_AUTH = 1 << 2, -+ BFD_FLAG_CTL = 1 << 3, -+ BFD_FLAG_FINAL = 1 << 4, -+ BFD_FLAG_POLL = 1 << 5 -+}; -+ -+#define BFD_FLAGS_MASK 0x3f -+ -+static char * -+bfd_get_status(enum bfd_state state) -+{ -+ switch (state) { -+ case BFD_STATE_ADMIN_DOWN: -+ return "admin_down"; -+ case BFD_STATE_DOWN: -+ return "down"; -+ case BFD_STATE_INIT: -+ return "init"; -+ case BFD_STATE_UP: -+ return "up"; -+ default: -+ return ""; -+ } -+} -+ - static struct hmap bfd_monitor_map; - -+#define BFD_UPDATE_BATCH_TH 10 -+static uint16_t bfd_pending_update; -+#define BFD_UPDATE_TIMEOUT 5000LL -+static long long bfd_last_update; -+ - struct bfd_entry { - struct hmap_node node; - bool erase; -@@ -6365,11 +6404,23 @@ struct bfd_entry { - * sessions on the system - */ - uint16_t udp_src; -- ovs_be32 disc; -+ ovs_be32 local_disc; -+ ovs_be32 remote_disc; -+ -+ uint32_t local_min_tx; -+ uint32_t local_min_rx; -+ uint32_t remote_min_rx; -+ -+ uint8_t local_mult; - - int64_t port_key; - int64_t metadata; - -+ enum bfd_state state; -+ bool change_state; -+ -+ uint32_t detection_timeout; -+ long long int last_rx; - long long int next_tx; - }; - -@@ -6377,6 +6428,7 @@ static void - bfd_monitor_init(void) - { - hmap_init(&bfd_monitor_map); -+ bfd_last_update = time_msec(); - } - - static void -@@ -6402,6 +6454,24 @@ pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port) - return NULL; - } - -+static struct bfd_entry * -+pinctrl_find_bfd_monitor_entry_by_disc(ovs_be32 ip, ovs_be32 disc) -+{ -+ char *ip_src = xasprintf(IP_FMT, IP_ARGS(ip)); -+ struct bfd_entry *ret = NULL, *entry; -+ -+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip_src, 0), -+ &bfd_monitor_map) { -+ if (entry->local_disc == disc) { -+ ret = entry; -+ break; -+ } -+ } -+ -+ free(ip_src); -+ return ret; -+} -+ - static bool - bfd_monitor_should_inject(void) - { -@@ -6453,9 +6523,60 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet) - udp->udp_dst = htons(BFD_DEST_PORT); - udp->udp_len = htons(sizeof *udp + sizeof *msg); - -- msg = dp_packet_put_uninit(packet, sizeof *msg); -+ msg = dp_packet_put_zeros(packet, sizeof *msg); - msg->vers_diag = (BFD_VERSION << 5); -+ msg->mult = entry->local_mult; - msg->length = BFD_PACKET_LEN; -+ msg->flags = entry->state << 6; -+ msg->my_disc = entry->local_disc; -+ msg->your_disc = entry->remote_disc; -+ /* min_tx and min_rx are in us - RFC 5880 page 9 */ -+ msg->min_tx = htonl(entry->local_min_tx * 1000); -+ msg->min_rx = htonl(entry->local_min_rx * 1000); -+} -+ -+static bool -+bfd_monitor_need_update(void) -+{ -+ long long int cur_time = time_msec(); -+ -+ if (bfd_pending_update == BFD_UPDATE_BATCH_TH) { -+ goto update; -+ } -+ -+ if (bfd_pending_update && -+ bfd_last_update + BFD_UPDATE_TIMEOUT < cur_time) { -+ goto update; -+ } -+ return false; -+ -+update: -+ bfd_last_update = cur_time; -+ bfd_pending_update = 0; -+ return true; -+} -+ -+static void -+bfd_check_detection_timeout(struct bfd_entry *entry) -+{ -+ if (entry->state == BFD_STATE_ADMIN_DOWN) { -+ return; -+ } -+ -+ if (!entry->detection_timeout) { -+ return; -+ } -+ -+ long long int cur_time = time_msec(); -+ if (cur_time < entry->last_rx + entry->detection_timeout) { -+ return; -+ } -+ -+ entry->state = BFD_STATE_DOWN; -+ entry->change_state = true; -+ bfd_last_update = cur_time; -+ bfd_pending_update = 0; -+ notify_pinctrl_main(); - } - - static void -@@ -6465,11 +6586,27 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - long long int cur_time = time_msec(); - struct bfd_entry *entry; - -+ if (bfd_monitor_need_update()) { -+ notify_pinctrl_main(); -+ } -+ - HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ unsigned long tx_timeout; -+ -+ bfd_check_detection_timeout(entry); -+ - if (cur_time < entry->next_tx) { - goto next; - } - -+ if (!entry->remote_min_rx) { -+ continue; -+ } -+ -+ if (entry->state == BFD_STATE_ADMIN_DOWN) { -+ continue; -+ } -+ - uint64_t packet_stub[256 / 8]; - struct dp_packet packet; - dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -@@ -6504,7 +6641,9 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - dp_packet_uninit(&packet); - ofpbuf_uninit(&ofpacts); - -- entry->next_tx = cur_time + 5000; -+ tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx); -+ tx_timeout -= random_range((tx_timeout * 25) / 100); -+ entry->next_tx = cur_time + tx_timeout; - next: - if (*bfd_time > entry->next_tx) { - *bfd_time = entry->next_tx; -@@ -6512,14 +6651,167 @@ next: - } - } - -+static bool -+pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) -+{ -+ if (ip_flow->dl_type != htons(ETH_TYPE_IP) && -+ ip_flow->dl_type != htons(ETH_TYPE_IPV6)) { -+ return false; -+ } -+ -+ if (ip_flow->nw_proto != IPPROTO_UDP) { -+ return false; -+ } -+ -+ struct udp_header *udp_hdr = dp_packet_l4(pkt_in); -+ if (udp_hdr->udp_dst != htons(BFD_DEST_PORT)) { -+ return false; -+ } -+ -+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in); -+ uint8_t version = msg->vers_diag >> 5; -+ if (version != BFD_VERSION) { -+ return false; -+ } -+ -+ enum bfd_flags flags = msg->flags & BFD_FLAGS_MASK; -+ if (flags & BFD_FLAG_AUTH) { -+ /* AUTH not supported yet */ -+ return false; -+ } -+ -+ if (msg->length < BFD_PACKET_LEN) { -+ return false; -+ } -+ -+ if (!msg->mult) { -+ return false; -+ } -+ -+ if (flags & BFD_FLAG_MULTIPOINT) { -+ return false; -+ } -+ -+ if (!msg->my_disc) { -+ return false; -+ } -+ -+ enum bfd_state peer_state = msg->flags >> 6; -+ if (peer_state >= BFD_STATE_INIT && !msg->your_disc) { -+ return false; -+ } -+ -+ return true; -+} -+ - static void --pinctrl_handle_bfd_msg(void) -+pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) - OVS_REQUIRES(pinctrl_mutex) - { -+ if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "BFD packet discarded"); -+ return; -+ } -+ -+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in); -+ struct bfd_entry *entry = pinctrl_find_bfd_monitor_entry_by_disc( -+ ip_flow->nw_src, msg->your_disc); -+ if (!entry) { -+ return; -+ } -+ -+ bool change_state = false; -+ entry->remote_disc = msg->my_disc; -+ uint32_t remote_min_tx = ntohl(msg->min_tx) / 1000; -+ entry->remote_min_rx = ntohl(msg->min_rx) / 1000; -+ entry->detection_timeout = msg->mult * MAX(remote_min_tx, -+ entry->local_min_rx); -+ -+ enum bfd_state peer_state = msg->flags >> 6; -+ if (peer_state == BFD_STATE_ADMIN_DOWN && -+ entry->state >= BFD_STATE_INIT) { -+ entry->state = BFD_STATE_DOWN; -+ entry->last_rx = time_msec(); -+ change_state = true; -+ goto out; -+ } -+ -+ /* bfd state machine */ -+ switch (entry->state) { -+ case BFD_STATE_DOWN: -+ if (peer_state == BFD_STATE_DOWN) { -+ entry->state = BFD_STATE_INIT; -+ change_state = true; -+ } -+ if (peer_state == BFD_STATE_INIT) { -+ entry->state = BFD_STATE_UP; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_INIT: -+ if (peer_state == BFD_STATE_INIT || -+ peer_state == BFD_STATE_UP) { -+ entry->state = BFD_STATE_UP; -+ change_state = true; -+ } -+ if (peer_state == BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_UP: -+ if (peer_state == BFD_STATE_ADMIN_DOWN || -+ peer_state == BFD_STATE_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_ADMIN_DOWN: -+ default: -+ break; -+ } -+ -+out: -+ /* let's try to bacth db updates */ -+ if (change_state) { -+ entry->change_state = true; -+ bfd_pending_update++; -+ } -+ if (bfd_monitor_need_update()) { -+ notify_pinctrl_main(); -+ } -+} -+ -+static void -+bfd_monitor_check_sb_conf(const struct sbrec_bfd *sb_bt, -+ struct bfd_entry *entry) -+{ -+ ovs_be32 ip_dst; -+ -+ if (ip_parse(sb_bt->dst_ip, &ip_dst) && ip_dst != entry->ip_dst) { -+ entry->ip_dst = ip_dst; -+ } -+ -+ if (sb_bt->min_tx != entry->local_min_tx) { -+ entry->local_min_tx = sb_bt->min_tx; -+ } -+ -+ if (sb_bt->min_rx != entry->local_min_rx) { -+ entry->local_min_rx = sb_bt->min_rx; -+ } -+ -+ if (sb_bt->detect_mult != entry->local_mult) { -+ entry->local_mult = sb_bt->detect_mult; -+ } - } - - static void --bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, -+bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ const struct sbrec_bfd_table *bfd_table, - struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct sbrec_chassis *chassis, - const struct sset *active_tunnels) -@@ -6599,15 +6891,39 @@ bfd_monitor_run(const struct sbrec_bfd_table *bfd_table, - entry->ip_src = ip_src; - entry->ip_dst = ip_dst; - entry->udp_src = bt->src_port; -- entry->disc = htonl(bt->disc); -+ entry->local_disc = htonl(bt->disc); - entry->next_tx = cur_time; -+ entry->last_rx = cur_time; -+ entry->detection_timeout = 30000; - entry->metadata = pb->datapath->tunnel_key; - entry->port_key = pb->tunnel_key; -+ entry->state = BFD_STATE_ADMIN_DOWN; -+ entry->local_min_tx = bt->min_tx; -+ entry->local_min_rx = bt->min_rx; -+ entry->remote_min_rx = 1; /* RFC5880 page 29 */ -+ entry->local_mult = bt->detect_mult; - - uint32_t hash = hash_string(bt->dst_ip, 0); - hmap_insert(&bfd_monitor_map, &entry->node, hash); -+ } else if (!strcmp(bt->status, "admin_down") && -+ entry->state != BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_ADMIN_DOWN; -+ entry->change_state = false; -+ entry->remote_disc = 0; -+ } else if (strcmp(bt->status, "admin_down") && -+ entry->state == BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ entry->change_state = false; -+ entry->remote_disc = 0; - changed = true; -+ } else if (entry->change_state && ovnsb_idl_txn) { -+ if (entry->state == BFD_STATE_DOWN) { -+ entry->remote_disc = 0; -+ } -+ sbrec_bfd_set_status(bt, bfd_get_status(entry->state)); -+ entry->change_state = false; - } -+ bfd_monitor_check_sb_conf(bt, entry); - entry->erase = false; - } - -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index 1f0f71f34..48c52a56a 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -1936,6 +1936,27 @@ next; -

    - - -+
  • -+

    -+ For each BFD port the two following priority-110 flows are added -+ to manage BFD traffic: -+ -+

      -+
    • -+ if ip4.src or ip6.src is any IP -+ address owned by the router port and udp.dst == 3784 -+ , the packet is advanced to the next pipeline stage. -+
    • -+ -+
    • -+ if ip4.dst or ip6.dst is any IP -+ address owned by the router port and udp.dst == 3784 -+ , the handle_bfd_msg action is executed. -+
    • -+
    -+

    -+
  • -+ -
  • -

    - L3 admission control: A priority-100 flow drops packets that match -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 77ea2181c..363bb0895 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -1473,6 +1473,8 @@ struct ovn_port { - - bool has_unknown; /* If the addresses have 'unknown' defined. */ - -+ bool has_bfd; -+ - /* The port's peer: - * - * - A switch port S of type "router" has a router port R as a peer, -@@ -7597,7 +7599,8 @@ static int bfd_get_unused_port(unsigned long *bfd_src_ports) - } - - static void --build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections) -+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections, -+ struct hmap *ports) - { - struct hmap sb_only = HMAP_INITIALIZER(&sb_only); - const struct sbrec_bfd *sb_bt; -@@ -7661,9 +7664,18 @@ build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections) - hash = hash_string(bfd_e->sb_bt->logical_port, hash); - hmap_insert(bfd_connections, &bfd_e->hmap_node, hash); - } -+ -+ struct ovn_port *op = ovn_port_find(ports, nb_bt->logical_port); -+ if (op) { -+ op->has_bfd = true; -+ } - } - - HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) { -+ struct ovn_port *op = ovn_port_find(ports, bfd_e->sb_bt->logical_port); -+ if (op) { -+ op->has_bfd = false; -+ } - sbrec_bfd_delete(bfd_e->sb_bt); - free(bfd_e); - } -@@ -8423,16 +8435,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op, - build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4, - &match, &priority); - -- struct ds actions = DS_EMPTY_INITIALIZER; -- ds_put_format(&actions, "ip.ttl--; "REG_ECMP_GROUP_ID" = 0; %s = ", -+ struct ds common_actions = DS_EMPTY_INITIALIZER; -+ ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ", - is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6); -- - if (gateway) { -- ds_put_cstr(&actions, gateway); -+ ds_put_cstr(&common_actions, gateway); - } else { -- ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6"); -+ ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6"); - } -- ds_put_format(&actions, "; " -+ ds_put_format(&common_actions, "; " - "%s = %s; " - "eth.src = %s; " - "outport = %s; " -@@ -8442,11 +8453,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op, - lrp_addr_s, - op->lrp_networks.ea_s, - op->json_key); -+ struct ds actions = DS_EMPTY_INITIALIZER; -+ ds_put_format(&actions, "ip.ttl--; %s", ds_cstr(&common_actions)); - - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority, - ds_cstr(&match), ds_cstr(&actions), - stage_hint); -+ if (op->has_bfd) { -+ ds_put_format(&match, " && udp.dst == 3784"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, -+ priority + 1, ds_cstr(&match), -+ ds_cstr(&common_actions), stage_hint); -+ } - ds_destroy(&match); -+ ds_destroy(&common_actions); - ds_destroy(&actions); - } - -@@ -9108,6 +9128,52 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, - ds_destroy(&actions); - } - -+static void -+build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op) -+{ -+ if (!op->has_bfd) { -+ return; -+ } -+ -+ struct ds ip_list = DS_EMPTY_INITIALIZER; -+ struct ds match = DS_EMPTY_INITIALIZER; -+ -+ if (op->lrp_networks.n_ipv4_addrs) { -+ op_put_v4_networks(&ip_list, op, false); -+ ds_put_format(&match, "ip4.src == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "next; ", -+ &op->nbrp->header_); -+ ds_clear(&match); -+ ds_put_format(&match, "ip4.dst == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "handle_bfd_msg(); ", -+ &op->nbrp->header_); -+ } -+ if (op->lrp_networks.n_ipv6_addrs) { -+ ds_clear(&ip_list); -+ ds_clear(&match); -+ -+ op_put_v6_networks(&ip_list, op); -+ ds_put_format(&match, "ip6.src == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "next; ", -+ &op->nbrp->header_); -+ ds_clear(&match); -+ ds_put_format(&match, "ip6.dst == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "handle_bfd_msg(); ", -+ &op->nbrp->header_); -+ } -+ -+ ds_destroy(&ip_list); -+ ds_destroy(&match); -+} -+ - /* Logical router ingress Table 0: L2 Admission Control - * Generic admission control flows (without inport check). - */ -@@ -10614,6 +10680,9 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op, - &op->nbrp->header_); - } - -+ /* BFD msg handling */ -+ build_lrouter_bfd_flows(lflows, op); -+ - /* ICMP time exceeded */ - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { - ds_clear(match); -@@ -12724,7 +12793,7 @@ ovnnb_db_run(struct northd_context *ctx, - build_ip_mcast(ctx, datapaths); - build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups); - build_meter_groups(ctx, &meter_groups); -- build_bfd_table(ctx, &bfd_connections); -+ build_bfd_table(ctx, &bfd_connections, ports); - build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups, - &igmp_groups, &meter_groups, &lbs); - ovn_update_ipv6_prefix(ports); -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index ce6c44db4..eee004328 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2322,3 +2322,58 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], - ]) - - AT_CLEANUP -+ -+AT_SETUP([ovn -- check BFD config propagation to SBDB]) -+AT_KEYWORDS([northd-bfd]) -+ovn_start -+ -+check ovn-nbctl --wait=sb lr-add r0 -+for i in $(seq 1 4); do -+ check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i 00:00:00:00:00:0$i 192.168.$i.1/24 -+ check ovn-nbctl --wait=sb ls-add sw$i -+ check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0 -+ check ovn-nbctl --wait=sb lsp-set-type sw$i-r0 router -+ check ovn-nbctl --wait=sb lsp-set-options sw$i-r0 router-port=r0-sw$i -+ check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i -+done -+ -+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10) -+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20 -+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down -+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0 -+ -+check_column 10 bfd detect_mult logical_port=r0-sw1 -+check_column "192.168.10.2" bfd dst_ip logical_port=r0-sw1 -+check_column 250 bfd min_rx logical_port=r0-sw1 -+check_column 250 bfd min_tx logical_port=r0-sw1 -+check_column admin_down bfd status logical_port=r0-sw1 -+ -+check_column 20 bfd detect_mult logical_port=r0-sw2 -+check_column "192.168.20.2" bfd dst_ip logical_port=r0-sw2 -+check_column 500 bfd min_rx logical_port=r0-sw2 -+check_column 500 bfd min_tx logical_port=r0-sw2 -+check_column admin_down bfd status logical_port=r0-sw2 -+ -+check_column 5 bfd detect_mult logical_port=r0-sw3 -+check_column "192.168.30.2" bfd dst_ip logical_port=r0-sw3 -+check_column 1000 bfd min_rx logical_port=r0-sw3 -+check_column 1000 bfd min_tx logical_port=r0-sw3 -+check_column admin_down bfd status logical_port=r0-sw3 -+ -+uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1) -+check ovn-nbctl set bfd $uuid min_tx=1000 -+check ovn-nbctl set bfd $uuid min_rx=1000 -+check ovn-nbctl set bfd $uuid detect_mult=100 -+ -+uuid_2=$(fetch_column nb:bfd _uuid logical_port=r0-sw2) -+check ovn-nbctl clear bfd $uuid_2 min_rx -+check_column 1000 bfd min_rx logical_port=r0-sw2 -+ -+check_column 1000 bfd min_tx logical_port=r0-sw1 -+check_column 1000 bfd min_rx logical_port=r0-sw1 -+check_column 100 bfd detect_mult logical_port=r0-sw1 -+ -+ovn-nbctl destroy bfd $uuid -+check_row_count bfd 2 -+ -+AT_CLEANUP --- -2.29.2 - diff --git a/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch b/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch deleted file mode 100644 index 009ab96..0000000 --- a/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch +++ /dev/null @@ -1,33 +0,0 @@ -From dff5ff1175d2c1f2a2619276aa287ecdeac04702 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:22 +0100 -Subject: [PATCH 14/16] ovn-nbctl: Fix IP leak on router NAT addition failure. - -Cleanup needed instead of direct return. - -Fixes: 43f42ecb3a5a ("Use normalized IP addreses in `ovn-nbctl lr-nat-add`") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 360b5bf20f23eb103edf86f3b13ab0a5fe0490db) ---- - utilities/ovn-nbctl.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 6f5117876..af9b396c3 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -4311,7 +4311,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx) - - 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); --- -2.28.0 - diff --git a/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch b/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch deleted file mode 100644 index 9765cbb..0000000 --- a/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch +++ /dev/null @@ -1,202 +0,0 @@ -From a3a3062985cadc2f2193b10ccb3404d587028c61 Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: -From: Lorenzo Bianconi -Date: Fri, 8 Jan 2021 17:36:23 +0100 -Subject: [PATCH 15/16] bfd: support demand mode on rx side. - -Introduce rx demand mode support according to RFC5880 [0]. -Demand mode on tx side is not supported yet. - -https://tools.ietf.org/html/rfc5880 -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - controller/pinctrl.c | 105 ++++++++++++++++++++++++++++--------------- - 1 file changed, 68 insertions(+), 37 deletions(-) - -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 6e363a0f9..5820ab659 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -330,7 +330,8 @@ static void bfd_monitor_destroy(void); - static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - OVS_REQUIRES(pinctrl_mutex); - static void --pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) -+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow, -+ struct dp_packet *pkt_in) - OVS_REQUIRES(pinctrl_mutex); - static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_bfd_table *bfd_table, -@@ -2981,7 +2982,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - - case ACTION_OPCODE_BFD_MSG: - ovs_mutex_lock(&pinctrl_mutex); -- pinctrl_handle_bfd_msg(&headers, &packet); -+ pinctrl_handle_bfd_msg(swconn, &headers, &packet); - ovs_mutex_unlock(&pinctrl_mutex); - break; - -@@ -6411,6 +6412,8 @@ struct bfd_entry { - uint32_t local_min_rx; - uint32_t remote_min_rx; - -+ bool remote_demand_mode; -+ - uint8_t local_mult; - - int64_t port_key; -@@ -6495,7 +6498,8 @@ bfd_monitor_wait(long long int timeout) - } - - static void --bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet) -+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet, -+ bool final) - { - struct udp_header *udp; - struct bfd_msg *msg; -@@ -6527,7 +6531,8 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet) - msg->vers_diag = (BFD_VERSION << 5); - msg->mult = entry->local_mult; - msg->length = BFD_PACKET_LEN; -- msg->flags = entry->state << 6; -+ msg->flags = final ? BFD_FLAG_FINAL : 0; -+ msg->flags |= entry->state << 6; - msg->my_disc = entry->local_disc; - msg->your_disc = entry->remote_disc; - /* min_tx and min_rx are in us - RFC 5880 page 9 */ -@@ -6535,6 +6540,46 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet) - msg->min_rx = htonl(entry->local_min_rx * 1000); - } - -+static void -+pinctrl_send_bfd_tx_msg(struct rconn *swconn, struct bfd_entry *entry, -+ bool final) -+{ -+ uint64_t packet_stub[256 / 8]; -+ struct dp_packet packet; -+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -+ bfd_monitor_put_bfd_msg(entry, &packet, final); -+ -+ uint64_t ofpacts_stub[4096 / 8]; -+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); -+ -+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ -+ uint32_t dp_key = entry->metadata; -+ uint32_t port_key = entry->port_key; -+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); -+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); -+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts); -+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); -+ resubmit->in_port = OFPP_CONTROLLER; -+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; -+ -+ struct ofputil_packet_out po = { -+ .packet = dp_packet_data(&packet), -+ .packet_len = dp_packet_size(&packet), -+ .buffer_id = UINT32_MAX, -+ .ofpacts = ofpacts.data, -+ .ofpacts_len = ofpacts.size, -+ }; -+ -+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); -+ enum ofp_version version = rconn_get_version(swconn); -+ enum ofputil_protocol proto = -+ ofputil_protocol_from_ofp_version(version); -+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); -+ dp_packet_uninit(&packet); -+ ofpbuf_uninit(&ofpacts); -+} -+ -+ - static bool - bfd_monitor_need_update(void) - { -@@ -6607,39 +6652,11 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) - continue; - } - -- uint64_t packet_stub[256 / 8]; -- struct dp_packet packet; -- dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -- bfd_monitor_put_bfd_msg(entry, &packet); -- -- uint64_t ofpacts_stub[4096 / 8]; -- struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); -- -- /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ -- uint32_t dp_key = entry->metadata; -- uint32_t port_key = entry->port_key; -- put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); -- put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); -- put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts); -- struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); -- resubmit->in_port = OFPP_CONTROLLER; -- resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; -- -- struct ofputil_packet_out po = { -- .packet = dp_packet_data(&packet), -- .packet_len = dp_packet_size(&packet), -- .buffer_id = UINT32_MAX, -- .ofpacts = ofpacts.data, -- .ofpacts_len = ofpacts.size, -- }; -+ if (entry->remote_demand_mode) { -+ continue; -+ } - -- match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); -- enum ofp_version version = rconn_get_version(swconn); -- enum ofputil_protocol proto = -- ofputil_protocol_from_ofp_version(version); -- queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); -- dp_packet_uninit(&packet); -- ofpbuf_uninit(&ofpacts); -+ pinctrl_send_bfd_tx_msg(swconn, entry, false); - - tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx); - tx_timeout -= random_range((tx_timeout * 25) / 100); -@@ -6696,6 +6713,10 @@ pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) - return false; - } - -+ if ((flags & BFD_FLAG_FINAL) && (flags & BFD_FLAG_POLL)) { -+ return false; -+ } -+ - enum bfd_state peer_state = msg->flags >> 6; - if (peer_state >= BFD_STATE_INIT && !msg->your_disc) { - return false; -@@ -6705,7 +6726,8 @@ pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) - } - - static void --pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) -+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow, -+ struct dp_packet *pkt_in) - OVS_REQUIRES(pinctrl_mutex) - { - if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) { -@@ -6775,6 +6797,15 @@ pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) - break; - } - -+ if (entry->state == BFD_STATE_UP && -+ (msg->flags & BFD_FLAG_DEMAND)) { -+ entry->remote_demand_mode = true; -+ } -+ -+ if (msg->flags & BFD_FLAG_POLL) { -+ pinctrl_send_bfd_tx_msg(swconn, entry, true); -+ } -+ - out: - /* let's try to bacth db updates */ - if (change_state) { --- -2.29.2 - diff --git a/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch b/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch deleted file mode 100644 index bf9294a..0000000 --- a/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch +++ /dev/null @@ -1,31 +0,0 @@ -From dcce158a3b80d3143b0b148753b28cc2cf26d36d Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:23 +0100 -Subject: [PATCH 15/16] ovn-nbctl: Fix IP leak on failure of lr policy - addition. - -Fixes: 742474bad730 ("ovn-nbctl: Enhance lr-policy-add to set the options.") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique - -(cherry-picked from master commit 47385c83f865306b5c85a61d530e2a9383640ceb) ---- - utilities/ovn-nbctl.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index af9b396c3..9d04a85a9 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3689,6 +3689,7 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - } else { - ctl_error(ctx, "No value specified for the option : %s", key); - free(key); -+ free(next_hop); - return; - } - free(key); --- -2.28.0 - diff --git a/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch b/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch deleted file mode 100644 index 7fae1d5..0000000 --- a/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch +++ /dev/null @@ -1,407 +0,0 @@ -From 986137dc1d4dc6905a7c5ab5e279856260966e12 Mon Sep 17 00:00:00 2001 -Message-Id: <986137dc1d4dc6905a7c5ab5e279856260966e12.1610458802.git.lorenzo.bianconi@redhat.com> -In-Reply-To: -References: -From: Lorenzo Bianconi -Date: Fri, 8 Jan 2021 17:36:24 +0100 -Subject: [PATCH 16/16] ovn: integrate bfd for static routes. - -Introduce the bfd reference in logical_router_static_router table -in order to check if the next-hop is properly running using the BFD -protocol. The CMS is supposed to populate bfd column with the proper -reference otherwise the BFD status is set to admin_down. -Add BFD tests in system-ovn.at. - -Acked-by: Mark Michelson -Signed-off-by: Lorenzo Bianconi -Signed-off-by: Numan Siddique ---- - NEWS | 3 +- - northd/ovn-northd.c | 45 +++++++++++---- - ovn-nb.ovsschema | 6 +- - ovn-nb.xml | 7 +++ - tests/atlocal.in | 3 + - tests/ovn-nbctl.at | 8 ++- - tests/ovn-northd.at | 8 +++ - tests/system-ovn.at | 136 ++++++++++++++++++++++++++++++++++++++++++++ - 8 files changed, 203 insertions(+), 13 deletions(-) - -diff --git a/NEWS b/NEWS -index 85f63503e..0b4b8f4d3 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,7 +1,8 @@ - Post-v20.12.0 - ------------------------- - - Support ECMP multiple nexthops for reroute router policies. -- - BFD protocol support according to RFC880 [0]. IPv6 is not suported yet. -+ - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD -+ availability check for OVN static routes. IPv6 is not suported yet. - [0] https://tools.ietf.org/html/rfc5880) - - OVN v20.12.0 - 18 Dec 2020 -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 363bb0895..fa2bd73c3 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -7952,7 +7952,8 @@ route_hash(struct parsed_route *route) - * Otherwise return NULL. */ - static struct parsed_route * - parsed_routes_add(struct ovs_list *routes, -- const struct nbrec_logical_router_static_route *route) -+ const struct nbrec_logical_router_static_route *route, -+ struct hmap *bfd_connections) - { - /* Verify that the next hop is an IP address with an all-ones mask. */ - struct in6_addr nexthop; -@@ -7993,6 +7994,25 @@ parsed_routes_add(struct ovs_list *routes, - return NULL; - } - -+ const struct nbrec_bfd *nb_bt = route->bfd; -+ if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) { -+ struct bfd_entry *bfd_e; -+ -+ bfd_e = bfd_port_lookup(bfd_connections, nb_bt->logical_port, -+ nb_bt->dst_ip); -+ if (bfd_e) { -+ bfd_e->ref = true; -+ } -+ -+ if (!strcmp(nb_bt->status, "admin_down")) { -+ nbrec_bfd_set_status(nb_bt, "down"); -+ } -+ -+ if (!strcmp(nb_bt->status, "down")) { -+ return NULL; -+ } -+ } -+ - struct parsed_route *pr = xzalloc(sizeof *pr); - pr->prefix = prefix; - pr->plen = plen; -@@ -9579,7 +9599,7 @@ build_ip_routing_flows_for_lrouter_port( - static void - build_static_route_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) -+ struct hmap *ports, struct hmap *bfd_connections) - { - if (od->nbr) { - ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -@@ -9591,7 +9611,8 @@ build_static_route_flows_for_lrouter( - struct ecmp_groups_node *group; - for (int i = 0; i < od->nbr->n_static_routes; i++) { - struct parsed_route *route = -- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); -+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i], -+ bfd_connections); - if (!route) { - continue; - } -@@ -11571,7 +11592,8 @@ struct lswitch_flow_build_info { - - static void - build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, -- struct lswitch_flow_build_info *lsi) -+ struct lswitch_flow_build_info *lsi, -+ struct hmap *bfd_connections) - { - /* Build Logical Switch Flows. */ - build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows, -@@ -11591,7 +11613,8 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - 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_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports, -+ bfd_connections); - build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match, - &lsi->actions); - build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports); -@@ -11655,7 +11678,8 @@ 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) -+ struct shash *meter_groups, struct hmap *lbs, -+ struct hmap *bfd_connections) - { - struct ovn_datapath *od; - struct ovn_port *op; -@@ -11682,7 +11706,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - * will move here and will be reogranized by iterator type. - */ - HMAP_FOR_EACH (od, key_node, datapaths) { -- build_lswitch_and_lrouter_iterate_by_od(od, &lsi); -+ build_lswitch_and_lrouter_iterate_by_od(od, &lsi, bfd_connections); - } - HMAP_FOR_EACH (op, key_node, ports) { - build_lswitch_and_lrouter_iterate_by_op(op, &lsi); -@@ -11780,13 +11804,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths, - struct hmap *ports, struct hmap *port_groups, - struct hmap *mcgroups, struct hmap *igmp_groups, - struct shash *meter_groups, -- struct hmap *lbs) -+ struct hmap *lbs, struct hmap *bfd_connections) - { - struct hmap lflows = HMAP_INITIALIZER(&lflows); - - build_lswitch_and_lrouter_flows(datapaths, ports, - port_groups, &lflows, mcgroups, -- igmp_groups, meter_groups, lbs); -+ igmp_groups, meter_groups, lbs, -+ bfd_connections); - - /* Collecting all unique datapath groups. */ - struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups); -@@ -12795,7 +12820,7 @@ ovnnb_db_run(struct northd_context *ctx, - build_meter_groups(ctx, &meter_groups); - build_bfd_table(ctx, &bfd_connections, ports); - build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups, -- &igmp_groups, &meter_groups, &lbs); -+ &igmp_groups, &meter_groups, &lbs, &bfd_connections); - ovn_update_ipv6_prefix(ports); - - sync_address_sets(ctx); -diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema -index aea932f55..29019809c 100644 ---- a/ovn-nb.ovsschema -+++ b/ovn-nb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Northbound", - "version": "5.31.0", -- "cksum": "1511492848 28473", -+ "cksum": "2352750632 28701", - "tables": { - "NB_Global": { - "columns": { -@@ -374,6 +374,10 @@ - "min": 0, "max": 1}}, - "nexthop": {"type": "string"}, - "output_port": {"type": {"key": "string", "min": 0, "max": 1}}, -+ "bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD", -+ "refType": "weak"}, -+ "min": 0, -+ "max": 1}}, - "options": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}, -diff --git a/ovn-nb.xml b/ovn-nb.xml -index cdc5e0f3a..105d8697e 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -2644,6 +2644,13 @@ -

    - - -+ -+

    -+ Reference to row if the route has associated a -+ BFD session -+

    -+
    -+ - - ovn-ic populates this key if the route is learned from the - global database. In this case the value -diff --git a/tests/atlocal.in b/tests/atlocal.in -index d9a4c91d4..5ebc8e117 100644 ---- a/tests/atlocal.in -+++ b/tests/atlocal.in -@@ -181,6 +181,9 @@ fi - # Set HAVE_DIBBLER-SERVER - find_command dibbler-server - -+# Set HAVE_BFDD_BEACON -+find_command bfdd-beacon -+ - # Turn off proxies. - unset http_proxy - unset https_proxy -diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 01edfcbc1..2827b223c 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1617,7 +1617,13 @@ IPv6 Routes - 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 - 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip - ::/0 2001:db8:0:f101::1 dst-ip --])]) -+]) -+ -+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24]) -+bfd_uuid=$(ovn-nbctl create bfd logical_port=lr0-p0 dst_ip=100.0.0.50 status=down min_tx=250 min_rx=250 detect_mult=10) -+AT_CHECK([ovn-nbctl lr-route-add lr0 100.0.0.0/24 192.168.0.1]) -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/24") -+AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid])]) - - dnl --------------------------------------------------------------------- - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index eee004328..91eb9a3d1 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -2373,6 +2373,14 @@ check_column 1000 bfd min_tx logical_port=r0-sw1 - check_column 1000 bfd min_rx logical_port=r0-sw1 - check_column 100 bfd detect_mult logical_port=r0-sw1 - -+check ovn-nbctl lr-route-add r0 100.0.0.0/8 192.168.10.2 -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+check_column down bfd status logical_port=r0-sw1 -+ -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+check_column admin_down bfd status logical_port=r0-sw1 -+ - ovn-nbctl destroy bfd $uuid - check_row_count bfd 2 - -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 1e73001ab..06d606166 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -5531,3 +5531,139 @@ as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d - /.*terminating with signal 15.*/d"]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- BFD]) -+AT_SKIP_IF([test $HAVE_BFDD_BEACON = no]) -+AT_SKIP_IF([test $HAVE_TCPDUMP = no]) -+AT_KEYWORDS([ovn-bfd]) -+ -+ovn_start -+OVS_TRAFFIC_VSWITCHD_START() -+ -+ADD_BR([br-int]) -+ADD_BR([br-ext]) -+ -+check ovs-ofctl add-flow br-ext action=normal -+# Set external-ids in br-int needed for ovn-controller -+check ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+# Start ovn-controller -+start_daemon ovn-controller -+ -+check ovn-nbctl lr-add R1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl ls-add public -+ -+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 -+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 -+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ -+ -- lrp-set-gateway-chassis rp-public 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 -+ -+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ -+ type=router options:router-port=rp-public \ -+ -- lsp-set-addresses public-rp router -+ -+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" -+ -+ADD_NAMESPACES(server) -+NS_CHECK_EXEC([server], [ip link set dev lo up]) -+ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \ -+ "172.16.1.1") -+ -+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) -+check ovn-nbctl lsp-add public public1 \ -+ -- lsp-set-addresses public1 unknown \ -+ -- lsp-set-type public1 localnet \ -+ -- lsp-set-options public1 network_name=phynet -+ -+NS_CHECK_EXEC([server], [bfdd-beacon --listen=172.16.1.50], [0]) -+NS_CHECK_EXEC([server], [bfdd-control allow 172.16.1.1], [0], [dnl -+Allowing connections from 172.16.1.1 -+]) -+ -+uuid=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=172.16.1.50 min_tx=250 min_rx=250 detect_mult=10) -+check ovn-nbctl lr-route-add R1 100.0.0.0/8 172.16.1.50 -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+check ovn-nbctl --wait=hv sync -+ -+wait_column "up" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep -q 172.16.1.50]) -+ -+# un-associate the bfd connection and the static route -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+wait_column "admin_down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down]) -+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &]) -+sleep 5 -+kill $(pidof tcpdump) -+AT_CHECK([grep -qi bfd bfd.pcap],[1]) -+ -+# restart the connection -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+wait_column "up" nb:bfd status logical_port=rp-public -+ -+# switch to gw router configuration -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+wait_column "admin_down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down]) -+check ovn-nbctl clear logical_router_port rp-public gateway_chassis -+check ovn-nbctl set logical_router R1 options:chassis=hv1 -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+wait_column "up" nb:bfd status logical_port=rp-public -+ -+# stop bfd endpoint -+NS_CHECK_EXEC([server], [bfdd-control stop], [0], [dnl -+stopping -+]) -+ -+wait_column "down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([test "$(ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep 172.16.1.50)" = ""]) -+ -+# remove bfd entry -+ovn-nbctl destroy bfd $uuid -+check_row_count bfd 0 -+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &]) -+sleep 5 -+kill $(pidof tcpdump) -+AT_CHECK([grep -qi bfd bfd.pcap],[1]) -+ -+kill $(pidof ovn-controller) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -+/.*terminating with signal 15.*/d"]) -+AT_CLEANUP --- -2.29.2 - diff --git a/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch b/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch deleted file mode 100644 index a54515a..0000000 --- a/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 7ea067754e208071f97e5ed89264094698fd7363 Mon Sep 17 00:00:00 2001 -From: Ilya Maximets -Date: Fri, 20 Nov 2020 01:17:24 +0100 -Subject: [PATCH 16/16] ovn-nbctl: Fix leak of array of new policies. - -CC: Tao YunXiang -Fixes: 5820502a5507 ("ovn-nbctl.c: Fix lr-policy-del command") -Acked-by: Dumitru Ceara -Signed-off-by: Ilya Maximets -Signed-off-by: Numan Siddique ---- - utilities/ovn-nbctl.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 9d04a85a9..24faaf20f 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3758,6 +3758,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - if (!shash_find(&ctx->options, "--if-exists")) { - ctl_error(ctx, "Logical router policy uuid is not found."); - } -+ free(new_policies); - return; - } - --- -2.28.0 - diff --git a/SOURCES/ovn-20.12.0.patch b/SOURCES/ovn-20.12.0.patch deleted file mode 100644 index b8ea6d5..0000000 --- a/SOURCES/ovn-20.12.0.patch +++ /dev/null @@ -1,22089 +0,0 @@ -diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh -index 0e9f87fa8..731dcacb9 100755 ---- a/.ci/linux-build.sh -+++ b/.ci/linux-build.sh -@@ -9,8 +9,7 @@ EXTRA_OPTS="--enable-Werror" - - function configure_ovs() - { -- git clone https://github.com/openvswitch/ovs.git ovs_src -- pushd ovs_src -+ pushd ovs - ./boot.sh && ./configure $* || { cat config.log; exit 1; } - make -j4 || { cat config.log; exit 1; } - popd -@@ -19,7 +18,7 @@ function configure_ovs() - function configure_ovn() - { - configure_ovs $* -- ./boot.sh && ./configure --with-ovs-source=$PWD/ovs_src $* || \ -+ ./boot.sh && ./configure $* || \ - { cat config.log; exit 1; } - } - -@@ -43,7 +42,7 @@ if [ "$TESTSUITE" ]; then - # 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" -+ export DISTCHECK_CONFIGURE_FLAGS="$OPTS" - if ! make distcheck -j4 TESTSUITEFLAGS="-j4" RECHECK=yes; then - # testsuite.log is necessary for debugging. - cat */_build/sub/tests/testsuite.log -diff --git a/.ci/osx-build.sh b/.ci/osx-build.sh -index 6617f0b9d..4b78b66dd 100755 ---- a/.ci/osx-build.sh -+++ b/.ci/osx-build.sh -@@ -7,8 +7,7 @@ EXTRA_OPTS="" - - function configure_ovs() - { -- git clone https://github.com/openvswitch/ovs.git ovs_src -- pushd ovs_src -+ pushd ovs - ./boot.sh && ./configure $* - make -j4 || { cat config.log; exit 1; } - popd -@@ -17,7 +16,7 @@ function configure_ovs() - function configure_ovn() - { - configure_ovs $* -- ./boot.sh && ./configure $* --with-ovs-source=$PWD/ovs_src -+ ./boot.sh && ./configure $* - } - - configure_ovn $EXTRA_OPTS $* -@@ -32,7 +31,7 @@ if ! "$@"; then - exit 1 - fi - if [ "$TESTSUITE" ] && [ "$CC" != "clang" ]; then -- export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS --with-ovs-source=$PWD/ovs_src" -+ export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS" - if ! make distcheck RECHECK=yes; then - # testsuite.log is necessary for debugging. - cat */_build/sub/tests/testsuite.log -diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml -index 7be75ca36..d825e257c 100644 ---- a/.github/workflows/test.yml -+++ b/.github/workflows/test.yml -@@ -48,6 +48,8 @@ jobs: - steps: - - name: checkout - uses: actions/checkout@v2 -+ with: -+ submodules: recursive - - - name: install required dependencies - run: sudo apt install -y ${{ env.dependencies }} -@@ -99,6 +101,8 @@ jobs: - steps: - - name: checkout - uses: actions/checkout@v2 -+ with: -+ submodules: recursive - - name: install dependencies - run: brew install automake libtool - - name: prepare -diff --git a/.gitignore b/.gitignore -index 7ca9b3859..68333384e 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -94,3 +94,5 @@ testsuite.tmp.orig - /.venv - /cxx-check - /*.ovsschema.stamp -+/compile_ovn.sh -+ -diff --git a/.gitmodules b/.gitmodules -new file mode 100644 -index 000000000..e083f6bde ---- /dev/null -+++ b/.gitmodules -@@ -0,0 +1,3 @@ -+[submodule "ovs"] -+ path = ovs -+ url = https://github.com/openvswitch/ovs -diff --git a/.gitreview b/.gitreview -new file mode 100644 -index 000000000..27e8042ac ---- /dev/null -+++ b/.gitreview -@@ -0,0 +1,6 @@ -+[gerrit] -+host=code.engineering.redhat.com -+port=22 -+project=ovn.git -+defaultbranch=ovn2.13 -+ -diff --git a/AUTHORS.rst b/AUTHORS.rst -index 5d926c11f..29c2c011c 100644 ---- a/AUTHORS.rst -+++ b/AUTHORS.rst -@@ -155,6 +155,7 @@ Geoffrey Wossum gwossum@acm.org - Gianluca Merlo gianluca.merlo@gmail.com - Giuseppe Lettieri g.lettieri@iet.unipi.it - Glen Gibb grg@stanford.edu -+Gongming Chen gmingchen@tencent.com - Guoshuai Li ligs@dtdream.com - Guolin Yang gyang@vmware.com - Guru Chaitanya Perakam gperakam@Brocade.com -diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst -index 65b1f4a40..cee99c63d 100644 ---- a/Documentation/intro/install/general.rst -+++ b/Documentation/intro/install/general.rst -@@ -66,6 +66,10 @@ 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/). -+ Open vSwitch is included as a submodule in the OVN source code. It is -+ kept at the minimum recommended version for OVN to operate optimally. -+ See below for instructions about how to use a different OVS source -+ location. - - - GNU make - -@@ -140,27 +144,44 @@ Bootstrapping - ------------- - - This step is not needed if you have downloaded a released tarball. If --you pulled the sources directly from an Open vSwitch Git tree or got a --Git tree snapshot, then run boot.sh in the top source directory to build -+you pulled the sources directly from an OVN Git tree or got a Git tree -+snapshot, then run boot.sh in the top source directory to build - the "configure" script:: - - $ ./boot.sh - --Before configuring OVN, clone, configure and build Open vSwitch. -+Before configuring OVN, build Open vSwitch. The easiest way to do this -+is to use the included OVS submodule in the OVN source:: -+ -+ $ git submodule update --init -+ $ cd ovs -+ $ ./boot.sh -+ $ ./configure -+ $ make -+ $ cd .. -+ -+It is not required to use the included OVS submodule; however the OVS -+submodule is guaranteed to be the minimum recommended version of OVS -+to ensure OVN's optimal operation. If you wish to use OVS source code -+from a different location on the file system, then be sure to configure -+and build OVS before building OVN. - - .. _general-configuring: - - Configuring - ----------- - --Configure the package by running the configure script. You need to --invoke configure with atleast the argument --with-ovs-source. --For example:: -+Then configure the package by running the configure script:: -+ -+ $ ./configure -+ -+If your OVS source directory is not the included OVS submodule, specify the -+location of the OVS source code using --with-ovs-source:: - - $ ./configure --with-ovs-source=/path/to/ovs/source - --If you have built Open vSwitch in a separate directory, then you --need to provide that path in the option - --with-ovs-build. -+If you have built Open vSwitch in a separate directory from its source -+code, then you need to provide that path in the option - --with-ovs-build. - - By default all files are installed under ``/usr/local``. OVN expects to find - its database in ``/usr/local/etc/ovn`` by default. -diff --git a/Makefile.am b/Makefile.am -index 7ce3d27e4..04a6d7c63 100644 ---- a/Makefile.am -+++ b/Makefile.am -@@ -48,6 +48,8 @@ AM_CFLAGS = -Wstrict-prototypes - AM_CFLAGS += $(WARNING_FLAGS) - AM_CFLAGS += $(OVS_CFLAGS) - -+AM_DISTCHECK_CONFIGURE_FLAGS = --with-ovs-source=$(PWD)/ovs -+ - if NDEBUG - AM_CPPFLAGS += -DNDEBUG - AM_CFLAGS += -fomit-frame-pointer -@@ -105,7 +107,9 @@ EXTRA_DIST = \ - ovn-ic-nb.ovsschema \ - ovn-ic-nb.xml \ - ovn-ic-sb.ovsschema \ -- ovn-ic-sb.xml -+ ovn-ic-sb.xml \ -+ .gitreview \ -+ compile_ovn.sh - bin_PROGRAMS = - sbin_PROGRAMS = - bin_SCRIPTS = -@@ -157,6 +161,7 @@ noinst_HEADERS += $(EXTRA_DIST) - - ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */' - ro_shell = printf '\043 Generated automatically -- do not modify! -*- buffer-read-only: t -*-\n' -+submodules = $(shell grep 'path =' $(srcdir)/.gitmodules | sed -E 's/[\t ]*path =\s*(.*)/\1/g' | xargs) - - SUFFIXES += .in - .in: -@@ -216,6 +221,8 @@ dist-hook-git: distfiles - @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1; then \ - (cd $(srcdir) && git ls-files) | grep -v '\.gitignore$$' | \ - grep -v '\.gitattributes$$' | \ -+ grep -v '\.gitmodules$$' | \ -+ grep -v "$(submodules)" | \ - LC_ALL=C sort -u > all-gitfiles; \ - LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \ - if test -s missing-distfiles; then \ -@@ -247,8 +254,8 @@ ALL_LOCAL += config-h-check - config-h-check: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ -- git --no-pager grep -L '#include ' `git ls-files | grep '\.c$$' | \ -- grep -vE '^ovs/datapath|^ovs/lib/sflow|^ovs/datapath-windows|^python|^ovs/python'`; \ -+ git --no-pager grep -L '#include ' `git ls-files | grep -v $(submodules) | grep '\.c$$' | \ -+ grep -vE '^python'`; \ - then \ - echo "See above for list of violations of the rule that"; \ - echo "every C source file must #include ."; \ -@@ -261,8 +268,7 @@ ALL_LOCAL += printf-check - printf-check: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ -- git --no-pager grep -n -E -e '%[-+ #0-9.*]*([ztj]|hh)' --and --not -e 'ovs_scan' `git ls-files | grep '\.[ch]$$' | \ -- grep -vE '^ovs/datapath|^ovs/lib/sflow'`; \ -+ git --no-pager grep -n -E -e '%[-+ #0-9.*]*([ztj]|hh)' --and --not -e 'ovs_scan' `git ls-files | grep -v $(submodules) | grep '\.[ch]$$'`; \ - then \ - echo "See above for list of violations of the rule that"; \ - echo "'z', 't', 'j', 'hh' printf() type modifiers are"; \ -@@ -288,7 +294,7 @@ ALL_LOCAL += check-assert-h-usage - check-assert-h-usage: - @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \ - (cd $(srcdir) && git --no-pager grep -l -E '[<]assert.h[>]') | \ -- $(EGREP) -v '^ovs/lib/(sflow_receiver|vlog).c$$|^ovs/tests/|^tests/'; \ -+ $(EGREP) -v '^tests/'; \ - then \ - echo "Files listed above unexpectedly #include <""assert.h"">."; \ - echo "Please use ovs_assert (from util.h) instead of assert."; \ -@@ -304,8 +310,7 @@ ALL_LOCAL += check-endian - check-endian: - @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \ - (cd $(srcdir) && git --no-pager grep -l -E \ -- -e 'BIG_ENDIAN|LITTLE_ENDIAN' --and --not -e 'BYTE_ORDER' | \ -- $(EGREP) -v '^ovs/datapath/|^ovs/include/sparse/rte_'); \ -+ -e 'BIG_ENDIAN|LITTLE_ENDIAN' --and --not -e 'BYTE_ORDER'); \ - then \ - echo "See above for list of files that misuse LITTLE""_ENDIAN"; \ - echo "or BIG""_ENDIAN. Please use WORDS_BIGENDIAN instead."; \ -@@ -329,9 +334,9 @@ check-tabs: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ - grep -ln "^ " \ -- `git ls-files \ -+ `git ls-files | grep -v $(submodules) \ - | grep -v -f build-aux/initial-tab-whitelist` /dev/null \ -- | $(EGREP) -v ':[ ]*/?\*'; \ -+ | $(EGREP) -v ':[ ]*/?\*'; \ - then \ - echo "See above for files that use tabs for indentation."; \ - echo "Please use spaces instead."; \ -@@ -344,8 +349,7 @@ thread-safety-check: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ - grep -n -f build-aux/thread-safety-blacklist \ -- `git ls-files | grep '\.[ch]$$' \ -- | $(EGREP) -v '^ovs/datapath|^ovs/lib/sflow'` /dev/null \ -+ `git ls-files | grep -v $(submodules) | grep '\.[ch]$$'` /dev/null \ - | $(EGREP) -v ':[ ]*/?\*'; \ - then \ - echo "See above for list of calls to functions that are"; \ -@@ -361,7 +365,7 @@ ALL_LOCAL += check-ifconfig - check-ifconfig: - @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \ - (cd $(srcdir) && git --no-pager grep -l -E -e 'ifconfig' | \ -- $(EGREP) -v 'Makefile.am|ovs-vsctl-bashcomp|openvswitch-custom\.te'); \ -+ $(EGREP) -v 'Makefile.am|openvswitch-custom\.te'); \ - then \ - echo "See above for list of files that use or reference"; \ - echo "'ifconfig'. Please use 'ip' instead."; \ -diff --git a/NEWS b/NEWS -index f71ec329c..57a9ba939 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,19 @@ -+Post-v20.12.0 -+------------------------- -+ - Support ECMP multiple nexthops for reroute router policies. -+ - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD -+ availability check for OVN static routes. -+ [0] https://tools.ietf.org/html/rfc5880) -+ - Change the semantic of the "Logical_Switch_Port.up" field such that it is -+ set to "true" only when all corresponding OVS openflow operations have -+ been processed. This also introduces a new "OVS.Interface.external-id", -+ "ovn-installed". This external-id is set by ovn-controller only after all -+ openflow operations corresponding to the OVS interface being added have -+ been processed. -+ - Add a new option to Load_Balancer.options, "hairpin_snat_ip", to allow -+ users to explicitly select which source IP should be used for load -+ balancer hairpin traffic. -+ - OVN v20.12.0 - 18 Dec 2020 - -------------------------- - - The "datapath" argument to ovn-trace is now optional, since the -diff --git a/acinclude.m4 b/acinclude.m4 -index a797adc82..2ca15cb33 100644 ---- a/acinclude.m4 -+++ b/acinclude.m4 -@@ -338,7 +338,7 @@ AC_DEFUN([OVN_CHECK_OVS], [ - AC_ERROR([$OVSDIR is not an OVS source directory]) - fi - else -- AC_ERROR([OVS source dir path needs to be specified (use --with-ovs-source)]) -+ OVSDIR=$srcdir/ovs - fi - - AC_MSG_RESULT([$OVSDIR]) -diff --git a/build-aux/initial-tab-whitelist b/build-aux/initial-tab-whitelist -index 216cd2ed3..b2f5a0791 100644 ---- a/build-aux/initial-tab-whitelist -+++ b/build-aux/initial-tab-whitelist -@@ -8,3 +8,4 @@ - ^xenserver/ - ^debian/rules.modules$ - ^debian/rules$ -+^\.gitmodules$ -diff --git a/compile_ovn.sh b/compile_ovn.sh -new file mode 100755 -index 000000000..1b980df4f ---- /dev/null -+++ b/compile_ovn.sh -@@ -0,0 +1,14 @@ -+#!/bin/bash -+ -+git submodule update --init -+ -+pushd ovs -+./boot.sh -+./configure --enable-Werror --enable-sparse -+make -j5 -+popd -+ -+./boot.sh -+./configure --enable-Werror --enable-sparse -+make -j5 -+ -diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c -index 83377157e..01d5a16d2 100644 ---- a/controller-vtep/binding.c -+++ b/controller-vtep/binding.c -@@ -109,7 +109,12 @@ 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); -+ } - } - } - -diff --git a/controller/automake.mk b/controller/automake.mk -index 45e1bdd36..9b8debd2f 100644 ---- a/controller/automake.mk -+++ b/controller/automake.mk -@@ -18,6 +18,8 @@ controller_ovn_controller_SOURCES = \ - controller/lport.h \ - controller/ofctrl.c \ - controller/ofctrl.h \ -+ controller/ofctrl-seqno.c \ -+ controller/ofctrl-seqno.h \ - controller/pinctrl.c \ - controller/pinctrl.h \ - controller/patch.c \ -@@ -25,7 +27,10 @@ controller_ovn_controller_SOURCES = \ - controller/ovn-controller.c \ - controller/ovn-controller.h \ - controller/physical.c \ -- controller/physical.h -+ controller/physical.h \ -+ controller/mac-learn.c \ -+ controller/mac-learn.h -+ - controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la - man_MANS += controller/ovn-controller.8 - EXTRA_DIST += controller/ovn-controller.8.xml -diff --git a/controller/binding.c b/controller/binding.c -index cb60c5d67..4e6c75696 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -18,6 +18,7 @@ - #include "ha-chassis.h" - #include "lflow.h" - #include "lport.h" -+#include "ofctrl-seqno.h" - #include "patch.h" - - #include "lib/bitmap.h" -@@ -34,6 +35,38 @@ - - VLOG_DEFINE_THIS_MODULE(binding); - -+/* External ID to be set in the OVS.Interface record when the OVS interface -+ * is ready for use, i.e., is bound to an OVN port and its corresponding -+ * flows have been installed. -+ */ -+#define OVN_INSTALLED_EXT_ID "ovn-installed" -+ -+/* Set of OVS interface IDs that have been released in the most recent -+ * processing iterations. This gets updated in release_lport() and is -+ * periodically emptied in binding_seqno_run(). -+ */ -+static struct sset binding_iface_released_set = -+ SSET_INITIALIZER(&binding_iface_released_set); -+ -+/* Set of OVS interface IDs that have been bound in the most recent -+ * processing iterations. This gets updated in release_lport() and is -+ * periodically emptied in binding_seqno_run(). -+ */ -+static struct sset binding_iface_bound_set = -+ SSET_INITIALIZER(&binding_iface_bound_set); -+ -+static void -+binding_iface_released_add(const char *iface_id) -+{ -+ sset_add(&binding_iface_released_set, iface_id); -+} -+ -+static void -+binding_iface_bound_add(const char *iface_id) -+{ -+ sset_add(&binding_iface_bound_set, iface_id); -+} -+ - #define OVN_QOS_TYPE "linux-htb" - - struct qos_queue { -@@ -688,6 +721,7 @@ local_binding_add_child(struct local_binding *lbinding, - struct local_binding *child) - { - local_binding_add(&lbinding->children, child); -+ child->parent = lbinding; - } - - static struct local_binding * -@@ -697,6 +731,13 @@ local_binding_find_child(struct local_binding *lbinding, - return local_binding_find(&lbinding->children, child_name); - } - -+static void -+local_binding_delete_child(struct local_binding *lbinding, -+ struct local_binding *child) -+{ -+ shash_find_and_delete(&lbinding->children, child->name); -+} -+ - static bool - is_lport_vif(const struct sbrec_port_binding *pb) - { -@@ -823,15 +864,52 @@ get_lport_type(const struct sbrec_port_binding *pb) - return LP_UNKNOWN; - } - -+/* For newly claimed ports, if 'notify_up' is 'false': -+ * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'. -+ * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for -+ * container and virtual ports). -+ * Otherwise request a notification to be sent when the OVS flows -+ * corresponding to 'pb' have been installed. -+ * -+ * Note: -+ * Updates (directly or through a notification) the 'pb->up' field only if -+ * it's explicitly set to 'false'. -+ * This is to ensure compatibility with older versions of ovn-northd. -+ */ -+static void -+claimed_lport_set_up(const struct sbrec_port_binding *pb, -+ const struct sbrec_port_binding *parent_pb, -+ const struct sbrec_chassis *chassis_rec, -+ bool notify_up) -+{ -+ if (!notify_up) { -+ bool up = true; -+ if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) { -+ sbrec_port_binding_set_up(pb, &up, 1); -+ } -+ return; -+ } -+ -+ if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) { -+ binding_iface_bound_add(pb->logical_port); -+ } -+} -+ - /* Returns false if lport is not claimed due to 'sb_readonly'. - * Returns true otherwise. - */ - static bool - claim_lport(const struct sbrec_port_binding *pb, -+ const struct sbrec_port_binding *parent_pb, - const struct sbrec_chassis *chassis_rec, - const struct ovsrec_interface *iface_rec, -- bool sb_readonly, struct hmap *tracked_datapaths) -+ bool sb_readonly, bool notify_up, -+ struct hmap *tracked_datapaths) - { -+ if (!sb_readonly) { -+ claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up); -+ } -+ - if (pb->chassis != chassis_rec) { - if (sb_readonly) { - return false; -@@ -900,7 +978,12 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - sbrec_port_binding_set_virtual_parent(pb, NULL); - } - -+ if (pb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(pb, &up, 1); -+ } - update_lport_tracking(pb, tracked_datapaths); -+ binding_iface_released_add(pb->logical_port); - VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); - return true; - } -@@ -958,8 +1041,7 @@ release_local_binding_children(const struct sbrec_chassis *chassis_rec, - } - } - -- /* Clear the local bindings' 'pb' and 'iface'. */ -- l->pb = NULL; -+ /* Clear the local bindings' 'iface'. */ - l->iface = NULL; - } - -@@ -998,8 +1080,12 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - if (lbinding_set) { - if (can_bind) { - /* We can claim the lport. */ -- if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface, -- !b_ctx_in->ovnsb_idl_txn, -+ const struct sbrec_port_binding *parent_pb = -+ lbinding->parent ? lbinding->parent->pb : NULL; -+ -+ if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec, -+ lbinding->iface, !b_ctx_in->ovnsb_idl_txn, -+ !lbinding->parent, - b_ctx_out->tracked_dp_bindings)){ - return false; - } -@@ -1203,8 +1289,8 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, - b_ctx_out->tracked_dp_bindings); - - update_local_lport_ids(pb, b_ctx_out); -- return claim_lport(pb, b_ctx_in->chassis_rec, NULL, -- !b_ctx_in->ovnsb_idl_txn, -+ return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL, -+ !b_ctx_in->ovnsb_idl_txn, false, - b_ctx_out->tracked_dp_bindings); - } else if (pb->chassis == b_ctx_in->chassis_rec) { - return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -@@ -2063,6 +2149,16 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - * when the interface change happens. */ - if (is_lport_container(pb)) { - remove_local_lports(pb->logical_port, b_ctx_out); -+ -+ /* If the container port is removed we should also remove it from -+ * its parent's children set. -+ */ -+ if (lbinding) { -+ if (lbinding->parent) { -+ local_binding_delete_child(lbinding->parent, lbinding); -+ } -+ local_binding_destroy(lbinding); -+ } - } - - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -@@ -2132,13 +2228,26 @@ bool - binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out) - { -- bool handled = true; -+ /* Run the tracked port binding loop twice to ensure correctness: -+ * 1. First to handle deleted changes. This is split in four sub-parts -+ * because child local bindings must be cleaned up first: -+ * a. Container ports first. -+ * b. Then virtual ports. -+ * c. Then regular VIFs. -+ * d. Last other ports. -+ * 2. Second to handle add/update changes. -+ */ -+ struct shash deleted_container_pbs = -+ SHASH_INITIALIZER(&deleted_container_pbs); -+ struct shash deleted_virtual_pbs = -+ SHASH_INITIALIZER(&deleted_virtual_pbs); -+ struct shash deleted_vif_pbs = -+ SHASH_INITIALIZER(&deleted_vif_pbs); -+ struct shash deleted_other_pbs = -+ SHASH_INITIALIZER(&deleted_other_pbs); - const struct sbrec_port_binding *pb; -+ bool handled = true; - -- /* Run the tracked port binding loop twice. One to handle deleted -- * changes. And another to handle add/update changes. -- * This will ensure correctness. -- */ - SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, - b_ctx_in->port_binding_table) { - if (!sbrec_port_binding_is_deleted(pb)) { -@@ -2146,18 +2255,60 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - } - - enum en_lport_type lport_type = get_lport_type(pb); -- if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) { -- handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in, -- b_ctx_out); -+ -+ if (lport_type == LP_VIF) { -+ if (is_lport_container(pb)) { -+ shash_add(&deleted_container_pbs, pb->logical_port, pb); -+ } else { -+ shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ } -+ } else if (lport_type == LP_VIRTUAL) { -+ shash_add(&deleted_virtual_pbs, pb->logical_port, pb); - } else { -- handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -+ shash_add(&deleted_other_pbs, pb->logical_port, pb); - } -+ } - -+ struct shash_node *node; -+ struct shash_node *node_next; -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_container_pbs, node); - if (!handled) { -- break; -+ goto delete_done; -+ } -+ } -+ -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_virtual_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIRTUAL, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_virtual_pbs, node); -+ if (!handled) { -+ goto delete_done; - } - } - -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_vif_pbs) { -+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ b_ctx_out); -+ shash_delete(&deleted_vif_pbs, node); -+ if (!handled) { -+ goto delete_done; -+ } -+ } -+ -+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_other_pbs) { -+ handle_deleted_lport(node->data, b_ctx_in, b_ctx_out); -+ shash_delete(&deleted_other_pbs, node); -+ } -+ -+delete_done: -+ shash_destroy(&deleted_container_pbs); -+ shash_destroy(&deleted_virtual_pbs); -+ shash_destroy(&deleted_vif_pbs); -+ shash_destroy(&deleted_other_pbs); -+ - if (!handled) { - return false; - } -@@ -2288,3 +2439,155 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - destroy_qos_map(&qos_map); - return handled; - } -+ -+/* Registered ofctrl seqno type for port_binding flow installation. */ -+static size_t binding_seq_type_pb_cfg; -+ -+/* Binding specific seqno to be acked by ofctrl when flows for new interfaces -+ * have been installed. -+ */ -+static uint32_t binding_iface_seqno = 0; -+ -+/* Map indexed by iface-id containing the sequence numbers that when acked -+ * indicate that the OVS flows for the iface-id have been installed. -+ */ -+static struct simap binding_iface_seqno_map = -+ SIMAP_INITIALIZER(&binding_iface_seqno_map); -+ -+void -+binding_init(void) -+{ -+ binding_seq_type_pb_cfg = ofctrl_seqno_add_type(); -+} -+ -+/* Processes new release/bind operations OVN ports. For newly bound ports -+ * it creates ofctrl seqno update requests that will be acked when -+ * corresponding OVS flows have been installed. -+ * -+ * NOTE: Should be called only when valid SB and OVS transactions are -+ * available. -+ */ -+void -+binding_seqno_run(struct shash *local_bindings) -+{ -+ const char *iface_id; -+ const char *iface_id_next; -+ -+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) { -+ struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ -+ /* If the local binding still exists (i.e., the OVS interface is -+ * still configured locally) then remove the external id and remove -+ * it from the in-flight seqno map. -+ */ -+ if (lb_node) { -+ struct local_binding *lb = lb_node->data; -+ -+ if (lb->iface && smap_get(&lb->iface->external_ids, -+ OVN_INSTALLED_EXT_ID)) { -+ ovsrec_interface_update_external_ids_delkey( -+ lb->iface, OVN_INSTALLED_EXT_ID); -+ } -+ } -+ simap_find_and_delete(&binding_iface_seqno_map, iface_id); -+ sset_delete(&binding_iface_released_set, -+ SSET_NODE_FROM_NAME(iface_id)); -+ } -+ -+ bool new_ifaces = false; -+ uint32_t new_seqno = binding_iface_seqno + 1; -+ -+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_bound_set) { -+ struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ -+ struct local_binding *lb = lb_node ? lb_node->data : NULL; -+ -+ /* Make sure the binding is still complete, i.e., both SB port_binding -+ * and OVS interface still exist. -+ * -+ * If so, then this is a newly bound interface, make sure we reset the -+ * Port_Binding 'up' field and the OVS Interface 'external-id'. -+ */ -+ if (lb && lb->pb && lb->iface) { -+ new_ifaces = true; -+ -+ if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) { -+ ovsrec_interface_update_external_ids_delkey( -+ lb->iface, OVN_INSTALLED_EXT_ID); -+ } -+ if (lb->pb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(lb->pb, &up, 1); -+ } -+ simap_put(&binding_iface_seqno_map, lb->name, new_seqno); -+ } -+ sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id)); -+ } -+ -+ /* Request a seqno update when the flows for new interfaces have been -+ * installed in OVS. -+ */ -+ if (new_ifaces) { -+ binding_iface_seqno = new_seqno; -+ ofctrl_seqno_update_create(binding_seq_type_pb_cfg, new_seqno); -+ } -+} -+ -+/* Processes ofctrl seqno ACKs for new bindings. Sets the -+ * 'OVN_INSTALLED_EXT_ID' external-id in the OVS interface and the -+ * Port_Binding.up field for all ports for which OVS flows have been -+ * installed. -+ * -+ * NOTE: Should be called only when valid SB and OVS transactions are -+ * available. -+ */ -+void -+binding_seqno_install(struct shash *local_bindings) -+{ -+ struct ofctrl_acked_seqnos *acked_seqnos = -+ ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg); -+ struct simap_node *node; -+ struct simap_node *node_next; -+ -+ SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { -+ struct shash_node *lb_node = shash_find(local_bindings, node->name); -+ -+ if (!lb_node) { -+ goto del_seqno; -+ } -+ -+ struct local_binding *lb = lb_node->data; -+ if (!lb->pb || !lb->iface) { -+ goto del_seqno; -+ } -+ -+ if (!ofctrl_acked_seqnos_contains(acked_seqnos, node->data)) { -+ continue; -+ } -+ -+ ovsrec_interface_update_external_ids_setkey(lb->iface, -+ OVN_INSTALLED_EXT_ID, -+ "true"); -+ if (lb->pb->n_up) { -+ bool up = true; -+ -+ sbrec_port_binding_set_up(lb->pb, &up, 1); -+ struct shash_node *child_node; -+ SHASH_FOR_EACH (child_node, &lb->children) { -+ struct local_binding *lb_child = child_node->data; -+ sbrec_port_binding_set_up(lb_child->pb, &up, 1); -+ } -+ } -+ -+del_seqno: -+ simap_delete(&binding_iface_seqno_map, node); -+ } -+ -+ ofctrl_acked_seqnos_destroy(acked_seqnos); -+} -+ -+void -+binding_seqno_flush(void) -+{ -+ simap_clear(&binding_iface_seqno_map); -+} -diff --git a/controller/binding.h b/controller/binding.h -index c9740560f..c9ebef4b1 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -100,6 +100,7 @@ struct local_binding { - - /* shash of 'struct local_binding' representing children. */ - struct shash children; -+ struct local_binding *parent; - }; - - static inline struct local_binding * -@@ -134,4 +135,9 @@ bool binding_handle_ovs_interface_changes(struct binding_ctx_in *, - bool binding_handle_port_binding_changes(struct binding_ctx_in *, - struct binding_ctx_out *); - void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); -+ -+void binding_init(void); -+void binding_seqno_run(struct shash *local_bindings); -+void binding_seqno_install(struct shash *local_bindings); -+void binding_seqno_flush(void); - #endif /* controller/binding.h */ -diff --git a/controller/chassis.c b/controller/chassis.c -index b4d4b0e37..0937e33e6 100644 ---- a/controller/chassis.c -+++ b/controller/chassis.c -@@ -28,6 +28,7 @@ - #include "lib/ovn-sb-idl.h" - #include "ovn-controller.h" - #include "lib/util.h" -+#include "ovn/features.h" - - VLOG_DEFINE_THIS_MODULE(chassis); - -@@ -293,6 +294,7 @@ chassis_build_other_config(struct smap *config, const char *bridge_mappings, - smap_replace(config, "iface-types", iface_types); - smap_replace(config, "ovn-chassis-mac-mappings", chassis_macs); - smap_replace(config, "is-interconn", is_interconn ? "true" : "false"); -+ smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true"); - } - - /* -@@ -363,6 +365,11 @@ chassis_other_config_changed(const char *bridge_mappings, - return true; - } - -+ if (!smap_get_bool(&chassis_rec->other_config, OVN_FEATURE_PORT_UP_NOTIF, -+ false)) { -+ return true; -+ } -+ - return false; - } - -diff --git a/controller/lflow.c b/controller/lflow.c -index c02585b1e..76a4deaa0 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -88,6 +88,11 @@ static void lflow_resource_destroy_lflow(struct lflow_resource_ref *, - static bool - lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp) - { -+ if (!strcmp(port_name, "none")) { -+ *portp = 0; -+ return true; -+ } -+ - const struct lookup_port_aux *aux = aux_; - - const struct sbrec_port_binding *pb -@@ -480,22 +485,19 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, - struct controller_event_options controller_event_opts; - controller_event_opts_init(&controller_event_opts); - -- /* Handle flow removing first (for deleted or updated lflows), and then -- * handle reprocessing or adding flows, so that when the flows being -- * removed and added with same match conditions can be processed in the -- * proper order */ -- -+ /* Flood remove the flows for all the tracked lflows. Its possible that -+ * lflow_add_flows_for_datapath() may have been called before calling -+ * this function. */ - struct hmap flood_remove_nodes = HMAP_INITIALIZER(&flood_remove_nodes); - struct ofctrl_flood_remove_node *ofrn, *next; - SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, - l_ctx_in->logical_flow_table) { -+ VLOG_DBG("delete lflow "UUID_FMT, UUID_ARGS(&lflow->header_.uuid)); -+ ofrn = xmalloc(sizeof *ofrn); -+ ofrn->sb_uuid = lflow->header_.uuid; -+ hmap_insert(&flood_remove_nodes, &ofrn->hmap_node, -+ uuid_hash(&ofrn->sb_uuid)); - if (!sbrec_logical_flow_is_new(lflow)) { -- VLOG_DBG("delete lflow "UUID_FMT, -- UUID_ARGS(&lflow->header_.uuid)); -- ofrn = xmalloc(sizeof *ofrn); -- ofrn->sb_uuid = lflow->header_.uuid; -- hmap_insert(&flood_remove_nodes, &ofrn->hmap_node, -- uuid_hash(&ofrn->sb_uuid)); - if (l_ctx_out->lflow_cache_map) { - lflow_cache_delete(l_ctx_out->lflow_cache_map, lflow); - } -@@ -525,21 +527,6 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in, - } - hmap_destroy(&flood_remove_nodes); - -- /* Now handle new lflows only. */ -- SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow, -- l_ctx_in->logical_flow_table) { -- if (sbrec_logical_flow_is_new(lflow)) { -- VLOG_DBG("add lflow "UUID_FMT, -- UUID_ARGS(&lflow->header_.uuid)); -- if (!consider_logical_flow(lflow, &dhcp_opts, &dhcpv6_opts, -- &nd_ra_opts, &controller_event_opts, -- l_ctx_in, l_ctx_out)) { -- ret = false; -- l_ctx_out->conj_id_overflow = true; -- break; -- } -- } -- } - dhcp_opts_destroy(&dhcp_opts); - dhcp_opts_destroy(&dhcpv6_opts); - nd_ra_opts_destroy(&nd_ra_opts); -@@ -668,9 +655,8 @@ update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs) - 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, -+ struct hmap *matches, uint8_t ptable, -+ uint8_t output_ptable, struct ofpbuf *ovnacts, - bool ingress, struct lflow_ctx_in *l_ctx_in, - struct lflow_ctx_out *l_ctx_out) - { -@@ -702,15 +688,14 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - .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, -+ .fdb_ptable = OFTABLE_GET_FDB, -+ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB, - }; - 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(dp->tunnel_key)); -- if (m->match.wc.masks.conj_id) { -- m->match.flow.conj_id += conj_id_ofs; -- } - if (datapath_is_switch(dp)) { - unsigned int reg_index - = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0; -@@ -744,7 +729,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - struct ofpact_conjunction *dst; - - dst = ofpact_put_CONJUNCTION(&conj); -- dst->id = src->id + conj_id_ofs; -+ dst->id = src->id; - dst->clause = src->clause; - dst->n_clauses = src->n_clauses; - } -@@ -915,9 +900,9 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - return true; - } - -- 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); -+ expr_matches_prepare(&matches, *l_ctx_out->conj_id_ofs); -+ add_matches_to_flow_table(lflow, dp, &matches, ptable, output_ptable, -+ &ovnacts, ingress, l_ctx_in, l_ctx_out); - - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); -@@ -930,10 +915,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - lflow_cache_get(l_ctx_out->lflow_cache_map, lflow); - - if (lc && lc->type == LCACHE_T_MATCHES) { -- /* 'matches' is cached. No need to do expr parsing. -+ /* 'matches' is cached. No need to do expr parsing and no need -+ * to call expr_matches_prepare() to update the conj ids. - * Add matches to flow table and return. */ -- add_matches_to_flow_table(lflow, dp, lc->expr_matches, lc->conj_id_ofs, -- ptable, output_ptable, &ovnacts, ingress, -+ add_matches_to_flow_table(lflow, dp, lc->expr_matches, ptable, -+ output_ptable, &ovnacts, ingress, - l_ctx_in, l_ctx_out); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); -@@ -1009,10 +995,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow, - } - } - -+ expr_matches_prepare(matches, lc->conj_id_ofs); -+ - /* Encode OVN logical actions into OpenFlow. */ -- add_matches_to_flow_table(lflow, dp, matches, lc->conj_id_ofs, -- ptable, output_ptable, &ovnacts, ingress, -- l_ctx_in, l_ctx_out); -+ add_matches_to_flow_table(lflow, dp, matches, ptable, output_ptable, -+ &ovnacts, ingress, l_ctx_in, l_ctx_out); - ovnacts_free(ovnacts.data, ovnacts.size); - ofpbuf_uninit(&ovnacts); - -@@ -1080,6 +1067,18 @@ put_load(const uint8_t *data, size_t len, - bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); - } - -+static void -+put_load64(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, -+ struct ofpbuf *ofpacts) -+{ -+ struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts, -+ mf_from_id(dst), NULL, -+ NULL); -+ ovs_be64 n_value = htonll(value); -+ bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits); -+ bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits); -+} -+ - static void - consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name, - const struct hmap *local_datapaths, -@@ -1173,6 +1172,184 @@ add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name, - } - } - -+/* Builds the "learn()" action to be triggered by packets initiating a -+ * hairpin session. -+ * -+ * This will generate flows in table OFTABLE_CHK_LB_HAIRPIN_REPLY of the form: -+ * - match: -+ * metadata=,ip/ipv6,ip.src=,ip.dst= -+ * nw_proto='lb_proto',tp_src_port= -+ * - action: -+ * set MLF_LOOKUP_LB_HAIRPIN_BIT=1 -+ */ -+static void -+add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip, -+ uint8_t lb_proto, bool has_l4_port, -+ uint64_t cookie, struct ofpbuf *ofpacts) -+{ -+ struct match match = MATCH_CATCHALL_INITIALIZER; -+ struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts); -+ struct ofpact_learn_spec *ol_spec; -+ unsigned int imm_bytes; -+ uint8_t *src_imm; -+ -+ /* Once learned, hairpin reply flows are permanent until the VIP/backend -+ * is removed. -+ */ -+ ol->flags = NX_LEARN_F_DELETE_LEARNED; -+ ol->idle_timeout = OFP_FLOW_PERMANENT; -+ ol->hard_timeout = OFP_FLOW_PERMANENT; -+ ol->priority = OFP_DEFAULT_PRIORITY; -+ ol->table_id = OFTABLE_CHK_LB_HAIRPIN_REPLY; -+ ol->cookie = htonll(cookie); -+ -+ /* Match on metadata of the packet that created the hairpin session. */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ -+ ol_spec->dst.field = mf_from_id(MFF_METADATA); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ ol_spec->src.field = mf_from_id(MFF_METADATA); -+ -+ /* Match on the same ETH type as the packet that created the hairpin -+ * session. -+ */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ union mf_value imm_eth_type = { -+ .be16 = !vip6 ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6) -+ }; -+ mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec'. */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_eth_type, imm_bytes); -+ -+ /* Hairpin replies have ip.src == . */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ if (!vip6) { -+ ol_spec->dst.field = mf_from_id(MFF_IPV4_SRC); -+ ol_spec->src.field = mf_from_id(MFF_IPV4_SRC); -+ } else { -+ ol_spec->dst.field = mf_from_id(MFF_IPV6_SRC); -+ ol_spec->src.field = mf_from_id(MFF_IPV6_SRC); -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ -+ /* Hairpin replies have ip.dst == . */ -+ union mf_value imm_ip; -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ if (!vip6) { -+ ol_spec->dst.field = mf_from_id(MFF_IPV4_DST); -+ imm_ip = (union mf_value) { -+ .be32 = vip -+ }; -+ } else { -+ ol_spec->dst.field = mf_from_id(MFF_IPV6_DST); -+ imm_ip = (union mf_value) { -+ .ipv6 = *vip6 -+ }; -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ mf_write_subfield_value(&ol_spec->dst, &imm_ip, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_ip, imm_bytes); -+ -+ /* Hairpin replies have the same nw_proto as packets that created the -+ * session. -+ */ -+ union mf_value imm_proto = { -+ .u8 = lb_proto, -+ }; -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_IP_PROTO); -+ ol_spec->src.field = mf_from_id(MFF_IP_PROTO); -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_proto, imm_bytes); -+ -+ /* Hairpin replies have source port == . */ -+ if (has_l4_port) { -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ switch (lb_proto) { -+ case IPPROTO_TCP: -+ ol_spec->dst.field = mf_from_id(MFF_TCP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_TCP_DST); -+ break; -+ case IPPROTO_UDP: -+ ol_spec->dst.field = mf_from_id(MFF_UDP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_UDP_DST); -+ break; -+ case IPPROTO_SCTP: -+ ol_spec->dst.field = mf_from_id(MFF_SCTP_SRC); -+ ol_spec->src.field = mf_from_id(MFF_SCTP_DST); -+ break; -+ default: -+ OVS_NOT_REACHED(); -+ break; -+ } -+ ol_spec->dst.ofs = 0; -+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_MATCH; -+ ol_spec->src_type = NX_LEARN_SRC_FIELD; -+ } -+ -+ /* Set MLF_LOOKUP_LB_HAIRPIN_BIT for hairpin replies. */ -+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec); -+ ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS); -+ ol_spec->dst.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT; -+ ol_spec->dst.n_bits = 1; -+ ol_spec->n_bits = ol_spec->dst.n_bits; -+ ol_spec->dst_type = NX_LEARN_DST_LOAD; -+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE; -+ union mf_value imm_reg_value = { -+ .u8 = 1 -+ }; -+ mf_write_subfield_value(&ol_spec->dst, &imm_reg_value, &match); -+ -+ /* Push value last, as this may reallocate 'ol_spec' */ -+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8); -+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes)); -+ memcpy(src_imm, &imm_reg_value, imm_bytes); -+ -+ ofpact_finish_LEARN(ofpacts, &ol); -+} -+ -+/* Adds flows to detect hairpin sessions. -+ * -+ * For backwards compatibilty with older ovn-northd versions, uses -+ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the -+ * original destination tuple stored by ovn-northd. -+ */ - static void - add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - struct ovn_lb_vip *lb_vip, -@@ -1182,43 +1359,81 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - { - uint64_t stub[1024 / 8]; - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); -+ struct match hairpin_match = MATCH_CATCHALL_INITIALIZER; - - 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; -+ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching -+ * on ct_state first. -+ */ -+ if (!lb->hairpin_orig_tuple) { -+ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT; -+ match_set_ct_state_masked(&hairpin_match, ct_state, ct_state); -+ } - - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip); -+ ovs_be32 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip); -+ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip); -+ ovs_be32 snat_vip4 = lb->hairpin_snat_ips.n_ipv4_addrs -+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr -+ : vip4; - - 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)); -+ match_set_nw_src(&hairpin_match, bip4); -+ match_set_nw_dst(&hairpin_match, bip4); -+ -+ if (!lb->hairpin_orig_tuple) { -+ match_set_ct_nw_dst(&hairpin_match, vip4); -+ } else { -+ match_set_reg(&hairpin_match, -+ MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0, -+ ntohl(vip4)); -+ } -+ -+ add_lb_vip_hairpin_reply_action(NULL, snat_vip4, lb_proto, -+ lb_backend->port, -+ lb->slb->header_.uuid.parts[0], -+ &ofpacts); - } else { -+ struct in6_addr *bip6 = &lb_backend->ip; -+ struct in6_addr *snat_vip6 = -+ lb->hairpin_snat_ips.n_ipv6_addrs -+ ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr -+ : &lb_vip->vip; - 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_ipv6_src(&hairpin_match, bip6); -+ match_set_ipv6_dst(&hairpin_match, bip6); - -- 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->hairpin_orig_tuple) { -+ match_set_ct_ipv6_dst(&hairpin_match, &lb_vip->vip); -+ } else { -+ ovs_be128 vip6_value; -+ -+ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value); -+ match_set_xxreg(&hairpin_match, -+ MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0, -+ ntoh128(vip6_value)); -+ } -+ -+ add_lb_vip_hairpin_reply_action(snat_vip6, 0, lb_proto, -+ lb_backend->port, -+ lb->slb->header_.uuid.parts[0], -+ &ofpacts); - } - - 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)); -+ if (!lb->hairpin_orig_tuple) { -+ match_set_ct_nw_proto(&hairpin_match, lb_proto); -+ match_set_ct_tp_dst(&hairpin_match, htons(lb_vip->vip_port)); -+ } else { -+ match_set_reg_masked(&hairpin_match, -+ MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0, -+ lb_vip->vip_port, UINT16_MAX); -+ } - } - - /* In the original direction, only match on traffic that was already -@@ -1239,23 +1454,19 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb, - 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); - } - -+/* Adds flows to perform SNAT for hairpin sessions. -+ * -+ * For backwards compatibilty with older ovn-northd versions, uses -+ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the -+ * original destination tuple stored by ovn-northd. -+ */ - static void - add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, - struct ovn_lb_vip *lb_vip, -+ uint8_t lb_proto, - struct ovn_desired_flow_table *flow_table) - { - uint64_t stub[1024 / 8]; -@@ -1279,25 +1490,65 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb, - - 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); -+ nat->range.addr.ipv4.min = -+ lb->hairpin_snat_ips.n_ipv4_addrs -+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr -+ : in6_addr_get_mapped_ipv4(&lb_vip->vip); - } else { - nat->range_af = AF_INET6; -- nat->range.addr.ipv6.min = lb_vip->vip; -+ nat->range.addr.ipv6.min -+ = lb->hairpin_snat_ips.n_ipv6_addrs -+ ? lb->hairpin_snat_ips.ipv6_addrs[0].addr -+ : lb_vip->vip; - } - ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset); - ofpact_finish(&ofpacts, &ct->ofpact); - - struct match match = MATCH_CATCHALL_INITIALIZER; -+ -+ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching -+ * on ct_state first. -+ */ -+ if (!lb->hairpin_orig_tuple) { -+ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT; -+ match_set_ct_state_masked(&match, ct_state, ct_state); -+ } -+ - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip); -+ - match_set_dl_type(&match, htons(ETH_TYPE_IP)); -- match_set_ct_nw_dst(&match, nat->range.addr.ipv4.min); -+ -+ if (!lb->hairpin_orig_tuple) { -+ match_set_ct_nw_dst(&match, vip4); -+ } else { -+ match_set_reg(&match, MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0, -+ ntohl(vip4)); -+ } - } else { - match_set_dl_type(&match, htons(ETH_TYPE_IPV6)); -- match_set_ct_ipv6_dst(&match, &lb_vip->vip); -+ -+ if (!lb->hairpin_orig_tuple) { -+ match_set_ct_ipv6_dst(&match, &lb_vip->vip); -+ } else { -+ ovs_be128 vip6_value; -+ -+ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value); -+ match_set_xxreg(&match, MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0, -+ ntoh128(vip6_value)); -+ } - } - -- uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT; -- match_set_ct_state_masked(&match, ct_state, ct_state); -+ match_set_nw_proto(&match, lb_proto); -+ if (lb_vip->vip_port) { -+ if (!lb->hairpin_orig_tuple) { -+ match_set_ct_nw_proto(&match, lb_proto); -+ match_set_ct_tp_dst(&match, htons(lb_vip->vip_port)); -+ } else { -+ match_set_reg_masked(&match, MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0, -+ lb_vip->vip_port, UINT16_MAX); -+ } -+ } - - for (size_t i = 0; i < lb->slb->n_datapaths; i++) { - match_set_metadata(&match, -@@ -1351,7 +1602,7 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb, - flow_table); - } - -- add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table); -+ add_lb_ct_snat_vip_flows(lb, lb_vip, lb_proto, flow_table); - } - - ovn_controller_lb_destroy(lb); -@@ -1404,6 +1655,61 @@ lflow_handle_changed_neighbors( - } - } - -+static void -+consider_fdb_flows(const struct sbrec_fdb *fdb, -+ const struct hmap *local_datapaths, -+ struct ovn_desired_flow_table *flow_table) -+{ -+ if (!get_local_datapath(local_datapaths, fdb->dp_key)) { -+ return; -+ } -+ -+ struct eth_addr mac; -+ if (!eth_addr_from_string(fdb->mac, &mac)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad 'mac' %s", fdb->mac); -+ return; -+ } -+ -+ struct match match = MATCH_CATCHALL_INITIALIZER; -+ match_set_metadata(&match, htonll(fdb->dp_key)); -+ match_set_dl_dst(&match, mac); -+ -+ uint64_t stub[1024 / 8]; -+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); -+ put_load64(fdb->port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); -+ ofctrl_add_flow(flow_table, OFTABLE_GET_FDB, 100, -+ fdb->header_.uuid.parts[0], &match, &ofpacts, -+ &fdb->header_.uuid); -+ ofpbuf_clear(&ofpacts); -+ -+ uint8_t value = 1; -+ put_load(&value, sizeof value, MFF_LOG_FLAGS, -+ MLF_LOOKUP_FDB_BIT, 1, &ofpacts); -+ -+ struct match lookup_match = MATCH_CATCHALL_INITIALIZER; -+ match_set_metadata(&lookup_match, htonll(fdb->dp_key)); -+ match_set_dl_src(&lookup_match, mac); -+ match_set_reg(&lookup_match, MFF_LOG_INPORT - MFF_REG0, fdb->port_key); -+ ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100, -+ fdb->header_.uuid.parts[0], &lookup_match, &ofpacts, -+ &fdb->header_.uuid); -+ ofpbuf_uninit(&ofpacts); -+} -+ -+/* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN -+ * southbound database. */ -+static void -+add_fdb_flows(const struct sbrec_fdb_table *fdb_table, -+ const struct hmap *local_datapaths, -+ struct ovn_desired_flow_table *flow_table) -+{ -+ const struct sbrec_fdb *fdb; -+ SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) { -+ consider_fdb_flows(fdb, local_datapaths, flow_table); -+ } -+} -+ - - /* Translates logical flows in the Logical_Flow table in the OVN_SB database - * into OpenFlow flows. See ovn-architecture(7) for more information. */ -@@ -1431,6 +1737,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out) - l_ctx_out->flow_table); - add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths, - l_ctx_out->flow_table); -+ add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths, -+ l_ctx_out->flow_table); - } - - void -@@ -1582,3 +1890,37 @@ lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in, - - return true; - } -+ -+bool -+lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in, -+ struct lflow_ctx_out *l_ctx_out) -+{ -+ const struct sbrec_fdb *fdb; -+ -+ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) { -+ if (sbrec_fdb_is_deleted(fdb)) { -+ VLOG_DBG("Remove fdb flows for deleted fdb "UUID_FMT, -+ UUID_ARGS(&fdb->header_.uuid)); -+ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid); -+ } -+ } -+ -+ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) { -+ if (sbrec_fdb_is_deleted(fdb)) { -+ continue; -+ } -+ -+ if (!sbrec_fdb_is_new(fdb)) { -+ VLOG_DBG("Remove fdb flows for updated fdb "UUID_FMT, -+ UUID_ARGS(&fdb->header_.uuid)); -+ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid); -+ } -+ -+ VLOG_DBG("Add fdb flows for fdb "UUID_FMT, -+ UUID_ARGS(&fdb->header_.uuid)); -+ consider_fdb_flows(fdb, l_ctx_in->local_datapaths, -+ l_ctx_out->flow_table); -+ } -+ -+ return true; -+} -diff --git a/controller/lflow.h b/controller/lflow.h -index ba79cc374..2eb2cb112 100644 ---- a/controller/lflow.h -+++ b/controller/lflow.h -@@ -60,9 +60,9 @@ struct uuid; - * you make any changes. */ - #define OFTABLE_PHY_TO_LOG 0 - #define OFTABLE_LOG_INGRESS_PIPELINE 8 /* First of LOG_PIPELINE_LEN tables. */ --#define OFTABLE_REMOTE_OUTPUT 32 --#define OFTABLE_LOCAL_OUTPUT 33 --#define OFTABLE_CHECK_LOOPBACK 34 -+#define OFTABLE_REMOTE_OUTPUT 37 -+#define OFTABLE_LOCAL_OUTPUT 38 -+#define OFTABLE_CHECK_LOOPBACK 39 - #define OFTABLE_LOG_EGRESS_PIPELINE 40 /* First of LOG_PIPELINE_LEN tables. */ - #define OFTABLE_SAVE_INPORT 64 - #define OFTABLE_LOG_TO_PHY 65 -@@ -71,9 +71,8 @@ struct uuid; - #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 -+#define OFTABLE_GET_FDB 71 -+#define OFTABLE_LOOKUP_FDB 72 - - enum ref_type { - REF_TYPE_ADDRSET, -@@ -136,6 +135,7 @@ struct lflow_ctx_in { - 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_fdb_table *fdb_table; - const struct sbrec_chassis *chassis; - const struct sbrec_load_balancer_table *lb_table; - const struct hmap *local_datapaths; -@@ -167,6 +167,7 @@ void lflow_handle_changed_neighbors( - const struct hmap *local_datapaths, - struct ovn_desired_flow_table *); - bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *); -+bool lflow_handle_changed_fdbs(struct lflow_ctx_in *, struct lflow_ctx_out *); - void lflow_destroy(void); - - void lflow_cache_init(struct hmap *); -diff --git a/controller/mac-learn.c b/controller/mac-learn.c -new file mode 100644 -index 000000000..27634dca8 ---- /dev/null -+++ b/controller/mac-learn.c -@@ -0,0 +1,180 @@ -+/* 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 "mac-learn.h" -+ -+/* OpenvSwitch lib includes. */ -+#include "openvswitch/vlog.h" -+#include "lib/packets.h" -+#include "lib/smap.h" -+ -+VLOG_DEFINE_THIS_MODULE(mac_learn); -+ -+#define MAX_MAC_BINDINGS 1000 -+#define MAX_FDB_ENTRIES 1000 -+ -+static size_t mac_binding_hash(uint32_t dp_key, uint32_t port_key, -+ struct in6_addr *); -+static struct mac_binding *mac_binding_find(struct hmap *mac_bindings, -+ uint32_t dp_key, -+ uint32_t port_key, -+ struct in6_addr *ip, size_t hash); -+static size_t fdb_entry_hash(uint32_t dp_key, struct eth_addr *); -+ -+static struct fdb_entry *fdb_entry_find(struct hmap *fdbs, uint32_t dp_key, -+ struct eth_addr *mac, size_t hash); -+ -+/* mac_binding functions. */ -+void -+ovn_mac_bindings_init(struct hmap *mac_bindings) -+{ -+ hmap_init(mac_bindings); -+} -+ -+void -+ovn_mac_bindings_flush(struct hmap *mac_bindings) -+{ -+ struct mac_binding *mb; -+ HMAP_FOR_EACH_POP (mb, hmap_node, mac_bindings) { -+ free(mb); -+ } -+} -+ -+void -+ovn_mac_bindings_destroy(struct hmap *mac_bindings) -+{ -+ ovn_mac_bindings_flush(mac_bindings); -+ hmap_destroy(mac_bindings); -+} -+ -+struct mac_binding * -+ovn_mac_binding_add(struct hmap *mac_bindings, uint32_t dp_key, -+ uint32_t port_key, struct in6_addr *ip, -+ struct eth_addr mac) -+{ -+ uint32_t hash = mac_binding_hash(dp_key, port_key, ip); -+ -+ struct mac_binding *mb = -+ mac_binding_find(mac_bindings, dp_key, port_key, ip, hash); -+ if (!mb) { -+ if (hmap_count(mac_bindings) >= MAX_MAC_BINDINGS) { -+ return NULL; -+ } -+ -+ mb = xmalloc(sizeof *mb); -+ mb->dp_key = dp_key; -+ mb->port_key = port_key; -+ mb->ip = *ip; -+ hmap_insert(mac_bindings, &mb->hmap_node, hash); -+ } -+ mb->mac = mac; -+ -+ return mb; -+} -+ -+/* fdb functions. */ -+void -+ovn_fdb_init(struct hmap *fdbs) -+{ -+ hmap_init(fdbs); -+} -+ -+void -+ovn_fdbs_flush(struct hmap *fdbs) -+{ -+ struct fdb_entry *fdb_e; -+ HMAP_FOR_EACH_POP (fdb_e, hmap_node, fdbs) { -+ free(fdb_e); -+ } -+} -+ -+void -+ovn_fdbs_destroy(struct hmap *fdbs) -+{ -+ ovn_fdbs_flush(fdbs); -+ hmap_destroy(fdbs); -+} -+ -+struct fdb_entry * -+ovn_fdb_add(struct hmap *fdbs, uint32_t dp_key, struct eth_addr mac, -+ uint32_t port_key) -+{ -+ uint32_t hash = fdb_entry_hash(dp_key, &mac); -+ -+ struct fdb_entry *fdb_e = -+ fdb_entry_find(fdbs, dp_key, &mac, hash); -+ if (!fdb_e) { -+ if (hmap_count(fdbs) >= MAX_FDB_ENTRIES) { -+ return NULL; -+ } -+ -+ fdb_e = xzalloc(sizeof *fdb_e); -+ fdb_e->dp_key = dp_key; -+ fdb_e->mac = mac; -+ hmap_insert(fdbs, &fdb_e->hmap_node, hash); -+ } -+ fdb_e->port_key = port_key; -+ -+ return fdb_e; -+ -+} -+ -+/* mac_binding related static functions. */ -+ -+static size_t -+mac_binding_hash(uint32_t dp_key, uint32_t port_key, struct in6_addr *ip) -+{ -+ return hash_bytes(ip, sizeof *ip, hash_2words(dp_key, port_key)); -+} -+ -+static struct mac_binding * -+mac_binding_find(struct hmap *mac_bindings, uint32_t dp_key, -+ uint32_t port_key, struct in6_addr *ip, size_t hash) -+{ -+ struct mac_binding *mb; -+ HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, mac_bindings) { -+ if (mb->dp_key == dp_key && mb->port_key == port_key && -+ IN6_ARE_ADDR_EQUAL(&mb->ip, ip)) { -+ return mb; -+ } -+ } -+ -+ return NULL; -+} -+ -+/* fdb related static functions. */ -+ -+static size_t -+fdb_entry_hash(uint32_t dp_key, struct eth_addr *mac) -+{ -+ uint64_t mac64 = eth_addr_to_uint64(*mac); -+ return hash_2words(dp_key, hash_uint64(mac64)); -+} -+ -+static struct fdb_entry * -+fdb_entry_find(struct hmap *fdbs, uint32_t dp_key, -+ struct eth_addr *mac, size_t hash) -+{ -+ struct fdb_entry *fdb_e; -+ HMAP_FOR_EACH_WITH_HASH (fdb_e, hmap_node, hash, fdbs) { -+ if (fdb_e->dp_key == dp_key && eth_addr_equals(fdb_e->mac, *mac)) { -+ return fdb_e; -+ } -+ } -+ -+ return NULL; -+} -diff --git a/controller/mac-learn.h b/controller/mac-learn.h -new file mode 100644 -index 000000000..e7e8ba2d3 ---- /dev/null -+++ b/controller/mac-learn.h -@@ -0,0 +1,66 @@ -+/* -+ * 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_MAC_LEARN_H -+#define OVN_MAC_LEARN_H 1 -+ -+#include -+#include -+#include "openvswitch/hmap.h" -+ -+struct mac_binding { -+ struct hmap_node hmap_node; /* In a hmap. */ -+ -+ /* Key. */ -+ uint32_t dp_key; -+ uint32_t port_key; /* Port from where this mac_binding is learnt. */ -+ struct in6_addr ip; -+ -+ /* Value. */ -+ struct eth_addr mac; -+}; -+ -+void ovn_mac_bindings_init(struct hmap *mac_bindings); -+void ovn_mac_bindings_flush(struct hmap *mac_bindings); -+void ovn_mac_bindings_destroy(struct hmap *mac_bindings); -+ -+struct mac_binding *ovn_mac_binding_add(struct hmap *mac_bindings, -+ uint32_t dp_key, uint32_t port_key, -+ struct in6_addr *ip, -+ struct eth_addr mac); -+ -+ -+ -+struct fdb_entry { -+ struct hmap_node hmap_node; /* In a hmap. */ -+ -+ /* Key. */ -+ uint32_t dp_key; -+ struct eth_addr mac; -+ -+ /* value. */ -+ uint32_t port_key; -+}; -+ -+void ovn_fdb_init(struct hmap *fdbs); -+void ovn_fdbs_flush(struct hmap *fdbs); -+void ovn_fdbs_destroy(struct hmap *fdbs); -+ -+struct fdb_entry *ovn_fdb_add(struct hmap *fdbs, -+ uint32_t dp_key, struct eth_addr mac, -+ uint32_t port_key); -+ -+#endif /* OVN_MAC_LEARN_H */ -diff --git a/controller/ofctrl-seqno.c b/controller/ofctrl-seqno.c -new file mode 100644 -index 000000000..c9334b078 ---- /dev/null -+++ b/controller/ofctrl-seqno.c -@@ -0,0 +1,254 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "hash.h" -+#include "ofctrl-seqno.h" -+#include "openvswitch/list.h" -+#include "util.h" -+ -+/* A sequence number update request, i.e., when the barrier corresponding to -+ * the 'flow_cfg' sequence number is replied to by OVS then it is safe -+ * to inform the application that the 'req_cfg' seqno has been processed. -+ */ -+struct ofctrl_seqno_update { -+ struct ovs_list list_node; /* In 'ofctrl_seqno_updates'. */ -+ size_t seqno_type; /* Application specific seqno type. -+ * Relevant only for 'req_cfg'. -+ */ -+ uint64_t flow_cfg; /* The seqno that needs to be acked by OVS -+ * before 'req_cfg' can be acked for the -+ * application. -+ */ -+ uint64_t req_cfg; /* Application specific seqno. */ -+}; -+ -+/* List of in flight sequence number updates. */ -+static struct ovs_list ofctrl_seqno_updates; -+ -+/* Last sequence number request sent to OVS. */ -+static uint64_t ofctrl_req_seqno; -+ -+/* State of seqno requests for a given application seqno type. */ -+struct ofctrl_seqno_state { -+ struct ovs_list acked_cfgs; /* Acked requests since the last time the -+ * application consumed acked requests. -+ */ -+ uint64_t cur_cfg; /* Last acked application seqno. */ -+ uint64_t req_cfg; /* Last requested application seqno. */ -+}; -+ -+/* Per application seqno type states. */ -+static size_t n_ofctrl_seqno_states; -+static struct ofctrl_seqno_state *ofctrl_seqno_states; -+ -+/* ofctrl_acked_seqnos related static function prototypes. */ -+static void ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos, -+ uint64_t last_acked); -+static void ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val); -+ -+/* ofctrl_seqno_update related static function prototypes. */ -+static void ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg); -+static void ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list); -+static void ofctrl_seqno_cfg_run(size_t seqno_type, -+ struct ofctrl_seqno_update *update); -+ -+/* Returns the collection of acked ofctrl_seqno_update requests of type -+ * 'seqno_type'. It's the responsibility of the caller to free memory by -+ * calling ofctrl_acked_seqnos_destroy(). -+ */ -+struct ofctrl_acked_seqnos * -+ofctrl_acked_seqnos_get(size_t seqno_type) -+{ -+ struct ofctrl_acked_seqnos *acked_seqnos = xmalloc(sizeof *acked_seqnos); -+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type]; -+ struct ofctrl_seqno_update *update; -+ -+ ofctrl_acked_seqnos_init(acked_seqnos, state->cur_cfg); -+ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ LIST_FOR_EACH_POP (update, list_node, &state->acked_cfgs) { -+ ofctrl_acked_seqnos_add(acked_seqnos, update->req_cfg); -+ free(update); -+ } -+ return acked_seqnos; -+} -+ -+void -+ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos) -+{ -+ if (!seqnos) { -+ return; -+ } -+ -+ struct ofctrl_ack_seqno *seqno_node; -+ HMAP_FOR_EACH_POP (seqno_node, node, &seqnos->acked) { -+ free(seqno_node); -+ } -+ hmap_destroy(&seqnos->acked); -+ free(seqnos); -+} -+ -+/* Returns true if 'val' is one of the acked sequence numbers in 'seqnos'. */ -+bool -+ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val) -+{ -+ struct ofctrl_ack_seqno *sn; -+ -+ HMAP_FOR_EACH_WITH_HASH (sn, node, hash_int(val, 0), &seqnos->acked) { -+ if (sn->seqno == val) { -+ return true; -+ } -+ } -+ return false; -+} -+ -+void -+ofctrl_seqno_init(void) -+{ -+ ovs_list_init(&ofctrl_seqno_updates); -+} -+ -+/* Adds a new type of application specific seqno updates. */ -+size_t -+ofctrl_seqno_add_type(void) -+{ -+ size_t new_type = n_ofctrl_seqno_states; -+ n_ofctrl_seqno_states++; -+ -+ struct ofctrl_seqno_state *new_states = -+ xzalloc(n_ofctrl_seqno_states * sizeof *new_states); -+ -+ for (size_t i = 0; i < n_ofctrl_seqno_states - 1; i++) { -+ ovs_list_move(&new_states[i].acked_cfgs, -+ &ofctrl_seqno_states[i].acked_cfgs); -+ } -+ ovs_list_init(&new_states[new_type].acked_cfgs); -+ -+ free(ofctrl_seqno_states); -+ ofctrl_seqno_states = new_states; -+ return new_type; -+} -+ -+/* Creates a new seqno update request for an application specific -+ * 'seqno_type'. -+ */ -+void -+ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg) -+{ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ -+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type]; -+ -+ /* If new_cfg didn't change since the last request there should already -+ * be an update pending. -+ */ -+ if (new_cfg == state->req_cfg) { -+ return; -+ } -+ -+ state->req_cfg = new_cfg; -+ ofctrl_seqno_update_create__(seqno_type, new_cfg); -+} -+ -+/* Should be called when the application is certain that all OVS flow updates -+ * corresponding to 'flow_cfg' were processed. Populates the application -+ * specific lists of acked requests in 'ofctrl_seqno_states'. -+ */ -+void -+ofctrl_seqno_run(uint64_t flow_cfg) -+{ -+ struct ofctrl_seqno_update *update, *prev; -+ LIST_FOR_EACH_SAFE (update, prev, list_node, &ofctrl_seqno_updates) { -+ if (flow_cfg < update->flow_cfg) { -+ break; -+ } -+ -+ ovs_list_remove(&update->list_node); -+ ofctrl_seqno_cfg_run(update->seqno_type, update); -+ } -+} -+ -+/* Returns the seqno to be used when sending a barrier request to OVS. */ -+uint64_t -+ofctrl_seqno_get_req_cfg(void) -+{ -+ return ofctrl_req_seqno; -+} -+ -+/* Should be called whenever the openflow connection to OVS is lost. Flushes -+ * all pending 'ofctrl_seqno_updates'. -+ */ -+void -+ofctrl_seqno_flush(void) -+{ -+ for (size_t i = 0; i < n_ofctrl_seqno_states; i++) { -+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_states[i].acked_cfgs); -+ } -+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_updates); -+ ofctrl_req_seqno = 0; -+} -+ -+static void -+ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos, -+ uint64_t last_acked) -+{ -+ hmap_init(&seqnos->acked); -+ seqnos->last_acked = last_acked; -+} -+ -+static void -+ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos, uint32_t val) -+{ -+ seqnos->last_acked = val; -+ -+ struct ofctrl_ack_seqno *sn = xmalloc(sizeof *sn); -+ hmap_insert(&seqnos->acked, &sn->node, hash_int(val, 0)); -+ sn->seqno = val; -+} -+ -+static void -+ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg) -+{ -+ struct ofctrl_seqno_update *update = xmalloc(sizeof *update); -+ -+ ofctrl_req_seqno++; -+ ovs_list_push_back(&ofctrl_seqno_updates, &update->list_node); -+ update->seqno_type = seqno_type; -+ update->flow_cfg = ofctrl_req_seqno; -+ update->req_cfg = req_cfg; -+} -+ -+static void -+ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list) -+{ -+ struct ofctrl_seqno_update *update; -+ -+ LIST_FOR_EACH_POP (update, list_node, seqno_list) { -+ free(update); -+ } -+} -+ -+static void -+ofctrl_seqno_cfg_run(size_t seqno_type, struct ofctrl_seqno_update *update) -+{ -+ ovs_assert(seqno_type < n_ofctrl_seqno_states); -+ ovs_list_push_back(&ofctrl_seqno_states[seqno_type].acked_cfgs, -+ &update->list_node); -+ ofctrl_seqno_states[seqno_type].cur_cfg = update->req_cfg; -+} -diff --git a/controller/ofctrl-seqno.h b/controller/ofctrl-seqno.h -new file mode 100644 -index 000000000..876947c26 ---- /dev/null -+++ b/controller/ofctrl-seqno.h -@@ -0,0 +1,49 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#ifndef OFCTRL_SEQNO_H -+#define OFCTRL_SEQNO_H 1 -+ -+#include -+ -+#include -+ -+/* Collection of acked ofctrl_seqno_update requests and the most recent -+ * 'last_acked' value. -+ */ -+struct ofctrl_acked_seqnos { -+ struct hmap acked; -+ uint64_t last_acked; -+}; -+ -+/* Acked application specific seqno. Stored in ofctrl_acked_seqnos.acked. */ -+struct ofctrl_ack_seqno { -+ struct hmap_node node; -+ uint64_t seqno; -+}; -+ -+struct ofctrl_acked_seqnos *ofctrl_acked_seqnos_get(size_t seqno_type); -+void ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos); -+bool ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos, -+ uint32_t val); -+ -+void ofctrl_seqno_init(void); -+size_t ofctrl_seqno_add_type(void); -+void ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg); -+void ofctrl_seqno_run(uint64_t flow_cfg); -+uint64_t ofctrl_seqno_get_req_cfg(void); -+void ofctrl_seqno_flush(void); -+ -+#endif /* controller/ofctrl-seqno.h */ -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index a1ac69531..415d9b7e1 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -268,13 +268,14 @@ enum ofctrl_state { - /* An in-flight update to the switch's flow table. - * - * When we receive a barrier reply from the switch with the given 'xid', we -- * know that the switch is caught up to northbound database sequence number -- * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so -- * that it can store it into our Chassis record's nb_cfg column). */ -+ * know that the switch is caught up to the requested sequence number -+ * 'req_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), -+ * so that it can store it into external state, e.g., our Chassis record's -+ * nb_cfg column). */ - struct ofctrl_flow_update { - struct ovs_list list_node; /* In 'flow_updates'. */ - ovs_be32 xid; /* OpenFlow transaction ID for barrier. */ -- int64_t nb_cfg; /* Northbound database sequence number. */ -+ uint64_t req_cfg; /* Requested sequence number. */ - }; - - static struct ofctrl_flow_update * -@@ -286,8 +287,8 @@ ofctrl_flow_update_from_list_node(const struct ovs_list *list_node) - /* Currently in-flight updates. */ - static struct ovs_list flow_updates; - --/* nb_cfg of latest committed flow update. */ --static int64_t cur_cfg; -+/* req_cfg of latest committed flow update. */ -+static uint64_t cur_cfg; - - /* Current state. */ - static enum ofctrl_state state; -@@ -632,8 +633,8 @@ recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type, - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_front(&flow_updates)); - if (fup->xid == oh->xid) { -- if (fup->nb_cfg >= cur_cfg) { -- cur_cfg = fup->nb_cfg; -+ if (fup->req_cfg >= cur_cfg) { -+ cur_cfg = fup->req_cfg; - } - ovs_list_remove(&fup->list_node); - free(fup); -@@ -763,7 +764,7 @@ ofctrl_destroy(void) - shash_destroy(&symtab); - } - --int64_t -+uint64_t - ofctrl_get_cur_cfg(void) - { - return cur_cfg; -@@ -1246,10 +1247,23 @@ ofctrl_flood_remove_flows(struct ovn_desired_flow_table *flow_table, - struct hmap *flood_remove_nodes) - { - struct ofctrl_flood_remove_node *ofrn; -+ int i, n = 0; -+ -+ /* flood_remove_flows_for_sb_uuid() will modify the 'flood_remove_nodes' -+ * hash map by inserting new items, so we can't use it for iteration. -+ * Copying the sb_uuids into an array. */ -+ struct uuid *sb_uuids; -+ sb_uuids = xmalloc(hmap_count(flood_remove_nodes) * sizeof *sb_uuids); -+ struct hmap flood_remove_uuids = HMAP_INITIALIZER(&flood_remove_uuids); - HMAP_FOR_EACH (ofrn, hmap_node, flood_remove_nodes) { -- flood_remove_flows_for_sb_uuid(flow_table, &ofrn->sb_uuid, -+ sb_uuids[n++] = ofrn->sb_uuid; -+ } -+ -+ for (i = 0; i < n; i++) { -+ flood_remove_flows_for_sb_uuid(flow_table, &sb_uuids[i], - flood_remove_nodes); - } -+ free(sb_uuids); - - /* remove any related group and meter info */ - HMAP_FOR_EACH (ofrn, hmap_node, flood_remove_nodes) { -@@ -1975,7 +1989,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - * tracked, so it must have been modified. */ - installed_flow_mod(&i->flow, &f->flow, msgs); - ovn_flow_log(&i->flow, "updating installed (tracked)"); -- } else { -+ } else if (!f->installed_flow) { - /* Adding a new flow that conflicts with an existing installed - * flow, so add it to the link. If this flow becomes active, - * e.g., it is less restrictive than the previous active flow -@@ -2024,28 +2038,28 @@ void - ofctrl_put(struct ovn_desired_flow_table *flow_table, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *meter_table, -- int64_t nb_cfg, -+ uint64_t req_cfg, - bool flow_changed) - { - static bool skipped_last_time = false; -- static int64_t old_nb_cfg = 0; -+ static uint64_t old_req_cfg = 0; - 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) { -+ old_req_cfg = req_cfg; -+ } else if (req_cfg != old_req_cfg) { -+ /* req_cfg changed since last ofctrl_put() call */ -+ if (cur_cfg == old_req_cfg) { - /* If there are no updates pending, we were up-to-date already, -- * update with the new nb_cfg. -+ * update with the new req_cfg. - */ - if (ovs_list_is_empty(&flow_updates)) { -- cur_cfg = nb_cfg; -- old_nb_cfg = nb_cfg; -+ cur_cfg = req_cfg; -+ old_req_cfg = req_cfg; - } - } else { - need_put = true; -- old_nb_cfg = nb_cfg; -+ old_req_cfg = req_cfg; - } - } - -@@ -2187,24 +2201,23 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - /* Track the flow update. */ - struct ofctrl_flow_update *fup, *prev; - LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) { -- if (nb_cfg < fup->nb_cfg) { -+ if (req_cfg < fup->req_cfg) { - /* This ofctrl_flow_update is for a configuration later than -- * 'nb_cfg'. This should not normally happen, because it means -- * that 'nb_cfg' in the SB_Global table of the southbound -- * database decreased, and it should normally be monotonically -- * increasing. */ -- VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64, -- fup->nb_cfg, nb_cfg); -+ * 'req_cfg'. This should not normally happen, because it -+ * means that the local seqno decreased and it should normally -+ * be monotonically increasing. */ -+ VLOG_WARN("req_cfg regressed from %"PRId64" to %"PRId64, -+ fup->req_cfg, req_cfg); - ovs_list_remove(&fup->list_node); - free(fup); -- } else if (nb_cfg == fup->nb_cfg) { -+ } else if (req_cfg == fup->req_cfg) { - /* This ofctrl_flow_update is for the same configuration as -- * 'nb_cfg'. Probably, some change to the physical topology -+ * 'req_cfg'. Probably, some change to the physical topology - * means that we had to revise the OpenFlow flow table even - * though the logical topology did not change. Update fp->xid, - * so that we don't send a notification that we're up-to-date - * until we're really caught up. */ -- VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg); -+ VLOG_DBG("advanced xid target for req_cfg=%"PRId64, req_cfg); - fup->xid = xid_; - goto done; - } else { -@@ -2216,18 +2229,18 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - fup = xmalloc(sizeof *fup); - ovs_list_push_back(&flow_updates, &fup->list_node); - fup->xid = xid_; -- fup->nb_cfg = nb_cfg; -+ fup->req_cfg = req_cfg; - done:; - } else if (!ovs_list_is_empty(&flow_updates)) { -- /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table -- * changes, so whenever we get up-to-date with the most recent flow -- * table update, we're also up-to-date with 'nb_cfg'. */ -+ /* Getting up-to-date with 'req_cfg' didn't require any extra flow -+ * table changes, so whenever we get up-to-date with the most recent -+ * flow table update, we're also up-to-date with 'req_cfg'. */ - struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node( - ovs_list_back(&flow_updates)); -- fup->nb_cfg = nb_cfg; -+ fup->req_cfg = req_cfg; - } else { - /* We were completely up-to-date before and still are. */ -- cur_cfg = nb_cfg; -+ cur_cfg = req_cfg; - } - - flow_table->change_tracked = true; -diff --git a/controller/ofctrl.h b/controller/ofctrl.h -index 64b0ea5dd..88769566a 100644 ---- a/controller/ofctrl.h -+++ b/controller/ofctrl.h -@@ -55,12 +55,12 @@ enum mf_field_id ofctrl_get_mf_field_id(void); - void ofctrl_put(struct ovn_desired_flow_table *, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *, -- int64_t nb_cfg, -+ uint64_t nb_cfg, - bool flow_changed); - bool ofctrl_can_put(void); - void ofctrl_wait(void); - void ofctrl_destroy(void); --int64_t ofctrl_get_cur_cfg(void); -+uint64_t ofctrl_get_cur_cfg(void); - - void ofctrl_ct_flush_zone(uint16_t zone_id); - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 366fc9c06..288e2e12d 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -39,6 +39,7 @@ - #include "lib/vswitch-idl.h" - #include "lport.h" - #include "ofctrl.h" -+#include "ofctrl-seqno.h" - #include "openvswitch/vconn.h" - #include "openvswitch/vlog.h" - #include "ovn/actions.h" -@@ -98,6 +99,9 @@ struct pending_pkt { - char *flow_s; - }; - -+/* Registered ofctrl seqno type for nb_cfg propagation. */ -+static size_t ofctrl_seq_type_nb_cfg; -+ - struct local_datapath * - get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key) - { -@@ -583,7 +587,18 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones, - pending->state = state; /* Skip flushing zone. */ - pending->zone = zone; - pending->add = add; -- shash_add(pending_ct_zones, name, pending); -+ -+ /* Its important that we add only one entry for the key 'name'. -+ * Replace 'pending' with 'existing' and free up 'existing'. -+ * Otherwise, we may end up in a continuous loop of adding -+ * and deleting the zone entry in the 'external_ids' of -+ * integration bridge. -+ */ -+ struct ct_zone_pending_entry *existing = -+ shash_replace(pending_ct_zones, name, pending); -+ if (existing) { -+ free(existing); -+ } - } - - static void -@@ -798,11 +813,11 @@ restore_ct_zones(const struct ovsrec_bridge_table *bridge_table, - } - } - --static int64_t -+static uint64_t - 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; -+ static uint64_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 -@@ -825,11 +840,14 @@ static void - store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, - const struct sbrec_chassis_private *chassis, - const struct ovsrec_bridge *br_int, -- unsigned int delay_nb_cfg_report, -- int64_t cur_cfg) -+ unsigned int delay_nb_cfg_report) - { -+ struct ofctrl_acked_seqnos *acked_nb_cfg_seqnos = -+ ofctrl_acked_seqnos_get(ofctrl_seq_type_nb_cfg); -+ uint64_t cur_cfg = acked_nb_cfg_seqnos->last_acked; -+ - if (!cur_cfg) { -- return; -+ goto done; - } - - if (sb_txn && chassis && cur_cfg != chassis->nb_cfg) { -@@ -850,6 +868,9 @@ store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, - cur_cfg_str); - free(cur_cfg_str); - } -+ -+done: -+ ofctrl_acked_seqnos_destroy(acked_nb_cfg_seqnos); - } - - static const char * -@@ -911,7 +932,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) - SB_NODE(dhcp_options, "dhcp_options") \ - SB_NODE(dhcpv6_options, "dhcpv6_options") \ - SB_NODE(dns, "dns") \ -- SB_NODE(load_balancer, "load_balancer") -+ SB_NODE(load_balancer, "load_balancer") \ -+ SB_NODE(fdb, "fdb") - - enum sb_engine_node { - #define SB_NODE(NAME, NAME_STR) SB_##NAME, -@@ -967,6 +989,12 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data) - struct ed_type_ofctrl_is_connected *of_data = data; - if (of_data->connected != ofctrl_is_connected()) { - of_data->connected = !of_data->connected; -+ -+ /* Flush ofctrl seqno requests when the ofctrl connection goes down. */ -+ if (!of_data->connected) { -+ ofctrl_seqno_flush(); -+ binding_seqno_flush(); -+ } - engine_set_node_state(node, EN_UPDATED); - return; - } -@@ -1836,6 +1864,10 @@ static void init_lflow_ctx(struct engine_node *node, - (struct sbrec_load_balancer_table *)EN_OVSDB_GET( - engine_get_input("SB_load_balancer", node)); - -+ struct sbrec_fdb_table *fdb_table = -+ (struct sbrec_fdb_table *)EN_OVSDB_GET( -+ engine_get_input("SB_fdb", node)); -+ - struct ovsrec_open_vswitch_table *ovs_table = - (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( - engine_get_input("OVS_open_vswitch", node)); -@@ -1873,6 +1905,7 @@ static void init_lflow_ctx(struct engine_node *node, - l_ctx_in->logical_flow_table = logical_flow_table; - l_ctx_in->logical_dp_group_table = logical_dp_group_table; - l_ctx_in->mc_group_table = multicast_group_table; -+ l_ctx_in->fdb_table = fdb_table, - l_ctx_in->chassis = chassis; - l_ctx_in->lb_table = lb_table; - l_ctx_in->local_datapaths = &rt_data->local_datapaths; -@@ -2313,6 +2346,23 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) - return handled; - } - -+static bool -+flow_output_sb_fdb_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_fdbs(&l_ctx_in, &l_ctx_out); -+ -+ engine_set_node_state(node, EN_UPDATED); -+ return handled; -+} -+ - struct ovn_controller_exit_args { - bool *exiting; - bool *restart; -@@ -2389,6 +2439,10 @@ main(int argc, char *argv[]) - - daemonize_complete(); - -+ /* Register ofctrl seqno types. */ -+ ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type(); -+ -+ binding_init(); - patch_init(); - pinctrl_init(); - lflow_init(); -@@ -2440,6 +2494,10 @@ main(int argc, char *argv[]) - = ip_mcast_index_create(ovnsb_idl_loop.idl); - struct ovsdb_idl_index *sbrec_igmp_group - = igmp_group_index_create(ovnsb_idl_loop.idl); -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac -+ = ovsdb_idl_index_create2(ovnsb_idl_loop.idl, -+ &sbrec_fdb_col_mac, -+ &sbrec_fdb_col_dp_key); - - ovsdb_idl_track_add_all(ovnsb_idl_loop.idl); - ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, -@@ -2566,6 +2624,8 @@ main(int argc, char *argv[]) - engine_add_input(&en_flow_output, &en_sb_dns, NULL); - engine_add_input(&en_flow_output, &en_sb_load_balancer, - flow_output_sb_load_balancer_handler); -+ engine_add_input(&en_flow_output, &en_sb_fdb, -+ flow_output_sb_fdb_handler); - - engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL); - engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL); -@@ -2624,6 +2684,7 @@ main(int argc, char *argv[]) - ofctrl_init(&flow_output_data->group_table, - &flow_output_data->meter_table, - get_ofctrl_probe_interval(ovs_idl_loop.idl)); -+ ofctrl_seqno_init(); - - unixctl_command_register("group-table-list", "", 0, 0, - extend_table_list, -@@ -2832,11 +2893,13 @@ main(int argc, char *argv[]) - sbrec_mac_binding_by_lport_ip, - sbrec_igmp_group, - sbrec_ip_multicast, -+ sbrec_fdb_by_dp_key_mac, - sbrec_dns_table_get(ovnsb_idl_loop.idl), - sbrec_controller_event_table_get( - ovnsb_idl_loop.idl), - sbrec_service_monitor_table_get( - ovnsb_idl_loop.idl), -+ sbrec_bfd_table_get(ovnsb_idl_loop.idl), - br_int, chassis, - &runtime_data->local_datapaths, - &runtime_data->active_tunnels); -@@ -2852,17 +2915,29 @@ main(int argc, char *argv[]) - sb_monitor_all); - } - } -+ -+ ofctrl_seqno_update_create( -+ ofctrl_seq_type_nb_cfg, -+ get_nb_cfg(sbrec_sb_global_table_get( -+ ovnsb_idl_loop.idl), -+ ovnsb_cond_seqno, -+ ovnsb_expected_cond_seqno)); -+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -+ binding_seqno_run(&runtime_data->local_bindings); -+ } -+ - 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), -+ ofctrl_seqno_get_req_cfg(), - engine_node_changed(&en_flow_output)); - } -+ ofctrl_seqno_run(ofctrl_get_cur_cfg()); -+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -+ binding_seqno_install(&runtime_data->local_bindings); -+ } - } - - } -@@ -2888,7 +2963,7 @@ main(int argc, char *argv[]) - } - - store_nb_cfg(ovnsb_idl_txn, ovs_idl_txn, chassis_private, -- br_int, delay_nb_cfg_report, ofctrl_get_cur_cfg()); -+ br_int, delay_nb_cfg_report); - - if (pending_pkt.conn) { - struct ed_type_addr_sets *as_data = -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index 7e3abf0a4..3dc10389d 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -26,6 +26,7 @@ - #include "flow.h" - #include "ha-chassis.h" - #include "lport.h" -+#include "mac-learn.h" - #include "nx-match.h" - #include "latch.h" - #include "lib/packets.h" -@@ -38,6 +39,7 @@ - #include "openvswitch/ofp-util.h" - #include "openvswitch/vlog.h" - #include "lib/random.h" -+#include "lib/crc32c.h" - - #include "lib/dhcp.h" - #include "ovn-controller.h" -@@ -192,7 +194,6 @@ static void run_put_mac_bindings( - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip) - OVS_REQUIRES(pinctrl_mutex); - static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn); --static void flush_put_mac_bindings(void); - static void send_mac_binding_buffered_pkts(struct rconn *swconn) - OVS_REQUIRES(pinctrl_mutex); - -@@ -323,6 +324,39 @@ put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits, - static void notify_pinctrl_main(void); - static void notify_pinctrl_handler(void); - -+static bool bfd_monitor_should_inject(void); -+static void bfd_monitor_wait(long long int timeout); -+static void bfd_monitor_init(void); -+static void bfd_monitor_destroy(void); -+static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) -+ OVS_REQUIRES(pinctrl_mutex); -+static void -+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow, -+ struct dp_packet *pkt_in) -+ OVS_REQUIRES(pinctrl_mutex); -+static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ const struct sbrec_bfd_table *bfd_table, -+ struct ovsdb_idl_index *sbrec_port_binding_by_name, -+ const struct sbrec_chassis *chassis, -+ const struct sset *active_tunnels) -+ OVS_REQUIRES(pinctrl_mutex); -+static void init_fdb_entries(void); -+static void destroy_fdb_entries(void); -+static const struct sbrec_fdb *fdb_lookup( -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, -+ uint32_t dp_key, const char *mac); -+static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, -+ const struct fdb_entry *fdb_e) -+ OVS_REQUIRES(pinctrl_mutex); -+static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac) -+ OVS_REQUIRES(pinctrl_mutex); -+static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn); -+static void pinctrl_handle_put_fdb(const struct flow *md, -+ const struct flow *headers) -+ OVS_REQUIRES(pinctrl_mutex); -+ - COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); - COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map); - COVERAGE_DEFINE(pinctrl_drop_controller_event); -@@ -487,6 +521,8 @@ pinctrl_init(void) - ip_mcast_snoop_init(); - init_put_vport_bindings(); - init_svc_monitors(); -+ bfd_monitor_init(); -+ init_fdb_entries(); - pinctrl.br_int_name = NULL; - pinctrl_handler_seq = seq_create(); - pinctrl_main_seq = seq_create(); -@@ -1380,6 +1416,11 @@ buffered_push_packet(struct buffered_packets *bp, - ofpbuf_init(&bi->ofpacts, 4096); - - reload_metadata(&bi->ofpacts, md); -+ /* reload pkt_mark field */ -+ const struct mf_field *pkt_mark_field = mf_from_id(MFF_PKT_MARK); -+ union mf_value pkt_mark_value; -+ mf_get_value(pkt_mark_field, &md->flow, &pkt_mark_value); -+ ofpact_put_set_field(&bi->ofpacts, pkt_mark_field, &pkt_mark_value, NULL); - bi->ofp_port = md->flow.in_port.ofp_port; - - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts); -@@ -1763,6 +1804,116 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, - dp_packet_uninit(&packet); - } - -+static void dp_packet_put_sctp_abort(struct dp_packet *packet, -+ bool reflect_tag) -+{ -+ struct sctp_chunk_header abort = { -+ .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT, -+ .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0, -+ .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN), -+ }; -+ -+ dp_packet_put(packet, &abort, sizeof abort); -+} -+ -+static void -+pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, -+ struct dp_packet *pkt_in, -+ const struct match *md, struct ofpbuf *userdata, -+ bool loopback) -+{ -+ if (ip_flow->nw_proto != IPPROTO_SCTP) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on non-SCTP packet"); -+ return; -+ } -+ -+ struct sctp_header *sh_in = dp_packet_l4(pkt_in); -+ if (!sh_in) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on malformed SCTP packet"); -+ return; -+ } -+ -+ const struct sctp_chunk_header *sh_in_chunk = -+ dp_packet_get_sctp_payload(pkt_in); -+ if (!sh_in_chunk) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on SCTP packet with no chunks"); -+ return; -+ } -+ -+ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_ABORT) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "sctp_abort action on incoming SCTP ABORT."); -+ return; -+ } -+ -+ const struct sctp_init_chunk *sh_in_init = NULL; -+ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs + -+ SCTP_HEADER_LEN + -+ SCTP_CHUNK_HEADER_LEN, -+ SCTP_INIT_CHUNK_LEN); -+ if (!sh_in_init) { -+ VLOG_WARN_RL(&rl, "Incomplete SCTP INIT chunk. Ignoring packet."); -+ return; -+ } -+ } -+ -+ uint64_t packet_stub[128 / 8]; -+ struct dp_packet packet; -+ -+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -+ -+ 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)) { -+ 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_SCTP, 63, SCTP_HEADER_LEN + -+ SCTP_CHUNK_HEADER_LEN); -+ } else { -+ 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_SCTP, 63, SCTP_HEADER_LEN + -+ SCTP_CHUNK_HEADER_LEN); -+ } -+ -+ struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh); -+ dp_packet_set_l4(&packet, sh); -+ sh->sctp_dst = ip_flow->tp_src; -+ sh->sctp_src = ip_flow->tp_dst; -+ put_16aligned_be32(&sh->sctp_csum, 0); -+ -+ bool tag_reflected; -+ if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) { -+ /* See RFC 4960 Section 8.4, item 3. */ -+ put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag); -+ tag_reflected = false; -+ } else { -+ /* See RFC 4960 Section 8.4, item 8. */ -+ sh->sctp_vtag = sh_in->sctp_vtag; -+ tag_reflected = true; -+ } -+ -+ dp_packet_put_sctp_abort(&packet, tag_reflected); -+ -+ put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh, -+ dp_packet_l4_size(&packet))); -+ -+ set_actions_and_enqueue_msg(swconn, &packet, md, userdata); -+ dp_packet_uninit(&packet); -+} -+ - static void - pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow, - struct dp_packet *pkt_in, -@@ -1770,6 +1921,8 @@ pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow, - { - if (ip_flow->nw_proto == IPPROTO_TCP) { - pinctrl_handle_tcp_reset(swconn, ip_flow, pkt_in, md, userdata, true); -+ } else if (ip_flow->nw_proto == IPPROTO_SCTP) { -+ pinctrl_handle_sctp_abort(swconn, ip_flow, pkt_in, md, userdata, true); - } else { - pinctrl_handle_icmp(swconn, ip_flow, pkt_in, md, userdata, true, true); - } -@@ -2884,6 +3037,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - ovs_mutex_unlock(&pinctrl_mutex); - break; - -+ case ACTION_OPCODE_PUT_FDB: -+ ovs_mutex_lock(&pinctrl_mutex); -+ pinctrl_handle_put_fdb(&pin.flow_metadata.flow, &headers); -+ ovs_mutex_unlock(&pinctrl_mutex); -+ break; -+ - case ACTION_OPCODE_PUT_DHCPV6_OPTS: - pinctrl_handle_put_dhcpv6_opts(swconn, &packet, &pin, &userdata, - &continuation); -@@ -2926,6 +3085,11 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - &userdata, false); - break; - -+ case ACTION_OPCODE_SCTP_ABORT: -+ pinctrl_handle_sctp_abort(swconn, &headers, &packet, -+ &pin.flow_metadata, &userdata, false); -+ break; -+ - case ACTION_OPCODE_REJECT: - pinctrl_handle_reject(swconn, &headers, &packet, &pin.flow_metadata, - &userdata); -@@ -2962,6 +3126,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) - ovs_mutex_unlock(&pinctrl_mutex); - break; - -+ case ACTION_OPCODE_BFD_MSG: -+ ovs_mutex_lock(&pinctrl_mutex); -+ pinctrl_handle_bfd_msg(swconn, &headers, &packet); -+ ovs_mutex_unlock(&pinctrl_mutex); -+ break; -+ - default: - VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, - ntohl(ah->opcode)); -@@ -3053,6 +3223,8 @@ pinctrl_handler(void *arg_) - swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP15_VERSION); - - while (!latch_is_set(&pctrl->pinctrl_thread_exit)) { -+ long long int bfd_time = LLONG_MAX; -+ - ovs_mutex_lock(&pinctrl_mutex); - pinctrl_rconn_setup(swconn, pctrl->br_int_name); - ip_mcast_snoop_run(); -@@ -3085,6 +3257,7 @@ pinctrl_handler(void *arg_) - send_ipv6_ras(swconn, &send_ipv6_ra_time); - send_ipv6_prefixd(swconn, &send_prefixd_time); - send_mac_binding_buffered_pkts(swconn); -+ bfd_monitor_send_msg(swconn, &bfd_time); - ovs_mutex_unlock(&pinctrl_mutex); - - ip_mcast_querier_run(swconn, &send_mcast_query_time); -@@ -3102,6 +3275,7 @@ pinctrl_handler(void *arg_) - ip_mcast_querier_wait(send_mcast_query_time); - svc_monitors_wait(svc_monitors_next_run_time); - ipv6_prefixd_wait(send_prefixd_time); -+ bfd_monitor_wait(bfd_time); - - new_seq = seq_read(pinctrl_handler_seq); - seq_wait(pinctrl_handler_seq, new_seq); -@@ -3146,9 +3320,11 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast_opts, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, - const struct sbrec_dns_table *dns_table, - const struct sbrec_controller_event_table *ce_table, - const struct sbrec_service_monitor_table *svc_mon_table, -+ const struct sbrec_bfd_table *bfd_table, - const struct ovsrec_bridge *br_int, - const struct sbrec_chassis *chassis, - const struct hmap *local_datapaths, -@@ -3179,6 +3355,9 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - local_datapaths); - sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name, - chassis); -+ bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name, -+ chassis, active_tunnels); -+ run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac); - ovs_mutex_unlock(&pinctrl_mutex); - } - -@@ -3702,6 +3881,7 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn) - wait_put_vport_bindings(ovnsb_idl_txn); - int64_t new_seq = seq_read(pinctrl_main_seq); - seq_wait(pinctrl_main_seq, new_seq); -+ wait_put_fdbs(ovnsb_idl_txn); - } - - /* Called by ovn-controller. */ -@@ -3722,6 +3902,8 @@ pinctrl_destroy(void) - destroy_dns_cache(); - ip_mcast_snoop_destroy(); - destroy_svc_monitors(); -+ bfd_monitor_destroy(); -+ destroy_fdb_entries(); - seq_destroy(pinctrl_main_seq); - seq_destroy(pinctrl_handler_seq); - } -@@ -3738,47 +3920,20 @@ pinctrl_destroy(void) - * available. */ - - /* Buffered "put_mac_binding" operation. */ --struct put_mac_binding { -- struct hmap_node hmap_node; /* In 'put_mac_bindings'. */ -- -- /* Key. */ -- uint32_t dp_key; -- uint32_t port_key; -- struct in6_addr ip_key; - -- /* Value. */ -- struct eth_addr mac; --}; -- --/* Contains "struct put_mac_binding"s. */ -+/* Contains "struct mac_binding"s. */ - static struct hmap put_mac_bindings; - - static void - init_put_mac_bindings(void) - { -- hmap_init(&put_mac_bindings); -+ ovn_mac_bindings_init(&put_mac_bindings); - } - - static void - destroy_put_mac_bindings(void) - { -- flush_put_mac_bindings(); -- hmap_destroy(&put_mac_bindings); --} -- --static struct put_mac_binding * --pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key, -- const struct in6_addr *ip_key, uint32_t hash) --{ -- struct put_mac_binding *pa; -- HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) { -- if (pa->dp_key == dp_key -- && pa->port_key == port_key -- && IN6_ARE_ADDR_EQUAL(&pa->ip_key, ip_key)) { -- return pa; -- } -- } -- return NULL; -+ ovn_mac_bindings_destroy(&put_mac_bindings); - } - - /* Called with in the pinctrl_handler thread context. */ -@@ -3798,23 +3953,14 @@ pinctrl_handle_put_mac_binding(const struct flow *md, - ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0)); - memcpy(&ip_key, &ip6, sizeof ip_key); - } -- uint32_t hash = hash_bytes(&ip_key, sizeof ip_key, -- hash_2words(dp_key, port_key)); -- struct put_mac_binding *pmb -- = pinctrl_find_put_mac_binding(dp_key, port_key, &ip_key, hash); -- if (!pmb) { -- if (hmap_count(&put_mac_bindings) >= 1000) { -- COVERAGE_INC(pinctrl_drop_put_mac_binding); -- return; -- } - -- pmb = xmalloc(sizeof *pmb); -- hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash); -- pmb->dp_key = dp_key; -- pmb->port_key = port_key; -- pmb->ip_key = ip_key; -+ struct mac_binding *mb = ovn_mac_binding_add(&put_mac_bindings, dp_key, -+ port_key, &ip_key, -+ headers->dl_src); -+ if (!mb) { -+ COVERAGE_INC(pinctrl_drop_put_mac_binding); -+ return; - } -- pmb->mac = headers->dl_src; - - /* We can send the buffered packet once the main ovn-controller - * thread calls pinctrl_run() and it writes the mac_bindings stored -@@ -3857,12 +4003,12 @@ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - /* 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) -+mac_binding_add_to_sb(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]; -@@ -3918,9 +4064,9 @@ send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn, - 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); -+ mac_binding_add_to_sb(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); - } - } -@@ -3930,30 +4076,30 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - struct ovsdb_idl_index *sbrec_port_binding_by_key, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, -- const struct put_mac_binding *pmb) -+ const struct mac_binding *mb) - { - /* Convert logical datapath and logical port key into lport. */ - const struct sbrec_port_binding *pb = lport_lookup_by_key( - sbrec_datapath_binding_by_key, sbrec_port_binding_by_key, -- pmb->dp_key, pmb->port_key); -+ mb->dp_key, mb->port_key); - if (!pb) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - - VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" " -- "and port %"PRIu32, pmb->dp_key, pmb->port_key); -+ "and port %"PRIu32, mb->dp_key, mb->port_key); - return; - } - - /* 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(pmb->mac)); -+ ETH_ADDR_FMT, ETH_ADDR_ARGS(mb->mac)); - - struct ds ip_s = DS_EMPTY_INITIALIZER; -- ipv6_format_mapped(&pmb->ip_key, &ip_s); -- mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, -- pb->logical_port, pb->datapath, pmb->mac, ds_cstr(&ip_s), -- false); -+ ipv6_format_mapped(&mb->ip, &ip_s); -+ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip, -+ pb->logical_port, pb->datapath, mb->mac, -+ ds_cstr(&ip_s), false); - ds_destroy(&ip_s); - } - -@@ -3970,14 +4116,14 @@ run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn, - return; - } - -- const struct put_mac_binding *pmb; -- HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) { -+ const struct mac_binding *mb; -+ HMAP_FOR_EACH (mb, hmap_node, &put_mac_bindings) { - run_put_mac_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key, - sbrec_port_binding_by_key, - sbrec_mac_binding_by_lport_ip, -- pmb); -+ mb); - } -- flush_put_mac_bindings(); -+ ovn_mac_bindings_flush(&put_mac_bindings); - } - - static void -@@ -4033,14 +4179,6 @@ wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn) - } - } - --static void --flush_put_mac_bindings(void) --{ -- struct put_mac_binding *pmb; -- HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) { -- free(pmb); -- } --} - - /* - * Send gratuitous/reverse ARP for vif on localnet. -@@ -5525,7 +5663,8 @@ may_inject_pkts(void) - !shash_is_empty(&send_garp_rarp_data) || - ipv6_prefixd_should_inject() || - !ovs_list_is_empty(&mcast_query_list) || -- !ovs_list_is_empty(&buffered_mac_bindings)); -+ !ovs_list_is_empty(&buffered_mac_bindings) || -+ bfd_monitor_should_inject()); - } - - static void -@@ -6312,6 +6451,665 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn, - - } - -+enum bfd_state { -+ BFD_STATE_ADMIN_DOWN, -+ BFD_STATE_DOWN, -+ BFD_STATE_INIT, -+ BFD_STATE_UP, -+}; -+ -+enum bfd_flags { -+ BFD_FLAG_MULTIPOINT = 1 << 0, -+ BFD_FLAG_DEMAND = 1 << 1, -+ BFD_FLAG_AUTH = 1 << 2, -+ BFD_FLAG_CTL = 1 << 3, -+ BFD_FLAG_FINAL = 1 << 4, -+ BFD_FLAG_POLL = 1 << 5 -+}; -+ -+#define BFD_FLAGS_MASK 0x3f -+ -+static char * -+bfd_get_status(enum bfd_state state) -+{ -+ switch (state) { -+ case BFD_STATE_ADMIN_DOWN: -+ return "admin_down"; -+ case BFD_STATE_DOWN: -+ return "down"; -+ case BFD_STATE_INIT: -+ return "init"; -+ case BFD_STATE_UP: -+ return "up"; -+ default: -+ return ""; -+ } -+} -+ -+static struct hmap bfd_monitor_map; -+ -+#define BFD_UPDATE_BATCH_TH 10 -+static uint16_t bfd_pending_update; -+#define BFD_UPDATE_TIMEOUT 5000LL -+static long long bfd_last_update; -+ -+struct bfd_entry { -+ struct hmap_node node; -+ bool erase; -+ -+ /* L2 source address */ -+ struct eth_addr src_mac; -+ /* IP source address */ -+ struct in6_addr ip_src; -+ /* IP destination address */ -+ struct in6_addr ip_dst; -+ /* RFC 5881 section 4 -+ * The source port MUST be in the range 49152 through 65535. -+ * The same UDP source port number MUST be used for all BFD -+ * Control packets associated with a particular session. -+ * The source port number SHOULD be unique among all BFD -+ * sessions on the system -+ */ -+ uint16_t udp_src; -+ ovs_be32 local_disc; -+ ovs_be32 remote_disc; -+ -+ uint32_t local_min_tx; -+ uint32_t local_min_rx; -+ uint32_t remote_min_rx; -+ -+ bool remote_demand_mode; -+ -+ uint8_t local_mult; -+ -+ int64_t port_key; -+ int64_t metadata; -+ -+ enum bfd_state state; -+ bool change_state; -+ -+ uint32_t detection_timeout; -+ long long int last_rx; -+ long long int next_tx; -+}; -+ -+static void -+bfd_monitor_init(void) -+{ -+ hmap_init(&bfd_monitor_map); -+ bfd_last_update = time_msec(); -+} -+ -+static void -+bfd_monitor_destroy(void) -+{ -+ struct bfd_entry *entry; -+ HMAP_FOR_EACH_POP (entry, node, &bfd_monitor_map) { -+ free(entry); -+ } -+ hmap_destroy(&bfd_monitor_map); -+} -+ -+static struct bfd_entry * -+pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port) -+{ -+ struct bfd_entry *entry; -+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0), -+ &bfd_monitor_map) { -+ if (entry->udp_src == port) { -+ return entry; -+ } -+ } -+ return NULL; -+} -+ -+static struct bfd_entry * -+pinctrl_find_bfd_monitor_entry_by_disc(char *ip, ovs_be32 disc) -+{ -+ struct bfd_entry *ret = NULL, *entry; -+ -+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0), -+ &bfd_monitor_map) { -+ if (entry->local_disc == disc) { -+ ret = entry; -+ break; -+ } -+ } -+ return ret; -+} -+ -+static bool -+bfd_monitor_should_inject(void) -+{ -+ long long int cur_time = time_msec(); -+ struct bfd_entry *entry; -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ if (entry->next_tx < cur_time) { -+ return true; -+ } -+ } -+ return false; -+} -+ -+static void -+bfd_monitor_wait(long long int timeout) -+{ -+ if (!hmap_is_empty(&bfd_monitor_map)) { -+ poll_timer_wait_until(timeout); -+ } -+} -+ -+static void -+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet, -+ bool final) -+{ -+ int payload_len = sizeof(struct udp_header) + sizeof(struct bfd_msg); -+ -+ /* Properly align after the ethernet header */ -+ dp_packet_reserve(packet, 2); -+ if (IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) { -+ ovs_be32 ip_src = in6_addr_get_mapped_ipv4(&entry->ip_src); -+ ovs_be32 ip_dst = in6_addr_get_mapped_ipv4(&entry->ip_dst); -+ pinctrl_compose_ipv4(packet, entry->src_mac, eth_addr_broadcast, -+ ip_src, ip_dst, IPPROTO_UDP, MAXTTL, payload_len); -+ } else { -+ pinctrl_compose_ipv6(packet, entry->src_mac, eth_addr_broadcast, -+ &entry->ip_src, &entry->ip_dst, IPPROTO_UDP, -+ MAXTTL, payload_len); -+ } -+ -+ struct udp_header *udp = dp_packet_put_zeros(packet, sizeof *udp); -+ udp->udp_len = htons(payload_len); -+ udp->udp_csum = 0; -+ udp->udp_src = htons(entry->udp_src); -+ udp->udp_dst = htons(BFD_DEST_PORT); -+ -+ struct bfd_msg *msg = dp_packet_put_zeros(packet, sizeof *msg); -+ msg->vers_diag = (BFD_VERSION << 5); -+ msg->mult = entry->local_mult; -+ msg->length = BFD_PACKET_LEN; -+ msg->flags = final ? BFD_FLAG_FINAL : 0; -+ msg->flags |= entry->state << 6; -+ msg->my_disc = entry->local_disc; -+ msg->your_disc = entry->remote_disc; -+ /* min_tx and min_rx are in us - RFC 5880 page 9 */ -+ msg->min_tx = htonl(entry->local_min_tx * 1000); -+ msg->min_rx = htonl(entry->local_min_rx * 1000); -+ -+ if (!IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) { -+ /* IPv6 needs UDP checksum calculated */ -+ uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(packet)); -+ int len = (uint8_t *)udp - (uint8_t *)dp_packet_eth(packet); -+ csum = csum_continue(csum, udp, dp_packet_size(packet) - len); -+ udp->udp_csum = csum_finish(csum); -+ if (!udp->udp_csum) { -+ udp->udp_csum = htons(0xffff); -+ } -+ } -+} -+ -+static void -+pinctrl_send_bfd_tx_msg(struct rconn *swconn, struct bfd_entry *entry, -+ bool final) -+{ -+ uint64_t packet_stub[256 / 8]; -+ struct dp_packet packet; -+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); -+ bfd_monitor_put_bfd_msg(entry, &packet, final); -+ -+ uint64_t ofpacts_stub[4096 / 8]; -+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); -+ -+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */ -+ uint32_t dp_key = entry->metadata; -+ uint32_t port_key = entry->port_key; -+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); -+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); -+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts); -+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); -+ resubmit->in_port = OFPP_CONTROLLER; -+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE; -+ -+ struct ofputil_packet_out po = { -+ .packet = dp_packet_data(&packet), -+ .packet_len = dp_packet_size(&packet), -+ .buffer_id = UINT32_MAX, -+ .ofpacts = ofpacts.data, -+ .ofpacts_len = ofpacts.size, -+ }; -+ -+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); -+ enum ofp_version version = rconn_get_version(swconn); -+ enum ofputil_protocol proto = -+ ofputil_protocol_from_ofp_version(version); -+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); -+ dp_packet_uninit(&packet); -+ ofpbuf_uninit(&ofpacts); -+} -+ -+ -+static bool -+bfd_monitor_need_update(void) -+{ -+ long long int cur_time = time_msec(); -+ -+ if (bfd_pending_update == BFD_UPDATE_BATCH_TH) { -+ goto update; -+ } -+ -+ if (bfd_pending_update && -+ bfd_last_update + BFD_UPDATE_TIMEOUT < cur_time) { -+ goto update; -+ } -+ return false; -+ -+update: -+ bfd_last_update = cur_time; -+ bfd_pending_update = 0; -+ return true; -+} -+ -+static void -+bfd_check_detection_timeout(struct bfd_entry *entry) -+{ -+ if (entry->state == BFD_STATE_ADMIN_DOWN) { -+ return; -+ } -+ -+ if (!entry->detection_timeout) { -+ return; -+ } -+ -+ long long int cur_time = time_msec(); -+ if (cur_time < entry->last_rx + entry->detection_timeout) { -+ return; -+ } -+ -+ entry->state = BFD_STATE_DOWN; -+ entry->change_state = true; -+ bfd_last_update = cur_time; -+ bfd_pending_update = 0; -+ notify_pinctrl_main(); -+} -+ -+static void -+bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ long long int cur_time = time_msec(); -+ struct bfd_entry *entry; -+ -+ if (bfd_monitor_need_update()) { -+ notify_pinctrl_main(); -+ } -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ unsigned long tx_timeout; -+ -+ bfd_check_detection_timeout(entry); -+ -+ if (cur_time < entry->next_tx) { -+ goto next; -+ } -+ -+ if (!entry->remote_min_rx) { -+ continue; -+ } -+ -+ if (entry->state == BFD_STATE_ADMIN_DOWN) { -+ continue; -+ } -+ -+ if (entry->remote_demand_mode) { -+ continue; -+ } -+ -+ pinctrl_send_bfd_tx_msg(swconn, entry, false); -+ -+ tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx); -+ tx_timeout -= random_range((tx_timeout * 25) / 100); -+ entry->next_tx = cur_time + tx_timeout; -+next: -+ if (*bfd_time > entry->next_tx) { -+ *bfd_time = entry->next_tx; -+ } -+ } -+} -+ -+static bool -+pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in) -+{ -+ if (ip_flow->dl_type != htons(ETH_TYPE_IP) && -+ ip_flow->dl_type != htons(ETH_TYPE_IPV6)) { -+ return false; -+ } -+ -+ if (ip_flow->nw_proto != IPPROTO_UDP) { -+ return false; -+ } -+ -+ struct udp_header *udp_hdr = dp_packet_l4(pkt_in); -+ if (udp_hdr->udp_dst != htons(BFD_DEST_PORT)) { -+ return false; -+ } -+ -+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in); -+ uint8_t version = msg->vers_diag >> 5; -+ if (version != BFD_VERSION) { -+ return false; -+ } -+ -+ enum bfd_flags flags = msg->flags & BFD_FLAGS_MASK; -+ if (flags & BFD_FLAG_AUTH) { -+ /* AUTH not supported yet */ -+ return false; -+ } -+ -+ if (msg->length < BFD_PACKET_LEN) { -+ return false; -+ } -+ -+ if (!msg->mult) { -+ return false; -+ } -+ -+ if (flags & BFD_FLAG_MULTIPOINT) { -+ return false; -+ } -+ -+ if (!msg->my_disc) { -+ return false; -+ } -+ -+ if ((flags & BFD_FLAG_FINAL) && (flags & BFD_FLAG_POLL)) { -+ return false; -+ } -+ -+ enum bfd_state peer_state = msg->flags >> 6; -+ if (peer_state >= BFD_STATE_INIT && !msg->your_disc) { -+ return false; -+ } -+ -+ return true; -+} -+ -+static void -+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow, -+ struct dp_packet *pkt_in) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "BFD packet discarded"); -+ return; -+ } -+ -+ char *ip_src; -+ if (ip_flow->dl_type == htons(ETH_TYPE_IP)) { -+ ip_src = normalize_ipv4_prefix(ip_flow->nw_src, 32); -+ } else { -+ ip_src = normalize_ipv6_prefix(&ip_flow->ipv6_src, 128); -+ } -+ -+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in); -+ struct bfd_entry *entry = -+ pinctrl_find_bfd_monitor_entry_by_disc(ip_src, msg->your_disc); -+ free(ip_src); -+ -+ if (!entry) { -+ return; -+ } -+ -+ bool change_state = false; -+ entry->remote_disc = msg->my_disc; -+ uint32_t remote_min_tx = ntohl(msg->min_tx) / 1000; -+ entry->remote_min_rx = ntohl(msg->min_rx) / 1000; -+ entry->detection_timeout = msg->mult * MAX(remote_min_tx, -+ entry->local_min_rx); -+ -+ enum bfd_state peer_state = msg->flags >> 6; -+ if (peer_state == BFD_STATE_ADMIN_DOWN && -+ entry->state >= BFD_STATE_INIT) { -+ entry->state = BFD_STATE_DOWN; -+ entry->last_rx = time_msec(); -+ change_state = true; -+ goto out; -+ } -+ -+ /* bfd state machine */ -+ switch (entry->state) { -+ case BFD_STATE_DOWN: -+ if (peer_state == BFD_STATE_DOWN) { -+ entry->state = BFD_STATE_INIT; -+ change_state = true; -+ } -+ if (peer_state == BFD_STATE_INIT) { -+ entry->state = BFD_STATE_UP; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_INIT: -+ if (peer_state == BFD_STATE_INIT || -+ peer_state == BFD_STATE_UP) { -+ entry->state = BFD_STATE_UP; -+ change_state = true; -+ } -+ if (peer_state == BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_UP: -+ if (peer_state == BFD_STATE_ADMIN_DOWN || -+ peer_state == BFD_STATE_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ change_state = true; -+ } -+ entry->last_rx = time_msec(); -+ break; -+ case BFD_STATE_ADMIN_DOWN: -+ default: -+ break; -+ } -+ -+ if (entry->state == BFD_STATE_UP && -+ (msg->flags & BFD_FLAG_DEMAND)) { -+ entry->remote_demand_mode = true; -+ } -+ -+ if (msg->flags & BFD_FLAG_POLL) { -+ pinctrl_send_bfd_tx_msg(swconn, entry, true); -+ } -+ -+out: -+ /* let's try to bacth db updates */ -+ if (change_state) { -+ entry->change_state = true; -+ bfd_pending_update++; -+ } -+ if (bfd_monitor_need_update()) { -+ notify_pinctrl_main(); -+ } -+} -+ -+static void -+bfd_monitor_check_sb_conf(const struct sbrec_bfd *sb_bt, -+ struct bfd_entry *entry) -+{ -+ struct lport_addresses dst_addr; -+ -+ if (extract_ip_addresses(sb_bt->dst_ip, &dst_addr)) { -+ struct in6_addr addr; -+ -+ if (dst_addr.n_ipv6_addrs > 0) { -+ addr = dst_addr.ipv6_addrs[0].addr; -+ } else { -+ addr = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr); -+ } -+ -+ if (!ipv6_addr_equals(&addr, &entry->ip_dst)) { -+ entry->ip_dst = addr; -+ } -+ destroy_lport_addresses(&dst_addr); -+ } -+ -+ if (sb_bt->min_tx != entry->local_min_tx) { -+ entry->local_min_tx = sb_bt->min_tx; -+ } -+ -+ if (sb_bt->min_rx != entry->local_min_rx) { -+ entry->local_min_rx = sb_bt->min_rx; -+ } -+ -+ if (sb_bt->detect_mult != entry->local_mult) { -+ entry->local_mult = sb_bt->detect_mult; -+ } -+} -+ -+static void -+bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ const struct sbrec_bfd_table *bfd_table, -+ struct ovsdb_idl_index *sbrec_port_binding_by_name, -+ const struct sbrec_chassis *chassis, -+ const struct sset *active_tunnels) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ struct bfd_entry *entry, *next_entry; -+ long long int cur_time = time_msec(); -+ bool changed = false; -+ -+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) { -+ entry->erase = true; -+ } -+ -+ const struct sbrec_bfd *bt; -+ SBREC_BFD_TABLE_FOR_EACH (bt, bfd_table) { -+ const struct sbrec_port_binding *pb -+ = lport_lookup_by_name(sbrec_port_binding_by_name, -+ bt->logical_port); -+ if (!pb) { -+ continue; -+ } -+ -+ const char *peer_s = smap_get(&pb->options, "peer"); -+ if (!peer_s) { -+ continue; -+ } -+ -+ const struct sbrec_port_binding *peer -+ = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s); -+ if (!peer) { -+ continue; -+ } -+ -+ char *redirect_name = xasprintf("cr-%s", pb->logical_port); -+ bool resident = lport_is_chassis_resident( -+ sbrec_port_binding_by_name, chassis, active_tunnels, -+ redirect_name); -+ free(redirect_name); -+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) && -+ !resident) { -+ continue; -+ } -+ -+ entry = pinctrl_find_bfd_monitor_entry_by_port( -+ bt->dst_ip, bt->src_port); -+ if (!entry) { -+ struct eth_addr ea = eth_addr_zero; -+ struct lport_addresses dst_addr; -+ struct in6_addr ip_src, ip_dst; -+ int i; -+ -+ ip_dst = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_DST_IP)); -+ ip_src = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_SRC_IP)); -+ -+ if (!extract_ip_addresses(bt->dst_ip, &dst_addr)) { -+ continue; -+ } -+ -+ for (i = 0; i < pb->n_mac; i++) { -+ struct lport_addresses laddrs; -+ -+ if (!extract_lsp_addresses(pb->mac[i], &laddrs)) { -+ continue; -+ } -+ -+ ea = laddrs.ea; -+ if (dst_addr.n_ipv6_addrs > 0 && laddrs.n_ipv6_addrs > 0) { -+ ip_dst = dst_addr.ipv6_addrs[0].addr; -+ ip_src = laddrs.ipv6_addrs[0].addr; -+ destroy_lport_addresses(&laddrs); -+ break; -+ } else if (laddrs.n_ipv4_addrs > 0) { -+ ip_dst = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr); -+ ip_src = in6_addr_mapped_ipv4(laddrs.ipv4_addrs[0].addr); -+ destroy_lport_addresses(&laddrs); -+ break; -+ } -+ destroy_lport_addresses(&laddrs); -+ } -+ destroy_lport_addresses(&dst_addr); -+ -+ if (eth_addr_is_zero(ea)) { -+ continue; -+ } -+ -+ entry = xzalloc(sizeof *entry); -+ entry->src_mac = ea; -+ entry->ip_src = ip_src; -+ entry->ip_dst = ip_dst; -+ entry->udp_src = bt->src_port; -+ entry->local_disc = htonl(bt->disc); -+ entry->next_tx = cur_time; -+ entry->last_rx = cur_time; -+ entry->detection_timeout = 30000; -+ entry->metadata = pb->datapath->tunnel_key; -+ entry->port_key = pb->tunnel_key; -+ entry->state = BFD_STATE_ADMIN_DOWN; -+ entry->local_min_tx = bt->min_tx; -+ entry->local_min_rx = bt->min_rx; -+ entry->remote_min_rx = 1; /* RFC5880 page 29 */ -+ entry->local_mult = bt->detect_mult; -+ -+ uint32_t hash = hash_string(bt->dst_ip, 0); -+ hmap_insert(&bfd_monitor_map, &entry->node, hash); -+ } else if (!strcmp(bt->status, "admin_down") && -+ entry->state != BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_ADMIN_DOWN; -+ entry->change_state = false; -+ entry->remote_disc = 0; -+ } else if (strcmp(bt->status, "admin_down") && -+ entry->state == BFD_STATE_ADMIN_DOWN) { -+ entry->state = BFD_STATE_DOWN; -+ entry->change_state = false; -+ entry->remote_disc = 0; -+ changed = true; -+ } else if (entry->change_state && ovnsb_idl_txn) { -+ if (entry->state == BFD_STATE_DOWN) { -+ entry->remote_disc = 0; -+ } -+ sbrec_bfd_set_status(bt, bfd_get_status(entry->state)); -+ entry->change_state = false; -+ } -+ bfd_monitor_check_sb_conf(bt, entry); -+ entry->erase = false; -+ } -+ -+ HMAP_FOR_EACH_SAFE (entry, next_entry, node, &bfd_monitor_map) { -+ if (entry->erase) { -+ hmap_remove(&bfd_monitor_map, &entry->node); -+ free(entry); -+ } -+ } -+ -+ if (changed) { -+ notify_pinctrl_handler(); -+ } -+} -+ - static uint16_t - get_random_src_port(void) - { -@@ -6724,3 +7522,94 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow, - svc_mon->next_send_time = time_msec() + svc_mon->interval; - } - } -+ -+static struct hmap put_fdbs; -+ -+/* MAC learning (fdb) related functions. Runs within the main -+ * ovn-controller thread context. */ -+ -+static void -+init_fdb_entries(void) -+{ -+ ovn_fdb_init(&put_fdbs); -+} -+ -+static void -+destroy_fdb_entries(void) -+{ -+ ovn_fdbs_destroy(&put_fdbs); -+} -+ -+static const struct sbrec_fdb * -+fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key, -+ const char *mac) -+{ -+ struct sbrec_fdb *fdb = sbrec_fdb_index_init_row(sbrec_fdb_by_dp_key_mac); -+ sbrec_fdb_index_set_dp_key(fdb, dp_key); -+ sbrec_fdb_index_set_mac(fdb, mac); -+ -+ const struct sbrec_fdb *retval -+ = sbrec_fdb_index_find(sbrec_fdb_by_dp_key_mac, fdb); -+ -+ sbrec_fdb_index_destroy_row(fdb); -+ -+ return retval; -+} -+ -+static void -+run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, -+ const struct fdb_entry *fdb_e) -+{ -+ /* 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(fdb_e->mac)); -+ -+ /* Update or add an FDB entry. */ -+ const struct sbrec_fdb *sb_fdb = -+ fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string); -+ if (!sb_fdb) { -+ sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn); -+ sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key); -+ sbrec_fdb_set_mac(sb_fdb, mac_string); -+ } -+ sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key); -+} -+ -+static void -+run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ if (!ovnsb_idl_txn) { -+ return; -+ } -+ -+ const struct fdb_entry *fdb_e; -+ HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) { -+ run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e); -+ } -+ ovn_fdbs_flush(&put_fdbs); -+} -+ -+ -+static void -+wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn) -+{ -+ if (ovnsb_idl_txn && !hmap_is_empty(&put_fdbs)) { -+ poll_immediate_wake(); -+ } -+} -+ -+/* Called with in the pinctrl_handler thread context. */ -+static void -+pinctrl_handle_put_fdb(const struct flow *md, const struct flow *headers) -+ OVS_REQUIRES(pinctrl_mutex) -+{ -+ uint32_t dp_key = ntohll(md->metadata); -+ uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0]; -+ -+ ovn_fdb_add(&put_fdbs, dp_key, headers->dl_src, port_key); -+ notify_pinctrl_main(); -+} -diff --git a/controller/pinctrl.h b/controller/pinctrl.h -index 4b101ec92..cc0a51984 100644 ---- a/controller/pinctrl.h -+++ b/controller/pinctrl.h -@@ -31,6 +31,7 @@ struct sbrec_chassis; - struct sbrec_dns_table; - struct sbrec_controller_event_table; - struct sbrec_service_monitor_table; -+struct sbrec_bfd_table; - - void pinctrl_init(void); - void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, -@@ -41,9 +42,11 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip, - struct ovsdb_idl_index *sbrec_igmp_groups, - struct ovsdb_idl_index *sbrec_ip_multicast_opts, -+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, - const struct sbrec_dns_table *, - const struct sbrec_controller_event_table *, - const struct sbrec_service_monitor_table *, -+ const struct sbrec_bfd_table *, - const struct ovsrec_bridge *, const struct sbrec_chassis *, - const struct hmap *local_datapaths, - const struct sset *active_tunnels); -diff --git a/controller/test-ofctrl-seqno.c b/controller/test-ofctrl-seqno.c -new file mode 100644 -index 000000000..fce88d4bd ---- /dev/null -+++ b/controller/test-ofctrl-seqno.c -@@ -0,0 +1,194 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "tests/ovstest.h" -+#include "sort.h" -+#include "util.h" -+ -+#include "ofctrl-seqno.h" -+ -+static void -+test_init(void) -+{ -+ ofctrl_seqno_init(); -+} -+ -+static bool -+test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index, -+ const char *descr, unsigned int *result) -+{ -+ if (index >= ctx->argc) { -+ fprintf(stderr, "Missing %s argument\n", descr); -+ return false; -+ } -+ -+ const char *arg = ctx->argv[index]; -+ if (!str_to_uint(arg, 10, result)) { -+ fprintf(stderr, "Invalid %s: %s\n", descr, arg); -+ return false; -+ } -+ return true; -+} -+ -+static int -+test_seqno_compare(size_t a, size_t b, void *values_) -+{ -+ uint64_t *values = values_; -+ -+ return values[a] == values[b] ? 0 : (values[a] < values[b] ? -1 : 1); -+} -+ -+static void -+test_seqno_swap(size_t a, size_t b, void *values_) -+{ -+ uint64_t *values = values_; -+ uint64_t tmp = values[a]; -+ -+ values[a] = values[b]; -+ values[b] = tmp; -+} -+ -+static void -+test_dump_acked_seqnos(size_t seqno_type) -+{ -+ struct ofctrl_acked_seqnos * acked_seqnos = -+ ofctrl_acked_seqnos_get(seqno_type); -+ -+ printf("ofctrl-seqno-type: %"PRIuSIZE"\n", seqno_type); -+ printf(" last-acked %"PRIu64"\n", acked_seqnos->last_acked); -+ -+ size_t n_acked = hmap_count(&acked_seqnos->acked); -+ uint64_t *acked = xmalloc(n_acked * sizeof *acked); -+ struct ofctrl_ack_seqno *ack_seqno; -+ size_t i = 0; -+ -+ /* A bit hacky but ignoring overflows the "total of all seqno + 1" should -+ * be a number that is not part of the acked seqnos. -+ */ -+ uint64_t total_seqno = 1; -+ HMAP_FOR_EACH (ack_seqno, node, &acked_seqnos->acked) { -+ ovs_assert(ofctrl_acked_seqnos_contains(acked_seqnos, -+ ack_seqno->seqno)); -+ total_seqno += ack_seqno->seqno; -+ acked[i++] = ack_seqno->seqno; -+ } -+ ovs_assert(!ofctrl_acked_seqnos_contains(acked_seqnos, total_seqno)); -+ -+ sort(n_acked, test_seqno_compare, test_seqno_swap, acked); -+ -+ for (i = 0; i < n_acked; i++) { -+ printf(" %"PRIu64"\n", acked[i]); -+ } -+ -+ free(acked); -+ ofctrl_acked_seqnos_destroy(acked_seqnos); -+} -+ -+static void -+test_ofctrl_seqno_add_type(struct ovs_cmdl_context *ctx) -+{ -+ unsigned int n_types; -+ -+ test_init(); -+ -+ if (!test_read_uint_value(ctx, 1, "n_types", &n_types)) { -+ return; -+ } -+ for (unsigned int i = 0; i < n_types; i++) { -+ printf("%"PRIuSIZE"\n", ofctrl_seqno_add_type()); -+ } -+} -+ -+static void -+test_ofctrl_seqno_ack_seqnos(struct ovs_cmdl_context *ctx) -+{ -+ unsigned int n_reqs = 0; -+ unsigned int shift = 2; -+ unsigned int n_types; -+ unsigned int n_acks; -+ -+ test_init(); -+ bool batch_acks = !strcmp(ctx->argv[1], "true"); -+ -+ if (!test_read_uint_value(ctx, shift++, "n_types", &n_types)) { -+ return; -+ } -+ -+ for (unsigned int i = 0; i < n_types; i++) { -+ ovs_assert(ofctrl_seqno_add_type() == i); -+ -+ /* Read number of app specific seqnos. */ -+ unsigned int n_app_seqnos; -+ -+ if (!test_read_uint_value(ctx, shift++, "n_app_seqnos", -+ &n_app_seqnos)) { -+ return; -+ } -+ -+ for (unsigned int j = 0; j < n_app_seqnos; j++, n_reqs++) { -+ unsigned int app_seqno; -+ -+ if (!test_read_uint_value(ctx, shift++, "app_seqno", &app_seqno)) { -+ return; -+ } -+ ofctrl_seqno_update_create(i, app_seqno); -+ } -+ } -+ printf("ofctrl-seqno-req-cfg: %u\n", n_reqs); -+ -+ if (!test_read_uint_value(ctx, shift++, "n_acks", &n_acks)) { -+ return; -+ } -+ for (unsigned int i = 0; i < n_acks; i++) { -+ unsigned int ack_seqno; -+ -+ if (!test_read_uint_value(ctx, shift++, "ack_seqno", &ack_seqno)) { -+ return; -+ } -+ ofctrl_seqno_run(ack_seqno); -+ -+ if (!batch_acks) { -+ for (unsigned int st = 0; st < n_types; st++) { -+ test_dump_acked_seqnos(st); -+ } -+ } -+ } -+ if (batch_acks) { -+ for (unsigned int st = 0; st < n_types; st++) { -+ test_dump_acked_seqnos(st); -+ } -+ } -+} -+ -+static void -+test_ofctrl_seqno_main(int argc, char *argv[]) -+{ -+ set_program_name(argv[0]); -+ static const struct ovs_cmdl_command commands[] = { -+ {"ofctrl_seqno_add_type", NULL, 1, 1, -+ test_ofctrl_seqno_add_type, OVS_RO}, -+ {"ofctrl_seqno_ack_seqnos", NULL, 2, INT_MAX, -+ test_ofctrl_seqno_ack_seqnos, OVS_RO}, -+ {NULL, NULL, 0, 0, NULL, OVS_RO}, -+ }; -+ struct ovs_cmdl_context ctx; -+ ctx.argc = argc - 1; -+ ctx.argv = argv + 1; -+ ovs_cmdl_run_command(&ctx, commands); -+} -+ -+OVSTEST_REGISTER("test-ofctrl-seqno", test_ofctrl_seqno_main); -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 9c1ebf4aa..040213177 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -105,6 +105,11 @@ struct ovn_extend_table; - OVNACT(CHK_LB_HAIRPIN, ovnact_result) \ - OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \ - OVNACT(CT_SNAT_TO_VIP, ovnact_null) \ -+ OVNACT(BFD_MSG, ovnact_null) \ -+ OVNACT(SCTP_ABORT, ovnact_nest) \ -+ OVNACT(PUT_FDB, ovnact_put_fdb) \ -+ OVNACT(GET_FDB, ovnact_get_fdb) \ -+ OVNACT(LOOKUP_FDB, ovnact_lookup_fdb) \ - - /* enum ovnact_type, with a member OVNACT_ for each action. */ - enum OVS_PACKED_ENUM ovnact_type { -@@ -413,6 +418,28 @@ struct ovnact_fwd_group { - uint8_t ltable; /* Logical table ID of next table. */ - }; - -+/* OVNACT_PUT_FDB. */ -+struct ovnact_put_fdb { -+ struct ovnact ovnact; -+ struct expr_field port; /* Logical port name. */ -+ struct expr_field mac; /* 48-bit Ethernet address. */ -+}; -+ -+/* OVNACT_GET_FDB. */ -+struct ovnact_get_fdb { -+ struct ovnact ovnact; -+ struct expr_field mac; /* 48-bit Ethernet address. */ -+ struct expr_field dst; /* 32-bit destination field. */ -+}; -+ -+/* OVNACT_LOOKUP_FDB. */ -+struct ovnact_lookup_fdb { -+ struct ovnact ovnact; -+ struct expr_field mac; /* 48-bit Ethernet address. */ -+ struct expr_field port; /* Logical port name. */ -+ struct expr_field dst; /* 1-bit destination field. */ -+}; -+ - /* Internal use by the helpers below. */ - void ovnact_init(struct ovnact *, enum ovnact_type, size_t len); - void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len); -@@ -627,6 +654,22 @@ enum action_opcode { - * The actions, in OpenFlow 1.3 format, follow the action_header. - */ - ACTION_OPCODE_REJECT, -+ -+ /* handle_bfd_msg { ...actions ...}." -+ * -+ * The actions, in OpenFlow 1.3 format, follow the action_header. -+ */ -+ ACTION_OPCODE_BFD_MSG, -+ -+ /* "sctp_abort { ...actions... }". -+ * -+ * The actions, in OpenFlow 1.3 format, follow the action_header. -+ */ -+ ACTION_OPCODE_SCTP_ABORT, -+ -+ /* put_fdb(inport, eth.src). -+ */ -+ ACTION_OPCODE_PUT_FDB, - }; - - /* Header. */ -@@ -748,6 +791,10 @@ struct ovnact_encode_params { - * 'chk_lb_hairpin_reply' to resubmit. */ - uint8_t ct_snat_vip_ptable; /* OpenFlow table for - * 'ct_snat_to_vip' to resubmit. */ -+ uint8_t fdb_ptable; /* OpenFlow table for -+ * 'get_fdb' to resubmit. */ -+ uint8_t fdb_lookup_ptable; /* OpenFlow table for -+ * 'lookup_fdb' to resubmit. */ - }; - - void ovnacts_encode(const struct ovnact[], size_t ovnacts_len, -diff --git a/include/ovn/automake.mk b/include/ovn/automake.mk -index 54b0e2c0e..582241a57 100644 ---- a/include/ovn/automake.mk -+++ b/include/ovn/automake.mk -@@ -2,5 +2,6 @@ ovnincludedir = $(includedir)/ovn - ovninclude_HEADERS = \ - include/ovn/actions.h \ - include/ovn/expr.h \ -+ include/ovn/features.h \ - include/ovn/lex.h \ - include/ovn/logical-fields.h -diff --git a/include/ovn/expr.h b/include/ovn/expr.h -index 0a83ec7a8..c2c821818 100644 ---- a/include/ovn/expr.h -+++ b/include/ovn/expr.h -@@ -477,6 +477,7 @@ uint32_t expr_to_matches(const struct expr *, - const void *aux, - struct hmap *matches); - void expr_matches_destroy(struct hmap *matches); -+void expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs); - void expr_matches_print(const struct hmap *matches, FILE *); - - /* Action parsing helper. */ -diff --git a/include/ovn/features.h b/include/ovn/features.h -new file mode 100644 -index 000000000..10ee46fcd ---- /dev/null -+++ b/include/ovn/features.h -@@ -0,0 +1,22 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#ifndef OVN_FEATURES_H -+#define OVN_FEATURES_H 1 -+ -+/* ovn-controller supported feature names. */ -+#define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif" -+ -+#endif -diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h -index aee474856..017176f98 100644 ---- a/include/ovn/logical-fields.h -+++ b/include/ovn/logical-fields.h -@@ -44,7 +44,13 @@ enum ovn_controller_event { - /* Logical registers. - * - * Make sure these don't overlap with the logical fields! */ --#define MFF_LOG_REG0 MFF_REG0 -+#define MFF_LOG_REG0 MFF_REG0 -+#define MFF_LOG_LB_ORIG_DIP_IPV4 MFF_REG1 -+#define MFF_LOG_LB_ORIG_TP_DPORT MFF_REG2 -+ -+#define MFF_LOG_XXREG0 MFF_XXREG0 -+#define MFF_LOG_LB_ORIG_DIP_IPV6 MFF_XXREG1 -+ - #define MFF_N_LOG_REGS 10 - - void ovn_init_symtab(struct shash *symtab); -@@ -59,6 +65,7 @@ enum mff_log_flags_bits { - MLF_NESTED_CONTAINER_BIT = 5, - MLF_LOOKUP_MAC_BIT = 6, - MLF_LOOKUP_LB_HAIRPIN_BIT = 7, -+ MLF_LOOKUP_FDB_BIT = 8, - }; - - /* MFF_LOG_FLAGS_REG flag assignments */ -@@ -92,6 +99,9 @@ enum mff_log_flags { - MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT), - - MLF_LOOKUP_LB_HAIRPIN = (1 << MLF_LOOKUP_LB_HAIRPIN_BIT), -+ -+ /* Indicate that the lookup in the fdb table was successful. */ -+ MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT), - }; - - /* OVN logical fields -diff --git a/lib/actions.c b/lib/actions.c -index fbaeb34bc..b3433f49e 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -1490,6 +1490,12 @@ parse_TCP_RESET(struct action_context *ctx) - parse_nested_action(ctx, OVNACT_TCP_RESET, "tcp", ctx->scope); - } - -+static void -+parse_SCTP_ABORT(struct action_context *ctx) -+{ -+ parse_nested_action(ctx, OVNACT_SCTP_ABORT, "sctp", ctx->scope); -+} -+ - static void - parse_ND_NA(struct action_context *ctx) - { -@@ -1571,6 +1577,12 @@ format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s) - format_nested_action(nest, "tcp_reset", s); - } - -+static void -+format_SCTP_ABORT(const struct ovnact_nest *nest, struct ds *s) -+{ -+ format_nested_action(nest, "sctp_abort", s); -+} -+ - static void - format_ND_NA(const struct ovnact_nest *nest, struct ds *s) - { -@@ -1700,6 +1712,14 @@ encode_TCP_RESET(const struct ovnact_nest *on, - encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts); - } - -+static void -+encode_SCTP_ABORT(const struct ovnact_nest *on, -+ const struct ovnact_encode_params *ep, -+ struct ofpbuf *ofpacts) -+{ -+ encode_nested_actions(on, ep, ACTION_OPCODE_SCTP_ABORT, ofpacts); -+} -+ - static void - encode_REJECT(const struct ovnact_nest *on, - const struct ovnact_encode_params *ep, -@@ -2742,6 +2762,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED, - encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts); - } - -+static void -+format_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, struct ds *s) -+{ -+ ds_put_cstr(s, "handle_bfd_msg();"); -+} -+ -+static void -+encode_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, -+ const struct ovnact_encode_params *ep OVS_UNUSED, -+ struct ofpbuf *ofpacts) -+{ -+ encode_controller_op(ACTION_OPCODE_BFD_MSG, ofpacts); -+} -+ -+static void -+parse_handle_bfd_msg(struct action_context *ctx OVS_UNUSED) -+{ -+ if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) { -+ return; -+ } -+ -+ ovnact_put_BFD_MSG(ctx->ovnacts); -+ lexer_force_match(ctx->lexer, LEX_T_RPAREN); -+} -+ - static void - parse_SET_QUEUE(struct action_context *ctx) - { -@@ -3698,6 +3743,172 @@ encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED, - emit_resubmit(ofpacts, ep->ct_snat_vip_ptable); - } - -+static void -+format_PUT_FDB(const struct ovnact_put_fdb *put_fdb, struct ds *s) -+{ -+ ds_put_cstr(s, "put_fdb("); -+ expr_field_format(&put_fdb->port, s); -+ ds_put_cstr(s, ", "); -+ expr_field_format(&put_fdb->mac, s); -+ ds_put_cstr(s, ");"); -+} -+ -+static void -+encode_PUT_FDB(const struct ovnact_put_fdb *put_fdb, -+ const struct ovnact_encode_params *ep OVS_UNUSED, -+ struct ofpbuf *ofpacts) -+{ -+ const struct arg args[] = { -+ { expr_resolve_field(&put_fdb->port), MFF_LOG_INPORT }, -+ { expr_resolve_field(&put_fdb->mac), MFF_ETH_SRC } -+ }; -+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts); -+ encode_controller_op(ACTION_OPCODE_PUT_FDB, ofpacts); -+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts); -+} -+ -+static void -+parse_put_fdb(struct action_context *ctx, struct ovnact_put_fdb *put_fdb) -+{ -+ lexer_force_match(ctx->lexer, LEX_T_LPAREN); -+ action_parse_field(ctx, 0, false, &put_fdb->port); -+ lexer_force_match(ctx->lexer, LEX_T_COMMA); -+ action_parse_field(ctx, 48, false, &put_fdb->mac); -+ lexer_force_match(ctx->lexer, LEX_T_RPAREN); -+} -+ -+static void -+ovnact_put_fdb_free(struct ovnact_put_fdb *put_fdb OVS_UNUSED) -+{ -+} -+ -+static void -+format_GET_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s) -+{ -+ expr_field_format(&get_fdb->dst, s); -+ ds_put_cstr(s, " = get_fdb("); -+ expr_field_format(&get_fdb->mac, s); -+ ds_put_cstr(s, ");"); -+} -+ -+static void -+encode_GET_FDB(const struct ovnact_get_fdb *get_fdb, -+ const struct ovnact_encode_params *ep, -+ struct ofpbuf *ofpacts) -+{ -+ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst); -+ ovs_assert(dst.field); -+ -+ const struct arg args[] = { -+ { expr_resolve_field(&get_fdb->mac), MFF_ETH_DST }, -+ }; -+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts); -+ put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts); -+ emit_resubmit(ofpacts, ep->fdb_ptable); -+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts); -+ -+ if (dst.field->id != MFF_LOG_OUTPORT) { -+ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts); -+ orm->dst = dst; -+ orm->src.field = mf_from_id(MFF_LOG_OUTPORT); -+ orm->src.ofs = 0; -+ orm->src.n_bits = 32; -+ } -+} -+ -+static void -+parse_get_fdb(struct action_context *ctx, -+ struct expr_field *dst, -+ struct ovnact_get_fdb *get_fdb) -+{ -+ lexer_get(ctx->lexer); /* Skip get_bfd. */ -+ lexer_get(ctx->lexer); /* Skip '('. */ -+ -+ /* Validate that the destination is a 32-bit, modifiable field if it -+ is not a string field (i.e 'inport' or 'outport'). */ -+ if (dst->n_bits) { -+ char *error = expr_type_check(dst, 32, true, ctx->scope); -+ if (error) { -+ lexer_error(ctx->lexer, "%s", error); -+ free(error); -+ return; -+ } -+ } -+ get_fdb->dst = *dst; -+ -+ action_parse_field(ctx, 48, false, &get_fdb->mac); -+ lexer_force_match(ctx->lexer, LEX_T_RPAREN); -+} -+ -+static void -+ovnact_get_fdb_free(struct ovnact_get_fdb *get_fdb OVS_UNUSED) -+{ -+} -+ -+static void -+format_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb, struct ds *s) -+{ -+ expr_field_format(&lookup_fdb->dst, s); -+ ds_put_cstr(s, " = lookup_fdb("); -+ expr_field_format(&lookup_fdb->port, s); -+ ds_put_cstr(s, ", "); -+ expr_field_format(&lookup_fdb->mac, s); -+ ds_put_cstr(s, ");"); -+} -+ -+static void -+encode_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb, -+ const struct ovnact_encode_params *ep, -+ struct ofpbuf *ofpacts) -+{ -+ const struct arg args[] = { -+ { expr_resolve_field(&lookup_fdb->port), MFF_LOG_INPORT }, -+ { expr_resolve_field(&lookup_fdb->mac), MFF_ETH_SRC }, -+ }; -+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts); -+ -+ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst); -+ ovs_assert(dst.field); -+ -+ put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_FDB_BIT, 1, ofpacts); -+ emit_resubmit(ofpacts, ep->fdb_lookup_ptable); -+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts); -+ -+ 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_FDB_BIT; -+ orm->src.n_bits = 1; -+} -+ -+static void -+parse_lookup_fdb(struct action_context *ctx, -+ struct expr_field *dst, -+ struct ovnact_lookup_fdb *lookup_fdb) -+{ -+ lexer_get(ctx->lexer); /* Skip lookup_bfd. */ -+ lexer_get(ctx->lexer); /* Skip '('. */ -+ -+ /* Validate that the destination is a 1-bit, modifiable field. */ -+ char *error = expr_type_check(dst, 1, true, ctx->scope); -+ if (error) { -+ lexer_error(ctx->lexer, "%s", error); -+ free(error); -+ return; -+ } -+ lookup_fdb->dst = *dst; -+ -+ action_parse_field(ctx, 0, false, &lookup_fdb->port); -+ lexer_force_match(ctx->lexer, LEX_T_COMMA); -+ action_parse_field(ctx, 48, false, &lookup_fdb->mac); -+ lexer_force_match(ctx->lexer, LEX_T_RPAREN); -+} -+ -+static void -+ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED) -+{ -+} -+ - /* Parses an assignment or exchange or put_dhcp_opts action. */ - static void - parse_set_action(struct action_context *ctx) -@@ -3758,6 +3969,14 @@ parse_set_action(struct action_context *ctx) - && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { - parse_chk_lb_hairpin_reply( - ctx, &lhs, ovnact_put_CHK_LB_HAIRPIN_REPLY(ctx->ovnacts)); -+ } else if (!strcmp(ctx->lexer->token.s, "get_fdb") -+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { -+ parse_get_fdb( -+ ctx, &lhs, ovnact_put_GET_FDB(ctx->ovnacts)); -+ } else if (!strcmp(ctx->lexer->token.s, "lookup_fdb") -+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { -+ parse_lookup_fdb( -+ ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts)); - } else { - parse_assignment_action(ctx, false, &lhs); - } -@@ -3812,6 +4031,8 @@ parse_action(struct action_context *ctx) - ovnact_put_IGMP(ctx->ovnacts); - } else if (lexer_match_id(ctx->lexer, "tcp_reset")) { - parse_TCP_RESET(ctx); -+ } else if (lexer_match_id(ctx->lexer, "sctp_abort")) { -+ parse_SCTP_ABORT(ctx); - } else if (lexer_match_id(ctx->lexer, "nd_na")) { - parse_ND_NA(ctx); - } else if (lexer_match_id(ctx->lexer, "nd_na_router")) { -@@ -3842,10 +4063,14 @@ parse_action(struct action_context *ctx) - 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, "handle_bfd_msg")) { -+ parse_handle_bfd_msg(ctx); - } 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 if (lexer_match_id(ctx->lexer, "put_fdb")) { -+ parse_put_fdb(ctx, ovnact_put_PUT_FDB(ctx->ovnacts)); - } else { - lexer_syntax_error(ctx->lexer, "expecting action"); - } -diff --git a/lib/expr.c b/lib/expr.c -index 4566d9110..796e88ac7 100644 ---- a/lib/expr.c -+++ b/lib/expr.c -@@ -3125,6 +3125,25 @@ expr_to_matches(const struct expr *expr, - return n_conjs; - } - -+/* Prepares the expr matches in the hmap 'matches' by updating the -+ * conj id offsets specified in 'conj_id_ofs'. -+ */ -+void -+expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs) -+{ -+ struct expr_match *m; -+ HMAP_FOR_EACH (m, hmap_node, matches) { -+ if (m->match.wc.masks.conj_id) { -+ m->match.flow.conj_id += conj_id_ofs; -+ } -+ -+ for (size_t i = 0; i < m->n; i++) { -+ struct cls_conjunction *src = &m->conjunctions[i]; -+ src->id += conj_id_ofs; -+ } -+ } -+} -+ - /* Destroys all of the 'struct expr_match'es in 'matches', as well as the - * 'matches' hmap itself. */ - void -diff --git a/lib/lb.c b/lib/lb.c -index a90042e58..f305e9a87 100644 ---- a/lib/lb.c -+++ b/lib/lb.c -@@ -170,6 +170,24 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip) - free(vip->backends_nb); - } - -+static void -+ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid, -+ const struct smap *lb_options, -+ struct lport_addresses *hairpin_addrs) -+{ -+ const char *addresses = smap_get(lb_options, "hairpin_snat_ip"); -+ -+ if (!addresses) { -+ return; -+ } -+ -+ if (!extract_ip_address(addresses, hairpin_addrs)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT, -+ addresses, UUID_ARGS(lb_uuid)); -+ } -+} -+ - struct ovn_northd_lb * - ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - struct hmap *ports, -@@ -189,6 +207,8 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; - struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips]; - -+ lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options, -+ "reject", false); - if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { - continue; - } -@@ -222,6 +242,9 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - ds_chomp(&sel_fields, ','); - lb->selection_fields = ds_steal_cstr(&sel_fields); - } -+ -+ ovn_lb_get_hairpin_snat_ip(&nbrec_lb->header_.uuid, &nbrec_lb->options, -+ &lb->hairpin_snat_ips); - return lb; - } - -@@ -258,6 +281,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb) - free(lb->vips); - free(lb->vips_nb); - free(lb->selection_fields); -+ destroy_lport_addresses(&lb->hairpin_snat_ips); - free(lb->dps); - free(lb); - } -@@ -287,6 +311,12 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) - * correct value. - */ - lb->n_vips = n_vips; -+ -+ lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options, -+ "hairpin_orig_tuple", -+ false); -+ ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options, -+ &lb->hairpin_snat_ips); - return lb; - } - -@@ -297,5 +327,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb) - ovn_lb_vip_destroy(&lb->vips[i]); - } - free(lb->vips); -+ destroy_lport_addresses(&lb->hairpin_snat_ips); - free(lb); - } -diff --git a/lib/lb.h b/lib/lb.h -index 6644ad0d8..9a78c72f3 100644 ---- a/lib/lb.h -+++ b/lib/lb.h -@@ -20,6 +20,7 @@ - #include - #include - #include "openvswitch/hmap.h" -+#include "ovn-util.h" - - struct nbrec_load_balancer; - struct sbrec_load_balancer; -@@ -37,6 +38,11 @@ struct ovn_northd_lb { - struct ovn_northd_lb_vip *vips_nb; - size_t n_vips; - -+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used -+ * as source for hairpinned -+ * traffic. -+ */ -+ - size_t n_dps; - size_t n_allocated_dps; - const struct sbrec_datapath_binding **dps; -@@ -49,6 +55,7 @@ struct ovn_lb_vip { - - struct ovn_lb_backend *backends; - size_t n_backends; -+ bool empty_backend_rej; - }; - - struct ovn_lb_backend { -@@ -88,6 +95,14 @@ struct ovn_controller_lb { - - struct ovn_lb_vip *vips; - size_t n_vips; -+ bool hairpin_orig_tuple; /* True if ovn-northd stores the original -+ * destination tuple in registers. -+ */ -+ -+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used -+ * as source for hairpinned -+ * traffic. -+ */ - }; - - struct ovn_controller_lb *ovn_controller_lb_create( -diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h -index c84a0e7a9..d00982449 100644 ---- a/lib/ovn-l7.h -+++ b/lib/ovn-l7.h -@@ -26,6 +26,25 @@ - #include "hash.h" - #include "ovn/logical-fields.h" - -+#define BFD_PACKET_LEN 24 -+#define BFD_DEST_PORT 3784 -+#define BFD_VERSION 1 -+#define BFD_DEFAULT_SRC_IP 0xA9FE0101 /* 169.254.1.1 */ -+#define BFD_DEFAULT_DST_IP 0xA9FE0100 /* 169.254.1.0 */ -+ -+struct bfd_msg { -+ uint8_t vers_diag; -+ uint8_t flags; -+ uint8_t mult; -+ uint8_t length; -+ ovs_be32 my_disc; -+ ovs_be32 your_disc; -+ ovs_be32 min_tx; -+ ovs_be32 min_rx; -+ ovs_be32 min_rx_echo; -+}; -+BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg)); -+ - /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */ - struct gen_opts_map { - struct hmap_node hmap_node; -diff --git a/lib/ovn-util.c b/lib/ovn-util.c -index 2136f90fe..8f6719471 100644 ---- a/lib/ovn-util.c -+++ b/lib/ovn-util.c -@@ -232,6 +232,27 @@ extract_ip_addresses(const char *address, struct lport_addresses *laddrs) - return false; - } - -+/* Extracts at most one IPv4 and at most one IPv6 address from 'address' -+ * which should be of the format 'IP1 [IP2]'. -+ * -+ * Return true if at most one IPv4 address and at most one IPv6 address -+ * is found in 'address'. IPs must be host IPs, i.e., no unmasked bits. -+ * -+ * The caller must call destroy_lport_addresses(). -+ */ -+bool extract_ip_address(const char *address, struct lport_addresses *laddrs) -+{ -+ if (!extract_ip_addresses(address, laddrs) || -+ laddrs->n_ipv4_addrs > 1 || -+ laddrs->n_ipv6_addrs > 1 || -+ (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) || -+ (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) { -+ destroy_lport_addresses(laddrs); -+ return false; -+ } -+ return true; -+} -+ - /* Extracts the mac, IPv4 and IPv6 addresses from the - * "nbrec_logical_router_port" parameter 'lrp'. Stores the IPv4 and - * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of -@@ -559,18 +580,30 @@ ovn_destroy_tnlids(struct hmap *tnlids) - hmap_destroy(tnlids); - } - -+/* Returns true if 'tnlid' is present in the hmap 'tnlids'. */ - bool --ovn_add_tnlid(struct hmap *set, uint32_t tnlid) -+ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid) - { - uint32_t hash = hash_int(tnlid, 0); - struct tnlid_node *node; -- HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, set) { -+ HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, tnlids) { - if (node->tnlid == tnlid) { -- return false; -+ return true; - } - } - -- node = xmalloc(sizeof *node); -+ return false; -+} -+ -+bool -+ovn_add_tnlid(struct hmap *set, uint32_t tnlid) -+{ -+ if (ovn_tnlid_present(set, tnlid)) { -+ return false; -+ } -+ -+ uint32_t hash = hash_int(tnlid, 0); -+ struct tnlid_node *node = xmalloc(sizeof *node); - hmap_insert(set, &node->hmap_node, hash); - node->tnlid = tnlid; - return true; -diff --git a/lib/ovn-util.h b/lib/ovn-util.h -index 679f47a97..40ecafe57 100644 ---- a/lib/ovn-util.h -+++ b/lib/ovn-util.h -@@ -72,6 +72,7 @@ bool extract_addresses(const char *address, struct lport_addresses *, - int *ofs); - bool extract_lsp_addresses(const char *address, struct lport_addresses *); - bool extract_ip_addresses(const char *address, struct lport_addresses *); -+bool extract_ip_address(const char *address, struct lport_addresses *); - bool extract_lrp_networks(const struct nbrec_logical_router_port *, - struct lport_addresses *); - bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding, -@@ -125,6 +126,7 @@ void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED, - struct hmap; - void ovn_destroy_tnlids(struct hmap *tnlids); - bool ovn_add_tnlid(struct hmap *set, uint32_t tnlid); -+bool ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid); - uint32_t ovn_allocate_tnlid(struct hmap *set, const char *name, uint32_t min, - uint32_t max, uint32_t *hint); - -@@ -227,4 +229,40 @@ bool ip_address_and_port_from_lb_key(const char *key, char **ip_address, - * value. */ - char *ovn_get_internal_version(void); - -+ -+/* OVN Packet definitions. These may eventually find a home in OVS's -+ * packets.h file. For the time being, they live here because OVN uses them -+ * and OVS does not. -+ */ -+#define SCTP_CHUNK_HEADER_LEN 4 -+struct sctp_chunk_header { -+ uint8_t sctp_chunk_type; -+ uint8_t sctp_chunk_flags; -+ ovs_be16 sctp_chunk_len; -+}; -+BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header)); -+ -+#define SCTP_INIT_CHUNK_LEN 16 -+struct sctp_init_chunk { -+ ovs_be32 initiate_tag; -+ ovs_be32 a_rwnd; -+ ovs_be16 num_outbound_streams; -+ ovs_be16 num_inbound_streams; -+ ovs_be32 initial_tsn; -+}; -+BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk)); -+ -+/* These are the only SCTP chunk types that OVN cares about. -+ * There is no need to define the other chunk types until they are -+ * needed. -+ */ -+#define SCTP_CHUNK_TYPE_INIT 1 -+#define SCTP_CHUNK_TYPE_ABORT 6 -+ -+/* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */ -+#define SCTP_ABORT_CHUNK_FLAG_T (1 << 0) -+ -+/* The number of tables for the ingress and egress pipelines. */ -+#define LOG_PIPELINE_LEN 29 -+ - #endif -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index a9a3a9f4f..55b1c9655 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -307,7 +307,73 @@ -
  • - - --

    Ingress Table 3: from-lport Pre-ACLs

    -+

    Ingress Table 3: Lookup MAC address learning table

    -+ -+

    -+ This table looks up the MAC learning table of the logical switch -+ datapath to check if the port-mac pair is present -+ or not. MAC is learnt only for logical switch VIF ports whose -+ port security is disabled and 'unknown' address set. -+

    -+ -+
      -+
    • -+

      -+ For each such logical port p whose port security -+ is disabled and 'unknown' address set following flow -+ is added. -+

      -+ -+
        -+
      • -+ Priority 100 flow with the match -+ inport == p and action -+ reg0[11] = lookup_fdb(inport, eth.src); next; -+
      • -+
      -+
    • -+ -+
    • -+ One priority-0 fallback flow that matches all packets and advances to -+ the next table. -+
    • -+
    -+ -+

    Ingress Table 4: Learn MAC of 'unknown' ports.

    -+ -+

    -+ This table learns the MAC addresses seen on the logical ports -+ whose port security is disabled and 'unknown' address set -+ if the lookup_fdb action returned false in the -+ previous table. -+

    -+ -+
      -+
    • -+

      -+ For each such logical port p whose port security -+ is disabled and 'unknown' address set following flow -+ is added. -+

      -+ -+
        -+
      • -+ Priority 100 flow with the match -+ inport == p && reg0[11] == 0 and -+ action put_fdb(inport, eth.src); next; which stores -+ the port-mac in the mac learning table of the -+ logical switch datapath and advances the packet to the next table. -+
      • -+
      -+
    • -+ -+
    • -+ One priority-0 fallback flow that matches all packets and advances to -+ the next table. -+
    • -+
    -+ -+

    Ingress Table 5: from-lport Pre-ACLs

    - -

    - This table prepares flows for possible stateful ACL processing in -@@ -332,7 +398,7 @@ - db="OVN_Northbound"/> table. -

    - --

    Ingress Table 4: Pre-LB

    -+

    Ingress Table 6: Pre-LB

    - -

    - This table prepares flows for possible stateful load balancing processing -@@ -399,7 +465,7 @@ - logical router datapath to logical switch datapath. -

    - --

    Ingress Table 5: Pre-stateful

    -+

    Ingress Table 7: Pre-stateful

    - -

    - This table prepares flows for all possible stateful processing -@@ -410,12 +476,13 @@ - ct_next; action. -

    - --

    Ingress Table 6: from-lport ACL hints

    -+

    Ingress Table 8: from-lport ACL hints

    - -

    - This table consists of logical flows that set hints - (reg0 bits) to be used in the next stage, in the ACL -- processing table. Multiple hints can be set for the same packet. -+ processing table, if stateful ACLs or load balancers are configured. -+ Multiple hints can be set for the same packet. - The possible hints are: -

    -
      -@@ -489,7 +556,7 @@ - -
    - --

    Ingress table 7: from-lport ACLs

    -+

    Ingress table 9: from-lport ACLs

    - -

    - Logical flows in this table closely reproduce those in the -@@ -518,8 +585,9 @@ - flows with the - tcp_reset { output <-> inport; - next(pipeline=egress,table=5);} -- action for TCP connections and icmp4/icmp6 action -- for UDP connections. -+ action for TCP connections,icmp4/icmp6 action -+ for UDP connections, and sctp_abort {output <-%gt; inport; -+ next(pipeline=egress,table=5);} action for SCTP associations. - -

  • - Other ACLs translate to drop; for new or untracked -@@ -597,7 +665,7 @@ -
  • - - --

    Ingress Table 8: from-lport QoS Marking

    -+

    Ingress Table 10: from-lport QoS Marking

    - -

    - Logical flows in this table closely reproduce those in the -@@ -619,7 +687,7 @@ - - - --

    Ingress Table 9: from-lport QoS Meter

    -+

    Ingress Table 11: from-lport QoS Meter

    - -

    - Logical flows in this table closely reproduce those in the -@@ -641,7 +709,7 @@ - - - --

    Ingress Table 10: LB

    -+

    Ingress Table 12: LB

    - -

    - It contains a priority-0 flow that simply moves traffic to the next -@@ -667,7 +735,7 @@ - connection.) -

    - --

    Ingress Table 11: Stateful

    -+

    Ingress Table 13: Stateful

    - -
      -
    • -@@ -687,7 +755,11 @@ - of VIP. If health check is enabled, then args - will only contain those endpoints whose service monitor status entry - in OVN_Southbound db is either online or -- empty. -+ empty. For IPv4 traffic the flow also loads the original destination -+ IP and transport port in registers reg1 and -+ reg2. For IPv6 traffic the flow also loads the original -+ destination IP and transport port in registers xxreg1 and -+ reg2. -
    • -
    • - For all the configured load balancing rules for a switch in -@@ -699,40 +771,54 @@ - VIP. The action on this flow is - ct_lb(args), where args contains comma - separated IP addresses of the same address family as VIP. -+ For IPv4 traffic the flow also loads the original destination -+ IP and transport port in registers reg1 and -+ reg2. For IPv6 traffic the flow also loads the original -+ destination IP and transport port in registers xxreg1 and -+ reg2. -+
    • -+ -+
    • -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -
    • -+ -
    • - A priority-100 flow commits packets to connection tracker using - ct_commit; next; action based on a hint provided by - the previous tables (with a match for reg0[1] == 1). -
    • -
    • -- A priority-100 flow sends the packets to connection tracker using -+ Priority-100 flows that send the packets to connection tracker using - ct_lb; as the action based on a hint provided by the -- previous tables (with a match for reg0[2] == 1). -+ previous tables (with a match for reg0[2] == 1 and -+ on supported load balancer protocols and address families). -+ For IPv4 traffic the flows also load the original destination -+ IP and transport port in registers reg1 and -+ reg2. For IPv6 traffic the flows also load the original -+ destination IP and transport port in registers xxreg1 and -+ reg2. -
    • -
    • - A priority-0 flow that simply moves traffic to the next table. -
    • -
    - --

    Ingress Table 12: Pre-Hairpin

    -+

    Ingress Table 14: Pre-Hairpin

    -
      -
    • - 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 -+ priority-100 flow is added with the match -+ ip && ct.trk 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. -+ IP matches the source IP) or not by executing the actions -+ reg0[6] = chk_lb_hairpin(); and -+ reg0[12] = chk_lb_hairpin_reply(); and advances the packet -+ to the next table. -
    • - -
    • -@@ -740,21 +826,30 @@ -
    • -
    - --

    Ingress Table 13: Nat-Hairpin

    -+

    Ingress Table 15: 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 -+ priority-100 flow is added with the match -+ ip && ct.new && ct.trk && -+ 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. -
    • - -
    • - 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 -+ priority-100 flow is added with the match -+ ip && ct.est && ct.trk && -+ reg0[6] == 1 which hairpins the traffic by -+ NATting source IP to the load balancer VIP by executing the action -+ ct_snat and advances the packet to the next table. -+
    • -+ -+
    • -+ If the logical switch has load balancer(s) configured, then a -+ priority-90 flow is added with the match -+ ip && reg0[12] == 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 -@@ -766,7 +861,7 @@ -
    • -
    - --

    Ingress Table 14: Hairpin

    -+

    Ingress Table 16: Hairpin

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

    Ingress Table 15: ARP/ND responder

    -+

    Ingress Table 17: ARP/ND responder

    - -

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

    Ingress Table 16: DHCP option processing

    -+

    Ingress Table 18: DHCP option processing

    - -

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

    Ingress Table 17: DHCP responses

    -+

    Ingress Table 19: DHCP responses

    - -

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

    Ingress Table 18 DNS Lookup

    -+

    Ingress Table 20 DNS Lookup

    - -

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

    Ingress Table 19 DNS Responses

    -+

    Ingress Table 21 DNS Responses

    - -

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

    Ingress table 20 External ports

    -+

    Ingress table 22 External ports

    - -

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

    Ingress Table 21 Destination Lookup

    -+

    Ingress Table 23 Destination Lookup

    - -

    - This table implements switching behavior. It contains these logical -@@ -1481,12 +1576,58 @@ output; - - -

  • -- One priority-0 fallback flow that matches all packets and outputs them -- to the MC_UNKNOWN multicast group, which -- ovn-northd populates with all enabled logical ports that -- accept unknown destination packets. As a small optimization, if no -- logical ports accept unknown destination packets, -- ovn-northd omits this multicast group and logical flow. -+ One priority-0 fallback flow that matches all packets with the -+ action outport = get_fdb(eth.dst); next;. The action -+ get_fdb gets the port for the eth.dst -+ in the MAC learning table of the logical switch datapath. If there -+ is no entry for eth.dst in the MAC learning table, -+ then it stores none in the outport. -+
  • -+ -+ -+

    Ingress Table 23 Destination unknown

    -+ -+

    -+ This table handles the packets whose destination was not found or -+ and looked up in the MAC learning table of the logical switch -+ datapath. It contains the following flows. -+

    -+ -+
      -+
    • -+

      -+ If the logical switch has logical ports with 'unknown' addresses set, -+ then the below logical flow is added -+

      -+ -+
        -+
      • -+ Priority 50 flow with the match outport == none then -+ outputs them to the MC_UNKNOWN multicast group, which -+ ovn-northd populates with all enabled logical ports -+ that accept unknown destination packets. As a small optimization, -+ if no logical ports accept unknown destination packets, -+ ovn-northd omits this multicast group and logical -+ flow. -+
      • -+
      -+ -+

      -+ If the logical switch has no logical ports with 'unknown' address -+ set, then the below logical flow is added -+

      -+ -+
        -+
      • -+ Priority 50 flow with the match outport == none -+ and drops the packets. -+
      • -+
      -+
    • -+ -+
    • -+ One priority-0 fallback flow that outputs the packet to the egress -+ stage with the outport learnt from get_fdb action. -
    • -
    - -@@ -1926,6 +2067,27 @@ next; -

    - - -+
  • -+

    -+ For each BFD port the two following priority-110 flows are added -+ to manage BFD traffic: -+ -+

      -+
    • -+ if ip4.src or ip6.src is any IP -+ address owned by the router port and udp.dst == 3784 -+ , the packet is advanced to the next pipeline stage. -+
    • -+ -+
    • -+ if ip4.dst or ip6.dst is any IP -+ address owned by the router port and udp.dst == 3784 -+ , the handle_bfd_msg action is executed. -+
    • -+
    -+

    -+
  • -+ -
  • -

    - L3 admission control: A priority-100 flow drops packets that match -@@ -2449,6 +2611,16 @@ icmp6 { - with an action ct_snat; . -

    - -+

    -+ If the Gateway router is configured with -+ lb_force_snat_ip=router_ip then for every logical router -+ port P attached to the Gateway router with the router ip -+ B, a priority-110 flow is added with the match -+ inport == P && ip4.dst == B or -+ inport == P && ip6.dst == B -+ with an action ct_snat; . -+

    -+ -

    - If the Gateway router has been configured to force SNAT any - previously load-balanced packets to B, a priority-100 flow -@@ -2592,6 +2764,15 @@ icmp6 { - packets, the above action will be replaced by - flags.force_snat_for_lb = 1; ct_dnat;. -

  • -+ -+
  • -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -+
  • - - -

    Ingress Table 6: DNAT on Gateway Routers

    -@@ -3022,14 +3203,36 @@ outport = P; - -
  • -

    -- If the policy action is reroute, then the logical -- flow is added with the following actions: -+ If the policy action is reroute with 2 or more nexthops -+ defined, then the logical flow is added with the following actions: -+

    -+ -+
    -+reg8[0..15] = GID;
    -+reg8[16..31] = select(1,..n);
    -+        
    -+ -+

    -+ where GID is the ECMP group id generated by -+ ovn-northd for this policy and n -+ is the number of nexthops. select action -+ selects one of the nexthop member id, stores it in the register -+ reg8[16..31] and advances the packet to the -+ next stage. -+

    -+
  • -+ -+
  • -+

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

    - -
    - [xx]reg0 = H;
    - eth.src = E;
    - outport = P;
    -+reg8[0..15] = 0;
    - flags.loopback = 1;
    - next;
    -         
    -@@ -3053,7 +3256,51 @@ next; -
  • - - --

    Ingress Table 13: ARP/ND Resolution

    -+

    Ingress Table 13: ECMP handling for router policies

    -+

    -+ This table handles the ECMP for the router policies configured -+ with multiple nexthops. -+

    -+ -+
      -+
    • -+

      -+ A priority-150 flow is added to advance the packet to the next stage -+ if the ECMP group id register reg8[0..15] is 0. -+

      -+
    • -+ -+
    • -+

      -+ For each ECMP reroute router policy with multiple nexthops, -+ a priority-100 flow is added for each nexthop H -+ with the match reg8[0..15] == GID && -+ reg8[16..31] == M where GID -+ is the router policy group id generated by ovn-northd -+ and M is the member id of the nexthop H -+ generated by ovn-northd. The following actions are added -+ to the flow: -+

      -+ -+
      -+[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. -+

      -+
    • -+
    -+ -+

    Ingress Table 14: ARP/ND Resolution

    - -

    - Any packet that reaches this table is an IP packet whose next-hop -@@ -3239,7 +3486,7 @@ next; - - - --

    Ingress Table 14: Check packet length

    -+

    Ingress Table 15: Check packet length

    - -

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

    - --

    Ingress Table 15: Handle larger packets

    -+

    Ingress Table 16: Handle larger packets

    - -

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

    - --

    Ingress Table 16: Gateway Redirect

    -+

    Ingress Table 17: Gateway Redirect

    - -

    - For distributed logical routers where one of the logical router -@@ -3370,7 +3617,7 @@ icmp6 { - - - --

    Ingress Table 17: ARP Request

    -+

    Ingress Table 18: ARP Request

    - -

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

    -+ -+ -+
  • -+

    -+ If the Gateway router in the OVN Northbound database has been -+ configured to force SNAT a packet (that has been previously -+ load-balanced) using router IP (i.e :lb_force_snat_ip=router_ip), then for -+ each logical router port P attached to the Gateway -+ router, a priority-110 flow matches -+ flags.force_snat_for_lb == 1 && outport == P -+ with an action ct_snat(R); -+ where R is the IP configured on the router port. -+ If R is an IPv4 address then the match will also -+ include ip4 and if it is an IPv6 address, then the -+ match will also include ip6. -+

    -+ -+

    -+ If the logical router port P is configured with multiple -+ IPv4 and multiple IPv6 addresses, only the first IPv4 and first IPv6 -+ address is considered. -+

    -+
  • -+ -+
  • -

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

    -+
  • -+ -+
  • -

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

    -+
  • - -+
  • -

    - If the NAT rule has allowed_ext_ips configured, then - there is an additional match ip4.dst == allowed_ext_ips - . Similarly, for IPV6, match would be ip6.dst == - allowed_ext_ips. -

    -+
  • - -+
  • -

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

    -+
  • - -+
  • -

    - A priority-0 logical flow with match 1 has actions - next;. -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 5a3227568..c81e3220c 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -38,6 +38,7 @@ - #include "lib/ovn-util.h" - #include "lib/lb.h" - #include "ovn/actions.h" -+#include "ovn/features.h" - #include "ovn/logical-fields.h" - #include "packets.h" - #include "openvswitch/poll-loop.h" -@@ -141,25 +142,28 @@ enum ovn_stage { - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ -- PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ -- PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \ -- PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \ -- PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 6, "ls_in_acl_hint") \ -- PIPELINE_STAGE(SWITCH, IN, ACL, 7, "ls_in_acl") \ -- PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 8, "ls_in_qos_mark") \ -- PIPELINE_STAGE(SWITCH, IN, QOS_METER, 9, "ls_in_qos_meter") \ -- 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, 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") \ -+ PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB , 3, "ls_in_lookup_fdb") \ -+ PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 4, "ls_in_put_fdb") \ -+ PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 5, "ls_in_pre_acl") \ -+ PIPELINE_STAGE(SWITCH, IN, PRE_LB, 6, "ls_in_pre_lb") \ -+ PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 7, "ls_in_pre_stateful") \ -+ PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 8, "ls_in_acl_hint") \ -+ PIPELINE_STAGE(SWITCH, IN, ACL, 9, "ls_in_acl") \ -+ PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 10, "ls_in_qos_mark") \ -+ PIPELINE_STAGE(SWITCH, IN, QOS_METER, 11, "ls_in_qos_meter") \ -+ PIPELINE_STAGE(SWITCH, IN, LB, 12, "ls_in_lb") \ -+ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 13, "ls_in_stateful") \ -+ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 14, "ls_in_pre_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 15, "ls_in_nat_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 16, "ls_in_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 17, "ls_in_arp_rsp") \ -+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 18, "ls_in_dhcp_options") \ -+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 19, "ls_in_dhcp_response") \ -+ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 20, "ls_in_dns_lookup") \ -+ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 21, "ls_in_dns_response") \ -+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 22, "ls_in_external_port") \ -+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 23, "ls_in_l2_lkup") \ -+ PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 24, "ls_in_l2_unknown") \ - \ - /* Logical switch egress stages. */ \ - PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ -@@ -188,11 +192,12 @@ enum ovn_stage { - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 10, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 11, "lr_in_ip_routing_ecmp") \ - PIPELINE_STAGE(ROUTER, IN, POLICY, 12, "lr_in_policy") \ -- PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 13, "lr_in_arp_resolve") \ -- PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 14, "lr_in_chk_pkt_len") \ -- PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 15,"lr_in_larger_pkts") \ -- PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 16, "lr_in_gw_redirect") \ -- PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 17, "lr_in_arp_request") \ -+ PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 13, "lr_in_policy_ecmp") \ -+ PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 14, "lr_in_arp_resolve") \ -+ PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 15, "lr_in_chk_pkt_len") \ -+ PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 16, "lr_in_larger_pkts") \ -+ PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 17, "lr_in_gw_redirect") \ -+ PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 18, "lr_in_arp_request") \ - \ - /* Logical router egress stages. */ \ - PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ -@@ -225,6 +230,12 @@ enum ovn_stage { - #define REGBIT_ACL_HINT_ALLOW "reg0[8]" - #define REGBIT_ACL_HINT_DROP "reg0[9]" - #define REGBIT_ACL_HINT_BLOCK "reg0[10]" -+#define REGBIT_LKUP_FDB "reg0[11]" -+#define REGBIT_HAIRPIN_REPLY "reg0[12]" -+ -+#define REG_ORIG_DIP_IPV4 "reg1" -+#define REG_ORIG_DIP_IPV6 "xxreg1" -+#define REG_ORIG_TP_DPORT "reg2[0..15]" - - /* Register definitions for switches and routers. */ - -@@ -259,12 +270,29 @@ enum ovn_stage { - * OVS register usage: - * - * Logical Switch pipeline: -- * +---------+----------------------------------------------+ -- * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} | -- * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | -- * +---------+----------------------------------------------+ -- * | R1 - R9 | UNUSED | -- * +---------+----------------------------------------------+ -+ * +----+----------------------------------------------+---+------------------+ -+ * | R0 | REGBIT_{CONNTRACK/DHCP/DNS} | | | -+ * | | REGBIT_{HAIRPIN/HAIRPIN_REPLY} | X | | -+ * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | X | | -+ * +----+----------------------------------------------+ X | | -+ * | R1 | ORIG_DIP_IPV4 (>= IN_STATEFUL) | R | | -+ * +----+----------------------------------------------+ E | | -+ * | R2 | ORIG_TP_DPORT (>= IN_STATEFUL) | G | | -+ * +----+----------------------------------------------+ 0 | | -+ * | R3 | UNUSED | | | -+ * +----+----------------------------------------------+---+------------------+ -+ * | R4 | UNUSED | | | -+ * +----+----------------------------------------------+ X | ORIG_DIP_IPV6 | -+ * | R5 | UNUSED | X | (>= IN_STATEFUL) | -+ * +----+----------------------------------------------+ R | | -+ * | R6 | UNUSED | E | | -+ * +----+----------------------------------------------+ G | | -+ * | R7 | UNUSED | 1 | | -+ * +----+----------------------------------------------+---+------------------+ -+ * | R8 | UNUSED | -+ * +----+----------------------------------------------+ -+ * | R9 | UNUSED | -+ * +----+----------------------------------------------+ - * - * Logical Router pipeline: - * +-----+--------------------------+---+-----------------+---+---------------+ -@@ -608,6 +636,8 @@ struct ovn_datapath { - struct hmap port_tnlids; - uint32_t port_key_hint; - -+ bool has_stateful_acl; -+ bool has_lb_vip; - bool has_unknown; - - /* IPAM data. */ -@@ -633,6 +663,7 @@ struct ovn_datapath { - - struct lport_addresses dnat_force_snat_addrs; - struct lport_addresses lb_force_snat_addrs; -+ bool lb_force_snat_router_ip; - - struct ovn_port **localnet_ports; - size_t n_localnet_ports; -@@ -646,6 +677,9 @@ struct ovn_datapath { - struct hmap nb_pgs; - }; - -+static bool ls_has_stateful_acl(struct ovn_datapath *od); -+static bool ls_has_lb_vip(struct ovn_datapath *od); -+ - /* Contains a NAT entry with the external addresses pre-parsed. */ - struct ovn_nat { - const struct nbrec_nat *nb; -@@ -723,14 +757,28 @@ init_nat_entries(struct ovn_datapath *od) - } - } - -- 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); -+ /* Check if 'lb_force_snat_ip' is configured with 'router_ip'. */ -+ const char *lb_force_snat = -+ smap_get(&od->nbr->options, "lb_force_snat_ip"); -+ if (lb_force_snat && !strcmp(lb_force_snat, "router_ip") -+ && smap_get(&od->nbr->options, "chassis")) { -+ /* Set it to true only if its gateway router and -+ * options:lb_force_snat_ip=router_ip. */ -+ od->lb_force_snat_router_ip = true; -+ } else { -+ od->lb_force_snat_router_ip = false; -+ -+ /* Check if 'lb_force_snat_ip' is configured with a set of -+ * IP address(es). */ -+ 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); -+ } - } - } - -@@ -872,6 +920,20 @@ ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid) - return NULL; - } - -+static struct ovn_datapath * -+ovn_datapath_find_by_key(struct hmap *datapaths, uint32_t dp_key) -+{ -+ struct ovn_datapath *od; -+ -+ HMAP_FOR_EACH (od, key_node, datapaths) { -+ if (od->tunnel_key == dp_key) { -+ return od; -+ } -+ } -+ -+ return NULL; -+} -+ - static bool - ovn_datapath_is_stale(const struct ovn_datapath *od) - { -@@ -1472,6 +1534,8 @@ struct ovn_port { - - bool has_unknown; /* If the addresses have 'unknown' defined. */ - -+ bool has_bfd; -+ - /* The port's peer: - * - * - A switch port S of type "router" has a router port R as a peer, -@@ -1543,17 +1607,38 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) - } - } - -+/* Returns the ovn_port that matches 'name'. If 'prefer_bound' is true and -+ * multiple ports share the same name, gives precendence to ports bound to -+ * an ovn_datapath. -+ */ - static struct ovn_port * --ovn_port_find(const struct hmap *ports, const char *name) -+ovn_port_find__(const struct hmap *ports, const char *name, -+ bool prefer_bound) - { -+ struct ovn_port *matched_op = NULL; - struct ovn_port *op; - - HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) { - if (!strcmp(op->key, name)) { -- return op; -+ matched_op = op; -+ if (!prefer_bound || op->od) { -+ return op; -+ } - } - } -- return NULL; -+ return matched_op; -+} -+ -+static struct ovn_port * -+ovn_port_find(const struct hmap *ports, const char *name) -+{ -+ return ovn_port_find__(ports, name, false); -+} -+ -+static struct ovn_port * -+ovn_port_find_bound(const struct hmap *ports, const char *name) -+{ -+ return ovn_port_find__(ports, name, true); - } - - /* Returns true if the logical switch port 'enabled' column is empty or -@@ -2336,15 +2421,13 @@ join_logical_ports(struct northd_context *ctx, - for (size_t i = 0; i < od->nbs->n_ports; i++) { - const struct nbrec_logical_switch_port *nbsp - = od->nbs->ports[i]; -- struct ovn_port *op = ovn_port_find(ports, nbsp->name); -- if (op && op->sb->datapath == od->sb) { -- if (op->nbsp || op->nbrp) { -- static struct vlog_rate_limit rl -- = VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "duplicate logical port %s", -- nbsp->name); -- continue; -- } -+ struct ovn_port *op = ovn_port_find_bound(ports, nbsp->name); -+ if (op && (op->od || op->nbsp || op->nbrp)) { -+ static struct vlog_rate_limit rl -+ = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "duplicate logical port %s", nbsp->name); -+ continue; -+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - ovn_port_set_nb(op, nbsp, NULL); - ovs_list_remove(&op->list); - -@@ -2435,16 +2518,15 @@ join_logical_ports(struct northd_context *ctx, - continue; - } - -- struct ovn_port *op = ovn_port_find(ports, nbrp->name); -- if (op && op->sb->datapath == od->sb) { -- if (op->nbsp || op->nbrp) { -- static struct vlog_rate_limit rl -- = VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "duplicate logical router port %s", -- nbrp->name); -- destroy_lport_addresses(&lrp_networks); -- continue; -- } -+ struct ovn_port *op = ovn_port_find_bound(ports, nbrp->name); -+ if (op && (op->od || op->nbsp || op->nbrp)) { -+ static struct vlog_rate_limit rl -+ = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "duplicate logical router port %s", -+ nbrp->name); -+ destroy_lport_addresses(&lrp_networks); -+ continue; -+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - ovn_port_set_nb(op, NULL, nbrp); - ovs_list_remove(&op->list); - ovs_list_push_back(both, &op->list); -@@ -2487,7 +2569,7 @@ join_logical_ports(struct northd_context *ctx, - char *redirect_name = - ovn_chassis_redirect_name(nbrp->name); - struct ovn_port *crp = ovn_port_find(ports, redirect_name); -- if (crp && crp->sb->datapath == od->sb) { -+ if (crp && crp->sb && crp->sb->datapath == od->sb) { - crp->derived = true; - ovn_port_set_nb(crp, NULL, nbrp); - ovs_list_remove(&crp->list); -@@ -3179,6 +3261,12 @@ ovn_port_update_sbrec(struct northd_context *ctx, - } else { - sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } -+ } else if (op->sb->ha_chassis_group) { -+ /* Clear the port bindings ha_chassis_group if the type is -+ * not external and if this column is set. This can happen -+ * when an external port is reset to type normal and -+ * ha_chassis_group cleared in the same transaction. */ -+ sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); - } - } else { - const char *chassis = NULL; -@@ -3308,6 +3396,14 @@ ovn_port_update_sbrec(struct northd_context *ctx, - if (op->tunnel_key != op->sb->tunnel_key) { - sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); - } -+ -+ /* ovn-controller will update 'Port_Binding.up' only if it was explicitly -+ * set to 'false'. -+ */ -+ if (!op->sb->n_up) { -+ bool up = false; -+ sbrec_port_binding_set_up(op->sb, &up, 1); -+ } - } - - /* Remove mac_binding entries that refer to logical_ports which are -@@ -3340,6 +3436,26 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx, - } - } - -+static void -+cleanup_stale_fdp_entries(struct northd_context *ctx, struct hmap *datapaths) -+{ -+ const struct sbrec_fdb *fdb_e, *next; -+ SBREC_FDB_FOR_EACH_SAFE (fdb_e, next, ctx->ovnsb_idl) { -+ bool delete = true; -+ struct ovn_datapath *od -+ = ovn_datapath_find_by_key(datapaths, fdb_e->dp_key); -+ if (od) { -+ if (ovn_tnlid_present(&od->port_tnlids, fdb_e->port_key)) { -+ delete = false; -+ } -+ } -+ -+ if (delete) { -+ sbrec_fdb_delete(fdb_e); -+ } -+ } -+} -+ - struct service_monitor_info { - struct hmap_node hmap_node; - const struct sbrec_service_monitor *sbrec_mon; -@@ -3436,12 +3552,12 @@ ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_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) -+void build_lb_vip_actions(struct ovn_lb_vip *lb_vip, -+ struct ovn_northd_lb_vip *lb_vip_nb, -+ struct ds *action, char *selection_fields, -+ bool ls_dp) - { -- bool skip_hash_fields = false; -+ bool skip_hash_fields = false, reject = false; - - if (lb_vip_nb->lb_health_check) { - ds_put_cstr(action, "ct_lb(backends="); -@@ -3463,18 +3579,30 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, - } - - if (!n_active_backends) { -- skip_hash_fields = true; -- ds_clear(action); -- ds_put_cstr(action, "drop;"); -+ if (!lb_vip->empty_backend_rej) { -+ ds_clear(action); -+ ds_put_cstr(action, "drop;"); -+ skip_hash_fields = true; -+ } else { -+ reject = true; -+ } - } else { - ds_chomp(action, ','); - ds_put_cstr(action, ");"); - } -+ } else if (lb_vip->empty_backend_rej && !lb_vip->n_backends) { -+ reject = true; - } else { - ds_put_format(action, "ct_lb(backends=%s);", lb_vip_nb->backend_ips); - } - -- if (!skip_hash_fields && selection_fields && selection_fields[0]) { -+ if (reject) { -+ int stage = ls_dp ? ovn_stage_get_table(S_SWITCH_OUT_QOS_MARK) -+ : ovn_stage_get_table(S_ROUTER_OUT_SNAT); -+ ds_clear(action); -+ ds_put_format(action, "reg0 = 0; reject { outport <-> inport; " -+ "next(pipeline=egress,table=%d);};", stage); -+ } else if (!skip_hash_fields && selection_fields && selection_fields[0]) { - ds_chomp(action, ';'); - ds_chomp(action, ')'); - ds_put_format(action, "; hash_fields=\"%s\");", selection_fields); -@@ -3547,10 +3675,18 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - /* Create SB Load balancer records if not present and sync - * the SB load balancer columns. */ - HMAP_FOR_EACH (lb, hmap_node, lbs) { -+ - if (!lb->n_dps) { - continue; - } - -+ /* Store the fact that northd provides the original (destination IP + -+ * transport port) tuple. -+ */ -+ struct smap options; -+ smap_clone(&options, &lb->nlb->options); -+ smap_replace(&options, "hairpin_orig_tuple", "true"); -+ - if (!lb->slb) { - sbrec_lb = sbrec_load_balancer_insert(ctx->ovnsb_txn); - lb->slb = sbrec_lb; -@@ -3564,9 +3700,11 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, - sbrec_load_balancer_set_name(lb->slb, lb->nlb->name); - sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips); - sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol); -+ sbrec_load_balancer_set_options(lb->slb, &options); - sbrec_load_balancer_set_datapaths( - lb->slb, (struct sbrec_datapath_binding **)lb->dps, - lb->n_dps); -+ smap_destroy(&options); - } - - /* Set the list of associated load balanacers to a logical switch -@@ -4822,7 +4960,7 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs) - } - - static bool --has_stateful_acl(struct ovn_datapath *od) -+ls_has_stateful_acl(struct ovn_datapath *od) - { - for (size_t i = 0; i < od->nbs->n_acls; i++) { - struct nbrec_acl *acl = od->nbs->acls[i]; -@@ -4905,50 +5043,82 @@ build_lswitch_input_port_sec_od( - } - - static void --build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, -- struct hmap *lflows) -+build_lswitch_learn_fdb_op( -+ struct ovn_port *op, struct hmap *lflows, -+ struct ds *actions, struct ds *match) - { -- struct ds actions = DS_EMPTY_INITIALIZER; -- struct ds match = DS_EMPTY_INITIALIZER; -- struct ovn_port *op; -+ if (op->nbsp && !op->n_ps_addrs && !strcmp(op->nbsp->type, "") && -+ op->has_unknown) { -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "inport == %s", op->json_key); -+ ds_put_format(actions, REGBIT_LKUP_FDB -+ " = lookup_fdb(inport, eth.src); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); - -- /* Egress table 8: Egress port security - IP (priorities 90 and 80) -- * if port security enabled. -- * -- * Egress table 9: Egress port security - L2 (priorities 50 and 150). -- * -- * Priority 50 rules implement port security for enabled logical port. -- * -- * Priority 150 rules drop packets to disabled logical ports, so that -- * they don't even receive multicast or broadcast packets. -- */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || lsp_is_external(op->nbsp)) { -- continue; -- } -+ ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0"); -+ ds_clear(actions); -+ ds_put_cstr(actions, "put_fdb(inport, eth.src); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PUT_FDB, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); -+ } -+} - -- ds_clear(&actions); -- ds_clear(&match); -+static void -+build_lswitch_learn_fdb_od( -+ struct ovn_datapath *od, struct hmap *lflows) -+{ -+ -+ if (od->nbs) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_LOOKUP_FDB, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PUT_FDB, 0, "1", "next;"); -+ } -+} -+ -+/* Egress table 8: Egress port security - IP (priorities 90 and 80) -+ * if port security enabled. -+ * -+ * Egress table 9: Egress port security - L2 (priorities 50 and 150). -+ * -+ * Priority 50 rules implement port security for enabled logical port. -+ * -+ * Priority 150 rules drop packets to disabled logical ports, so that -+ * they don't even receive multicast or broadcast packets. -+ */ -+static void -+build_lswitch_output_port_sec_op(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, -+ struct ds *actions) -+{ -+ -+ if (op->nbsp && (!lsp_is_external(op->nbsp))) { -+ -+ ds_clear(actions); -+ ds_clear(match); - -- ds_put_format(&match, "outport == %s", op->json_key); -+ ds_put_format(match, "outport == %s", op->json_key); - if (lsp_is_enabled(op->nbsp)) { - build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs, -- &match); -+ match); - - if (!strcmp(op->nbsp->type, "localnet")) { - 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_format(actions, "set_queue(%s); ", queue_id); - } - } -- ds_put_cstr(&actions, "output;"); -+ ds_put_cstr(actions, "output;"); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, -- 50, ds_cstr(&match), ds_cstr(&actions), -+ 50, ds_cstr(match), ds_cstr(actions), - &op->nbsp->header_); - } else { - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, -- 150, ds_cstr(&match), "drop;", -+ 150, ds_cstr(match), "drop;", - &op->nbsp->header_); - } - -@@ -4956,23 +5126,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, - build_port_security_ip(P_OUT, op, lflows, &op->nbsp->header_); - } - } -+} - -- /* Egress tables 8: Egress port security - IP (priority 0) -- * Egress table 9: Egress port security L2 - multicast/broadcast -- * (priority 100). */ -- struct ovn_datapath *od; -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -+/* Egress tables 8: Egress port security - IP (priority 0) -+ * Egress table 9: Egress port security L2 - multicast/broadcast -+ * (priority 100). */ -+static void -+build_lswitch_output_port_sec_od(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast", - "output;"); - } -- -- ds_destroy(&match); -- ds_destroy(&actions); - } - - static void -@@ -5008,8 +5175,6 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op, - static void - build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) - { -- bool has_stateful = has_stateful_acl(od); -- - /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are - * allowed by default. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;"); -@@ -5024,7 +5189,7 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) - /* If there are any stateful ACL rules in this datapath, we must - * send all IP packets through the conntrack action, which handles - * defragmentation, in order to match L4 headers. */ -- if (has_stateful) { -+ if (od->has_stateful_acl) { - for (size_t i = 0; i < od->n_router_ports; i++) { - skip_port_from_conntrack(od, od->router_ports[i], - S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL, -@@ -5084,7 +5249,10 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - struct nbrec_load_balancer *lb, - int pl, struct shash *meter_groups) - { -- if (!controller_event_en || lb_vip->n_backends) { -+ bool controller_event = smap_get_bool(&lb->options, "event", false) || -+ controller_event_en; /* deprecated */ -+ if (!controller_event || lb_vip->n_backends || -+ lb_vip->empty_backend_rej) { - return; - } - -@@ -5124,7 +5292,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, - } - - static bool --has_lb_vip(struct ovn_datapath *od) -+ls_has_lb_vip(struct ovn_datapath *od) - { - for (int i = 0; i < od->nbs->n_load_balancer; i++) { - struct nbrec_load_balancer *nb_lb = od->nbs->load_balancer[i]; -@@ -5267,6 +5435,13 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) - for (size_t i = 0; i < ARRAY_SIZE(stages); i++) { - enum ovn_stage stage = stages[i]; - -+ /* In any case, advance to the next stage. */ -+ ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); -+ -+ if (!od->has_stateful_acl && !od->has_lb_vip) { -+ continue; -+ } -+ - /* New, not already established connections, may hit either allow - * or drop ACLs. For allow ACLs, the connection must also be committed - * to conntrack so we set REGBIT_ACL_HINT_ALLOW_NEW. -@@ -5327,9 +5502,6 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) - ovn_lflow_add(lflows, od, stage, 1, "ct.est && ct_label.blocked == 0", - REGBIT_ACL_HINT_BLOCK " = 1; " - "next;"); -- -- /* In any case, advance to the next stage. */ -- ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); - } - } - -@@ -5661,7 +5833,7 @@ static void - build_acls(struct ovn_datapath *od, struct hmap *lflows, - struct hmap *port_groups, const struct shash *meter_groups) - { -- bool has_stateful = (has_stateful_acl(od) || has_lb_vip(od)); -+ bool has_stateful = od->has_stateful_acl || od->has_lb_vip; - - /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by - * default. A related rule at priority 1 is added below if there -@@ -5930,7 +6102,7 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows) - } - } - -- if (has_lb_vip(od)) { -+ if (od->has_lb_vip) { - /* Ingress and Egress LB Table (Priority 65534). - * - * Send established traffic through conntrack for just NAT. */ -@@ -5953,11 +6125,20 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, - struct ovn_lb_vip *lb_vip = &lb->vips[i]; - struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; - -+ struct ds action = DS_EMPTY_INITIALIZER; - const char *ip_match = NULL; -+ -+ /* Store the original destination IP to be used when generating -+ * hairpin flows. -+ */ - if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { - ip_match = "ip4"; -+ ds_put_format(&action, REG_ORIG_DIP_IPV4 " = %s; ", -+ lb_vip->vip_str); - } else { - ip_match = "ip6"; -+ ds_put_format(&action, REG_ORIG_DIP_IPV6 " = %s; ", -+ lb_vip->vip_str); - } - - const char *proto = NULL; -@@ -5970,12 +6151,17 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, - proto = "sctp"; - } - } -+ -+ /* Store the original destination port to be used when generating -+ * hairpin flows. -+ */ -+ ds_put_format(&action, REG_ORIG_TP_DPORT " = %"PRIu16"; ", -+ lb_vip->vip_port); - } - - /* New connections in Ingress table. */ -- struct ds action = DS_EMPTY_INITIALIZER; -- build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &action, -- lb->selection_fields); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, &action, -+ lb->selection_fields, true); - - struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, -@@ -6021,9 +6207,39 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) - * REGBIT_CONNTRACK_COMMIT is set for new connections and - * REGBIT_CONNTRACK_NAT is set for established connections. So they - * don't overlap. -+ * -+ * In the ingress pipeline, also store the original destination IP and -+ * transport port to be used when detecting hairpin packets. - */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100, -- REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); -+ const char *lb_protocols[] = {"tcp", "udp", "sctp"}; -+ struct ds actions = DS_EMPTY_INITIALIZER; -+ struct ds match = DS_EMPTY_INITIALIZER; -+ -+ for (size_t i = 0; i < ARRAY_SIZE(lb_protocols); i++) { -+ ds_clear(&match); -+ ds_clear(&actions); -+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip4 && %s", -+ lb_protocols[i]); -+ ds_put_format(&actions, REG_ORIG_DIP_IPV4 " = ip4.dst; " -+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;", -+ lb_protocols[i]); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100, -+ ds_cstr(&match), ds_cstr(&actions)); -+ -+ ds_clear(&match); -+ ds_clear(&actions); -+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip6 && %s", -+ lb_protocols[i]); -+ ds_put_format(&actions, REG_ORIG_DIP_IPV6 " = ip6.dst; " -+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;", -+ lb_protocols[i]); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100, -+ ds_cstr(&match), ds_cstr(&actions)); -+ } -+ -+ ds_destroy(&actions); -+ ds_destroy(&match); -+ - ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100, - REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); - -@@ -6051,40 +6267,50 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) - ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;"); - -- 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;", -+ if (od->has_lb_vip) { -+ /* Check if the packet needs to be hairpinned. -+ * Set REGBIT_HAIRPIN in the original direction and -+ * REGBIT_HAIRPIN_REPLY in the reply direction. -+ */ -+ ovn_lflow_add_with_hint( -+ lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, "ip && ct.trk", -+ REGBIT_HAIRPIN " = chk_lb_hairpin(); " -+ REGBIT_HAIRPIN_REPLY " = chk_lb_hairpin_reply(); " -+ "next;", -+ &od->nbs->header_); -+ -+ /* If packet needs to be hairpinned, snat the src ip with the VIP -+ * for new sessions. */ -+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100, -+ "ip && ct.new && ct.trk" -+ " && "REGBIT_HAIRPIN " == 1", -+ "ct_snat_to_vip; 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. */ -+ /* If packet needs to be hairpinned, for established sessions there -+ * should already be an SNAT conntrack entry. -+ */ - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100, -- "ip && (ct.new || ct.est) && ct.trk && ct.dnat" -+ "ip && ct.est && ct.trk" - " && "REGBIT_HAIRPIN " == 1", -- "ct_snat_to_vip; next;", -+ "ct_snat;", - &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;", -+ "ip && "REGBIT_HAIRPIN_REPLY " == 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;"); -+ ovn_lflow_add( -+ lflows, od, S_SWITCH_IN_HAIRPIN, 1, -+ "("REGBIT_HAIRPIN " == 1 || " REGBIT_HAIRPIN_REPLY " == 1)", -+ "eth.dst <-> eth.src; outport = inport; flags.loopback = 1; " -+ "output;"); - } - } - -@@ -6754,9 +6980,7 @@ is_vlan_transparent(const struct ovn_datapath *od) - } - - static void --build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, -- struct hmap *lflows, struct hmap *mcgroups, -- struct hmap *igmp_groups, struct hmap *lbs) -+build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows) - { - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ -@@ -6765,32 +6989,111 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct ds actions = DS_EMPTY_INITIALIZER; - struct ovn_datapath *od; - -- /* 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 24: Destination lookup for unknown MACs (priority 0). */ -+ HMAP_FOR_EACH (od, key_node, datapaths) { -+ if (!od->nbs) { - continue; - } - -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", -+ "outport = get_fdb(eth.dst); next;"); -+ -+ if (od->has_unknown) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50, -+ "outport == \"none\"", -+ "outport = \""MC_UNKNOWN"\"; output;"); -+ } else { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50, -+ "outport == \"none\"", "drop;"); -+ } -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1", -+ "output;"); -+ } -+ -+ ds_destroy(&match); -+ 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) { -+ od->has_stateful_acl = ls_has_stateful_acl(od); -+ od->has_lb_vip = ls_has_lb_vip(od); -+ -+ 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. */ -+ } -+} -+ -+/* Ingress table 13: ARP/ND responder, skip requests coming from localnet -+ * and vtep ports. (priority 100); see ovn-northd.8.xml for the -+ * rationale. */ -+ -+static void -+build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match) -+{ -+ if (op->nbsp) { - if ((!strcmp(op->nbsp->type, "localnet")) || - (!strcmp(op->nbsp->type, "vtep"))) { -- ds_clear(&match); -- ds_put_format(&match, "inport == %s", op->json_key); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, -- 100, ds_cstr(&match), "next;", -+ 100, ds_cstr(match), "next;", - &op->nbsp->header_); - } - } -+} - -- /* Ingress table 13: ARP/ND responder, reply for known IPs. -- * (priority 50). */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp) { -- continue; -- } -- -+/* Ingress table 13: ARP/ND responder, reply for known IPs. -+ * (priority 50). */ -+static void -+build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op, -+ struct hmap *lflows, -+ struct hmap *ports, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (op->nbsp) { - if (!strcmp(op->nbsp->type, "virtual")) { - /* Handle - * - GARPs for virtual ip which belongs to a logical port -@@ -6806,7 +7109,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - "virtual-parents"); - if (!virtual_ip || !virtual_parents || - !ip_parse(virtual_ip, &ip)) { -- continue; -+ return; - } - - char *tokstr = xstrdup(virtual_parents); -@@ -6821,21 +7124,21 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - continue; - } - -- ds_clear(&match); -- ds_put_format(&match, "inport == \"%s\" && " -+ ds_clear(match); -+ ds_put_format(match, "inport == \"%s\" && " - "((arp.op == 1 && arp.spa == %s && " - "arp.tpa == %s) || (arp.op == 2 && " - "arp.spa == %s))", - vparent, virtual_ip, virtual_ip, - virtual_ip); -- ds_clear(&actions); -- ds_put_format(&actions, -+ ds_clear(actions); -+ ds_put_format(actions, - "bind_vport(%s, inport); " - "next;", - op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), ds_cstr(&actions), -+ ds_cstr(match), ds_cstr(actions), - &vp->nbsp->header_); - } - -@@ -6850,20 +7153,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - if (check_lsp_is_up && - !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) && - strcmp(op->nbsp->type, "localport")) { -- continue; -+ return; - } - - if (lsp_is_external(op->nbsp) || op->has_unknown) { -- continue; -+ return; - } - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -- ds_clear(&match); -- ds_put_format(&match, "arp.tpa == %s && arp.op == 1", -+ ds_clear(match); -+ ds_put_format(match, "arp.tpa == %s && arp.op == 1", - op->lsp_addrs[i].ipv4_addrs[j].addr_s); -- ds_clear(&actions); -- ds_put_format(&actions, -+ ds_clear(actions); -+ ds_put_format(actions, - "eth.dst = eth.src; " - "eth.src = %s; " - "arp.op = 2; /* ARP reply */ " -@@ -6878,8 +7181,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - op->lsp_addrs[i].ipv4_addrs[j].addr_s); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_ARP_ND_RSP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -+ ds_cstr(match), -+ ds_cstr(actions), - &op->nbsp->header_); - - /* Do not reply to an ARP request from the port that owns -@@ -6894,10 +7197,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - * address is intended to detect situations where the - * network is not working as configured, so dropping the - * request would frustrate that intent.) */ -- ds_put_format(&match, " && inport == %s", op->json_key); -+ ds_put_format(match, " && inport == %s", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), "next;", -+ ds_cstr(match), "next;", - &op->nbsp->header_); - } - -@@ -6905,15 +7208,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - * unicast IPv6 address and its all-nodes multicast address, - * but always respond with the unicast IPv6 address. */ - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -- ds_clear(&match); -- ds_put_format(&match, -+ ds_clear(match); -+ ds_put_format(match, - "nd_ns && ip6.dst == {%s, %s} && nd.target == %s", - op->lsp_addrs[i].ipv6_addrs[j].addr_s, - op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s, - op->lsp_addrs[i].ipv6_addrs[j].addr_s); - -- ds_clear(&actions); -- ds_put_format(&actions, -+ ds_clear(actions); -+ ds_put_format(actions, - "%s { " - "eth.src = %s; " - "ip6.src = %s; " -@@ -6930,93 +7233,99 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - op->lsp_addrs[i].ea_s); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_ARP_ND_RSP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -+ ds_cstr(match), -+ ds_cstr(actions), - &op->nbsp->header_); - - /* Do not reply to a solicitation from the port that owns - * the address (otherwise DAD detection will fail). */ -- ds_put_format(&match, " && inport == %s", op->json_key); -+ ds_put_format(match, " && inport == %s", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_ARP_ND_RSP, 100, -- ds_cstr(&match), "next;", -+ ds_cstr(match), "next;", - &op->nbsp->header_); - } - } - } - } -+} - -- /* Ingress table 13: ARP/ND responder, by default goto next. -- * (priority 0)*/ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -+/* Ingress table 13: ARP/ND responder, by default goto next. -+ * (priority 0)*/ -+static void -+build_lswitch_arp_nd_responder_default(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); - } -+} - -- /* Ingress table 13: ARP/ND responder for service monitor source ip. -- * (priority 110)*/ -- struct ovn_northd_lb *lb; -- HMAP_FOR_EACH (lb, hmap_node, lbs) { -- for (size_t i = 0; i < lb->n_vips; i++) { -- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; -- if (!lb_vip_nb->lb_health_check) { -+/* Ingress table 13: ARP/ND responder for service monitor source ip. -+ * (priority 110)*/ -+static void -+build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb, -+ struct hmap *lflows, -+ struct ds *actions, -+ struct ds *match) -+{ -+ for (size_t i = 0; i < lb->n_vips; i++) { -+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; -+ if (!lb_vip_nb->lb_health_check) { -+ continue; -+ } -+ -+ for (size_t j = 0; j < lb_vip_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; - } - -- 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", -- backend_nb->svc_mon_src_ip); -- ds_clear(&actions); -- 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;", -- svc_monitor_mac, svc_monitor_mac, -- backend_nb->svc_mon_src_ip); -- ovn_lflow_add_with_hint(lflows, -- backend_nb->op->od, -- S_SWITCH_IN_ARP_ND_RSP, 110, -- ds_cstr(&match), ds_cstr(&actions), -- &lb->nlb->header_); -- } -+ ds_clear(match); -+ ds_put_format(match, "arp.tpa == %s && arp.op == 1", -+ backend_nb->svc_mon_src_ip); -+ ds_clear(actions); -+ 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;", -+ svc_monitor_mac, svc_monitor_mac, -+ backend_nb->svc_mon_src_ip); -+ ovn_lflow_add_with_hint(lflows, -+ backend_nb->op->od, -+ S_SWITCH_IN_ARP_ND_RSP, 110, -+ ds_cstr(match), ds_cstr(actions), -+ &lb->nlb->header_); - } - } -+} - - -- /* Logical switch ingress table 14 and 15: DHCP options and response -- * priority 100 flows. */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp) { -- continue; -- } -- -+/* Logical switch ingress table 14 and 15: DHCP options and response -+ * priority 100 flows. */ -+static void -+build_lswitch_dhcp_options_and_response(struct ovn_port *op, -+ struct hmap *lflows) -+{ -+ if (op->nbsp) { - 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; -+ return; - } - - if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) { - /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport. - */ -- continue; -+ return; - } - - bool is_external = lsp_is_external(op->nbsp); -@@ -7024,7 +7333,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - !op->nbsp->ha_chassis_group)) { - /* If it's an external port and there are no localnet ports - * and if it doesn't belong to an HA chassis group ignore it. */ -- continue; -+ return; - } - - for (size_t i = 0; i < op->n_lsp_addrs; i++) { -@@ -7047,14 +7356,35 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - } - } - } -+} - -- /* Logical switch ingress table 17 and 18: DNS lookup and response -- * priority 100 flows. -- */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs || !ls_has_dns_records(od->nbs)) { -- continue; -- } -+/* Ingress table 14 and 15: DHCP options and response, by default goto -+ * next. (priority 0). -+ * Ingress table 16 and 17: DNS lookup and response, by default goto next. -+ * (priority 0). -+ * Ingress table 18 - External port handling, by default goto next. -+ * (priority 0). */ -+static void -+build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;"); -+ } -+} -+ -+/* Logical switch ingress table 17 and 18: DNS lookup and response -+* priority 100 flows. -+*/ -+static void -+build_lswitch_dns_lookup_and_response(struct ovn_datapath *od, -+ struct hmap *lflows) -+{ -+ if (od->nbs && ls_has_dns_records(od->nbs)) { - - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, - "udp.dst == 53", -@@ -7071,47 +7401,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, - dns_match, dns_action); - } -+} - -- /* Ingress table 14 and 15: DHCP options and response, by default goto -- * next. (priority 0). -- * Ingress table 16 and 17: DNS lookup and response, by default goto next. -- * (priority 0). -- * Ingress table 18 - External port handling, by default goto next. -- * (priority 0). */ -- -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -- -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;"); -- } -- -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || !lsp_is_external(op->nbsp)) { -- continue; -- } -+/* Table 18: External port. Drop ARP request for router ips from -+ * external ports on chassis not binding those ports. -+ * This makes the router pipeline to be run only on the chassis -+ * binding the external ports. */ -+static void -+build_lswitch_external_port(struct ovn_port *op, -+ struct hmap *lflows) -+{ -+ if (op->nbsp && lsp_is_external(op->nbsp)) { - -- /* Table 18: External port. Drop ARP request for router ips from -- * external ports on chassis not binding those ports. -- * This makes the router pipeline to be run only on the chassis -- * binding the external ports. */ - for (size_t i = 0; i < op->od->n_localnet_ports; i++) { - build_drop_arp_nd_flows_for_unbound_router_ports( - op, op->od->localnet_ports[i], lflows); - } - } -+} - -- /* Ingress table 19: Destination lookup, broadcast and multicast handling -- * (priority 70 - 100). */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -- continue; -- } -+/* Ingress table 19: Destination lookup, broadcast and multicast handling -+ * (priority 70 - 100). */ -+static void -+build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od, -+ struct hmap *lflows, -+ struct ds *actions) -+{ -+ if (od->nbs) { - - ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, - "eth.dst == $svc_monitor_mac", -@@ -7120,22 +7436,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw; - - if (mcast_sw_info->enabled) { -- ds_clear(&actions); -+ ds_clear(actions); - if (mcast_sw_info->flood_reports) { -- ds_put_cstr(&actions, -+ ds_put_cstr(actions, - "clone { " - "outport = \""MC_MROUTER_STATIC"\"; " - "output; " - "};"); - } -- ds_put_cstr(&actions, "igmp;"); -+ ds_put_cstr(actions, "igmp;"); - /* Punt IGMP traffic to controller. */ - ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100, -- "ip4 && ip.proto == 2", ds_cstr(&actions)); -+ "ip4 && ip.proto == 2", ds_cstr(actions)); - - /* Punt MLD traffic to controller. */ - ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100, -- "mldv1 || mldv2", ds_cstr(&actions)); -+ "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. -@@ -7157,10 +7473,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - * handled by the L2 multicast flow. - */ - if (!mcast_sw_info->flood_unregistered) { -- ds_clear(&actions); -+ ds_clear(actions); - - if (mcast_sw_info->flood_relay) { -- ds_put_cstr(&actions, -+ ds_put_cstr(actions, - "clone { " - "outport = \""MC_MROUTER_FLOOD"\"; " - "output; " -@@ -7168,7 +7484,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - } - - if (mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;"); -+ ds_put_cstr(actions, "outport =\""MC_STATIC"\"; output;"); - } - - /* Explicitly drop the traffic if relay or static flooding -@@ -7176,30 +7492,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - */ - if (!mcast_sw_info->flood_relay && - !mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, "drop;"); -+ ds_put_cstr(actions, "drop;"); - } - - ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 80, - "ip4.mcast || ip6.mcast", -- ds_cstr(&actions)); -+ ds_cstr(actions)); - } - } - - ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast", - "outport = \""MC_FLOOD"\"; output;"); - } -+} - -- /* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD -- * (priority 90). */ -- struct ovn_igmp_group *igmp_group; - -- HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) { -- if (!igmp_group->datapath) { -- continue; -- } -+/* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD -+ * (priority 90). */ -+static void -+build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group, -+ struct hmap *lflows, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (igmp_group->datapath) { - -- ds_clear(&match); -- ds_clear(&actions); -+ ds_clear(match); -+ ds_clear(actions); - - struct mcast_switch_info *mcast_sw_info = - &igmp_group->datapath->mcast_info.sw; -@@ -7211,57 +7530,62 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - ovs_be32 group_address = - in6_addr_get_mapped_ipv4(&igmp_group->address); - if (ip_is_local_multicast(group_address)) { -- continue; -+ return; - } - - if (mcast_sw_info->active_v4_flows >= mcast_sw_info->table_size) { -- continue; -+ return; - } - mcast_sw_info->active_v4_flows++; -- ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ", -+ ds_put_format(match, "eth.mcast && ip4 && ip4.dst == %s ", - igmp_group->mcgroup.name); - } else { - /* RFC 4291, section 2.7.1: Skip groups that correspond to all - * hosts. - */ - if (ipv6_is_all_hosts(&igmp_group->address)) { -- continue; -+ return; - } - if (mcast_sw_info->active_v6_flows >= mcast_sw_info->table_size) { -- continue; -+ return; - } - mcast_sw_info->active_v6_flows++; -- ds_put_format(&match, "eth.mcast && ip6 && ip6.dst == %s ", -+ ds_put_format(match, "eth.mcast && ip6 && ip6.dst == %s ", - igmp_group->mcgroup.name); - } - - /* Also flood traffic to all multicast routers with relay enabled. */ - if (mcast_sw_info->flood_relay) { -- ds_put_cstr(&actions, -+ ds_put_cstr(actions, - "clone { " - "outport = \""MC_MROUTER_FLOOD "\"; " - "output; " - "};"); - } - if (mcast_sw_info->flood_static) { -- ds_put_cstr(&actions, -+ ds_put_cstr(actions, - "clone { " - "outport =\""MC_STATIC"\"; " - "output; " - "};"); - } -- ds_put_format(&actions, "outport = \"%s\"; output; ", -+ ds_put_format(actions, "outport = \"%s\"; output; ", - igmp_group->mcgroup.name); - - ovn_lflow_add_unique(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, -- 90, ds_cstr(&match), ds_cstr(&actions)); -+ 90, ds_cstr(match), ds_cstr(actions)); - } -+} - -- /* Ingress table 19: Destination lookup, unicast handling (priority 50), */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp || lsp_is_external(op->nbsp)) { -- continue; -- } -+/* Ingress table 19: Destination lookup, unicast handling (priority 50), */ -+static void -+build_lswitch_ip_unicast_lookup(struct ovn_port *op, -+ struct hmap *lflows, -+ struct hmap *mcgroups, -+ struct ds *actions, -+ struct ds *match) -+{ -+ if (op->nbsp && (!lsp_is_external(op->nbsp))) { - - /* For ports connected to logical routers add flows to bypass the - * broadcast flooding of ARP/ND requests in table 19. We direct the -@@ -7279,15 +7603,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - struct eth_addr mac; - if (ovs_scan(op->nbsp->addresses[i], - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -- 50, ds_cstr(&match), -- ds_cstr(&actions), -+ 50, ds_cstr(match), -+ ds_cstr(actions), - &op->nbsp->header_); - } else if (!strcmp(op->nbsp->addresses[i], "unknown")) { - if (lsp_is_enabled(op->nbsp)) { -@@ -7300,15 +7624,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { - continue; - } -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP, -- 50, ds_cstr(&match), -- ds_cstr(&actions), -+ 50, ds_cstr(match), -+ ds_cstr(actions), - &op->nbsp->header_); - } else if (!strcmp(op->nbsp->addresses[i], "router")) { - if (!op->peer || !op->peer->nbrp -@@ -7316,8 +7640,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { - continue; - } -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT, -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT, - ETH_ADDR_ARGS(mac)); - if (op->peer->od->l3dgw_port - && op->peer->od->l3redirect_port -@@ -7343,16 +7667,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - } - - if (add_chassis_resident_check) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -+ ds_put_format(match, " && is_chassis_resident(%s)", - op->peer->od->l3redirect_port->json_key); - } - } - -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", op->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_L2_LKUP, 50, -- ds_cstr(&match), ds_cstr(&actions), -+ ds_cstr(match), ds_cstr(actions), - &op->nbsp->header_); - - /* Add ethernet addresses specified in NAT rules on -@@ -7366,19 +7690,19 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - && nat->logical_port && nat->external_mac - && eth_addr_from_string(nat->external_mac, &mac)) { - -- ds_clear(&match); -- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT - " && is_chassis_resident(\"%s\")", - ETH_ADDR_ARGS(mac), - nat->logical_port); - -- ds_clear(&actions); -- ds_put_format(&actions, "outport = %s; output;", -+ ds_clear(actions); -+ ds_put_format(actions, "outport = %s; output;", - op->json_key); - ovn_lflow_add_with_hint(lflows, op->od, - S_SWITCH_IN_L2_LKUP, 50, -- ds_cstr(&match), -- ds_cstr(&actions), -+ ds_cstr(match), -+ ds_cstr(actions), - &op->nbsp->header_); - } - } -@@ -7392,71 +7716,202 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, - } - } - } -+} - -- /* Ingress table 19: Destination lookup for unknown MACs (priority 0). */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbs) { -+struct bfd_entry { -+ struct hmap_node hmap_node; -+ -+ const struct sbrec_bfd *sb_bt; -+ -+ bool ref; -+}; -+ -+static struct bfd_entry * -+bfd_port_lookup(struct hmap *bfd_map, const char *logical_port, -+ const char *dst_ip) -+{ -+ struct bfd_entry *bfd_e; -+ uint32_t hash; -+ -+ hash = hash_string(dst_ip, 0); -+ hash = hash_string(logical_port, hash); -+ HMAP_FOR_EACH_WITH_HASH (bfd_e, hmap_node, hash, bfd_map) { -+ if (!strcmp(bfd_e->sb_bt->logical_port, logical_port) && -+ !strcmp(bfd_e->sb_bt->dst_ip, dst_ip)) { -+ return bfd_e; -+ } -+ } -+ return NULL; -+} -+ -+static void -+bfd_cleanup_connections(struct northd_context *ctx, struct hmap *bfd_map) -+{ -+ const struct nbrec_bfd *nb_bt; -+ struct bfd_entry *bfd_e; -+ -+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) { -+ bfd_e = bfd_port_lookup(bfd_map, nb_bt->logical_port, nb_bt->dst_ip); -+ if (!bfd_e) { - continue; - } - -- if (od->has_unknown) { -- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", -- "outport = \""MC_UNKNOWN"\"; output;"); -+ if (!bfd_e->ref && strcmp(nb_bt->status, "admin_down")) { -+ /* no user for this bfd connection */ -+ nbrec_bfd_set_status(nb_bt, "admin_down"); - } - } - -- build_lswitch_output_port_sec(ports, datapaths, lflows); -- -- ds_destroy(&match); -- ds_destroy(&actions); -+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, bfd_map) { -+ free(bfd_e); -+ } - } - --/* Build pre-ACL and ACL tables for both ingress and egress. -- * Ingress tables 3 through 10. Egress tables 0 through 7. */ -+#define BFD_DEF_MINTX 1000 /* 1s */ -+#define BFD_DEF_MINRX 1000 /* 1s */ -+#define BFD_DEF_DETECT_MULT 5 -+ - 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) -+build_bfd_update_sb_conf(const struct nbrec_bfd *nb_bt, -+ const struct sbrec_bfd *sb_bt) - { -- 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); -+ if (strcmp(nb_bt->dst_ip, sb_bt->dst_ip)) { -+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip); -+ } -+ -+ if (strcmp(nb_bt->logical_port, sb_bt->logical_port)) { -+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port); -+ } -+ -+ if (strcmp(nb_bt->status, sb_bt->status)) { -+ sbrec_bfd_set_status(sb_bt, nb_bt->status); -+ } -+ -+ int detect_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0] -+ : BFD_DEF_DETECT_MULT; -+ if (detect_mult != sb_bt->detect_mult) { -+ sbrec_bfd_set_detect_mult(sb_bt, detect_mult); -+ } -+ -+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX; -+ if (min_tx != sb_bt->min_tx) { -+ sbrec_bfd_set_min_tx(sb_bt, min_tx); -+ } -+ -+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX; -+ if (min_rx != sb_bt->min_rx) { -+ sbrec_bfd_set_min_rx(sb_bt, min_rx); - } - } - --/* Logical switch ingress table 0: Admission control framework (priority -- * 100). */ -+/* RFC 5881 section 4 -+ * The source port MUST be in the range 49152 through 65535. -+ * The same UDP source port number MUST be used for all BFD -+ * Control packets associated with a particular session. -+ * The source port number SHOULD be unique among all BFD -+ * sessions on the system -+ */ -+#define BFD_UDP_SRC_PORT_START 49152 -+#define BFD_UDP_SRC_PORT_LEN (65535 - BFD_UDP_SRC_PORT_START) -+ -+static int bfd_get_unused_port(unsigned long *bfd_src_ports) -+{ -+ int port; -+ -+ port = bitmap_scan(bfd_src_ports, 0, 0, BFD_UDP_SRC_PORT_LEN); -+ if (port == BFD_UDP_SRC_PORT_LEN) { -+ return -ENOSPC; -+ } -+ bitmap_set1(bfd_src_ports, port); -+ -+ return port + BFD_UDP_SRC_PORT_START; -+} -+ - static void --build_lswitch_lflows_admission_control(struct ovn_datapath *od, -- struct hmap *lflows) -+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections, -+ struct hmap *ports) - { -- 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;"); -+ struct hmap sb_only = HMAP_INITIALIZER(&sb_only); -+ const struct sbrec_bfd *sb_bt; -+ unsigned long *bfd_src_ports; -+ struct bfd_entry *bfd_e; -+ uint32_t hash; -+ -+ bfd_src_ports = bitmap_allocate(BFD_UDP_SRC_PORT_LEN); -+ -+ SBREC_BFD_FOR_EACH (sb_bt, ctx->ovnsb_idl) { -+ bfd_e = xmalloc(sizeof *bfd_e); -+ bfd_e->sb_bt = sb_bt; -+ hash = hash_string(sb_bt->dst_ip, 0); -+ hash = hash_string(sb_bt->logical_port, hash); -+ hmap_insert(&sb_only, &bfd_e->hmap_node, hash); -+ bitmap_set1(bfd_src_ports, sb_bt->src_port - BFD_UDP_SRC_PORT_START); -+ } -+ -+ const struct nbrec_bfd *nb_bt; -+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) { -+ if (!nb_bt->status) { -+ /* default state is admin_down */ -+ nbrec_bfd_set_status(nb_bt, "admin_down"); - } - -- /* Broadcast/multicast source address is invalid. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", -- "drop;"); -+ bfd_e = bfd_port_lookup(&sb_only, nb_bt->logical_port, nb_bt->dst_ip); -+ if (!bfd_e) { -+ int udp_src = bfd_get_unused_port(bfd_src_ports); -+ if (udp_src < 0) { -+ continue; -+ } - -- /* Port security flows have priority 50 -- * (see build_lswitch_input_port_sec()) and will continue -- * to the next table if packet source is acceptable. */ -+ sb_bt = sbrec_bfd_insert(ctx->ovnsb_txn); -+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port); -+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip); -+ sbrec_bfd_set_disc(sb_bt, 1 + random_uint32()); -+ sbrec_bfd_set_src_port(sb_bt, udp_src); -+ sbrec_bfd_set_status(sb_bt, nb_bt->status); -+ -+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX; -+ sbrec_bfd_set_min_tx(sb_bt, min_tx); -+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX; -+ sbrec_bfd_set_min_rx(sb_bt, min_rx); -+ int d_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0] -+ : BFD_DEF_DETECT_MULT; -+ sbrec_bfd_set_detect_mult(sb_bt, d_mult); -+ } else if (strcmp(bfd_e->sb_bt->status, nb_bt->status)) { -+ if (!strcmp(nb_bt->status, "admin_down") || -+ !strcmp(bfd_e->sb_bt->status, "admin_down")) { -+ sbrec_bfd_set_status(bfd_e->sb_bt, nb_bt->status); -+ } else { -+ nbrec_bfd_set_status(nb_bt, bfd_e->sb_bt->status); -+ } -+ } -+ if (bfd_e) { -+ build_bfd_update_sb_conf(nb_bt, bfd_e->sb_bt); -+ -+ hmap_remove(&sb_only, &bfd_e->hmap_node); -+ bfd_e->ref = false; -+ hash = hash_string(bfd_e->sb_bt->dst_ip, 0); -+ hash = hash_string(bfd_e->sb_bt->logical_port, hash); -+ hmap_insert(bfd_connections, &bfd_e->hmap_node, hash); -+ } -+ -+ struct ovn_port *op = ovn_port_find(ports, nb_bt->logical_port); -+ if (op) { -+ op->has_bfd = true; -+ } - } --} - -+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) { -+ struct ovn_port *op = ovn_port_find(ports, bfd_e->sb_bt->logical_port); -+ if (op) { -+ op->has_bfd = false; -+ } -+ sbrec_bfd_delete(bfd_e->sb_bt); -+ free(bfd_e); -+ } -+ hmap_destroy(&sb_only); -+ -+ bitmap_free(bfd_src_ports); -+} - - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. -@@ -7549,33 +8004,39 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - struct ds actions = DS_EMPTY_INITIALIZER; - - if (!strcmp(rule->action, "reroute")) { -+ ovs_assert(rule->n_nexthops <= 1); -+ -+ char *nexthop = -+ (rule->n_nexthops == 1 ? rule->nexthops[0] : rule->nexthop); - struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( -- od, ports, rule->priority, rule->nexthop); -+ od, ports, rule->priority, nexthop); - if (!out_port) { - return; - } - -- const char *lrp_addr_s = find_lrp_member_ip(out_port, rule->nexthop); -+ const char *lrp_addr_s = find_lrp_member_ip(out_port, nexthop); - if (!lrp_addr_s) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy " - " priority %"PRId64" nexthop %s", -- rule->priority, rule->nexthop); -+ rule->priority, nexthop); - return; - } - uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0); - if (pkt_mark) { - ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); - } -- bool is_ipv4 = strchr(rule->nexthop, '.') ? true : false; -+ -+ bool is_ipv4 = strchr(nexthop, '.') ? true : false; - ds_put_format(&actions, "%s = %s; " - "%s = %s; " - "eth.src = %s; " - "outport = %s; " - "flags.loopback = 1; " -+ REG_ECMP_GROUP_ID" = 0; " - "next;", - is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6, -- rule->nexthop, -+ nexthop, - is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6, - lrp_addr_s, - out_port->lrp_networks.ea_s, -@@ -7588,7 +8049,7 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - if (pkt_mark) { - ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); - } -- ds_put_cstr(&actions, "next;"); -+ ds_put_cstr(&actions, REG_ECMP_GROUP_ID" = 0; next;"); - } - ds_put_format(&match, "%s", rule->match); - -@@ -7598,15 +8059,116 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, - ds_destroy(&actions); - } - --struct parsed_route { -- struct ovs_list list_node; -- struct in6_addr prefix; -- unsigned int plen; -- bool is_src_route; -- uint32_t hash; -- const struct nbrec_logical_router_static_route *route; -- bool ecmp_symmetric_reply; --}; -+static void -+build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od, -+ struct hmap *ports, -+ const struct nbrec_logical_router_policy *rule, -+ uint16_t ecmp_group_id) -+{ -+ ovs_assert(rule->n_nexthops > 1); -+ -+ bool nexthops_is_ipv4 = true; -+ -+ /* Check that all the nexthops belong to the same addr family before -+ * adding logical flows. */ -+ for (uint16_t i = 0; i < rule->n_nexthops; i++) { -+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false; -+ -+ if (i == 0) { -+ nexthops_is_ipv4 = is_ipv4; -+ } -+ -+ if (is_ipv4 != nexthops_is_ipv4) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "nexthop [%s] of the router policy with " -+ "the match [%s] do not belong to the same address " -+ "family as other next hops", -+ rule->nexthops[i], rule->match); -+ return; -+ } -+ } -+ -+ struct ds match = DS_EMPTY_INITIALIZER; -+ struct ds actions = DS_EMPTY_INITIALIZER; -+ -+ for (size_t i = 0; i < rule->n_nexthops; i++) { -+ struct ovn_port *out_port = get_outport_for_routing_policy_nexthop( -+ od, ports, rule->priority, rule->nexthops[i]); -+ if (!out_port) { -+ goto cleanup; -+ } -+ -+ const char *lrp_addr_s = -+ find_lrp_member_ip(out_port, rule->nexthops[i]); -+ if (!lrp_addr_s) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy " -+ " priority %"PRId64" nexthop %s", -+ rule->priority, rule->nexthops[i]); -+ goto cleanup; -+ } -+ -+ ds_clear(&actions); -+ uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0); -+ if (pkt_mark) { -+ ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); -+ } -+ -+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false; -+ -+ ds_put_format(&actions, "%s = %s; " -+ "%s = %s; " -+ "eth.src = %s; " -+ "outport = %s; " -+ "flags.loopback = 1; " -+ "next;", -+ is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6, -+ rule->nexthops[i], -+ is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6, -+ lrp_addr_s, -+ out_port->lrp_networks.ea_s, -+ out_port->json_key); -+ -+ ds_clear(&match); -+ ds_put_format(&match, REG_ECMP_GROUP_ID" == %"PRIu16" && " -+ REG_ECMP_MEMBER_ID" == %"PRIuSIZE, -+ ecmp_group_id, i + 1); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY_ECMP, -+ 100, ds_cstr(&match), -+ ds_cstr(&actions), &rule->header_); -+ } -+ -+ ds_clear(&actions); -+ ds_put_format(&actions, "%s = %"PRIu16 -+ "; %s = select(", REG_ECMP_GROUP_ID, ecmp_group_id, -+ REG_ECMP_MEMBER_ID); -+ -+ for (size_t i = 0; i < rule->n_nexthops; i++) { -+ if (i > 0) { -+ ds_put_cstr(&actions, ", "); -+ } -+ -+ ds_put_format(&actions, "%"PRIuSIZE, i + 1); -+ } -+ ds_put_cstr(&actions, ");"); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY, -+ rule->priority, rule->match, -+ ds_cstr(&actions), &rule->header_); -+ -+cleanup: -+ ds_destroy(&match); -+ ds_destroy(&actions); -+} -+ -+struct parsed_route { -+ struct ovs_list list_node; -+ struct in6_addr prefix; -+ unsigned int plen; -+ bool is_src_route; -+ uint32_t hash; -+ const struct nbrec_logical_router_static_route *route; -+ bool ecmp_symmetric_reply; -+}; - - static uint32_t - route_hash(struct parsed_route *route) -@@ -7619,7 +8181,8 @@ route_hash(struct parsed_route *route) - * Otherwise return NULL. */ - static struct parsed_route * - parsed_routes_add(struct ovs_list *routes, -- const struct nbrec_logical_router_static_route *route) -+ const struct nbrec_logical_router_static_route *route, -+ struct hmap *bfd_connections) - { - /* Verify that the next hop is an IP address with an all-ones mask. */ - struct in6_addr nexthop; -@@ -7660,6 +8223,25 @@ parsed_routes_add(struct ovs_list *routes, - return NULL; - } - -+ const struct nbrec_bfd *nb_bt = route->bfd; -+ if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) { -+ struct bfd_entry *bfd_e; -+ -+ bfd_e = bfd_port_lookup(bfd_connections, nb_bt->logical_port, -+ nb_bt->dst_ip); -+ if (bfd_e) { -+ bfd_e->ref = true; -+ } -+ -+ if (!strcmp(nb_bt->status, "admin_down")) { -+ nbrec_bfd_set_status(nb_bt, "down"); -+ } -+ -+ if (!strcmp(nb_bt->status, "down")) { -+ return NULL; -+ } -+ } -+ - struct parsed_route *pr = xzalloc(sizeof *pr); - pr->prefix = prefix; - pr->plen = plen; -@@ -8102,16 +8684,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op, - build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4, - &match, &priority); - -- struct ds actions = DS_EMPTY_INITIALIZER; -- ds_put_format(&actions, "ip.ttl--; "REG_ECMP_GROUP_ID" = 0; %s = ", -+ struct ds common_actions = DS_EMPTY_INITIALIZER; -+ ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ", - is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6); -- - if (gateway) { -- ds_put_cstr(&actions, gateway); -+ ds_put_cstr(&common_actions, gateway); - } else { -- ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6"); -+ ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6"); - } -- ds_put_format(&actions, "; " -+ ds_put_format(&common_actions, "; " - "%s = %s; " - "eth.src = %s; " - "outport = %s; " -@@ -8121,11 +8702,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op, - lrp_addr_s, - op->lrp_networks.ea_s, - op->json_key); -+ struct ds actions = DS_EMPTY_INITIALIZER; -+ ds_put_format(&actions, "ip.ttl--; %s", ds_cstr(&common_actions)); - - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority, - ds_cstr(&match), ds_cstr(&actions), - stage_hint); -+ if (op->has_bfd) { -+ ds_put_format(&match, " && udp.dst == 3784"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, -+ priority + 1, ds_cstr(&match), -+ ds_cstr(&common_actions), stage_hint); -+ } - ds_destroy(&match); -+ ds_destroy(&common_actions); - ds_destroy(&actions); - } - -@@ -8203,15 +8793,10 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, - return false; - } - -- if (!extract_ip_addresses(addresses, laddrs) || -- laddrs->n_ipv4_addrs > 1 || -- laddrs->n_ipv6_addrs > 1 || -- (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) || -- (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) { -+ if (!extract_ip_address(addresses, laddrs)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"", - addresses, UUID_ARGS(&od->key)); -- destroy_lport_addresses(laddrs); - return false; - } - -@@ -8221,7 +8806,7 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, - 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 ovn_lb_vip *lb_vip, -+ bool force_snat_for_lb, struct ovn_lb_vip *lb_vip, - const char *proto, struct nbrec_load_balancer *lb, - struct shash *meter_groups, struct sset *nat_entries) - { -@@ -8230,7 +8815,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - - /* A match and actions for new connections. */ - char *new_match = xasprintf("ct.new && %s", ds_cstr(match)); -- if (lb_force_snat_ip) { -+ if (force_snat_for_lb) { - char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s", - ds_cstr(actions)); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, -@@ -8243,7 +8828,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - - /* A match and actions for established connections. */ - char *est_match = xasprintf("ct.est && %s", ds_cstr(match)); -- if (lb_force_snat_ip) { -+ if (force_snat_for_lb) { - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, - est_match, - "flags.force_snat_for_lb = 1; ct_dnat;", -@@ -8320,7 +8905,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - ds_put_format(&undnat_match, ") && outport == %s && " - "is_chassis_resident(%s)", od->l3dgw_port->json_key, - od->l3redirect_port->json_key); -- if (lb_force_snat_ip) { -+ if (force_snat_for_lb) { - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, - ds_cstr(&undnat_match), - "flags.force_snat_for_lb = 1; ct_dnat;", -@@ -8788,2375 +9373,2531 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, - } - - static void --build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, -- struct hmap *lflows, struct shash *meter_groups, -- struct hmap *lbs) -+build_lrouter_force_snat_flows_op(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, struct ds *actions) - { -- /* This flow table structure is documented in ovn-northd(8), so please -- * update ovn-northd.8.xml if you change anything. */ -+ if (!op->nbrp || !op->peer || !op->od->lb_force_snat_router_ip) { -+ return; -+ } - -- struct ds match = DS_EMPTY_INITIALIZER; -- struct ds actions = DS_EMPTY_INITIALIZER; -+ if (op->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_clear(actions); - -- struct ovn_datapath *od; -- struct ovn_port *op; -+ ds_put_format(match, "inport == %s && ip4.dst == %s", -+ op->json_key, op->lrp_networks.ipv4_addrs[0].addr_s); -+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110, -+ ds_cstr(match), "ct_snat;"); - -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbr) { -- continue; -- } -+ ds_clear(match); - -- /* 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. -- */ -- for (int i = 0; i < od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &od->nat_entries[i]; -+ /* Higher priority rules to force SNAT with the router port ip. -+ * This only takes effect when the packet has already been -+ * load balanced once. */ -+ ds_put_format(match, "flags.force_snat_for_lb == 1 && ip4 && " -+ "outport == %s", op->json_key); -+ ds_put_format(actions, "ct_snat(%s);", -+ op->lrp_networks.ipv4_addrs[0].addr_s); -+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_SNAT, 110, -+ ds_cstr(match), ds_cstr(actions)); -+ if (op->lrp_networks.n_ipv4_addrs > 2) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "Logical router port %s is configured with " -+ "multiple IPv4 addresses. Only the first " -+ "IP [%s] is considered as SNAT for load " -+ "balancer", op->json_key, -+ op->lrp_networks.ipv4_addrs[0].addr_s); -+ } -+ } -+ -+ /* op->lrp_networks.ipv6_addrs will always have LLA and that will be -+ * last in the list. So add the flows only if n_ipv6_addrs > 1. */ -+ if (op->lrp_networks.n_ipv6_addrs > 1) { -+ ds_clear(match); -+ ds_clear(actions); - -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } -+ ds_put_format(match, "inport == %s && ip6.dst == %s", -+ op->json_key, op->lrp_networks.ipv6_addrs[0].addr_s); -+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110, -+ ds_cstr(match), "ct_snat;"); - -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { -- continue; -- } -- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -+ ds_clear(match); -+ -+ /* Higher priority rules to force SNAT with the router port ip. -+ * This only takes effect when the packet has already been -+ * load balanced once. */ -+ ds_put_format(match, "flags.force_snat_for_lb == 1 && ip6 && " -+ "outport == %s", op->json_key); -+ ds_put_format(actions, "ct_snat(%s);", -+ op->lrp_networks.ipv6_addrs[0].addr_s); -+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_SNAT, 110, -+ ds_cstr(match), ds_cstr(actions)); -+ if (op->lrp_networks.n_ipv6_addrs > 2) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -+ VLOG_WARN_RL(&rl, "Logical router port %s is configured with " -+ "multiple IPv6 addresses. Only the first " -+ "IP [%s] is considered as SNAT for load " -+ "balancer", op->json_key, -+ op->lrp_networks.ipv6_addrs[0].addr_s); - } -+ } -+} - -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; -+static void -+build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op) -+{ -+ if (!op->has_bfd) { -+ return; -+ } - -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -+ struct ds ip_list = DS_EMPTY_INITIALIZER; -+ struct ds match = DS_EMPTY_INITIALIZER; - -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -- } -+ if (op->lrp_networks.n_ipv4_addrs) { -+ op_put_v4_networks(&ip_list, op, false); -+ ds_put_format(&match, "ip4.src == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "next; ", -+ &op->nbrp->header_); -+ ds_clear(&match); -+ ds_put_format(&match, "ip4.dst == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "handle_bfd_msg(); ", -+ &op->nbrp->header_); - } -+ if (op->lrp_networks.n_ipv6_addrs) { -+ ds_clear(&ip_list); -+ ds_clear(&match); - -- /* Logical router ingress table 3: IP Input for IPv4. */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbrp) { -- continue; -+ op_put_v6_networks(&ip_list, op); -+ ds_put_format(&match, "ip6.src == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "next; ", -+ &op->nbrp->header_); -+ ds_clear(&match); -+ ds_put_format(&match, "ip6.dst == %s && udp.dst == 3784", -+ ds_cstr(&ip_list)); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, -+ ds_cstr(&match), "handle_bfd_msg(); ", -+ &op->nbrp->header_); -+ } -+ -+ ds_destroy(&ip_list); -+ ds_destroy(&match); -+} -+ -+/* 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 -+ * on their Ethernet headers. -+ */ -+static void -+build_adm_ctrl_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 from disabled logical ports (since logical flow -+ * tables are default-drop). */ -+ return; - } - - if (op->derived) { -- /* No ingress packets are accepted on a chassisredirect -- * port, so no need to program flows for that port. */ -- continue; -+ /* No ingress packets should be received on a chassisredirect -+ * port. */ -+ return; - } - -- if (op->lrp_networks.n_ipv4_addrs) { -- /* L3 admission control: drop packets that originate from an -- * IPv4 address owned by the router or a broadcast address -- * known to the router (priority 100). */ -- ds_clear(&match); -- ds_put_cstr(&match, "ip4.src == "); -- op_put_v4_networks(&match, op, true); -- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -- ds_cstr(&match), "drop;", -- &op->nbrp->header_); -+ /* Store the ethernet address of the port receiving the packet. -+ * This will save us from having to match on inport further down in -+ * the pipeline. -+ */ -+ ds_clear(actions); -+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -+ op->lrp_networks.ea_s); - -- /* ICMP echo reply. These flows reply to ICMP echo requests -- * received for the router's IP address. Since packets only -- * get here as part of the logical router datapath, the inport -- * (i.e. the incoming locally attached net) does not matter. -- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -- ds_clear(&match); -- ds_put_cstr(&match, "ip4.dst == "); -- op_put_v4_networks(&match, op, false); -- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); -+ ds_clear(match); -+ ds_put_format(match, "eth.mcast && inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); - -- const char * icmp_actions = "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 0; " -- "flags.loopback = 1; " -- "next; "; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -- ds_cstr(&match), icmp_actions, -- &op->nbrp->header_); -+ ds_clear(match); -+ ds_put_format(match, "eth.dst == %s && inport == %s", -+ op->lrp_networks.ea_s, op->json_key); -+ 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 gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+} - -- /* ICMP time exceeded */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_clear(&actions); - -- ds_put_format(&match, -- "inport == %s && ip4 && " -- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -- ds_put_format(&actions, -- "icmp4 {" -- "eth.dst <-> eth.src; " -- "icmp4.type = 11; /* Time exceeded */ " -- "icmp4.code = 0; /* TTL exceeded in transit */ " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "next; };", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -- ds_cstr(&match), ds_cstr(&actions), -- &op->nbrp->header_); -- } -+/* Logical router ingress Table 1 and 2: Neighbor lookup and learning -+ * lflows for logical routers. */ -+static void -+build_neigh_learning_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- /* ARP reply. These flows reply to ARP requests for the router's own -- * IP address. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, "arp.spa == %s/%u", -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); -+ /* Learn MAC bindings from ARP/IPv6 ND. -+ * -+ * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the -+ * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp' -+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit. -+ * If "always_learn_from_arp_request" is set to false, it will also -+ * lookup for the (arp.spa) in the mac binding table using the -+ * "lookup_arp_ip" action for ARP request packets, and stores the -+ * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit -+ * to "1" directly for ARP response packets. -+ * -+ * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup -+ * for the (nd.target, nd.tll) in the mac binding table using the -+ * 'lookup_nd' action and stores the result in -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -+ * "always_learn_from_arp_request" is set to false, -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set. -+ * -+ * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup -+ * for the (ip6.src, nd.sll) in the mac binding table using the -+ * 'lookup_nd' action and stores the result in -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -+ * "always_learn_from_arp_request" is set to false, it will also lookup -+ * for the (ip6.src) in the mac binding table using the "lookup_nd_ip" -+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ * bit. -+ * -+ * Table LEARN_NEIGHBOR learns the mac-binding using the action -+ * - 'put_arp/put_nd'. Learning mac-binding is skipped if -+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set. -+ * -+ * */ - -- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -- && op->peer->od->n_localnet_ports) { -- bool add_chassis_resident_check = false; -- if (op == op->od->l3dgw_port) { -- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -- * should only be sent from the gateway chassis, so that -- * upstream MAC learning points to the gateway chassis. -- * Also need to avoid generation of multiple ARP responses -- * from different chassis. */ -- add_chassis_resident_check = true; -- } else { -- /* Check if the option 'reside-on-redirect-chassis' -- * is set to true on the router port. If set to true -- * and if peer's logical switch has a localnet port, it -- * means the router pipeline for the packets from -- * peer's logical switch is be run on the chassis -- * hosting the gateway port and it should reply to the -- * ARP requests for the router port IPs. -- */ -- add_chassis_resident_check = smap_get_bool( -- &op->nbrp->options, -- "reside-on-redirect-chassis", false); -- } -+ /* Flows for LOOKUP_NEIGHBOR. */ -+ bool learn_from_arp_request = smap_get_bool(&od->nbr->options, -+ "always_learn_from_arp_request", true); -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -+ "arp.op == 2", ds_cstr(actions)); - -- if (add_chassis_resident_check) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -- } -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_nd(inport, nd.target, nd.tll); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", -+ ds_cstr(actions)); - -- build_lrouter_arp_flow(op->od, op, -- op->lrp_networks.ipv4_addrs[i].addr_s, -- REG_INPORT_ETH_ADDR, &match, false, 90, -- &op->nbrp->header_, lflows); -- } -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_nd(inport, ip6.src, nd.sll); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ " = lookup_nd_ip(inport, ip6.src); "); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", -+ ds_cstr(actions)); - -- /* A set to hold all load-balancer vips that need ARP responses. */ -- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); -+ /* For other packet types, we can skip neighbor learning. -+ * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", -+ REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); - -- const char *ip_address; -- SSET_FOR_EACH (ip_address, &all_ips_v4) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -+ /* Flows for LEARN_NEIGHBOR. */ -+ /* Skip Neighbor learning if not required. */ -+ ds_clear(match); -+ ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s", -+ learn_from_arp_request ? "" : -+ " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, -+ ds_cstr(match), "next;"); - -- build_lrouter_arp_flow(op->od, op, -- ip_address, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "arp", "put_arp(inport, arp.spa, arp.sha); next;"); - -- SSET_FOR_EACH (ip_address, &all_ips_v6) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); - -- build_lrouter_nd_flow(op->od, op, "nd_na", -- ip_address, NULL, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -+ "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); -+ } - -- sset_destroy(&all_ips_v4); -- sset_destroy(&all_ips_v6); -+} - -- if (!smap_get(&op->od->nbr->options, "chassis") -- && !op->od->l3dgw_port) { -- /* UDP/TCP port unreachable. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && udp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- const char *action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 3; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->header_); -+/* Logical router ingress Table 1: Neighbor lookup lflows -+ * for logical router ports. */ -+static void -+build_neigh_learning_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (op->nbrp) { - -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "tcp_reset {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->header_); -+ bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options, -+ "always_learn_from_arp_request", true); - -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag", -+ /* Check if we need to learn mac-binding from ARP requests. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ if (!learn_from_arp_request) { -+ /* ARP request to this address should always get learned, -+ * so add a priority-110 flow to set -+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */ -+ ds_clear(match); -+ ds_put_format(match, -+ "inport == %s && arp.spa == %s/%u && " -+ "arp.tpa == %s && arp.op == 1", -+ op->json_key, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen, - op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 2; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 70, ds_cstr(&match), action, -+ if (op->od->l3dgw_port && op == op->od->l3dgw_port -+ && op->od->l3redirect_port) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); " -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;" -+ " next;"; -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, -+ ds_cstr(match), actions_s, - &op->nbrp->header_); - } -+ ds_clear(match); -+ ds_put_format(match, -+ "inport == %s && arp.spa == %s/%u && arp.op == 1", -+ op->json_key, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); -+ if (op->od->l3dgw_port && op == op->od->l3dgw_port -+ && op->od->l3redirect_port) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -+ learn_from_arp_request ? "" : -+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -+ " = lookup_arp_ip(inport, arp.spa); "); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); - } -+ } -+} - -- /* Drop IP traffic destined to router owned IPs except if the IP is -- * also a SNAT IP. Those are dropped later, in stage -- * "lr_in_arp_resolve", if unSNAT was unsuccessful. -- * -- * Priority 60. -- */ -- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false, -- lflows); -- -- /* 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; -- } -+/* 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) -+{ -+ if (!op->nbrp || op->nbrp->peer || !op->peer) { -+ return; -+ } - -- for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &op->od->nat_entries[i]; -+ if (!op->lrp_networks.n_ipv6_addrs) { -+ return; -+ } - -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } -+ struct smap options; -+ smap_clone(&options, &op->sb->options); - -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { -- continue; -- } -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } -+ /* 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; -+ } -+ smap_add(&options, "ipv6_prefix_delegation", -+ prefix_delegation ? "true" : "false"); - -- /* 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; -+ bool ipv6_prefix = smap_get_bool(&op->nbrp->options, -+ "prefix", false); -+ if (!lrport_is_enabled(op->nbrp)) { -+ ipv6_prefix = false; -+ } -+ smap_add(&options, "ipv6_prefix", -+ ipv6_prefix ? "true" : "false"); -+ sbrec_port_binding_set_options(op->sb, &options); - -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } -+ smap_destroy(&options); - -- 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); -- } -+ const char *address_mode = smap_get( -+ &op->nbrp->ipv6_ra_configs, "address_mode"); -+ -+ if (!address_mode) { -+ return; -+ } -+ 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); -+ return; - } - -- /* NAT, Defrag and load balancing. */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbr) { -- continue; -- } -+ if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", -+ false)) { -+ copy_ra_to_sb(op, address_mode); -+ } - -- /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -+ op->json_key); -+ ds_clear(actions); - -- /* Send the IPv6 NS packets to next table. When ovn-controller -- * generates IPv6 NS (for the action - nd_ns{}), the injected -- * packet would go through conntrack - which is not required. */ -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); -+ const char *mtu_s = smap_get( -+ &op->nbrp->ipv6_ra_configs, "mtu"); - -- /* NAT rules are only valid on Gateway routers and routers with -- * l3dgw_port (router has a port with gateway chassis -- * specified). */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- continue; -- } -+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; - -- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); -+ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -+ "addr_mode = \"%s\", slla = %s", -+ address_mode, op->lrp_networks.ea_s); -+ if (mtu > 0) { -+ ds_put_format(actions, ", mtu = %u", mtu); -+ } - -- bool dnat_force_snat_ip = -- !lport_addresses_is_empty(&od->dnat_force_snat_addrs); -- bool lb_force_snat_ip = -- !lport_addresses_is_empty(&od->lb_force_snat_addrs); -+ const char *prf = smap_get_def( -+ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -+ if (strcmp(prf, "MEDIUM")) { -+ ds_put_format(actions, ", router_preference = \"%s\"", prf); -+ } - -- for (int i = 0; i < od->nbr->n_nat; i++) { -- const struct nbrec_nat *nat; -+ bool add_rs_response_flow = false; - -- nat = od->nbr->nat[i]; -+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -+ continue; -+ } - -- ovs_be32 ip, mask; -- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -- bool is_v6 = false; -- bool stateless = lrouter_nat_is_stateless(nat); -- struct nbrec_address_set *allowed_ext_ips = -- nat->allowed_ext_ips; -- struct nbrec_address_set *exempted_ext_ips = -- nat->exempted_ext_ips; -+ ds_put_format(actions, ", prefix = %s/%u", -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen); - -- if (allowed_ext_ips && exempted_ext_ips) { -- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -- "both allowed and exempt external ips set", -- UUID_ARGS(&(nat->header_.uuid))); -- continue; -- } -+ add_rs_response_flow = true; -+ } - -- char *error = ip_parse_masked(nat->external_ip, &ip, &mask); -- if (error || mask != OVS_BE32_MAX) { -- free(error); -- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad external ip %s for nat", -- nat->external_ip); -- free(error); -- continue; -- } -- /* It was an invalid IPv4 address, but valid IPv6. -- * Treat the rest of the handling of this NAT rule -- * as IPv6. */ -- is_v6 = true; -- } -+ if (add_rs_response_flow) { -+ ds_put_cstr(actions, "); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -+ 50, ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ ds_clear(actions); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); - -- /* Check the validity of nat->logical_ip. 'logical_ip' can -- * be a subnet when the type is "snat". */ -- int cidr_bits; -- if (is_v6) { -- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -- cidr_bits = ipv6_count_cidr_bits(&mask_v6); -- } else { -- error = ip_parse_masked(nat->logical_ip, &ip, &mask); -- cidr_bits = ip_count_cidr_bits(mask); -- } -- if (!strcmp(nat->type, "snat")) { -- if (error) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -- "in router "UUID_FMT"", -- nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -- } -- } else { -- if (error || (!is_v6 && mask != OVS_BE32_MAX) -- || (is_v6 && memcmp(&mask_v6, &v6_exact, -- sizeof mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -- } -- } -+ char ip6_str[INET6_ADDRSTRLEN + 1]; -+ struct in6_addr lla; -+ in6_generate_lla(op->lrp_networks.ea, &lla); -+ memset(ip6_str, 0, sizeof(ip6_str)); -+ ipv6_string_mapped(ip6_str, &lla); -+ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -+ "ip6.dst = ip6.src; ip6.src = %s; " -+ "outport = inport; flags.loopback = 1; " -+ "output;", -+ op->lrp_networks.ea_s, ip6_str); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_ND_RA_RESPONSE, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+} - -- /* For distributed router NAT, determine whether this NAT rule -- * satisfies the conditions for distributed NAT processing. */ -- bool distributed = false; -- struct eth_addr mac; -- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -- nat->logical_port && nat->external_mac) { -- if (eth_addr_from_string(nat->external_mac, &mac)) { -- distributed = true; -- } else { -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -- continue; -- } -- } -+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -+ * responder, by default goto next. (priority 0). */ -+static void -+build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -+ } -+} - -- /* Ingress UNSNAT table: It is for already established connections' -- * reverse traffic. i.e., SNAT has already been done in egress -- * pipeline and now the packet has entered the ingress pipeline as -- * part of a reply. We undo the SNAT here. -- * -- * Undoing SNAT has to happen before DNAT processing. This is -- * because when the packet was DNATed in ingress pipeline, it did -- * not know about the possibility of eventual additional SNAT in -- * egress pipeline. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(&actions, "ct_snat;"); -- } -+/* Logical router ingress table IP_ROUTING : IP Routing. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * routed to the address in 'ip[46].dst'. -+ * -+ * For regular routes without ECMP, table IP_ROUTING sets outport to the -+ * correct output port, eth.src to the output port's MAC address, and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table. -+ * -+ * For ECMP routes, i.e. multiple routes with same policy and prefix, table -+ * IP_ROUTING remembers ECMP group id and selects a member id, and advances -+ * to table IP_ROUTING_ECMP, which sets outport, eth.src and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -+ */ -+static void -+build_ip_routing_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows) -+{ -+ if (op->nbrp) { - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 90, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } -+ } -+} - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(&actions, "ct_snat;"); -- } -+static void -+build_static_route_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports, struct hmap *bfd_connections) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -+ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -+ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -+ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -+ struct ecmp_groups_node *group; -+ for (int i = 0; i < od->nbr->n_static_routes; i++) { -+ struct parsed_route *route = -+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i], -+ bfd_connections); -+ if (!route) { -+ continue; -+ } -+ group = ecmp_groups_find(&ecmp_groups, route); -+ if (group) { -+ ecmp_groups_add_route(group, route); -+ } else { -+ const struct parsed_route *existed_route = -+ unique_routes_remove(&unique_routes, route); -+ if (existed_route) { -+ group = ecmp_groups_add(&ecmp_groups, existed_route); -+ if (group) { -+ ecmp_groups_add_route(group, route); -+ } -+ } else { -+ unique_routes_add(&unique_routes, route); - } - } -+ } -+ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -+ /* add a flow in IP_ROUTING, and one flow for each member in -+ * IP_ROUTING_ECMP. */ -+ build_ecmp_route_flow(lflows, od, ports, group); -+ } -+ const struct unique_routes_node *ur; -+ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -+ build_static_route_flow(lflows, od, ports, ur->route); -+ } -+ ecmp_groups_destroy(&ecmp_groups); -+ unique_routes_destroy(&unique_routes); -+ parsed_routes_destroy(&parsed_routes); -+ } -+} - -- /* Ingress DNAT table: Packets enter the pipeline with destination -- * IP address that needs to be DNATted from a external IP address -- * to a logical IP address. */ -- if (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- /* Packet when it goes from the initiator to destination. -- * We need to set flags.loopback because the router can -- * send the packet back through the same interface. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- ds_clear(&actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, true, mask); -- } -+/* IP Multicast lookup. Here we set the output port, adjust TTL and -+ * advance to next table (priority 500). -+ */ -+static void -+build_mcast_lookup_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- if (dnat_force_snat_ip) { -- /* Indicate to the future tables that a DNAT has taken -- * place and a force SNAT needs to be done in the -- * Egress SNAT table. */ -- ds_put_format(&actions, -- "flags.force_snat_for_dnat = 1; "); -- } -+ /* Drop IPv6 multicast traffic that shouldn't be forwarded, -+ * i.e., router solicitation and router advertisement. -+ */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -+ "nd_rs || nd_ra", "drop;"); -+ if (!od->mcast_info.rtr.relay) { -+ return; -+ } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "flags.loopback = 1; " -- "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(&actions, "flags.loopback = 1; " -- "ct_dnat(%s", nat->logical_ip); -+ struct ovn_igmp_group *igmp_group; - -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { -+ ds_clear(match); -+ ds_clear(actions); -+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -+ ds_put_format(match, "ip4 && ip4.dst == %s ", -+ igmp_group->mcgroup.name); -+ } else { -+ ds_put_format(match, "ip6 && ip6.dst == %s ", -+ igmp_group->mcgroup.name); -+ } -+ if (od->mcast_info.rtr.flood_static) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } -+ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -+ igmp_group->mcgroup.name); -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -+ ds_cstr(match), ds_cstr(actions)); -+ } - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+ /* If needed, flood unregistered multicast on statically configured -+ * ports. Otherwise drop any multicast traffic. -+ */ -+ if (od->mcast_info.rtr.flood_static) { -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } else { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", "drop;"); -+ } -+ } -+} - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, true, mask); -- } -+/* Logical router ingress table POLICY: Policy. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * permitted/denied/rerouted to the address in the rule's nexthop. -+ * This table sets outport to the correct out_port, -+ * eth.src to the output port's MAC address, -+ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table for ARP/ND resolution. */ -+static void -+build_ingress_policy_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports) -+{ -+ if (od->nbr) { -+ /* This is a catch-all rule. It has the lowest priority (0) -+ * does a match-all("1") and pass-through (next) */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", -+ REG_ECMP_GROUP_ID" = 0; next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(&actions, "ct_dnat(%s", nat->logical_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ /* Convert routing policies to flows. */ -+ uint16_t ecmp_group_id = 1; -+ for (int i = 0; i < od->nbr->n_policies; i++) { -+ const struct nbrec_logical_router_policy *rule -+ = od->nbr->policies[i]; -+ bool is_ecmp_reroute = -+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -+ if (is_ecmp_reroute) { -+ build_ecmp_routing_policy_flows(lflows, od, ports, rule, -+ ecmp_group_id); -+ ecmp_group_id++; -+ } else { -+ build_routing_policy_flow(lflows, od, ports, rule, -+ &rule->header_); - } -+ } -+ } -+} - -- /* ARP resolve for NAT IPs. */ -- if (od->l3dgw_port) { -- if (!strcmp(nat->type, "snat")) { -- ds_clear(&match); -- ds_put_format( -- &match, "inport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -- 120, ds_cstr(&match), "next;", -- &nat->header_); -- } -+/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ -+static void -+build_arp_resolve_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows) -+{ -+ if (od->nbr) { -+ /* Multicast packets already have the outport set so just advance to -+ * next table (priority 500). */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, -+ "ip4.mcast || ip6.mcast", "next;"); - -- if (!sset_contains(&nat_entries, nat->external_ip)) { -- ds_clear(&match); -- ds_put_format( -- &match, "outport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -- nat->external_ip); -- ds_clear(&actions); -- ds_put_format( -- &actions, "eth.dst = %s; next;", -- distributed ? nat->external_mac : -- od->l3dgw_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, od, -- S_ROUTER_IN_ARP_RESOLVE, -- 100, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- sset_add(&nat_entries, nat->external_ip); -- } -- } else { -- /* Add the NAT external_ip to the nat_entries even for -- * gateway routers. This is required for adding load balancer -- * flows.*/ -- sset_add(&nat_entries, nat->external_ip); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", -+ "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); - -- /* Egress UNDNAT table: It is for already established connections' -- * reverse traffic. i.e., DNAT has already been done in ingress -- * pipeline and now the packet has entered the egress pipeline as -- * part of a reply. We undo the DNAT here. -- * -- * Note that this only applies for NAT on a distributed router. -- * Undo DNAT on a gateway router is done in the ingress DNAT -- * pipeline stage. */ -- if (od->l3dgw_port && (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat"))) { -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -- if (distributed) { -- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -- } -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", -+ "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); -+ } -+} - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_dnat;"); -- } -+/* Local router ingress table ARP_RESOLVE: ARP Resolution. -+ * -+ * Any unicast packet that reaches this table is an IP packet whose -+ * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 -+ * (ip4.dst/ipv6.dst is the final destination). -+ * This table resolves the IP address in -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and -+ * an Ethernet address in eth.dst. -+ */ -+static void -+build_arp_resolve_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ struct hmap *ports, -+ struct ds *match, struct ds *actions) -+{ -+ if (op->nbsp && !lsp_is_enabled(op->nbsp)) { -+ return; -+ } - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -+ if (op->nbrp) { -+ /* This is a logical router port. If next-hop IP address in -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this -+ * router port, then the packet is intended to eventually be sent -+ * to this logical port. Set the destination mac address using -+ * this port's mac address. -+ * -+ * The packet is still in peer's logical pipeline. So the match -+ * should be on peer's outport. */ -+ if (op->peer && op->nbrp->peer) { -+ if (op->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 "== ", -+ op->peer->json_key); -+ op_put_v4_networks(match, op, false); - -- /* Egress SNAT table: Packets enter the egress pipeline with -- * source ip address that needs to be SNATted to a external ip -- * address. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip); -- ds_clear(&actions); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, op->peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, false, mask); -- } -+ if (op->lrp_networks.n_ipv6_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == ", -+ op->peer->json_key); -+ op_put_v6_networks(match, op); - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_snat(%s", -- nat->external_ip); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, op->peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+ } - -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -- } -+ if (!op->derived && op->od->l3redirect_port) { -+ const char *redirect_type = smap_get(&op->nbrp->options, -+ "redirect-type"); -+ if (redirect_type && !strcasecmp(redirect_type, "bridged")) { -+ /* Packet is on a non gateway chassis and -+ * has an unresolved ARP on a network behind gateway -+ * chassis attached router port. Since, redirect type -+ * is "bridged", instead of calling "get_arp" -+ * on this node, we will redirect the packet to gateway -+ * chassis, by setting destination mac router port mac.*/ -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ "!is_chassis_resident(%s)", op->json_key, -+ op->od->l3redirect_port->json_key); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ op->lrp_networks.ea_s); - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- cidr_bits + 1, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } else { -- uint16_t priority = cidr_bits + 1; -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_ARP_RESOLVE, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+ } - -- /* Distributed router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- priority += 128; -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(&actions); -+ /* Drop IP traffic destined to router owned IPs. Part of it is dropped -+ * in stage "lr_in_ip_input" but traffic that could have been unSNATed -+ * but didn't match any existing session might still end up here. -+ * -+ * Priority 1. -+ */ -+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, -+ lflows); -+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) -+ && strcmp(op->nbsp->type, "virtual")) { -+ /* This is a logical switch port that backs a VM or a container. -+ * Extract its addresses. For each of the address, go through all -+ * the router ports attached to the switch (to which this port -+ * connects) and if the address in question is reachable from the -+ * router port, add an ARP/ND entry in that router's pipeline. */ - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, -- is_v6, false, mask); -+ for (size_t i = 0; i < op->n_lsp_addrs; i++) { -+ const char *ea_s = op->lsp_addrs[i].ea_s; -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; -+ for (size_t k = 0; k < op->od->n_router_ports; k++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; - } - -- if (distributed) { -- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; - } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(&actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(&actions, "ct_snat(%s", -- nat->external_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(&actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(&actions, ");"); -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; - } - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- priority, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, ip_s); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); - } - } - -- /* Logical router ingress table 0: -- * For NAT on a distributed router, add rules allowing -- * ingress traffic with eth.dst matching nat->external_mac -- * on the l3dgw_port instance where nat->logical_port is -- * resident. */ -- if (distributed) { -- /* Store the ethernet address of the port receiving the packet. -- * This will save us from having to match on inport further -- * down in the pipeline. -- */ -- ds_clear(&actions); -- ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;", -- od->l3dgw_port->lrp_networks.ea_s); -+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -+ const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; -+ for (size_t k = 0; k < op->od->n_router_ports; k++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- ds_clear(&match); -- ds_put_format(&match, -- "eth.dst == "ETH_ADDR_FMT" && inport == %s" -- " && is_chassis_resident(\"%s\")", -- ETH_ADDR_ARGS(mac), -- od->l3dgw_port->json_key, -- nat->logical_port); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- /* Ingress Gateway Redirect Table: For NAT on a distributed -- * router, add flows that are specific to a NAT rule. These -- * flows indicate the presence of an applicable NAT rule that -- * can be applied in a distributed manner. -- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -- * NAT external IP and NAT external mac so the ARP request -- * generated in the following stage is sent out with proper IP/MAC -- * src addresses. -- */ -- if (distributed) { -- ds_clear(&match); -- ds_clear(&actions); -- ds_put_format(&match, -- "ip%s.src == %s && outport == %s && " -- "is_chassis_resident(\"%s\")", -- is_v6 ? "6" : "4", nat->logical_ip, -- od->l3dgw_port->json_key, nat->logical_port); -- ds_put_format(&actions, "eth.src = %s; %s = %s; next;", -- nat->external_mac, -- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -- nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -- 100, ds_cstr(&match), -- ds_cstr(&actions), &nat->header_); -- } -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; -+ } - -- /* Egress Loopback table: For NAT on a distributed router. -- * If packets in the egress pipeline on the distributed -- * gateway port have ip.dst matching a NAT external IP, then -- * loop a clone of the packet back to the beginning of the -- * ingress pipeline with inport = outport. */ -- if (od->l3dgw_port) { -- /* Distributed router. */ -- ds_clear(&match); -- ds_put_format(&match, "ip%s.dst == %s && outport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } else { -- ds_put_format(&match, " && is_chassis_resident(\"%s\")", -- nat->logical_port); -- } -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == %s", -+ peer->json_key, ip_s); - -- ds_clear(&actions); -- ds_put_format(&actions, -- "clone { ct_clear; " -- "inport = outport; outport = \"\"; " -- "flags = 0; flags.loopback = 1; "); -- for (int j = 0; j < MFF_N_LOG_REGS; j++) { -- ds_put_format(&actions, "reg%d = 0; ", j); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); - } -- ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; " -- "next(pipeline=ingress, table=%d); };", -- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); - } - } -+ } 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. -+ * If the logical port doesn't have virtual parent set in -+ * Port_Binding table, then add the flow to set eth.dst to -+ * 00:00:00:00:00:00 and advance to next table so that ARP is -+ * resolved by router pipeline using the arp{} action. -+ * The MAC_Binding entry for the virtual ip might be invalid. */ -+ ovs_be32 ip; - -- /* Handle force SNAT options set in the gateway router. */ -- if (!od->l3dgw_port) { -- if (dnat_force_snat_ip) { -- if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -- "dnat"); -- } -- if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -- "dnat"); -- } -- } -- if (lb_force_snat_ip) { -- if (od->lb_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -- } -- if (od->lb_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); -- } -- } -- -- /* For gateway router, re-circulate every packet through -- * the DNAT zone. This helps with the following. -- * -- * Any packet that needs to be unDNATed in the reverse -- * direction gets unDNATed. Ideally this could be done in -- * the egress pipeline. But since the gateway router -- * does not have any feature that depends on the source -- * ip address being external IP address for IP routing, -- * we can do it here, saving a future re-circulation. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -- "ip", "flags.loopback = 1; ct_dnat;"); -+ const char *vip = smap_get(&op->nbsp->options, -+ "virtual-ip"); -+ const char *virtual_parents = smap_get(&op->nbsp->options, -+ "virtual-parents"); -+ if (!vip || !virtual_parents || -+ !ip_parse(vip, &ip) || !op->sb) { -+ return; - } - -- /* Load balancing and packet defrag are only valid on -- * Gateway routers or router with gateway port. */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- sset_destroy(&nat_entries); -- continue; -- } -+ if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || -+ !op->sb->chassis) { -+ /* The virtual port is not claimed yet. */ -+ for (size_t i = 0; i < op->od->n_router_ports; i++) { -+ const char *peer_name = smap_get( -+ &op->od->router_ports[i]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- /* A set to hold all ips that need defragmentation and tracking. */ -- struct sset all_ips = SSET_INITIALIZER(&all_ips); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- for (int i = 0; i < od->nbr->n_load_balancer; i++) { -- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -- struct ovn_northd_lb *lb = -- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -- ovs_assert(lb); -+ if (find_lrp_member_ip(peer, vip)) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, vip); - -- for (size_t j = 0; j < lb->n_vips; j++) { -- struct ovn_lb_vip *lb_vip = &lb->vips[j]; -- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -- ds_clear(&actions); -- build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &actions, -- lb->selection_fields); -+ const char *arp_actions = -+ "eth.dst = 00:00:00:00:00:00; next;"; -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ arp_actions, -+ &op->nbsp->header_); -+ break; -+ } -+ } -+ } else { -+ struct ovn_port *vp = -+ ovn_port_find(ports, op->sb->virtual_parent); -+ if (!vp || !vp->nbsp) { -+ return; -+ } - -- if (!sset_contains(&all_ips, lb_vip->vip_str)) { -- sset_add(&all_ips, lb_vip->vip_str); -- /* If there are any load balancing rules, we should send -- * the packet to conntrack for defragmentation and -- * tracking. This helps with two things. -- * -- * 1. With tracking, we can send only new connections to -- * pick a DNAT ip address from a group. -- * 2. If there are L4 ports in load balancing rules, we -- * need the defragmentation to match on L4 ports. */ -- ds_clear(&match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(&match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(&match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -+ for (size_t i = 0; i < vp->n_lsp_addrs; i++) { -+ bool found_vip_network = false; -+ const char *ea_s = vp->lsp_addrs[i].ea_s; -+ for (size_t j = 0; j < vp->od->n_router_ports; j++) { -+ /* Get the Logical_Router_Port that the -+ * Logical_Switch_Port is connected to, as -+ * 'peer'. */ -+ const char *peer_name = smap_get( -+ &vp->od->router_ports[j]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; - } -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -- 100, ds_cstr(&match), "ct_next;", -- &nb_lb->header_); -- } - -- /* Higher priority rules are added for load-balancing in DNAT -- * table. For every match (on a VIP[:port]), we add two flows -- * via add_router_lb_flow(). One flow is for specific matching -- * on ct.new with an action of "ct_lb($targets);". The other -- * flow is for ct.est with an action of "ct_dnat;". */ -- ds_clear(&match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(&match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(&match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -- } -+ struct ovn_port *peer = -+ ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- int prio = 110; -- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -- bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -- "sctp"); -- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; -+ if (!find_lrp_member_ip(peer, vip)) { -+ continue; -+ } - -- if (lb_vip->vip_port) { -- ds_put_format(&match, " && %s && %s.dst == %d", proto, -- proto, lb_vip->vip_port); -- prio = 120; -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, vip); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), -+ ds_cstr(actions), -+ &op->nbsp->header_); -+ found_vip_network = true; -+ break; - } - -- if (od->l3redirect_port) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -+ if (found_vip_network) { -+ break; - } -- add_router_lb_flow(lflows, od, &match, &actions, prio, -- lb_force_snat_ip, lb_vip, proto, -- nb_lb, meter_groups, &nat_entries); - } - } -- sset_destroy(&all_ips); -- sset_destroy(&nat_entries); -- } -- -- ds_destroy(&match); -- ds_destroy(&actions); --} -+ } else if (lsp_is_router(op->nbsp)) { -+ /* This is a logical switch port that connects to a router. */ - --/* 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;"); -- } --} -+ /* The peer of this switch port is the router port for which -+ * we need to add logical flows such that it can resolve -+ * ARP entries for all the other router ports connected to -+ * the switch in question. */ - --/* Logical router ingress Table 0: L2 Admission Control -- * This table drops packets that the router shouldn’t see at all based -- * on their Ethernet headers. -- */ --static void --build_adm_ctrl_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 from disabled logical ports (since logical flow -- * tables are default-drop). */ -+ const char *peer_name = smap_get(&op->nbsp->options, -+ "router-port"); -+ if (!peer_name) { - return; - } - -- if (op->derived) { -- /* No ingress packets should be received on a chassisredirect -- * port. */ -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { - return; - } - -- /* Store the ethernet address of the port receiving the packet. -- * This will save us from having to match on inport further down in -- * the pipeline. -- */ -- ds_clear(actions); -- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -- op->lrp_networks.ea_s); -- -- ds_clear(match); -- ds_put_format(match, "eth.mcast && inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- -- ds_clear(match); -- ds_put_format(match, "eth.dst == %s && inport == %s", -- op->lrp_networks.ea_s, op->json_key); -- 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 gateway chassis. */ -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ if (peer->od->nbr && -+ smap_get_bool(&peer->od->nbr->options, -+ "dynamic_neigh_routers", false)) { -+ return; - } -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } --} -- -- --/* Logical router ingress Table 1 and 2: Neighbor lookup and learning -- * lflows for logical routers. */ --static void --build_neigh_learning_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- if (od->nbr) { -- -- /* Learn MAC bindings from ARP/IPv6 ND. -- * -- * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the -- * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp' -- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit. -- * If "always_learn_from_arp_request" is set to false, it will also -- * lookup for the (arp.spa) in the mac binding table using the -- * "lookup_arp_ip" action for ARP request packets, and stores the -- * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit -- * to "1" directly for ARP response packets. -- * -- * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup -- * for the (nd.target, nd.tll) in the mac binding table using the -- * 'lookup_nd' action and stores the result in -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -- * "always_learn_from_arp_request" is set to false, -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set. -- * -- * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup -- * for the (ip6.src, nd.sll) in the mac binding table using the -- * 'lookup_nd' action and stores the result in -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If -- * "always_learn_from_arp_request" is set to false, it will also lookup -- * for the (ip6.src) in the mac binding table using the "lookup_nd_ip" -- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- * bit. -- * -- * Table LEARN_NEIGHBOR learns the mac-binding using the action -- * - 'put_arp/put_nd'. Learning mac-binding is skipped if -- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set. -- * -- * */ -- -- /* Flows for LOOKUP_NEIGHBOR. */ -- bool learn_from_arp_request = smap_get_bool(&od->nbr->options, -- "always_learn_from_arp_request", true); -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -- "arp.op == 2", ds_cstr(actions)); -- -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_nd(inport, nd.target, nd.tll); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", -- ds_cstr(actions)); -- -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_nd(inport, ip6.src, nd.sll); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- " = lookup_nd_ip(inport, ip6.src); "); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", -- ds_cstr(actions)); -- -- /* For other packet types, we can skip neighbor learning. -- * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", -- REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); -- -- /* Flows for LEARN_NEIGHBOR. */ -- /* Skip Neighbor learning if not required. */ -- ds_clear(match); -- ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s", -- learn_from_arp_request ? "" : -- " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, -- ds_cstr(match), "next;"); -- -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "arp", "put_arp(inport, arp.spa, arp.sha); next;"); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); -- -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); -- } -+ for (size_t i = 0; i < op->od->n_router_ports; i++) { -+ const char *router_port_name = smap_get( -+ &op->od->router_ports[i]->nbsp->options, -+ "router-port"); -+ struct ovn_port *router_port = ovn_port_find(ports, -+ router_port_name); -+ if (!router_port || !router_port->nbrp) { -+ continue; -+ } - --} -+ /* Skip the router port under consideration. */ -+ if (router_port == peer) { -+ continue; -+ } - --/* Logical router ingress Table 1: Neighbor lookup lflows -- * for logical router ports. */ --static void --build_neigh_learning_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- if (op->nbrp) { -+ if (router_port->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == ", -+ peer->json_key); -+ op_put_v4_networks(match, router_port, false); - -- bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options, -- "always_learn_from_arp_request", true); -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ router_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); -+ } - -- /* Check if we need to learn mac-binding from ARP requests. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- if (!learn_from_arp_request) { -- /* ARP request to this address should always get learned, -- * so add a priority-110 flow to set -- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */ -+ if (router_port->lrp_networks.n_ipv6_addrs) { - ds_clear(match); -- ds_put_format(match, -- "inport == %s && arp.spa == %s/%u && " -- "arp.tpa == %s && arp.op == 1", -- op->json_key, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen, -- op->lrp_networks.ipv4_addrs[i].addr_s); -- if (op->od->l3dgw_port && op == op->od->l3dgw_port -- && op->od->l3redirect_port) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } -- const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); " -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;" -- " next;"; -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, -- ds_cstr(match), actions_s, -- &op->nbrp->header_); -- } -- ds_clear(match); -- ds_put_format(match, -- "inport == %s && arp.spa == %s/%u && arp.op == 1", -- op->json_key, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); -- if (op->od->l3dgw_port && op == op->od->l3dgw_port -- && op->od->l3redirect_port) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV6 " == ", -+ peer->json_key); -+ op_put_v6_networks(match, router_port); -+ -+ ds_clear(actions); -+ ds_put_format(actions, "eth.dst = %s; next;", -+ router_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, peer->od, -+ S_ROUTER_IN_ARP_RESOLVE, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); - } -- ds_clear(actions); -- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT -- " = lookup_arp(inport, arp.spa, arp.sha); %snext;", -- learn_from_arp_request ? "" : -- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT -- " = lookup_arp_ip(inport, arp.spa); "); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); - } - } -+ - } - --/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router -- * Adv (RA) options and response. */ -+/* Local router ingress table CHK_PKT_LEN: Check packet length. -+ * -+ * Any IPv4 packet with outport set to the distributed gateway -+ * router port, check the packet length and store the result in the -+ * 'REGBIT_PKT_LARGER' register bit. -+ * -+ * Local router ingress table LARGER_PKTS: Handle larger packets. -+ * -+ * Any IPv4 packet with outport set to the distributed gateway -+ * router port and the 'REGBIT_PKT_LARGER' register bit is set, -+ * generate ICMPv4 packet with type 3 (Destination Unreachable) and -+ * code 4 (Fragmentation needed). -+ * */ - static void --build_ND_RA_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows, -+build_check_pkt_len_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports, - struct ds *match, struct ds *actions) - { -- if (!op->nbrp || op->nbrp->peer || !op->peer) { -- return; -- } -- -- if (!op->lrp_networks.n_ipv6_addrs) { -- 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; -- } -- smap_add(&options, "ipv6_prefix_delegation", -- prefix_delegation ? "true" : "false"); -+ if (od->nbr) { - -- bool ipv6_prefix = smap_get_bool(&op->nbrp->options, -- "prefix", false); -- if (!lrport_is_enabled(op->nbrp)) { -- ipv6_prefix = false; -- } -- smap_add(&options, "ipv6_prefix", -- ipv6_prefix ? "true" : "false"); -- sbrec_port_binding_set_options(op->sb, &options); -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", -+ "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", -+ "next;"); - -- smap_destroy(&options); -+ if (od->l3dgw_port && od->l3redirect_port) { -+ int gw_mtu = 0; -+ if (od->l3dgw_port->nbrp) { -+ gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options, -+ "gateway_mtu", 0); -+ } -+ /* Add the flows only if gateway_mtu is configured. */ -+ if (gw_mtu <= 0) { -+ return; -+ } - -- const char *address_mode = smap_get( -- &op->nbrp->ipv6_ra_configs, "address_mode"); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s", od->l3dgw_port->json_key); - -- if (!address_mode) { -- return; -- } -- 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); -- return; -- } -+ ds_clear(actions); -+ ds_put_format(actions, -+ REGBIT_PKT_LARGER" = check_pkt_larger(%d);" -+ " next;", gw_mtu + VLAN_ETH_HEADER_LEN); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &od->l3dgw_port->nbrp->header_); - -- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", -- false)) { -- copy_ra_to_sb(op, address_mode); -- } -+ for (size_t i = 0; i < od->nbr->n_ports; i++) { -+ struct ovn_port *rp = ovn_port_find(ports, -+ od->nbr->ports[i]->name); -+ if (!rp || rp == od->l3dgw_port) { -+ continue; -+ } - -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -- op->json_key); -- ds_clear(actions); -+ if (rp->lrp_networks.ipv4_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && outport == %s" -+ " && ip4 && "REGBIT_PKT_LARGER, -+ rp->json_key, od->l3dgw_port->json_key); - -- const char *mtu_s = smap_get( -- &op->nbrp->ipv6_ra_configs, "mtu"); -+ ds_clear(actions); -+ /* Set icmp4.frag_mtu to gw_mtu */ -+ ds_put_format(actions, -+ "icmp4_error {" -+ REGBIT_EGRESS_LOOPBACK" = 1; " -+ "eth.dst = %s; " -+ "ip4.dst = ip4.src; " -+ "ip4.src = %s; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; /* Destination Unreachable. */ " -+ "icmp4.code = 4; /* Frag Needed and DF was Set. */ " -+ "icmp4.frag_mtu = %d; " -+ "next(pipeline=ingress, table=%d); };", -+ rp->lrp_networks.ea_s, -+ rp->lrp_networks.ipv4_addrs[0].addr_s, -+ 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), -+ &rp->nbrp->header_); -+ } - -- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; -+ if (rp->lrp_networks.ipv6_addrs) { -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && outport == %s" -+ " && ip6 && "REGBIT_PKT_LARGER, -+ rp->json_key, od->l3dgw_port->json_key); - -- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -- "addr_mode = \"%s\", slla = %s", -- address_mode, op->lrp_networks.ea_s); -- if (mtu > 0) { -- ds_put_format(actions, ", mtu = %u", mtu); -+ ds_clear(actions); -+ /* Set icmp6.frag_mtu to gw_mtu */ -+ ds_put_format(actions, -+ "icmp6_error {" -+ REGBIT_EGRESS_LOOPBACK" = 1; " -+ "eth.dst = %s; " -+ "ip6.dst = ip6.src; " -+ "ip6.src = %s; " -+ "ip.ttl = 255; " -+ "icmp6.type = 2; /* Packet Too Big. */ " -+ "icmp6.code = 0; " -+ "icmp6.frag_mtu = %d; " -+ "next(pipeline=ingress, table=%d); };", -+ rp->lrp_networks.ea_s, -+ rp->lrp_networks.ipv6_addrs[0].addr_s, -+ 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), -+ &rp->nbrp->header_); -+ } -+ } -+ } - } -+} - -- const char *prf = smap_get_def( -- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -- if (strcmp(prf, "MEDIUM")) { -- ds_put_format(actions, ", router_preference = \"%s\"", prf); -- } -+/* 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; - -- bool add_rs_response_flow = false; -+ if (od->l3dgw_port->nbrp) { -+ stage_hint = &od->l3dgw_port->nbrp->header_; -+ } - -- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -- continue; -+ /* For 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); - } - -- ds_put_format(actions, ", prefix = %s/%u", -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen); -- -- add_rs_response_flow = true; -- } -- -- if (add_rs_response_flow) { -- ds_put_cstr(actions, "); next;"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -- 50, ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- ds_clear(actions); -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); -- -- char ip6_str[INET6_ADDRSTRLEN + 1]; -- struct in6_addr lla; -- in6_generate_lla(op->lrp_networks.ea, &lla); -- memset(ip6_str, 0, sizeof(ip6_str)); -- ipv6_string_mapped(ip6_str, &lla); -- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -- "ip6.dst = ip6.src; ip6.src = %s; " -- "outport = inport; flags.loopback = 1; " -- "output;", -- op->lrp_networks.ea_s, ip6_str); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ND_RA_RESPONSE, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); - } - } - --/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -- * responder, by default goto next. (priority 0). */ -+/* 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_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) -+build_arp_request_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) - { - if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -+ 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 ingress table IP_ROUTING : IP Routing. -- * -- * A packet that arrives at this table is an IP packet that should be -- * routed to the address in 'ip[46].dst'. -- * -- * For regular routes without ECMP, table IP_ROUTING sets outport to the -- * correct output port, eth.src to the output port's MAC address, and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table. -+/* Logical router egress table DELIVERY: Delivery (priority 100-110). - * -- * For ECMP routes, i.e. multiple routes with same policy and prefix, table -- * IP_ROUTING remembers ECMP group id and selects a member id, and advances -- * to table IP_ROUTING_ECMP, which sets outport, eth.src and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -+ * 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_ip_routing_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows) -+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; -+ } - -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -- &op->nbrp->header_); -+ 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; - } - -- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -- &op->nbrp->header_); -+ /* If 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_static_route_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) -+build_dhcpv6_reply_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, -+ struct ds *match) - { -- if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -- REG_ECMP_GROUP_ID" == 0", "next;"); -- -- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -- struct ecmp_groups_node *group; -- for (int i = 0; i < od->nbr->n_static_routes; i++) { -- struct parsed_route *route = -- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); -- if (!route) { -- continue; -- } -- group = ecmp_groups_find(&ecmp_groups, route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } else { -- const struct parsed_route *existed_route = -- unique_routes_remove(&unique_routes, route); -- if (existed_route) { -- group = ecmp_groups_add(&ecmp_groups, existed_route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } -- } else { -- unique_routes_add(&unique_routes, route); -- } -- } -- } -- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -- /* add a flow in IP_ROUTING, and one flow for each member in -- * IP_ROUTING_ECMP. */ -- build_ecmp_route_flow(lflows, od, ports, group); -- } -- const struct unique_routes_node *ur; -- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -- build_static_route_flow(lflows, od, ports, ur->route); -+ 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;"); - } -- ecmp_groups_destroy(&ecmp_groups); -- unique_routes_destroy(&unique_routes); -- parsed_routes_destroy(&parsed_routes); - } -+ - } - --/* IP Multicast lookup. Here we set the output port, adjust TTL and -- * advance to next table (priority 500). -- */ - static void --build_mcast_lookup_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -+build_ipv6_input_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, - struct ds *match, struct ds *actions) - { -- if (od->nbr) { -+ 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"); - -- /* Drop IPv6 multicast traffic that shouldn't be forwarded, -- * i.e., router solicitation and router advertisement. -- */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -- "nd_rs || nd_ra", "drop;"); -- if (!od->mcast_info.rtr.relay) { -- return; -+ 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_); - } - -- struct ovn_igmp_group *igmp_group; -- -- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { -+ /* 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); -- ds_clear(actions); -- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -- ds_put_format(match, "ip4 && ip4.dst == %s ", -- igmp_group->mcgroup.name); -- } else { -- ds_put_format(match, "ip6 && ip6.dst == %s ", -- igmp_group->mcgroup.name); -- } -- if (od->mcast_info.rtr.flood_static) { -- ds_put_cstr(actions, -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); -+ 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); - } -- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -- igmp_group->mcgroup.name); -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -- ds_cstr(match), ds_cstr(actions)); -+ -+ build_lrouter_nd_flow(op->od, op, "nd_na_router", -+ op->lrp_networks.ipv6_addrs[i].addr_s, -+ op->lrp_networks.ipv6_addrs[i].sn_addr_s, -+ REG_INPORT_ETH_ADDR, match, false, 90, -+ &op->nbrp->header_, lflows); - } - -- /* If needed, flood unregistered multicast on statically configured -- * ports. Otherwise drop any multicast traffic. -- */ -- if (od->mcast_info.rtr.flood_static) { -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); -- } else { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", "drop;"); -+ /* UDP/TCP/SCTP port unreachable */ -+ if (!smap_get(&op->od->nbr->options, "chassis") -+ && !op->od->l3dgw_port) { -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, -+ "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 && sctp", -+ op->lrp_networks.ipv6_addrs[i].addr_s); -+ action = "sctp_abort {" -+ "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_); -+ } - } -- } --} - --/* Logical router ingress table POLICY: Policy. -- * -- * A packet that arrives at this table is an IP packet that should be -- * permitted/denied/rerouted to the address in the rule's nexthop. -- * This table sets outport to the correct out_port, -- * eth.src to the output port's MAC address, -- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table for ARP/ND resolution. */ --static void --build_ingress_policy_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) --{ -- if (od->nbr) { -- /* This is a catch-all rule. It has the lowest priority (0) -- * does a match-all("1") and pass-through (next) */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;"); -+ /* 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; -+ } - -- /* Convert routing policies to flows. */ -- for (int i = 0; i < od->nbr->n_policies; i++) { -- const struct nbrec_logical_router_policy *rule -- = od->nbr->policies[i]; -- build_routing_policy_flow(lflows, od, ports, rule, &rule->header_); -+ 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_); - } - } -+ - } - --/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ - static void --build_arp_resolve_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows) -+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od, -+ struct hmap *lflows) - { - if (od->nbr) { -- /* Multicast packets already have the outport set so just advance to -- * next table (priority 500). */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, -- "ip4.mcast || ip6.mcast", "next;"); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", -- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); -+ /* 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. -+ */ -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &od->nat_entries[i]; - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", -- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ 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); -+ } -+ -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; -+ -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; -+ } -+ -+ struct ovn_nat *nat_entry = -+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -+ struct ovn_nat, ext_addr_list_node); -+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -+ } - } - } - --/* Local router ingress table ARP_RESOLVE: ARP Resolution. -- * -- * Any unicast packet that reaches this table is an IP packet whose -- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 -- * (ip4.dst/ipv6.dst is the final destination). -- * This table resolves the IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and -- * an Ethernet address in eth.dst. -- */ -+/* Logical router ingress table 3: IP Input for IPv4. */ - 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) -+build_lrouter_ipv4_ip_input(struct ovn_port *op, -+ struct hmap *lflows, -+ struct ds *match, struct ds *actions) - { -- if (op->nbsp && !lsp_is_enabled(op->nbsp)) { -- return; -- } -+ /* No ingress packets are accepted on a chassisredirect -+ * port, so no need to program flows for that port. */ -+ if (op->nbrp && (!op->derived)) { -+ if (op->lrp_networks.n_ipv4_addrs) { -+ /* L3 admission control: drop packets that originate from an -+ * IPv4 address owned by the router or a broadcast address -+ * known to the router (priority 100). */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.src == "); -+ op_put_v4_networks(match, op, true); -+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -+ ds_cstr(match), "drop;", -+ &op->nbrp->header_); - -- if (op->nbrp) { -- /* This is a logical router port. If next-hop IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this -- * router port, then the packet is intended to eventually be sent -- * to this logical port. Set the destination mac address using -- * this port's mac address. -- * -- * The packet is still in peer's logical pipeline. So the match -- * should be on peer's outport. */ -- if (op->peer && op->nbrp->peer) { -- if (op->lrp_networks.n_ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 "== ", -- op->peer->json_key); -- op_put_v4_networks(match, op, false); -+ /* ICMP echo reply. These flows reply to ICMP echo requests -+ * received for the router's IP address. Since packets only -+ * get here as part of the logical router datapath, the inport -+ * (i.e. the incoming locally attached net) does not matter. -+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.dst == "); -+ op_put_v4_networks(match, op, false); -+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } -+ const char * icmp_actions = "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 0; " -+ "flags.loopback = 1; " -+ "next; "; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -+ ds_cstr(match), icmp_actions, -+ &op->nbrp->header_); -+ } - -- if (op->lrp_networks.n_ipv6_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == ", -- op->peer->json_key); -- op_put_v6_networks(match, op); -+ /* BFD msg handling */ -+ build_lrouter_bfd_flows(lflows, op); - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } -+ /* ICMP time exceeded */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_clear(actions); -+ -+ ds_put_format(match, -+ "inport == %s && ip4 && " -+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -+ ds_put_format(actions, -+ "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "icmp4.type = 11; /* Time exceeded */ " -+ "icmp4.code = 0; /* TTL exceeded in transit */ " -+ "ip4.dst = ip4.src; " -+ "ip4.src = %s; " -+ "ip.ttl = 255; " -+ "next; };", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); - } - -- if (!op->derived && op->od->l3redirect_port) { -- const char *redirect_type = smap_get(&op->nbrp->options, -- "redirect-type"); -- if (redirect_type && !strcasecmp(redirect_type, "bridged")) { -- /* Packet is on a non gateway chassis and -- * has an unresolved ARP on a network behind gateway -- * chassis attached router port. Since, redirect type -- * is "bridged", instead of calling "get_arp" -- * on this node, we will redirect the packet to gateway -- * chassis, by setting destination mac router port mac.*/ -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- "!is_chassis_resident(%s)", op->json_key, -- op->od->l3redirect_port->json_key); -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -+ /* ARP reply. These flows reply to ARP requests for the router's own -+ * IP address. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, "arp.spa == %s/%u", -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); - -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ARP_RESOLVE, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } -- } -+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -+ && op->peer->od->n_localnet_ports) { -+ bool add_chassis_resident_check = false; -+ if (op == op->od->l3dgw_port) { -+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -+ * should only be sent from the gateway chassis, so that -+ * upstream MAC learning points to the gateway chassis. -+ * Also need to avoid generation of multiple ARP responses -+ * from different chassis. */ -+ add_chassis_resident_check = true; -+ } else { -+ /* Check if the option 'reside-on-redirect-chassis' -+ * is set to true on the router port. If set to true -+ * and if peer's logical switch has a localnet port, it -+ * means the router pipeline for the packets from -+ * peer's logical switch is be run on the chassis -+ * hosting the gateway port and it should reply to the -+ * ARP requests for the router port IPs. -+ */ -+ add_chassis_resident_check = smap_get_bool( -+ &op->nbrp->options, -+ "reside-on-redirect-chassis", false); -+ } - -- /* Drop IP traffic destined to router owned IPs. Part of it is dropped -- * in stage "lr_in_ip_input" but traffic that could have been unSNATed -- * but didn't match any existing session might still end up here. -- * -- * Priority 1. -- */ -- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, -- lflows); -- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) -- && strcmp(op->nbsp->type, "virtual")) { -- /* This is a logical switch port that backs a VM or a container. -- * Extract its addresses. For each of the address, go through all -- * the router ports attached to the switch (to which this port -- * connects) and if the address in question is reachable from the -- * router port, add an ARP/ND entry in that router's pipeline. */ -+ if (add_chassis_resident_check) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } -+ } - -- for (size_t i = 0; i < op->n_lsp_addrs; i++) { -- const char *ea_s = op->lsp_addrs[i].ea_s; -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ build_lrouter_arp_flow(op->od, op, -+ op->lrp_networks.ipv4_addrs[i].addr_s, -+ REG_INPORT_ETH_ADDR, match, false, 90, -+ &op->nbrp->header_, lflows); -+ } - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ /* A set to hold all load-balancer vips that need ARP responses. */ -+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); - -- if (!find_lrp_member_ip(peer, ip_s)) { -- continue; -- } -+ const char *ip_address; -+ SSET_FOR_EACH (ip_address, &all_ips_v4) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); -+ } - -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, ip_s); -+ build_lrouter_arp_flow(op->od, op, -+ ip_address, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); -+ } - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- } -+ SSET_FOR_EACH (ip_address, &all_ips_v6) { -+ ds_clear(match); -+ if (op == op->od->l3dgw_port) { -+ ds_put_format(match, "is_chassis_resident(%s)", -+ op->od->l3redirect_port->json_key); - } - -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ build_lrouter_nd_flow(op->od, op, "nd_na", -+ ip_address, NULL, REG_INPORT_ETH_ADDR, -+ match, false, 90, NULL, lflows); -+ } - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ sset_destroy(&all_ips_v4); -+ sset_destroy(&all_ips_v6); - -- if (!find_lrp_member_ip(peer, ip_s)) { -- continue; -- } -+ if (!smap_get(&op->od->nbr->options, "chassis") -+ && !op->od->l3dgw_port) { -+ /* UDP/TCP/SCTP port unreachable. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && udp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ const char *action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 3; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); - -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == %s", -- peer->json_key, ip_s); -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "tcp_reset {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- } -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag && sctp", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "sctp_abort {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 80, ds_cstr(match), action, -+ &op->nbrp->header_); -+ -+ ds_clear(match); -+ ds_put_format(match, -+ "ip4 && ip4.dst == %s && !ip.later_frag", -+ op->lrp_networks.ipv4_addrs[i].addr_s); -+ action = "icmp4 {" -+ "eth.dst <-> eth.src; " -+ "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 3; " -+ "icmp4.code = 2; " -+ "next; };"; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -+ 70, ds_cstr(match), action, -+ &op->nbrp->header_); - } - } -- } 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. -- * If the logical port doesn't have virtual parent set in -- * Port_Binding table, then add the flow to set eth.dst to -- * 00:00:00:00:00:00 and advance to next table so that ARP is -- * resolved by router pipeline using the arp{} action. -- * The MAC_Binding entry for the virtual ip might be invalid. */ -- ovs_be32 ip; - -- const char *vip = smap_get(&op->nbsp->options, -- "virtual-ip"); -- const char *virtual_parents = smap_get(&op->nbsp->options, -- "virtual-parents"); -- if (!vip || !virtual_parents || -- !ip_parse(vip, &ip) || !op->sb) { -+ /* 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. -+ * -+ * If op->pd->lb_force_snat_router_ip is true, it means the IP of the -+ * router port is also SNAT IP. -+ * -+ * Priority 60. -+ */ -+ if (!op->od->lb_force_snat_router_ip) { -+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false, -+ lflows); -+ } -+ /* 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) { - return; - } - -- if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || -- !op->sb->chassis) { -- /* The virtual port is not claimed yet. */ -- for (size_t i = 0; i < op->od->n_router_ports; i++) { -- const char *peer_name = smap_get( -- &op->od->router_ports[i]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -- -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -- -- if (find_lrp_member_ip(peer, vip)) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -+ struct ovn_nat *nat_entry = &op->od->nat_entries[i]; - -- const char *arp_actions = -- "eth.dst = 00:00:00:00:00:00; next;"; -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- arp_actions, -- &op->nbsp->header_); -- break; -- } -- } -- } else { -- struct ovn_port *vp = -- ovn_port_find(ports, op->sb->virtual_parent); -- if (!vp || !vp->nbsp) { -- return; -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ continue; - } - -- for (size_t i = 0; i < vp->n_lsp_addrs; i++) { -- bool found_vip_network = false; -- const char *ea_s = vp->lsp_addrs[i].ea_s; -- for (size_t j = 0; j < vp->od->n_router_ports; j++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &vp->od->router_ports[j]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately -+ * below. -+ */ -+ if (!strcmp(nat_entry->nb->type, "snat")) { -+ continue; -+ } -+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ } - -- struct ovn_port *peer = -- ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; - -- if (!find_lrp_member_ip(peer, vip)) { -- continue; -- } -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { -+ continue; -+ } - -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -+ struct ovn_nat *nat_entry = -+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -+ struct ovn_nat, ext_addr_list_node); -+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -+ } -+ } -+} - -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- found_vip_network = true; -- break; -- } -+/* NAT, Defrag and load balancing. */ -+static void -+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, -+ struct hmap *lflows, -+ struct shash *meter_groups, -+ struct hmap *lbs, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- if (found_vip_network) { -- break; -- } -- } -- } -- } else if (lsp_is_router(op->nbsp)) { -- /* This is a logical switch port that connects to a router. */ -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); - -- /* The peer of this switch port is the router port for which -- * we need to add logical flows such that it can resolve -- * ARP entries for all the other router ports connected to -- * the switch in question. */ -+ /* Send the IPv6 NS packets to next table. When ovn-controller -+ * generates IPv6 NS (for the action - nd_ns{}), the injected -+ * packet would go through conntrack - which is not required. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); - -- const char *peer_name = smap_get(&op->nbsp->options, -- "router-port"); -- if (!peer_name) { -+ /* NAT rules are only valid on Gateway routers and routers with -+ * l3dgw_port (router has a port with gateway chassis -+ * specified). */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - return; - } - -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- return; -- } -+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - -- if (peer->od->nbr && -- smap_get_bool(&peer->od->nbr->options, -- "dynamic_neigh_routers", false)) { -- return; -- } -+ 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 (size_t i = 0; i < op->od->n_router_ports; i++) { -- const char *router_port_name = smap_get( -- &op->od->router_ports[i]->nbsp->options, -- "router-port"); -- struct ovn_port *router_port = ovn_port_find(ports, -- router_port_name); -- if (!router_port || !router_port->nbrp) { -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ const struct nbrec_nat *nat; -+ -+ nat = od->nbr->nat[i]; -+ -+ ovs_be32 ip, mask; -+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -+ bool is_v6 = false; -+ bool stateless = lrouter_nat_is_stateless(nat); -+ struct nbrec_address_set *allowed_ext_ips = -+ nat->allowed_ext_ips; -+ struct nbrec_address_set *exempted_ext_ips = -+ nat->exempted_ext_ips; -+ -+ if (allowed_ext_ips && exempted_ext_ips) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -+ "both allowed and exempt external ips set", -+ UUID_ARGS(&(nat->header_.uuid))); - continue; - } - -- /* Skip the router port under consideration. */ -- if (router_port == peer) { -- continue; -+ char *error = ip_parse_masked(nat->external_ip, &ip, &mask); -+ if (error || mask != OVS_BE32_MAX) { -+ free(error); -+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad external ip %s for nat", -+ nat->external_ip); -+ free(error); -+ continue; -+ } -+ /* It was an invalid IPv4 address, but valid IPv6. -+ * Treat the rest of the handling of this NAT rule -+ * as IPv6. */ -+ is_v6 = true; - } - -- if (router_port->lrp_networks.n_ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == ", -- peer->json_key); -- op_put_v4_networks(match, router_port, false); -- -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- router_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbsp->header_); -+ /* Check the validity of nat->logical_ip. 'logical_ip' can -+ * be a subnet when the type is "snat". */ -+ int cidr_bits; -+ if (is_v6) { -+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -+ cidr_bits = ipv6_count_cidr_bits(&mask_v6); -+ } else { -+ error = ip_parse_masked(nat->logical_ip, &ip, &mask); -+ cidr_bits = ip_count_cidr_bits(mask); -+ } -+ if (!strcmp(nat->type, "snat")) { -+ if (error) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -+ "in router "UUID_FMT"", -+ nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ continue; -+ } -+ } else { -+ if (error || (!is_v6 && mask != OVS_BE32_MAX) -+ || (is_v6 && memcmp(&mask_v6, &v6_exact, -+ sizeof mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ continue; -+ } - } - -- if (router_port->lrp_networks.n_ipv6_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == ", -- peer->json_key); -- op_put_v6_networks(match, router_port); -- -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- router_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbsp->header_); -+ /* For distributed router NAT, determine whether this NAT rule -+ * satisfies the conditions for distributed NAT processing. */ -+ bool distributed = false; -+ struct eth_addr mac; -+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -+ nat->logical_port && nat->external_mac) { -+ if (eth_addr_from_string(nat->external_mac, &mac)) { -+ distributed = true; -+ } else { -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -+ continue; -+ } - } -- } -- } - --} -+ /* Ingress UNSNAT table: It is for already established connections' -+ * reverse traffic. i.e., SNAT has already been done in egress -+ * pipeline and now the packet has entered the ingress pipeline as -+ * part of a reply. We undo the SNAT here. -+ * -+ * Undoing SNAT has to happen before DNAT processing. This is -+ * because when the packet was DNATed in ingress pipeline, it did -+ * not know about the possibility of eventual additional SNAT in -+ * egress pipeline. */ -+ if (!strcmp(nat->type, "snat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); -+ } - --/* Local router ingress table CHK_PKT_LEN: Check packet length. -- * -- * Any IPv4 packet with outport set to the distributed gateway -- * router port, check the packet length and store the result in the -- * 'REGBIT_PKT_LARGER' register bit. -- * -- * Local router ingress table LARGER_PKTS: Handle larger packets. -- * -- * Any IPv4 packet with outport set to the distributed gateway -- * router port and the 'REGBIT_PKT_LARGER' register bit is set, -- * generate ICMPv4 packet with type 3 (Destination Unreachable) and -- * code 4 (Fragmentation needed). -- * */ --static void --build_check_pkt_len_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports, -- struct ds *match, struct ds *actions) --{ -- if (od->nbr) { -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 90, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ - -- /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", -- "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", -- "next;"); -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s" -+ " && inport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } - -- if (od->l3dgw_port && od->l3redirect_port) { -- int gw_mtu = 0; -- if (od->l3dgw_port->nbrp) { -- gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options, -- "gateway_mtu", 0); -- } -- /* Add the flows only if gateway_mtu is configured. */ -- if (gw_mtu <= 0) { -- return; -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); -+ } -+ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - } - -- ds_clear(match); -- ds_put_format(match, "outport == %s", od->l3dgw_port->json_key); -+ /* Ingress DNAT table: Packets enter the pipeline with destination -+ * IP address that needs to be DNATted from a external IP address -+ * to a logical IP address. */ -+ if (!strcmp(nat->type, "dnat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ /* Packet when it goes from the initiator to destination. -+ * We need to set flags.loopback because the router can -+ * send the packet back through the same interface. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip); -+ ds_clear(actions); -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); -+ } -+ -+ if (dnat_force_snat_ip) { -+ /* Indicate to the future tables that a DNAT has taken -+ * place and a force SNAT needs to be done in the -+ * Egress SNAT table. */ -+ ds_put_format(actions, -+ "flags.force_snat_for_dnat = 1; "); -+ } - -- ds_clear(actions); -- ds_put_format(actions, -- REGBIT_PKT_LARGER" = check_pkt_larger(%d);" -- " next;", gw_mtu + VLAN_ETH_HEADER_LEN); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50, -- ds_cstr(match), ds_cstr(actions), -- &od->l3dgw_port->nbrp->header_); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "flags.loopback = 1; " -+ "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_format(actions, "flags.loopback = 1; " -+ "ct_dnat(%s", nat->logical_ip); - -- for (size_t i = 0; i < od->nbr->n_ports; i++) { -- struct ovn_port *rp = ovn_port_find(ports, -- od->nbr->ports[i]->name); -- if (!rp || rp == od->l3dgw_port) { -- continue; -- } -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- if (rp->lrp_networks.ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "inport == %s && outport == %s" -- " && ip4 && "REGBIT_PKT_LARGER, -- rp->json_key, od->l3dgw_port->json_key); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ - -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s" -+ " && inport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } - ds_clear(actions); -- /* Set icmp4.frag_mtu to gw_mtu */ -- ds_put_format(actions, -- "icmp4_error {" -- REGBIT_EGRESS_LOOPBACK" = 1; " -- "eth.dst = %s; " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "icmp4.type = 3; /* Destination Unreachable. */ " -- "icmp4.code = 4; /* Frag Needed and DF was Set. */ " -- "icmp4.frag_mtu = %d; " -- "next(pipeline=ingress, table=%d); };", -- rp->lrp_networks.ea_s, -- rp->lrp_networks.ipv4_addrs[0].addr_s, -- gw_mtu, -- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -- ovn_lflow_add_with_hint(lflows, od, -- S_ROUTER_IN_LARGER_PKTS, 50, -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); -+ } -+ -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } -+ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(match), ds_cstr(actions), -- &rp->nbrp->header_); -+ &nat->header_); - } -+ } - -- if (rp->lrp_networks.ipv6_addrs) { -+ /* ARP resolve for NAT IPs. */ -+ if (od->l3dgw_port) { -+ if (!strcmp(nat->type, "snat")) { - ds_clear(match); -- ds_put_format(match, "inport == %s && outport == %s" -- " && ip6 && "REGBIT_PKT_LARGER, -- rp->json_key, od->l3dgw_port->json_key); -+ ds_put_format( -+ match, "inport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -+ 120, ds_cstr(match), "next;", -+ &nat->header_); -+ } - -+ if (!sset_contains(&nat_entries, nat->external_ip)) { -+ ds_clear(match); -+ ds_put_format( -+ match, "outport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -+ nat->external_ip); - ds_clear(actions); -- /* Set icmp6.frag_mtu to gw_mtu */ -- ds_put_format(actions, -- "icmp6_error {" -- REGBIT_EGRESS_LOOPBACK" = 1; " -- "eth.dst = %s; " -- "ip6.dst = ip6.src; " -- "ip6.src = %s; " -- "ip.ttl = 255; " -- "icmp6.type = 2; /* Packet Too Big. */ " -- "icmp6.code = 0; " -- "icmp6.frag_mtu = %d; " -- "next(pipeline=ingress, table=%d); };", -- rp->lrp_networks.ea_s, -- rp->lrp_networks.ipv6_addrs[0].addr_s, -- gw_mtu, -- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -+ ds_put_format( -+ actions, "eth.dst = %s; next;", -+ distributed ? nat->external_mac : -+ od->l3dgw_port->lrp_networks.ea_s); - ovn_lflow_add_with_hint(lflows, od, -- S_ROUTER_IN_LARGER_PKTS, 50, -- ds_cstr(match), ds_cstr(actions), -- &rp->nbrp->header_); -+ S_ROUTER_IN_ARP_RESOLVE, -+ 100, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ sset_add(&nat_entries, nat->external_ip); - } -+ } else { -+ /* Add the NAT external_ip to the nat_entries even for -+ * gateway routers. This is required for adding load balancer -+ * flows.*/ -+ sset_add(&nat_entries, nat->external_ip); - } -- } -- } --} -- --/* 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;"); -- } --} -+ /* Egress UNDNAT table: It is for already established connections' -+ * reverse traffic. i.e., DNAT has already been done in ingress -+ * pipeline and now the packet has entered the egress pipeline as -+ * part of a reply. We undo the DNAT here. -+ * -+ * Note that this only applies for NAT on a distributed router. -+ * Undo DNAT on a gateway router is done in the ingress DNAT -+ * pipeline stage. */ -+ if (od->l3dgw_port && (!strcmp(nat->type, "dnat") -+ || !strcmp(nat->type, "dnat_and_snat"))) { -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s" -+ " && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - --/* 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; -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat;"); -+ } - -- 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; -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); - } - -- ds_clear(match); -- ds_put_format(match, "eth.dst == 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;"); -- } --} -+ /* Egress SNAT table: Packets enter the egress pipeline with -+ * source ip address that needs to be SNATted to a external ip -+ * address. */ -+ if (!strcmp(nat->type, "snat") -+ || !strcmp(nat->type, "dnat_and_snat")) { -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip); -+ ds_clear(actions); - --/* 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 (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- 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 (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", -+ nat->external_ip); - -- /* If 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)); -- } -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(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;"); -- } -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ cidr_bits + 1, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ uint16_t priority = cidr_bits + 1; - --} -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s" -+ " && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ priority += 128; -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); - --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;"); -+ if (allowed_ext_ips || exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- /* 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;"); -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - -- /* 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;"); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", -+ nat->external_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- /* Drop other reserved multicast. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83, -- "ip6.mcast_rsvd", "drop;"); -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ priority, ds_cstr(match), -+ ds_cstr(actions), -+ &nat->header_); -+ } -+ } - -- /* 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;"); -+ /* Logical router ingress table 0: -+ * For NAT on a distributed router, add rules allowing -+ * ingress traffic with eth.dst matching nat->external_mac -+ * on the l3dgw_port instance where nat->logical_port is -+ * resident. */ -+ if (distributed) { -+ /* Store the ethernet address of the port receiving the packet. -+ * This will save us from having to match on inport further -+ * down in the pipeline. -+ */ -+ ds_clear(actions); -+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -+ od->l3dgw_port->lrp_networks.ea_s); - -- /* 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;"); -+ ds_clear(match); -+ ds_put_format(match, -+ "eth.dst == "ETH_ADDR_FMT" && inport == %s" -+ " && is_chassis_resident(\"%s\")", -+ ETH_ADDR_ARGS(mac), -+ od->l3dgw_port->json_key, -+ nat->logical_port); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - -- /* TTL discard */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, -- "ip4 && ip.ttl == {0, 1}", "drop;"); -+ /* Ingress Gateway Redirect Table: For NAT on a distributed -+ * router, add flows that are specific to a NAT rule. These -+ * flows indicate the presence of an applicable NAT rule that -+ * can be applied in a distributed manner. -+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -+ * NAT external IP and NAT external mac so the ARP request -+ * generated in the following stage is sent out with proper IP/MAC -+ * src addresses. -+ */ -+ if (distributed) { -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, -+ "ip%s.src == %s && outport == %s && " -+ "is_chassis_resident(\"%s\")", -+ is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key, nat->logical_port); -+ ds_put_format(actions, "eth.src = %s; %s = %s; next;", -+ nat->external_mac, -+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -+ nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -+ 100, ds_cstr(match), -+ ds_cstr(actions), &nat->header_); -+ } - -- /* 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;"); -- } --} -+ /* Egress Loopback table: For NAT on a distributed router. -+ * If packets in the egress pipeline on the distributed -+ * gateway port have ip.dst matching a NAT external IP, then -+ * loop a clone of the packet back to the beginning of the -+ * ingress pipeline with inport = outport. */ -+ if (od->l3dgw_port) { -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip%s.dst == %s && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } else { -+ ds_put_format(match, " && is_chassis_resident(\"%s\")", -+ nat->logical_port); -+ } - --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;"); -+ ds_clear(actions); -+ ds_put_format(actions, -+ "clone { ct_clear; " -+ "inport = outport; outport = \"\"; " -+ "flags = 0; flags.loopback = 1; "); -+ for (int j = 0; j < MFF_N_LOG_REGS; j++) { -+ ds_put_format(actions, "reg%d = 0; ", j); -+ } -+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " -+ "next(pipeline=ingress, table=%d); };", -+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } - } -- } - --} -+ /* Handle force SNAT options set in the gateway router. */ -+ if (!od->l3dgw_port) { -+ if (dnat_force_snat_ip) { -+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -+ "dnat"); -+ } -+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -+ "dnat"); -+ } -+ } -+ if (lb_force_snat_ip) { -+ if (od->lb_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -+ } -+ if (od->lb_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); -+ } -+ } - --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"); -+ /* For gateway router, re-circulate every packet through -+ * the DNAT zone. This helps with the following. -+ * -+ * Any packet that needs to be unDNATed in the reverse -+ * direction gets unDNATed. Ideally this could be done in -+ * the egress pipeline. But since the gateway router -+ * does not have any feature that depends on the source -+ * ip address being external IP address for IP routing, -+ * we can do it here, saving a future re-circulation. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -+ "ip", "flags.loopback = 1; ct_dnat;"); -+ } - -- 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_); -+ /* Load balancing and packet defrag are only valid on -+ * Gateway routers or router with gateway port. */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -+ sset_destroy(&nat_entries); -+ return; - } - -- /* 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); -- } -+ /* A set to hold all ips that need defragmentation and tracking. */ -+ struct sset all_ips = SSET_INITIALIZER(&all_ips); - -- 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); -- } -+ for (int i = 0; i < od->nbr->n_load_balancer; i++) { -+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -+ struct ovn_northd_lb *lb = -+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -+ ovs_assert(lb); - -- /* 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_); -+ for (size_t j = 0; j < lb->n_vips; j++) { -+ struct ovn_lb_vip *lb_vip = &lb->vips[j]; -+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -+ ds_clear(actions); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions, -+ lb->selection_fields, false); - -- 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_); -+ if (!sset_contains(&all_ips, lb_vip->vip_str)) { -+ sset_add(&all_ips, lb_vip->vip_str); -+ /* If there are any load balancing rules, we should send -+ * the packet to conntrack for defragmentation and -+ * tracking. This helps with two things. -+ * -+ * 1. With tracking, we can send only new connections to -+ * pick a DNAT ip address from a group. -+ * 2. If there are L4 ports in load balancing rules, we -+ * need the defragmentation to match on L4 ports. */ -+ ds_clear(match); -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -+ 100, ds_cstr(match), "ct_next;", -+ &nb_lb->header_); -+ } - -+ /* Higher priority rules are added for load-balancing in DNAT -+ * table. For every match (on a VIP[:port]), we add two flows -+ * via add_router_lb_flow(). One flow is for specific matching -+ * on ct.new with an action of "ct_lb($targets);". The other -+ * flow is for ct.est with an action of "ct_dnat;". */ - ds_clear(match); -- 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_); -- } -- } -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } - -- /* 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; -- } -+ int prio = 110; -+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -+ "sctp"); -+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; - -- ds_clear(match); -- ds_clear(actions); -+ if (lb_vip->vip_port) { -+ ds_put_format(match, " && %s && %s.dst == %d", proto, -+ proto, lb_vip->vip_port); -+ prio = 120; -+ } - -- 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_); -+ if (od->l3redirect_port && -+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ bool force_snat_for_lb = -+ lb_force_snat_ip || od->lb_force_snat_router_ip; -+ add_router_lb_flow(lflows, od, match, actions, prio, -+ force_snat_for_lb, lb_vip, proto, -+ nb_lb, meter_groups, &nat_entries); -+ } - } -+ sset_destroy(&all_ips); -+ sset_destroy(&nat_entries); - } -- - } - -+ -+ - struct lswitch_flow_build_info { - struct hmap *datapaths; - struct hmap *ports; -@@ -11177,7 +11918,8 @@ struct lswitch_flow_build_info { - - static void - build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, -- struct lswitch_flow_build_info *lsi) -+ struct lswitch_flow_build_info *lsi, -+ struct hmap *bfd_connections) - { - /* Build Logical Switch Flows. */ - build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows, -@@ -11186,13 +11928,20 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_fwd_group_lflows(od, lsi->lflows); - build_lswitch_lflows_admission_control(od, lsi->lflows); - build_lswitch_input_port_sec_od(od, lsi->lflows); -+ build_lswitch_learn_fdb_od(od, lsi->lflows); -+ build_lswitch_arp_nd_responder_default(od, lsi->lflows); -+ build_lswitch_dns_lookup_and_response(od, lsi->lflows); -+ build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows); -+ build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions); -+ build_lswitch_output_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_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports, -+ bfd_connections); - build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match, - &lsi->actions); - build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports); -@@ -11204,6 +11953,9 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match, - &lsi->actions); - build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); -+ build_lrouter_arp_nd_for_datapath(od, lsi->lflows); -+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups, -+ lsi->lbs, &lsi->match, &lsi->actions); - } - - /* Helper function to combine all lflow generation which is iterated by port. -@@ -11216,6 +11968,20 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - /* Build Logical Switch Flows. */ - build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions, - &lsi->match); -+ build_lswitch_learn_fdb_op(op, lsi->lflows, &lsi->actions, -+ &lsi->match); -+ build_lswitch_arp_nd_responder_skip_local(op, lsi->lflows, -+ &lsi->match); -+ build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows, -+ lsi->ports, -+ &lsi->actions, -+ &lsi->match); -+ build_lswitch_dhcp_options_and_response(op,lsi->lflows); -+ build_lswitch_external_port(op, lsi->lflows); -+ build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups, -+ &lsi->actions, &lsi->match); -+ build_lswitch_output_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, -@@ -11232,6 +11998,10 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, - build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); - build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows, - &lsi->match, &lsi->actions); -+ build_lrouter_ipv4_ip_input(op, lsi->lflows, -+ &lsi->match, &lsi->actions); -+ build_lrouter_force_snat_flows_op(op, lsi->lflows, &lsi->match, -+ &lsi->actions); - } - - static void -@@ -11239,10 +12009,13 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - struct hmap *port_groups, struct hmap *lflows, - struct hmap *mcgroups, - struct hmap *igmp_groups, -- struct shash *meter_groups, struct hmap *lbs) -+ struct shash *meter_groups, struct hmap *lbs, -+ struct hmap *bfd_connections) - { - struct ovn_datapath *od; - struct ovn_port *op; -+ struct ovn_northd_lb *lb; -+ struct ovn_igmp_group *igmp_group; - - char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); - -@@ -11264,22 +12037,28 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, - * will move here and will be reogranized by iterator type. - */ - HMAP_FOR_EACH (od, key_node, datapaths) { -- build_lswitch_and_lrouter_iterate_by_od(od, &lsi); -+ build_lswitch_and_lrouter_iterate_by_od(od, &lsi, bfd_connections); - } - HMAP_FOR_EACH (op, key_node, ports) { - build_lswitch_and_lrouter_iterate_by_op(op, &lsi); - } -+ HMAP_FOR_EACH (lb, hmap_node, lbs) { -+ build_lswitch_arp_nd_service_monitor(lb, lsi.lflows, -+ &lsi.actions, -+ &lsi.match); -+ } -+ HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) { -+ build_lswitch_ip_mcast_igmp_mld(igmp_group, -+ lsi.lflows, -+ &lsi.actions, -+ &lsi.match); -+ } - free(svc_check_match); - - ds_destroy(&lsi.match); - ds_destroy(&lsi.actions); - -- /* 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); -+ build_lswitch_flows(datapaths, lflows); - } - - struct ovn_dp_group { -@@ -11356,13 +12135,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths, - struct hmap *ports, struct hmap *port_groups, - struct hmap *mcgroups, struct hmap *igmp_groups, - struct shash *meter_groups, -- struct hmap *lbs) -+ struct hmap *lbs, struct hmap *bfd_connections) - { - struct hmap lflows = HMAP_INITIALIZER(&lflows); - - build_lswitch_and_lrouter_flows(datapaths, ports, - port_groups, &lflows, mcgroups, -- igmp_groups, meter_groups, lbs); -+ igmp_groups, meter_groups, lbs, -+ bfd_connections); - - /* Collecting all unique datapath groups. */ - struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups); -@@ -11801,17 +12581,20 @@ 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) -+ struct shash *sb_meters, -+ struct sset *used_sb_meters) - { -+ const struct sbrec_meter *sb_meter; - bool new_sb_meter = false; - -- const struct sbrec_meter *sb_meter = shash_find_and_delete(sb_meters, -- meter_name); -+ sb_meter = shash_find_data(sb_meters, meter_name); - if (!sb_meter) { - sb_meter = sbrec_meter_insert(ctx->ovnsb_txn); - sbrec_meter_set_name(sb_meter, meter_name); -+ shash_add(sb_meters, sb_meter->name, sb_meter); - new_sb_meter = true; - } -+ sset_add(used_sb_meters, meter_name); - - if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { - struct sbrec_meter_band **sb_bands; -@@ -11833,6 +12616,24 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, - sbrec_meter_set_unit(sb_meter, nb_meter->unit); - } - -+static void -+sync_acl_fair_meter(struct northd_context *ctx, struct shash *meter_groups, -+ const struct nbrec_acl *acl, struct shash *sb_meters, -+ struct sset *used_sb_meters) -+{ -+ const struct nbrec_meter *nb_meter = -+ fair_meter_lookup_by_name(meter_groups, acl->meter); -+ -+ if (!nb_meter) { -+ return; -+ } -+ -+ char *meter_name = alloc_acl_log_unique_meter_name(acl); -+ sync_meters_iterate_nb_meter(ctx, meter_name, nb_meter, sb_meters, -+ used_sb_meters); -+ free(meter_name); -+} -+ - /* Each entry in the Meter and Meter_Band tables in OVN_Northbound have - * a corresponding entries in the Meter and Meter_Band tables in - * OVN_Southbound. Additionally, ACL logs that use fair meters have -@@ -11840,9 +12641,10 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, - */ - static void - sync_meters(struct northd_context *ctx, struct hmap *datapaths, -- struct shash *meter_groups) -+ struct shash *meter_groups, struct hmap *port_groups) - { - struct shash sb_meters = SHASH_INITIALIZER(&sb_meters); -+ struct sset used_sb_meters = SSET_INITIALIZER(&used_sb_meters); - - const struct sbrec_meter *sb_meter; - SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) { -@@ -11852,7 +12654,7 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, - const struct nbrec_meter *nb_meter; - NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) { - sync_meters_iterate_nb_meter(ctx, nb_meter->name, nb_meter, -- &sb_meters); -+ &sb_meters, &used_sb_meters); - } - - /* -@@ -11866,19 +12668,28 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, - continue; - } - 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; -+ sync_acl_fair_meter(ctx, meter_groups, od->nbs->acls[i], -+ &sb_meters, &used_sb_meters); -+ } -+ 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++) { -+ sync_acl_fair_meter(ctx, meter_groups, pg->nb_pg->acls[i], -+ &sb_meters, &used_sb_meters); -+ } - } -- -- 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); - } - } - -+ const char *used_meter; -+ const char *used_meter_next; -+ SSET_FOR_EACH_SAFE (used_meter, used_meter_next, &used_sb_meters) { -+ shash_find_and_delete(&sb_meters, used_meter); -+ sset_delete(&used_sb_meters, SSET_NODE_FROM_NAME(used_meter)); -+ } -+ sset_destroy(&used_sb_meters); -+ - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &sb_meters) { - sbrec_meter_delete(node->data); -@@ -12274,6 +13085,7 @@ ovnnb_db_run(struct northd_context *ctx, - struct hmap igmp_groups; - struct shash meter_groups = SHASH_INITIALIZER(&meter_groups); - struct hmap lbs; -+ struct hmap bfd_connections = HMAP_INITIALIZER(&bfd_connections); - - /* Sync ipsec configuration. - * Copy nb_cfg from northbound to southbound database. -@@ -12354,6 +13166,7 @@ ovnnb_db_run(struct northd_context *ctx, - - use_logical_dp_groups = smap_get_bool(&nb->options, - "use_logical_dp_groups", false); -+ /* deprecated, use --event instead */ - controller_event_en = smap_get_bool(&nb->options, - "controller_event", false); - check_lsp_is_up = !smap_get_bool(&nb->options, -@@ -12368,14 +13181,16 @@ ovnnb_db_run(struct northd_context *ctx, - build_ip_mcast(ctx, datapaths); - build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups); - build_meter_groups(ctx, &meter_groups); -+ build_bfd_table(ctx, &bfd_connections, ports); - build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups, -- &igmp_groups, &meter_groups, &lbs); -+ &igmp_groups, &meter_groups, &lbs, &bfd_connections); - ovn_update_ipv6_prefix(ports); - - sync_address_sets(ctx); - sync_port_groups(ctx, &port_groups); -- sync_meters(ctx, datapaths, &meter_groups); -+ sync_meters(ctx, datapaths, &meter_groups, &port_groups); - sync_dns_entries(ctx, datapaths); -+ cleanup_stale_fdp_entries(ctx, datapaths); - - struct ovn_northd_lb *lb; - HMAP_FOR_EACH_POP (lb, hmap_node, &lbs) { -@@ -12393,9 +13208,13 @@ ovnnb_db_run(struct northd_context *ctx, - HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) { - ovn_port_group_destroy(&port_groups, pg); - } -+ -+ bfd_cleanup_connections(ctx, &bfd_connections); -+ - hmap_destroy(&igmp_groups); - hmap_destroy(&mcast_groups); - hmap_destroy(&port_groups); -+ hmap_destroy(&bfd_connections); - - struct shash_node *node, *next; - SHASH_FOR_EACH_SAFE (node, next, &meter_groups) { -@@ -12542,7 +13361,17 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, - continue; - } - -- bool up = (sb->chassis || lsp_is_router(op->nbsp)); -+ bool up = false; -+ -+ if (lsp_is_router(op->nbsp)) { -+ up = true; -+ } else if (sb->chassis) { -+ up = smap_get_bool(&sb->chassis->other_config, -+ OVN_FEATURE_PORT_UP_NOTIF, false) -+ ? sb->n_up && sb->up[0] -+ : true; -+ } -+ - if (!op->nbsp->up || *op->nbsp->up != up) { - nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); - } -@@ -12690,7 +13519,7 @@ static const char *rbac_encap_update[] = - static const char *rbac_port_binding_auth[] = - {""}; - static const char *rbac_port_binding_update[] = -- {"chassis"}; -+ {"chassis", "up"}; - - static const char *rbac_mac_binding_auth[] = - {""}; -@@ -13176,6 +14005,8 @@ main(int argc, char *argv[]) - &sbrec_port_binding_col_ha_chassis_group); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_port_binding_col_virtual_parent); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, -+ &sbrec_port_binding_col_up); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, - &sbrec_gateway_chassis_col_chassis); - ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name); -@@ -13324,9 +14155,25 @@ main(int argc, char *argv[]) - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_name); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_vips); - add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_protocol); -+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_options); - add_column_noalert(ovnsb_idl_loop.idl, - &sbrec_load_balancer_col_external_ids); - -+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_bfd); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_logical_port); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_dst_ip); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_status); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_tx); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_rx); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_detect_mult); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_disc); -+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_src_port); -+ -+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_fdb); -+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_mac); -+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_dp_key); -+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_port_key); -+ - struct ovsdb_idl_index *sbrec_chassis_by_name - = chassis_index_create(ovnsb_idl_loop.idl); - -@@ -13449,6 +14296,7 @@ main(int argc, char *argv[]) - } - } - -+ - free(ovn_internal_version); - unixctl_server_destroy(unixctl); - ovsdb_idl_loop_destroy(&ovnnb_idl_loop); -diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema -index 269e3a888..29019809c 100644 ---- a/ovn-nb.ovsschema -+++ b/ovn-nb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Northbound", -- "version": "5.28.0", -- "cksum": "610359755 26847", -+ "version": "5.31.0", -+ "cksum": "2352750632 28701", - "tables": { - "NB_Global": { - "columns": { -@@ -188,6 +188,11 @@ - ["eth_src", "eth_dst", "ip_src", "ip_dst", - "tp_src", "tp_dst"]]}, - "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", -+ "value": "string", -+ "min": 0, -+ "max": "unlimited"}}, - "external_ids": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, -@@ -369,6 +374,10 @@ - "min": 0, "max": 1}}, - "nexthop": {"type": "string"}, - "output_port": {"type": {"key": "string", "min": 0, "max": 1}}, -+ "bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD", -+ "refType": "weak"}, -+ "min": 0, -+ "max": 1}}, - "options": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}, -@@ -386,6 +395,8 @@ - "key": {"type": "string", - "enum": ["set", ["allow", "drop", "reroute"]]}}}, - "nexthop": {"type": {"key": "string", "min": 0, "max": 1}}, -+ "nexthops": {"type": { -+ "key": "string", "min": 0, "max": "unlimited"}}, - "options": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}, -@@ -519,5 +530,30 @@ - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, - "indexes": [["name"]], -+ "isRoot": true}, -+ "BFD": { -+ "columns": { -+ "logical_port": {"type": "string"}, -+ "dst_ip": {"type": "string"}, -+ "min_tx": {"type": {"key": {"type": "integer", -+ "minInteger": 1}, -+ "min": 0, "max": 1}}, -+ "min_rx": {"type": {"key": {"type": "integer"}, -+ "min": 0, "max": 1}}, -+ "detect_mult": {"type": {"key": {"type": "integer", -+ "minInteger": 1}, -+ "min": 0, "max": 1}}, -+ "status": { -+ "type": {"key": {"type": "string", -+ "enum": ["set", ["down", "init", "up", -+ "admin_down"]]}, -+ "min": 0, "max": 1}}, -+ "external_ids": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}}, -+ "indexes": [["logical_port", "dst_ip"]], - "isRoot": true}} - } -diff --git a/ovn-nb.xml b/ovn-nb.xml -index c9ab25ceb..09b755f1a 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -1635,6 +1635,24 @@ - See External IDs at the beginning of this document. - - -+ -+ -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable empty_lb -+ SB controller event for this load balancer. -+ -+ -+ -+ IP to be used as source IP for packets that have been hair-pinned -+ after load balancing. The default behavior when the option is not set -+ is to use the load balancer VIP as source IP. This option may have -+ exactly one IPv4 and/or one IPv6 address on it, separated by a space -+ character. -+ -+ - - - -@@ -1917,16 +1935,29 @@ - - -

    -- If set, indicates a set of IP addresses to use to force SNAT a packet -- that has already been load-balanced in the gateway router. When -- multiple gateway routers are configured, a packet can potentially -- enter any of the gateway routers, get DNATted as part of the load- -- balancing and eventually reach the logical switch port. -- For the return traffic to go back to the same gateway router (for -- unDNATing), the packet needs a SNAT in the first place. This can be -- achieved by setting the above option with a gateway specific set of -- IP addresses. This option may have exactly one IPv4 and/or one IPv6 -- address on it, separated by a space character. -+ If set, this option can take two possible type of values. Either -+ a set of IP addresses or the string value - router_ip. -+

    -+ -+

    -+ If a set of IP addresses are configured, it indicates to use to -+ force SNAT a packet that has already been load-balanced in the -+ gateway router. When multiple gateway routers are configured, a -+ packet can potentially enter any of the gateway routers, get -+ DNATted as part of the load-balancing and eventually reach the -+ logical switch port. For the return traffic to go back to the -+ same gateway router (for unDNATing), the packet needs a SNAT in the -+ first place. This can be achieved by setting the above option with -+ a gateway specific set of IP addresses. This option may have exactly -+ one IPv4 and/or one IPv6 address on it, separated by a space -+ character. -+

    -+ -+

    -+ If it is configured with the value router_ip, then -+ the load balanced packet is SNATed with the IP of router port -+ (attached to the gateway router) selected as the destination after -+ taking the routing decision. -

    -
    - -@@ -2634,6 +2665,13 @@ -

    -
    - -+ -+

    -+ Reference to row if the route has associated a -+ BFD session -+

    -+
    -+ - - ovn-ic populates this key if the route is learned from the - global database. In this case the value -@@ -2713,18 +2751,34 @@ - - -
  • -- reroute: Reroute packet to . -+ reroute: Reroute packet to or -+ . -
  • - -
    - - -+

    -+ Note: This column is deprecated in favor of . -+

    -

    - Next-hop IP address for this route, which should be the IP - address of a connected router port or the IP address of a logical port. -

    -
    - -+ -+

    -+ Next-hop ECMP IP addresses for this route. Each IP in the list should -+ be the IP address of a connected router port or the IP address of a -+ logical port. -+

    -+ -+

    -+ One IP from the list is selected as next hop. -+

    -+
    -+ - -

    - Marks the packet with the value specified when the router policy -@@ -3702,4 +3756,71 @@ - - -

    -+ -+ -+

    -+ Contains BFD parameter for ovn-controller bfd configuration. -+

    -+ -+ -+ -+ OVN logical port when BFD engine is running. -+ -+ -+ -+ BFD peer IP address. -+ -+ -+ -+ This is the minimum interval, in milliseconds, that the local -+ system would like to use when transmitting BFD Control packets, -+ less any jitter applied. The value zero is reserved. Default -+ value is 1000 ms. -+ -+ -+ -+ This is the minimum interval, in milliseconds, between received -+ BFD Control packets that this system is capable of supporting, -+ less any jitter applied by the sender. If this value is zero, -+ the transmitting system does not want the remote system to send -+ any periodic BFD Control packets. -+ -+ -+ -+ Detection time multiplier. The negotiated transmit interval, -+ multiplied by this value, provides the Detection Time for the -+ receiving system in Asynchronous mode. Default value is 5. -+ -+ -+ -+ Reserved for future use. -+ -+ -+ -+ See External IDs at the beginning of this document. -+ -+ -+ -+ -+ -+

    -+ BFD port logical states. Possible values are: -+

      -+
    • -+ admin_down -+
    • -+
    • -+ down -+
    • -+
    • -+ init -+
    • -+
    • -+ up -+
    • -+
    -+

    -+
    -+
    -+
    - -diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema -index 5228839b8..b5d3338f4 100644 ---- a/ovn-sb.ovsschema -+++ b/ovn-sb.ovsschema -@@ -1,7 +1,7 @@ - { - "name": "OVN_Southbound", -- "version": "20.12.0", -- "cksum": "3969471120 24441", -+ "version": "20.16.1", -+ "cksum": "4243908307 26536", - "tables": { - "SB_Global": { - "columns": { -@@ -103,7 +103,7 @@ - "egress"]]}}}, - "table_id": {"type": {"key": {"type": "integer", - "minInteger": 0, -- "maxInteger": 23}}}, -+ "maxInteger": 32}}}, - "priority": {"type": {"key": {"type": "integer", - "minInteger": 0, - "maxInteger": 65535}}}, -@@ -225,6 +225,7 @@ - "nat_addresses": {"type": {"key": "string", - "min": 0, - "max": "unlimited"}}, -+ "up": {"type": {"key": "boolean", "min": 0, "max": 1}}, - "external_ids": {"type": {"key": "string", - "value": "string", - "min": 0, -@@ -481,9 +482,50 @@ - "type": {"key": {"type": "uuid", - "refTable": "Datapath_Binding"}, - "min": 0, "max": "unlimited"}}, -+ "options": { -+ "type": {"key": "string", -+ "value": "string", -+ "min": 0, -+ "max": "unlimited"}}, -+ "external_ids": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}}, -+ "isRoot": true}, -+ "BFD": { -+ "columns": { -+ "src_port": {"type": {"key": {"type": "integer", -+ "minInteger": 49152, -+ "maxInteger": 65535}}}, -+ "disc": {"type": {"key": {"type": "integer"}}}, -+ "logical_port": {"type": "string"}, -+ "dst_ip": {"type": "string"}, -+ "min_tx": {"type": {"key": {"type": "integer"}}}, -+ "min_rx": {"type": {"key": {"type": "integer"}}}, -+ "detect_mult": {"type": {"key": {"type": "integer"}}}, -+ "status": { -+ "type": {"key": {"type": "string", -+ "enum": ["set", ["down", "init", "up", -+ "admin_down"]]}}}, - "external_ids": { -+ "type": {"key": "string", "value": "string", -+ "min": 0, "max": "unlimited"}}, -+ "options": { - "type": {"key": "string", "value": "string", - "min": 0, "max": "unlimited"}}}, -+ "indexes": [["logical_port", "dst_ip", "src_port", "disc"]], -+ "isRoot": true}, -+ "FDB": { -+ "columns": { -+ "mac": {"type": "string"}, -+ "dp_key": { -+ "type": {"key": {"type": "integer", -+ "minInteger": 1, -+ "maxInteger": 16777215}}}, -+ "port_key": { -+ "type": {"key": {"type": "integer", -+ "minInteger": 1, -+ "maxInteger": 16777215}}}}, -+ "indexes": [["mac", "dp_key"]], - "isRoot": true} - } - } -diff --git a/ovn-sb.xml b/ovn-sb.xml -index c13994848..258a12b4e 100644 ---- a/ovn-sb.xml -+++ b/ovn-sb.xml -@@ -322,6 +322,11 @@ - table. See ovn-controller(8) for more information. - - -+ -+ ovn-controller populates this key with true -+ when it supports Port_Binding.up. -+ -+ - - The overall purpose of these columns is described under Common - Columns at the beginning of this document. -@@ -1521,6 +1526,68 @@ -

    - - -+
    P = get_fdb(A);
    -+ -+
    -+

    -+ Parameters:48-bit MAC address field A. -+

    -+ -+

    -+ Looks up A in fdb table. If an entry is found, stores -+ the logical port key to the out parameter P. -+

    -+ -+

    Example: outport = get_fdb(eth.src);

    -+
    -+ -+
    -+ put_fdb(P, A); -+
    -+ -+
    -+

    -+ Parameters: logical port string field P, 48-bit -+ MAC address field A. -+

    -+ -+

    -+ Adds or updates the entry for Ethernet address A in -+ fdb table, setting its logical port key to P. -+

    -+ -+

    Example: put_fdb(inport, arp.spa);

    -+
    -+ -+
    -+ R = lookup_fdb(P, A); -+
    -+ -+
    -+

    -+ Parameters: 48-bit MAC address field M, -+ logical port string field P. -+

    -+ -+

    -+ Result: stored to a 1-bit subfield R. -+

    -+ -+

    -+ Looks up A in fdb table. If an entry is found -+ and the the logical port key is P, P, -+ stores 1 in the 1-bit subfield -+ R, else 0. -+

    -+ -+

    -+ Example: -+ -+ reg0[0] = lookup_fdb(inport, eth.src); -+ -+

    -+
    -+ -
    nd_ns { action; ... };
    -
    -

    -@@ -2771,6 +2838,14 @@ tcp.flags = RST; -

    - - -+ -+

    -+ This is set to true whenever all OVS flows -+ required by this Port_Binding have been installed. This is -+ populated by ovn-controller. -+

    -+
    -+ - -

    - A number that represents the logical port in the key (e.g. STT key or -@@ -4225,10 +4300,126 @@ tcp.flags = RST; - Datapaths to which this load balancer applies to. - - -+ -+ -+ IP to be used as source IP for packets that have been hair-pinned after -+ load balancing. This value is automatically populated by -+ ovn-northd. -+ -+ -+ This value is automatically set to true by -+ ovn-northd when original destination IP and transport port -+ of the load balanced packets are stored in registers -+ reg1, reg2, xxreg1. -+ -+ -+ - - - See External IDs at the beginning of this document. - - - -+ -+ -+

    -+ Contains BFD parameter for ovn-controller bfd configuration. -+

    -+ -+ -+ -+ udp source port used in bfd control packets. -+ The source port MUST be in the range 49152 through 65535 -+ (RFC5881 section 4). -+ -+ -+ -+ A unique, nonzero discriminator value generated by the transmitting -+ system, used to demultiplex multiple BFD sessions between the same pair -+ of systems. -+ -+ -+ -+ OVN logical port when BFD engine is running. -+ -+ -+ -+ BFD peer IP address. -+ -+ -+ -+ This is the minimum interval, in milliseconds, that the local -+ system would like to use when transmitting BFD Control packets, -+ less any jitter applied. The value zero is reserved. -+ -+ -+ -+ This is the minimum interval, in milliseconds, between received -+ BFD Control packets that this system is capable of supporting, -+ less any jitter applied by the sender. If this value is zero, -+ the transmitting system does not want the remote system to send -+ any periodic BFD Control packets. -+ -+ -+ -+ Detection time multiplier. The negotiated transmit interval, -+ multiplied by this value, provides the Detection Time for the -+ receiving system in Asynchronous mode. -+ -+ -+ -+ Reserved for future use. -+ -+ -+ -+ See External IDs at the beginning of this document. -+ -+ -+ -+ -+ -+

    -+ BFD port logical states. Possible values are: -+

      -+
    • -+ admin_down -+
    • -+
    • -+ down -+
    • -+
    • -+ init -+
    • -+
    • -+ up -+
    • -+
    -+

    -+
    -+
    -+
    -+ -+ -+

    -+ This table is primarily used to learn the MACs observed on a VIF -+ which belongs to a Logical_Switch_Port record in -+ OVN_Northbound whose port security is disabled -+ and 'unknown' address set. If port security is disabled on a -+ Logical_Switch_Port record, OVN should allow traffic -+ with any source mac from the VIF. This table will be used to deliver -+ a packet to the VIF, If a packet's eth.dst is learnt. -+

    -+ -+ -+ The learnt mac address. -+ -+ -+ -+ The key of the datapath on which this FDB was learnt. -+ -+ -+ -+ The key of the port binding on which this FDB was learnt. -+ -+
    - -diff --git a/ovs b/ovs -new file mode 160000 -index 000000000..ac09cbfcb ---- /dev/null -+++ b/ovs -@@ -0,0 +1 @@ -+Subproject commit ac09cbfcb70ac6f443f039d5934448bd80f74493 -diff --git a/tests/atlocal.in b/tests/atlocal.in -index d9a4c91d4..5ebc8e117 100644 ---- a/tests/atlocal.in -+++ b/tests/atlocal.in -@@ -181,6 +181,9 @@ fi - # Set HAVE_DIBBLER-SERVER - find_command dibbler-server - -+# Set HAVE_BFDD_BEACON -+find_command bfdd-beacon -+ - # Turn off proxies. - unset http_proxy - unset https_proxy -diff --git a/tests/automake.mk b/tests/automake.mk -index c5c286eae..c09f615d5 100644 ---- a/tests/automake.mk -+++ b/tests/automake.mk -@@ -31,7 +31,8 @@ TESTSUITE_AT = \ - tests/ovn-controller-vtep.at \ - tests/ovn-ic.at \ - tests/ovn-macros.at \ -- tests/ovn-performance.at -+ tests/ovn-performance.at \ -+ tests/ovn-ofctrl-seqno.at - - SYSTEM_KMOD_TESTSUITE_AT = \ - tests/system-common-macros.at \ -@@ -202,7 +203,10 @@ noinst_PROGRAMS += tests/ovstest - tests_ovstest_SOURCES = \ - tests/ovstest.c \ - tests/ovstest.h \ -- tests/test-ovn.c -+ tests/test-ovn.c \ -+ controller/test-ofctrl-seqno.c \ -+ controller/ofctrl-seqno.c \ -+ controller/ofctrl-seqno.h - - tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ - $(OVS_LIBDIR)/libopenvswitch.la lib/libovn.la -diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at -index dd5d3848d..3d7ac08b3 100644 ---- a/tests/ofproto-macros.at -+++ b/tests/ofproto-macros.at -@@ -12,7 +12,10 @@ strip_n_bytes () { - - # Strips 'cookie=...' from ovs-ofctl output. - strip_cookie () { -- sed 's/ cookie=0x[0-9a-fA-F]*,//' -+ sed ' -+s/ cookie=0x[0-9a-fA-F]*,// -+s/cookie=0x[0-9a-fA-F]*,// -+' - } - - # Strips out uninteresting parts of ovs-ofctl output, as well as parts -@@ -37,7 +40,7 @@ s/dir\/[0-9]*\/br0.mgmt/dir\/XXXX\/br0.mgmt/ - # 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 -+ ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie | sort - } - - # Filter (multiline) vconn debug messages from ovs-vswitchd.log. -diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at -index cb582811f..b2261d285 100644 ---- a/tests/ovn-controller-vtep.at -+++ b/tests/ovn-controller-vtep.at -@@ -177,22 +177,22 @@ AT_CLEANUP - AT_SETUP([ovn-controller-vtep - binding 1]) - OVN_CONTROLLER_VTEP_START - --# adds logical switch 'lswitch0' and vlan_bindings. -+AS_BOX([add logical switch 'lswitch0' and vlan_bindings]) - 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]) --check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]' -+wait_row_count Port_Binding 1 logical_port=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 - ${chassis_uuid} - ]) - --# adds another logical switch 'lswitch1' and vlan_bindings. -+AS_BOX([add another logical switch 'lswitch1' and vlan_bindings]) - 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]) --check ovn-sbctl wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]' -+wait_row_count Port_Binding 1 logical_port=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 -@@ -201,7 +201,7 @@ ${chassis_uuid} - ${chassis_uuid} - ]) - --# adds another logical switch port in ovn-nb database for lswitch0. -+AS_BOX([add another logical switch port in ovn-nb database for lswitch0]) - OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0]) - - # confirms the warning log. -@@ -228,7 +228,7 @@ ${chassis_uuid} - ${chassis_uuid} - ]) - --# deletes physical ports from vtep. -+AS_BOX([delete physical ports from vtep]) - AT_CHECK([ovs-vsctl del-port p0 -- del-port p1]) - OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"]) - OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"]) -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 1b4679963..f818f9cea 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -414,3 +414,20 @@ OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1]) - - OVN_CLEANUP([hv1]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- features]) -+AT_KEYWORDS([features]) -+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. -+OVS_WAIT_UNTIL([ -+ test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"' -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index 59e500c57..2ba29a960 100644 ---- a/tests/ovn-macros.at -+++ b/tests/ovn-macros.at -@@ -417,6 +417,22 @@ wait_column() { - echo "$column in $db table $table has value $found, from the following rows:" - ovn-${db}ctl list $table]) - } -+ -+# wait_for_ports_up [PORT...] -+# -+# With arguments, waits for specified Logical_Switch_Ports to come up. -+# Without arguments, waits for all "plain" and router -+# Logical_Switch_Ports to come up. -+wait_for_ports_up() { -+ if test $# = 0; then -+ wait_row_count nb:Logical_Switch_Port 0 up!=true type='""' -+ wait_row_count nb:Logical_Switch_Port 0 up!=true type=router -+ else -+ for port; do -+ wait_row_count nb:Logical_Switch_Port 1 up=true name=$port -+ done -+ fi -+} - OVS_END_SHELL_HELPERS - - m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) -diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 01edfcbc1..6d91aa4c5 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1539,34 +1539,34 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - dnl Add ecmp routes - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.0/24 11.0.0.1]) - AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2]) --AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2]) - AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3]) --AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3 lp0]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.4 lp0]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.1 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.1 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp -+]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2], [1], [], -+ [ovn-nbctl: duplicate nexthop for the same ECMP route - ]) - - dnl Delete ecmp routes - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.2 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp - ]) - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.2]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -- 10.0.0.0/24 11.0.0.3 dst-ip -- 10.0.0.0/24 11.0.0.3 dst-ip lp0 -+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp -+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp - ]) --AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.3 lp0]) -+AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.4 lp0]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes - 10.0.0.0/24 11.0.0.3 dst-ip -@@ -1605,7 +1605,15 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0]) - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1]) - AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1]) - AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0]) --AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::2]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::3]) -+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::4]) -+AT_CHECK([ovn-nbctl lr-route-add lr0 2002:0db8:1::/64 2001:0db8:0:f103::5]) -+AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6]) -+AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6], [1], [], -+ [ovn-nbctl: duplicate nexthop for the same ECMP route -+]) - - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -@@ -1615,9 +1623,20 @@ IPv4 Routes - - IPv6 Routes - 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 -- 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip -+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::2 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::3 dst-ip ecmp -+ 2001:db8:1::/64 2001:db8:0:f103::4 dst-ip ecmp -+ 2002:db8:1::/64 2001:db8:0:f103::5 dst-ip -+ 2003:db8:1::/64 2001:db8:0:f103::6 dst-ip ecmp-symmetric-reply - ::/0 2001:db8:0:f101::1 dst-ip --])]) -+]) -+ -+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24]) -+bfd_uuid=$(ovn-nbctl create bfd logical_port=lr0-p0 dst_ip=100.0.0.50 status=down min_tx=250 min_rx=250 detect_mult=10) -+AT_CHECK([ovn-nbctl lr-route-add lr0 100.0.0.0/24 192.168.0.1]) -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/24") -+AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid])]) - - dnl --------------------------------------------------------------------- - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 90ca0a4db..11d4a9c86 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -605,11 +605,12 @@ wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]' - 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 -+AS_BOX([Clear ha_chassis_group for sw0-pext2 and reset port type to normal in the same txn]) - --wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]' -+check ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 \ -+ha_chassis_group -- set logical_switch_port sw0-pext2 'type=""' - wait_row_count HA_Chassis_Group 0 -+wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]' - check_row_count HA_Chassis 0 - - as ovn-sb -@@ -624,61 +625,66 @@ AT_CLEANUP - AT_SETUP([ovn -- ovn-northd pause and resume]) - ovn_start - --AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`]) --AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: active --]) --AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \ --is-paused`]) --AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0], --[Status: standby --]) -- --ovn-nbctl ls-add sw0 -- --OVS_WAIT_UNTIL([ -- ovn-sbctl lflow-list sw0 -- test 0 = $?]) -+get_northd_status() { -+ as northd ovn-appctl -t ovn-northd is-paused -+ as northd ovn-appctl -t ovn-northd status -+ as northd-backup ovn-appctl -t ovn-northd is-paused -+ as northd-backup ovn-appctl -t ovn-northd status -+} - --ovn-nbctl ls-del sw0 --OVS_WAIT_UNTIL([ -- ovn-sbctl lflow-list sw0 -- test 1 = $?]) -+AS_BOX([Pause the backup]) -+# This forces the main northd to become active (otherwise there's no -+# guarantee, ovn_start is racy). -+check as northd-backup ovs-appctl -t ovn-northd pause -+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false -+Status: active -+true -+Status: paused -+]) -+ -+AS_BOX([Resume the backup]) -+check as northd-backup ovs-appctl -t ovn-northd resume -+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false -+Status: active -+false -+Status: standby -+]) -+ -+AS_BOX([Check that ovn-northd is active]) -+# Check that ovn-northd is active, by verifying that it creates and -+# destroys southbound datapaths as one would expect. -+check_row_count Datapath_Binding 0 -+check ovn-nbctl --wait=sb ls-add sw0 -+check_row_count Datapath_Binding 1 -+check ovn-nbctl --wait=sb ls-del sw0 -+check_row_count Datapath_Binding 0 - --# Now pause the ovn-northd --as northd ovs-appctl -t ovn-northd pause --as northd-backup ovs-appctl -t ovn-northd pause --AT_CHECK([test xtrue = x`as northd ovn-appctl -t ovn-northd is-paused`]) --AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: paused --]) --AT_CHECK([test xtrue = x`as northd-backup ovn-appctl -t ovn-northd is-paused`]) --AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0], --[Status: paused -+AS_BOX([Pause the main northd]) -+check as northd ovs-appctl -t ovn-northd pause -+check as northd-backup ovs-appctl -t ovn-northd pause -+AT_CHECK([get_northd_status], [0], [true -+Status: paused -+true -+Status: paused - ]) - --ovn-nbctl ls-add sw0 -- --# There should be no logical flows for sw0 datapath. --OVS_WAIT_UNTIL([ -- ovn-sbctl lflow-list sw0 -- test 1 = $?]) -- --# Now resume ovn-northd --as northd ovs-appctl -t ovn-northd resume --AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`]) --OVS_WAIT_UNTIL([as northd ovn-appctl -t ovn-northd status], [0], --[Status: active --]) -+AS_BOX([Verify that ovn-northd is paused]) -+# Now ovn-northd won't respond by adding a datapath, because it's paused. -+check ovn-nbctl ls-add sw0 -+check sleep 5 -+check_row_count Datapath_Binding 0 - --as northd-backup ovs-appctl -t ovn-northd resume --AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \ --is-paused`]) --AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0], --[Status: standby -+AS_BOX([Resume the main northd]) -+check as northd ovs-appctl -t ovn-northd resume -+check as northd-backup ovs-appctl -t ovn-northd resume -+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false -+Status: active -+false -+Status: standby - ]) - --OVS_WAIT_UNTIL([ -- ovn-sbctl lflow-list sw0 -- test 0 = $?]) -+check ovn-nbctl --wait=sb sync -+check_row_count Datapath_Binding 1 - - AT_CLEANUP - -@@ -849,7 +855,7 @@ uuid=$(fetch_column Port_Binding _uuid logical_port=cr-DR-S1) - echo "CR-LRP UUID is: " $uuid - - check ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1 --check ovn-nbctl --wait=hv sync -+check ovn-nbctl --wait=sb 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\" -@@ -1048,7 +1054,7 @@ health_check @hc | uuidfilt], [0], [<0> - - wait_row_count Service_Monitor 0 - --# create logical switches and ports -+AS_BOX([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" -@@ -1072,54 +1078,57 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 - AT_CAPTURE_FILE([sbflows]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed 's/table=..//'], 0, [dnl -- (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.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - --# Delete the Load_Balancer_Health_Check -+AS_BOX([Delete the Load_Balancer_Health_Check]) - ovn-nbctl --wait=sb clear load_balancer . health_check - wait_row_count Service_Monitor 0 - - AT_CAPTURE_FILE([sbflows2]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], --[ (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.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - --# Create the Load_Balancer_Health_Check again. -+AS_BOX([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 - wait_row_count Service_Monitor 2 -+check ovn-nbctl --wait=sb sync - - ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt - 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);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - --# Get the uuid of both the service_monitor -+AS_BOX([Get the uuid of both the service_monitor]) - sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1) - sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1) - - AT_CAPTURE_FILE([sbflows3]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], --[ (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.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - --# Set the service monitor for sw1-p1 to offline -+AS_BOX([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 -+check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows4]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], --[ (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.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);) - ]) - --# Set the service monitor for sw0-p1 to offline -+AS_BOX([Set the service monitor for sw0-p1 to offline]) - ovn-sbctl set service_monitor $sm_sw0_p1 status=offline - - wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline -+check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows5]) - OVS_WAIT_FOR_OUTPUT( -@@ -1131,32 +1140,34 @@ OVS_WAIT_FOR_OUTPUT( - (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 -+AS_BOX([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 - - wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online -+check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows7]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, --[ (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.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - --# Set the service monitor for sw1-p1 to error -+AS_BOX([Set the service monitor for sw1-p1 to error]) - ovn-sbctl set service_monitor $sm_sw1_p1 status=error - wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error -+check ovn-nbctl --wait=sb sync - - 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 | 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);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);) - ]) - --# Add one more vip to lb1 -+AS_BOX([Add one more vip to lb1]) - 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 -+AS_BOX([create health_check for new vip - 10.0.0.40]) - AT_CHECK( - [ovn-nbctl --wait=sb \ - -- --id=@hc create Load_Balancer_Health_Check vip=10.0.0.40\\:1000 \ -@@ -1176,34 +1187,35 @@ 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);) -+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);) - ]) - --# Set the service monitor for sw1-p1 to online -+AS_BOX([Set the service monitor for sw1-p1 to online]) - check ovn-sbctl set service_monitor sw1-p1 status=online - - wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online -+check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows10]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], - 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);) -+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) - ]) - --# Associate lb1 to sw1 -+AS_BOX([Associate lb1 to sw1]) - check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 - AT_CAPTURE_FILE([sbflows11]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], - 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);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) - ]) - --# Now create lb2 same as lb1 but udp protocol. -+AS_BOX([Now create lb2 same as lb1 but udp protocol.]) - 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 -@@ -1214,15 +1226,17 @@ AT_CHECK([ovn-nbctl -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10 - - check ovn-nbctl ls-lb-add sw0 lb2 - check ovn-nbctl ls-lb-add sw1 lb2 -+check ovn-nbctl --wait=sb sync - - wait_row_count Service_Monitor 5 - --# Change the svc_monitor_mac. This should get reflected in service_monitor table rows. -+AS_BOX([Change the svc_monitor_mac.]) -+# This should get reflected in service_monitor table rows. - check ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03" - - 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 -+AS_BOX([Change the source ip for 10.0.0.3 backend ip in lb2]) - check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100 - - wait_row_count Service_Monitor 1 logical_port=sw0-p1 src_ip=10.0.0.100 -@@ -1233,6 +1247,31 @@ wait_row_count Service_Monitor 2 - ovn-nbctl --wait=sb lb-del lb2 - wait_row_count Service_Monitor 0 - -+check ovn-nbctl --reject lb-add lb3 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 -+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 -+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -+wait_row_count Service_Monitor 0 -+ -+check ovn-nbctl --wait=sb ls-lb-add sw0 lb3 -+AT_CHECK([ovn-nbctl --wait=sb -- --id=@hc create \ -+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer lb3 \ -+health_check @hc | uuidfilt], [0], [<0> -+]) -+wait_row_count Service_Monitor 2 -+ -+# Set the service monitor for sw0-p1 and sw1-p1 to online -+sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1) -+sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1) -+ -+ovn-sbctl set service_monitor $sm_sw0_p1 status=offline -+ovn-sbctl set service_monitor $sm_sw1_p1 status=offline -+ -+AT_CAPTURE_FILE([sbflows12]) -+OVS_WAIT_FOR_OUTPUT( -+ [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};) -+]) -+ - AT_CLEANUP - - AT_SETUP([ovn -- Load balancer VIP in NAT entries]) -@@ -1704,7 +1743,7 @@ 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 -+check ovn-nbctl --wait=sb sync - - AS_BOX([1]) - -@@ -1713,28 +1752,12 @@ 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); };) -+AT_CHECK( -+ [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl -+sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) -+sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) - ]) - - AS_BOX([2]) -@@ -1746,22 +1769,11 @@ 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); };) -+AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl -+sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) - ]) - - AS_BOX([3]) -@@ -1773,42 +1785,19 @@ 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_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl -+sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) - ]) - - AT_CLEANUP -@@ -1818,20 +1807,25 @@ AT_KEYWORDS([acl log meter fair]) - ovn_start - - check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add sw1 - 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 lsp-add sw1 sw1-p3 -- lsp-set-addresses sw1-p3 "50:54:00:00:00:03 10.0.0.13" -+check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 sw1-p3 - - 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 -+check ovn-nbctl acl-add pg0 to-lport 1002 'outport == "pg0" && ip4.src == 10.0.0.11' drop - - 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) -+acl3=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.11' | 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 set acl $acl3 log=true severity=info meter=meter_me name=acl_three - check ovn-nbctl --wait=sb sync - - check_row_count nb:meter 1 -@@ -1840,8 +1834,9 @@ check_column meter_me nb:meter name - check_acl_lflow() { - acl_log_name=$1 - meter_name=$2 -+ ls=$3 - # echo checking that logical flow for acl log $acl_log_name has $meter_name -- AT_CHECK([ovn-sbctl lflow-list | grep ls_out_acl | \ -+ AT_CHECK([ovn-sbctl lflow-list $ls | grep ls_out_acl | \ - grep "\"${acl_log_name}\"" | \ - grep -c "meter=\"${meter_name}\""], [0], [1 - ]) -@@ -1857,59 +1852,144 @@ check_meter_by_name() { - - # 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_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - 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_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3} - - # 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 4 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]) -+AT_FAIL_IF([test "$n_meter_bands" != 1 && test "$n_meter_bands" != 4]) - 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} -+check_acl_lflow acl_one meter_me__${acl1} sw0 -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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} -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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 -+check_acl_lflow acl_two meter_me sw0 -+check_acl_lflow acl_three meter_me sw0 -+check_acl_lflow acl_three meter_me sw1 - - # 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} -+check_acl_lflow acl_two meter_me__${acl2} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw0 -+check_acl_lflow acl_three meter_me__${acl3} sw1 - - # 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 meter_me meter_me__${acl3} - check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} - - AT_CLEANUP - -+AT_SETUP([ovn -- ACL skip hints for stateless config]) -+AT_KEYWORDS([acl]) -+ovn_start -+ -+check ovn-nbctl --wait=sb \ -+ -- ls-add ls \ -+ -- lsp-add ls lsp \ -+ -- acl-add ls from-lport 1 "ip" allow \ -+ -- acl-add ls to-lport 1 "ip" allow -+ -+AS_BOX([Check no match on ct_state with stateless ACLs]) -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+]) -+ -+AS_BOX([Check match ct_state with stateful ACLs]) -+check ovn-nbctl --wait=sb \ -+ -- acl-add ls from-lport 2 "udp" allow-related \ -+ -- acl-add ls to-lport 2 "udp" allow-related -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+]) -+ -+AS_BOX([Check match ct_state with load balancer]) -+check ovn-nbctl --wait=sb \ -+ -- acl-del ls from-lport 2 "udp" \ -+ -- acl-del ls to-lport 2 "udp" \ -+ -- lb-add lb "10.0.0.1" "10.0.0.2" \ -+ -- ls-lb-add ls lb -+ -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+]) -+ -+AT_CLEANUP -+ - AT_SETUP([datapath requested-tnl-key]) - AT_KEYWORDS([requested tnl tunnel key keys]) - ovn_start -@@ -2092,6 +2172,12 @@ 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__: Set hairpin_snat_ip on lb1 and check that SB DB is updated." -+check ovn-nbctl --wait=sb set Load_Balancer lb1 options:hairpin_snat_ip="42.42.42.42 4242::4242" -+check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 options='{hairpin_orig_tuple="true", hairpin_snat_ip="42.42.42.42 4242::4242"}' -+ - echo - echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's load_balancers are updated accordingly." - -@@ -2100,6 +2186,35 @@ check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw - - AT_CLEANUP - -+AT_SETUP([ovn -- LS load balancer hairpin logical flows]) -+ovn_start -+ -+check ovn-nbctl \ -+ -- ls-add sw0 \ -+ -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \ -+ -- ls-lb-add sw0 lb0 -+ -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl -+ table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;) -+ table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl -+ table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;) -+ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;) -+ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;) -+ table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;) -+]) -+ -+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl -+ table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;) -+ table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;) -+]) -+ -+AT_CLEANUP -+ - AT_SETUP([ovn -- logical gatapath groups]) - AT_KEYWORDS([use_logical_dp_groups]) - ovn_start -@@ -2173,3 +2288,498 @@ 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 -+ -+AT_SETUP([ovn -- Router policies - ECMP reroute]) -+AT_KEYWORDS([router policies ecmp reroute]) -+ovn_start -+ -+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" -+ -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl lsp-add sw1 sw1-port1 -+check ovn-nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3" -+ -+# Create a logical router and attach both logical switches -+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 1000::a/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 00:00:00:00:ff:01 -+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 2000::a/64 -+check ovn-nbctl lsp-add sw1 sw1-lr0 -+check ovn-nbctl lsp-set-type sw1-lr0 router -+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 -+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr-sw1 -+ -+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 -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102 -+ -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = 0; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.4" reroute 172.168.0.101,172.168.0.102,172.168.0.103 -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.5" reroute 172.168.0.110 -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.3" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.4" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb add logical_router_policy . nexthops "2000\:\:b" -+ovn-sbctl dump-flows lr0 > lr0flows3 -+AT_CAPTURE_FILE([lr0flows3]) -+ -+AT_CHECK([grep "lr_in_policy" lr0flows3 | \ -+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \ -+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl -+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;) -+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;) -+]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check BFD config propagation to SBDB]) -+AT_KEYWORDS([northd-bfd]) -+ovn_start -+ -+check ovn-nbctl --wait=sb lr-add r0 -+for i in $(seq 1 5); do -+ check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i 00:00:00:00:00:0$i 192.168.$i.1/24 -+ check ovn-nbctl --wait=sb ls-add sw$i -+ check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0 -+ check ovn-nbctl --wait=sb lsp-set-type sw$i-r0 router -+ check ovn-nbctl --wait=sb lsp-set-options sw$i-r0 router-port=r0-sw$i -+ check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i -+done -+ -+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10) -+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20 -+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down -+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0 -+ -+check_column 10 bfd detect_mult logical_port=r0-sw1 -+check_column "192.168.10.2" bfd dst_ip logical_port=r0-sw1 -+check_column 250 bfd min_rx logical_port=r0-sw1 -+check_column 250 bfd min_tx logical_port=r0-sw1 -+check_column admin_down bfd status logical_port=r0-sw1 -+ -+check_column 20 bfd detect_mult logical_port=r0-sw2 -+check_column "192.168.20.2" bfd dst_ip logical_port=r0-sw2 -+check_column 500 bfd min_rx logical_port=r0-sw2 -+check_column 500 bfd min_tx logical_port=r0-sw2 -+check_column admin_down bfd status logical_port=r0-sw2 -+ -+check_column 5 bfd detect_mult logical_port=r0-sw3 -+check_column "192.168.30.2" bfd dst_ip logical_port=r0-sw3 -+check_column 1000 bfd min_rx logical_port=r0-sw3 -+check_column 1000 bfd min_tx logical_port=r0-sw3 -+check_column admin_down bfd status logical_port=r0-sw3 -+ -+uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1) -+check ovn-nbctl set bfd $uuid min_tx=1000 -+check ovn-nbctl set bfd $uuid min_rx=1000 -+check ovn-nbctl set bfd $uuid detect_mult=100 -+ -+uuid_2=$(fetch_column nb:bfd _uuid logical_port=r0-sw2) -+check ovn-nbctl clear bfd $uuid_2 min_rx -+check_column 1000 bfd min_rx logical_port=r0-sw2 -+ -+check_column 1000 bfd min_tx logical_port=r0-sw1 -+check_column 1000 bfd min_rx logical_port=r0-sw1 -+check_column 100 bfd detect_mult logical_port=r0-sw1 -+ -+check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.10.2 -+check_column down bfd status logical_port=r0-sw1 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0]) -+ -+check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.20.2 -+check_column down bfd status logical_port=r0-sw2 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.20.2 | grep -q bfd],[0]) -+ -+check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw5 -+check_column down bfd status logical_port=r0-sw5 -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.50.2 | grep -q bfd],[0]) -+ -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+check_column admin_down bfd status logical_port=r0-sw1 -+ -+ovn-nbctl destroy bfd $uuid -+check_row_count bfd 3 -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check LSP attached to multiple LS]) -+ovn_start -+ -+check ovn-nbctl ls-add ls1 \ -+ -- ls-add ls2 \ -+ -- lsp-add ls1 p1 -+check ovn-nbctl --wait=sb sync -+ -+uuid=$(fetch_column nb:Logical_Switch_Port _uuid name=p1) -+check ovn-nbctl set Logical_Switch ls2 ports=$uuid -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check LRP attached to multiple LR]) -+ovn_start -+ -+check ovn-nbctl lr-add lr1 \ -+ -- lr-add lr2 \ -+ -- lrp-add lr1 p1 00:00:00:00:00:01 10.0.0.1/24 -+check ovn-nbctl --wait=sb sync -+ -+uuid=$(fetch_column nb:Logical_Router_Port _uuid name=p1) -+check ovn-nbctl set Logical_Router lr2 ports=$uuid -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical router port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- check duplicate LSP/LRP]) -+ovn_start -+ -+check ovn-nbctl ls-add ls \ -+ -- lsp-add ls p1 \ -+ -- lr-add lr \ -+ -- lrp-add lr p1 00:00:00:00:00:01 10.0.0.1/24 -+check ovn-nbctl --wait=sb sync -+ -+AT_CHECK([grep -qE 'duplicate logical.*port p1' northd/ovn-northd.log], [0]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- Port_Binding.up backwards compatibility]) -+ovn_start -+ -+ovn-nbctl ls-add ls1 -+ovn-nbctl --wait=sb lsp-add ls1 lsp1 -+ -+# Simulate the fact that lsp1 had been previously bound on hv1 by an -+# ovn-controller running an older version. -+ovn-sbctl \ -+ --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \ -+ -- --id=@c create chassis name=hv1 encaps=@e \ -+ -- set Port_Binding lsp1 chassis=@c -+ -+wait_for_ports_up lsp1 -+ -+# Simulate the fact that hv1 is aware of Port_Binding.up, ovn-northd -+# should transition the port state to down. -+check ovn-sbctl set chassis hv1 other_config:port-up-notif=true -+wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1 -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers]) -+ovn_start -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add sw1 -+ -+# Create a logical router and attach both logical switches -+check ovn-nbctl lr-add lr0 -+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 -+check ovn-nbctl lsp-add sw0 sw0-lr0 -+check ovn-nbctl lsp-set-type sw0-lr0 router -+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 -+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 -+ -+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 -+check ovn-nbctl lsp-add sw1 sw1-lr0 -+check ovn-nbctl lsp-set-type sw1-lr0 router -+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 -+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 -+ -+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 -+ -+check ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.4:8080 -+check ovn-nbctl lr-lb-add lr0 lb1 -+check ovn-nbctl set logical_router lr0 options:chassis=ch1 -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CAPTURE_FILE([lr0flows]) -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+]) -+ -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+]) -+ -+check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4" -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CAPTURE_FILE([lr0flows]) -+ -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(ip4 && ip4.dst == 20.0.0.4), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);) -+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);) -+]) -+ -+check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip" -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CAPTURE_FILE([lr0flows]) -+ -+AT_CHECK([grep "lr_in_ip_input" lr0flows | grep "priority=60" | sort], [0], [dnl -+]) -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) -+]) -+ -+check ovn-nbctl --wait=sb remove logical_router lr0 options chassis -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CAPTURE_FILE([lr0flows]) -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+]) -+ -+check ovn-nbctl set logical_router lr0 options:chassis=ch1 -+check ovn-nbctl --wait=sb add logical_router_port lr0-sw1 networks "bef0\:\:1/64" -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CAPTURE_FILE([lr0flows]) -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) -+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);) -+]) -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- FDB cleanup]) -+ -+ovn_start -+ -+ovn-nbctl ls-add sw0 -+ovn-nbctl lsp-add sw0 sw0-p1 -+ovn-nbctl lsp-add sw0 sw0-p2 -+ovn-nbctl lsp-add sw0 sw0-p3 -+ -+ovn-nbctl ls-add sw1 -+ovn-nbctl lsp-add sw1 sw1-p1 -+ovn-nbctl lsp-add sw1 sw1-p2 -+ovn-nbctl --wait=sb lsp-add sw1 sw1-p3 -+ -+sw0_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0) -+sw1_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw1) -+sw0p1_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p1) -+sw0p2_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p2) -+sw1p1_key=$(fetch_column port_binding tunnel_key logical_port=sw1-p1) -+ -+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:01" dp_key=$sw0_key port_key=$sw0p1_key -+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:02" dp_key=$sw0_key port_key=$sw0p1_key -+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:03" dp_key=$sw0_key port_key=$sw0p2_key -+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:01" dp_key=$sw1_key port_key=$sw1p1_key -+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:02" dp_key=$sw1_key port_key=$sw1p1_key -+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key port_key=$sw1p1_key -+ -+wait_row_count FDB 6 -+ -+ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key port_key=10 -+wait_row_count FDB 6 -+ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=4 port_key=10 -+wait_row_count FDB 6 -+ -+ovn-nbctl --wait=sb ls-del sw1 -+wait_row_count FDB 3 -+ -+ovn-nbctl lsp-del sw0-p3 -+wait_row_count FDB 3 -+ -+ovn-nbctl lsp-del sw0-p1 -+wait_row_count FDB 1 -+ -+check_column '00:00:00:00:00:03' FDB mac -+ovn-sbctl list fdb -+ -+check_column $sw0_key FDB dp_key -+check_column $sw0p2_key FDB port_key -+ -+ovn-nbctl --wait=sb lsp-add sw0-p1 -+wait_row_count FDB 1 -+ -+ovn-nbctl lsp-del sw0-p2 -+ovn-nbctl lsp-add sw0-p2 -+wait_row_count FDB 0 -+ -+ovn-sbctl list FDB -+ -+AT_CLEANUP -+ -+AT_SETUP([ovn -- HA chassis group cleanup for external port ]) -+ovn_start -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-type sw0-p1 external -+ -+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1 -+check ovn-sbctl chassis-add ch2 geneve 127.0.0.2 -+ -+check ovn-nbctl ha-chassis-group-add hagrp1 -+check ovn-nbctl ha-chassis-group-add-chassis hagrp1 ch1 20 -+check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 10 -+ -+ha_grp1_uuid=$(fetch_column nb:ha_chassis_group _uuid) -+echo "ha grp1 uuid = $ha_grp1_uuid" -+ovn-nbctl list ha_chassis_group -+check ovn-nbctl set logical_switch_port sw0-p1 ha_chassis_group=$ha_grp1_uuid -+ -+wait_row_count ha_chassis_group 1 -+check ovn-nbctl clear logical_switch_port sw0-p1 ha_chassis_group -+wait_row_count ha_chassis_group 0 -+ -+check ovn-nbctl set logical_switch_port sw0-p1 ha_chassis_group=$ha_grp1_uuid -+wait_row_count ha_chassis_group 1 -+sb_ha_grp1_uuid=$(fetch_column ha_chassis_group _uuid) -+ -+echo -+echo "__file__:__line__:Check that port_binding sw0-p1 has ha_chassis_group set" -+ -+check_column "$sb_ha_grp1_uuid" Port_Binding ha_chassis_group logical_port=sw0-p1 -+ -+AS_BOX([Clear ha_chassis_group for sw0-p1 and reset port type to normal port in the same txn]) -+ -+check ovn-nbctl clear logical_switch_port sw0-p1 ha_chassis_group -- set logical_switch_port sw0-p1 'type=""' -+wait_row_count ha_chassis_group 0 -+check_column "" Port_Binding chassis logical_port=sw0-p1 -+ -+AT_CLEANUP -diff --git a/tests/ovn-ofctrl-seqno.at b/tests/ovn-ofctrl-seqno.at -new file mode 100644 -index 000000000..59dfea947 ---- /dev/null -+++ b/tests/ovn-ofctrl-seqno.at -@@ -0,0 +1,226 @@ -+# -+# Unit tests for the controller/ofctrl-seqno.c module. -+# -+AT_BANNER([OVN unit tests - ofctrl-seqno]) -+ -+AT_SETUP([ovn -- unit test -- ofctrl-seqno add-type]) -+ -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 1], [0], [dnl -+0 -+]) -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 2], [0], [dnl -+0 -+1 -+]) -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 3], [0], [dnl -+0 -+1 -+2 -+]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- unit test -- ofctrl-seqno ack-seqnos]) -+ -+AS_BOX([No Ack Batching, 1 seqno type]) -+n_types=1 -+n_app_seqnos=3 -+app_seqnos="40 41 42" -+ -+n_acks=1 -+acks="1" -+echo "ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+]) -+ -+n_acks=2 -+acks="1 2" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 42 -+]) -+ -+AS_BOX([Ack Batching, 1 seqno type]) -+n_types=1 -+n_app_seqnos=3 -+app_seqnos="40 41 42" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+]) -+ -+n_acks=2 -+acks="1 2" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 40 -+ 41 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 3 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+]) -+ -+AS_BOX([No Ack Batching, 2 seqno types]) -+n_types=2 -+n_app_seqnos=3 -+app_seqnos1="40 41 42" -+app_seqnos2="50 51 52" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+ofctrl-seqno-type: 0 -+ last-acked 41 -+ 41 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="4 5 6" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 50 -+ 50 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ofctrl-seqno-type: 1 -+ last-acked 51 -+ 51 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ofctrl-seqno-type: 1 -+ last-acked 52 -+ 52 -+]) -+ -+AS_BOX([Ack Batching, 2 seqno types]) -+n_types=2 -+n_app_seqnos=3 -+app_seqnos1="40 41 42" -+app_seqnos2="50 51 52" -+ -+n_acks=1 -+acks="1" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 40 -+ 40 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="1 2 3" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 0 -+]) -+ -+n_acks=3 -+acks="4 5 6" -+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \ -+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \ -+ ${n_acks} ${acks}], [0], [dnl -+ofctrl-seqno-req-cfg: 6 -+ofctrl-seqno-type: 0 -+ last-acked 42 -+ 40 -+ 41 -+ 42 -+ofctrl-seqno-type: 1 -+ last-acked 52 -+ 50 -+ 51 -+ 52 -+]) -+AT_CLEANUP -diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at -index 6cc5b2174..e510c6cef 100644 ---- a/tests/ovn-performance.at -+++ b/tests/ovn-performance.at -@@ -232,37 +232,32 @@ AT_SETUP([ovn -- ovn-controller incremental processing]) - - ovn_start - net_add n1 --for i in 1 2; do -+for i in `seq 1 5`; do - sim_add hv$i - as hv$i - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.$i --done -- --for i in 1 2 3; do -- sim_add gw$i -- as gw$i -- ovs-vsctl add-br br-phys -- ovs-vsctl add-br br-ex -- ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex" -- j=$((i + 2)) -- ovn_attach n1 br-phys 192.168.0.$j -- ip link add vgw$i type dummy -- ovs-vsctl add-port br-ex vgw$i -+ if [[ $i -ge 3 ]] ; then -+ ovs-vsctl add-br br-ex -+ ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex" -+ ip link add vgw$i type dummy -+ ovs-vsctl add-port br-ex vgw$i -+ fi - done - - # Wait for the tunnel ports to be created and up. - # Otherwise this may affect the lflow_run count. -+for i in `seq 1 5`; do -+ for j in `seq 1 5`; do -+ if [[ $i -ne $j ]] ; then -+ OVS_WAIT_UNTIL([ -+ test $(as hv$i ovs-vsctl list interface ovn-hv$j-0 | \ -+ grep -c tunnel_egress_iface_carrier=up) -eq 1 -+ ]) -+ fi -+ done -+done - --OVS_WAIT_UNTIL([ -- test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \ --grep tunnel_egress_iface_carrier=up | wc -l) -eq 1 --]) -- --OVS_WAIT_UNTIL([ -- test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \ --grep tunnel_egress_iface_carrier=up | wc -l) -eq 1 --]) - - # Add router lr1 - OVN_CONTROLLER_EXPECT_NO_HIT( -@@ -463,63 +458,63 @@ OVN_CONTROLLER_EXPECT_NO_HIT( - ) - - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 >0 =0 =0], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw1 30 && ovn-nbctl --wait=hv sync] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [=0 =0 >0 =0 =0], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv3 30 && ovn-nbctl --wait=hv sync] - ) - --# After this, BFD should be enabled from hv1 and hv2 to gw1. --# So there should be lflow_run hits in hv1, hv2, gw1 and gw2 -+# After this, BFD should be enabled from hv1 and hv2 to hv3. -+# So there should be lflow_run hits in hv1, hv2, hv3 and hv4 - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 >0 =0], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 20 && ovn-nbctl --wait=hv sync] --) -- --OVN_CONTROLLER_EXPECT_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw3 10 && ovn-nbctl --wait=hv sync] --) -- --# create QoS rule --OVN_CONTROLLER_EXPECT_NO_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 >0 =0], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 20 && ovn-nbctl --wait=hv sync] - ) - - OVN_CONTROLLER_EXPECT_HIT( -- [gw1], [lflow_run], -- [as gw1 ovs-vsctl set interface vgw1 external-ids:ovn-egress-iface=true] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv5 10 && ovn-nbctl --wait=hv sync] - ) - --# Make gw2 master. There is remote possibility that full recompute --# triggers for gw2 after it becomes master. Most of the time --# there will be no recompute. --ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 40 --gw2_ch=$(ovn-sbctl --bare --columns _uuid list chassis gw2) --OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$gw2_ch]) -+# Make hv4 master. There is remote possibility that full recompute -+# triggers for hv1-hv5 after hv4 becomes master because of updates to the -+# ovn-hv$i-0 interfaces. Most of the time there will be no recompute. -+ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 40 -+hv4_ch=$(ovn-sbctl --bare --columns _uuid list chassis hv4) -+OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$hv4_ch]) - - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 =0 >=0 =0], -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>=0 >=0 >=0 >=0 >=0], - [ovn-nbctl --wait=hv sync] - ) - --# Delete gw2 from gateway chassis -+# Delete hv4 from gateway chassis - OVN_CONTROLLER_EXPECT_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw2 && ovn-nbctl --wait=hv sync] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv4 && ovn-nbctl --wait=hv sync] - ) - --# Delete gw1 from gateway chassis --# After this, the BFD should be disabled entirely as gw3 is the -+# Delete hv3 from gateway chassis -+# After this, the BFD should be disabled entirely as hv5 is the - # only gateway chassis. - OVN_CONTROLLER_EXPECT_HIT_COND( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 =0 >0], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw1] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 =0 >0], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv3] - ) - --# Delete gw3 from gateway chassis. There should be no lflow_run. -+# Delete hv5 from gateway chassis. There should be no lflow_run. - OVN_CONTROLLER_EXPECT_NO_HIT( -- [hv1 hv2 gw1 gw2 gw3], [lflow_run], -- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw3] -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv5] -+) -+ -+# create QoS rule -+OVN_CONTROLLER_EXPECT_NO_HIT( -+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], -+ [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000] -+) -+ -+OVN_CONTROLLER_EXPECT_HIT( -+ [hv3], [lflow_run], -+ [as hv3 ovs-vsctl set interface vgw3 external-ids:ovn-egress-iface=true] - ) - - for i in 1 2; do -diff --git a/tests/ovn.at b/tests/ovn.at -index 2e0bc9c53..bd59c0a77 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -1637,6 +1637,17 @@ tcp_reset { }; - encodes as controller(userdata=00.00.00.0b.00.00.00.00) - has prereqs tcp - -+# sctp_abort -+sctp_abort {eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; -+ formats as sctp_abort { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output; -+ encodes as controller(userdata=00.00.00.18.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) -+ has prereqs sctp -+ -+sctp_abort { }; -+ formats as sctp_abort { drop; }; -+ encodes as controller(userdata=00.00.00.18.00.00.00.00) -+ has prereqs sctp -+ - # 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) -@@ -1807,6 +1818,68 @@ ct_snat_to_vip; - ct_snat_to_vip(foo); - Syntax error at `(' expecting `;'. - -+# bfd packets -+handle_bfd_msg(); -+ encodes as controller(userdata=00.00.00.17.00.00.00.00) -+ -+# put_fdb -+put_fdb(inport, arp.sha); -+ encodes as push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.19.00.00.00.00),pop:NXM_OF_ETH_SRC[] -+ has prereqs eth.type == 0x806 -+ -+put_fdb(inport, eth.src); -+ encodes as controller(userdata=00.00.00.19.00.00.00.00) -+ -+put_fdb(inport, ip4.src); -+ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required. -+ -+# get_fdb -+outport = get_fdb(eth.dst); -+ encodes as set_field:0->reg15,resubmit(,71) -+ -+outport = get_fdb(eth.src); -+ encodes as push:NXM_OF_ETH_DST[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[] -+ -+inport = get_fdb(arp.sha); -+ encodes as push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_REG14[] -+ has prereqs eth.type == 0x806 -+ -+reg0 = get_fdb(arp.tha); -+ encodes as push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_THA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_XXREG0[96..127] -+ has prereqs eth.type == 0x806 -+ -+reg0[1..3] = get_fdb(eth.src); -+ Cannot use 3-bit field reg0[1..3] where 32-bit field is required. -+ -+reg15 = get_fdb(eth.dst); -+ Syntax error at `reg15' expecting field name. -+ -+outport = get_fdb(ip4.dst); -+ Cannot use 32-bit field ip4.dst[0..31] where 48-bit field is required. -+ -+# lookup_fdb -+reg0[0] = lookup_fdb(inport, eth.src); -+ encodes as set_field:0/0x100->reg10,resubmit(,72),move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96] -+ -+reg1[4] = lookup_fdb(outport, eth.dst); -+ encodes as push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_OF_ETH_DST[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[68] -+ -+reg0[0] = lookup_fdb(outport, arp.sha); -+ encodes as push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96] -+ has prereqs eth.type == 0x806 -+ -+reg0 = lookup_fdb(outport, arp.sha); -+ Cannot use 32-bit field reg0[0..31] where 1-bit field is required. -+ -+outport = lookup_fdb(outport, arp.sha); -+ Cannot use string field outport where numeric field is required. -+ -+reg1[1] = lookup_fdb(outport, ip4.src); -+ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required. -+ -+reg1[1] = lookup_fdb(ip4.src, eth.src); -+ Cannot use numeric field ip4.src where string field is required. -+ - # Miscellaneous negative tests. - ; - Syntax error at `;'. -@@ -1837,53 +1910,46 @@ ovn_start - # Turn on port security on all the vifs except vif[123]1. - # Make vif13, vif2[23], vif3[123] destinations for unknown MACs. - # Add some ACLs for Ethertypes 1234, 1235, 1236. --ovn-nbctl ls-add lsw0 -+check ovn-nbctl ls-add lsw0 - net_add n1 - 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 -- ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j -- ovn-nbctl lsp-add lsw0 lp$i$j -+ check ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j -+ check ovn-nbctl lsp-add lsw0 lp$i$j - 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 -+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown - else - if test $j = 3; then - ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j" - else - ip_addrs="192.168.0.$i$j" - fi -- ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs" -- ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j -+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs" -+ check ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j - fi - done - done --ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop --ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop --ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop -+check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop -+check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop -+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop - ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\" --ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop -- --get_lsp_uuid () { -- ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }' --} -+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop - --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 pg-add pg1 lp22 lp33 -+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop - check ovn-nbctl --wait=hv sync -+wait_for_ports_up - - # 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). - OVN_POPULATE_ARP - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -- - # Make sure there is no attempt to adding duplicated flows by ovn-controller - AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) - AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"]) -@@ -2078,11 +2144,7 @@ done - - # set address for lp13 with invalid characters. - # lp13 should be configured with only 192.168.0.13. --ovn-nbctl lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13" -- --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+check ovn-nbctl --wait=hv lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13" - - sip=`ip_to_hex 192 168 0 11` - tip=`ip_to_hex 192 168 0 13` -@@ -2155,7 +2217,11 @@ for i in 1 2; do - done - done - --sleep 1 -+# Wait for bindings to take effect. -+wait_row_count Port_Binding 1 logical_port=lp11 'encap!=[[]]' -+wait_row_count Port_Binding 1 logical_port=lp12 'encap!=[[]]' -+wait_row_count Port_Binding 1 logical_port=lp21 'encap!=[[]]' -+wait_row_count Port_Binding 1 logical_port=lp22 'encap!=[[]]' - - # dump port bindings; since we have vxlan and geneve tunnels, we expect the - # ports to be bound to geneve tunnels. -@@ -2175,9 +2241,8 @@ check_row_count Port_Binding 1 logical_port=lp22 encap=$encap_rec - # for ARP resolution). - OVN_POPULATE_ARP - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Make sure there is no attempt to adding duplicated flows by ovn-controller - AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) -@@ -2567,6 +2632,7 @@ for i in 1 2; do - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) - done - done -+wait_for_ports_up - ovn-nbctl --wait=sb sync - ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([sbflows]) -@@ -2733,6 +2799,7 @@ for hv in 1 2; do - done - - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - - ovn-sbctl dump-flows > sbflows -@@ -2866,6 +2933,7 @@ for hv in 1 2; do - done - - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - ovn-nbctl show - ovn-sbctl dump-flows > sbflows -@@ -3003,6 +3071,7 @@ for i in 1 2; do - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) - done - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - ovn-nbctl show - ovn-sbctl dump-flows > sbflows -@@ -3213,6 +3282,7 @@ for tag in 10 20; do - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) - done - done -+wait_for_ports_up - ovn-nbctl --wait=sb sync - ovn-sbctl dump-flows - -@@ -3371,9 +3441,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-t - # for ARP resolution). - OVN_POPULATE_ARP - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # test_packet INPORT DST SRC ETHTYPE OUTPORT... - # -@@ -3537,9 +3606,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-t - # for ARP resolution). - OVN_POPULATE_ARP - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # test_packet INPORT DST SRC ETHTYPE OUTPORT... - # -@@ -3728,6 +3796,7 @@ for i in 1 2 3; do - done - done - -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - # Pre-populate the hypervisors' ARP tables so that we don't lose any -@@ -3735,9 +3804,6 @@ check ovn-nbctl --wait=hv sync - # for ARP resolution). - OVN_POPULATE_ARP - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. -- - # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... - # - # This shell function causes a packet to be received on INPORT. The packet's -@@ -4134,8 +4200,8 @@ done - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... - # -@@ -4307,8 +4373,8 @@ done - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Given the name of a logical port, prints the name of the hypervisor - # on which it is located. -@@ -4740,8 +4806,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Packet to send. - packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac && -@@ -4851,9 +4917,8 @@ ovs-vsctl -- add-port br-int vif2 -- \ - ofport-request=1 - - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Send ip packets between the two ports. - -@@ -4885,11 +4950,7 @@ as hv1 ovs-ofctl dump-flows br-int - - - #Disable router R1 --ovn-nbctl set Logical_Router R1 enabled=false -- --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+ovn-nbctl --wait=hv set Logical_Router R1 enabled=false - - echo "---------SB dump-----" - ovn-sbctl list datapath_binding -@@ -4964,10 +5025,11 @@ ovs-vsctl -- add-port br-int vif2 -- \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+ovs-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - - # Send ip packets between the two ports. - -@@ -4979,39 +5041,11 @@ dst_ip=`ip_to_hex 172 16 1 2` - packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - -- --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 logical_flow --echo "---------------------" -- --echo "------ hv1 dump ----------" --as hv1 ovs-ofctl dump-flows br-int -- - #Disable router R1 --ovn-nbctl set Logical_Router R1 enabled=false -- --echo "---------SB dump-----" --ovn-sbctl list datapath_binding --echo "---------------------" --ovn-sbctl list logical_flow --echo "---------------------" -- --echo "------ hv1 dump ----------" --as hv1 ovs-ofctl dump-flows br-int -+ovn-nbctl --wait=hv set Logical_Router R1 enabled=false - --# Allow some time for the disabling of logical router R1 to propagate. --# XXX This should be more systematic. --sleep 1 -+ovs-sbctl dump-flows > sbflows2 -+AT_CAPTURE_FILE([sbflows2]) - - as hv1 ovs-appctl netdev-dummy/receive vif1 $packet - -@@ -5114,8 +5148,11 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - - # Send ip packets between foo1 and alice1 - src_mac="f00000010203" -@@ -5133,25 +5170,6 @@ dst_ip=`ip_to_hex 172 16 2 2` - packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - --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 "---------------------" -- --echo "------ hv1 dump ----------" --as hv1 ovs-ofctl dump-flows br-int --echo "------ hv2 dump ----------" --as hv2 ovs-ofctl dump-flows br-int -- - # Packet to Expect at bob1 - src_mac="000000010205" - dst_mac="f00000010205" -@@ -5333,8 +5351,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Send ip packets between foo1 and alice1 - src_mac="f00000010203" -@@ -5469,7 +5487,8 @@ as hv1 ovs-appctl vlog/set dbg - - OVN_POPULATE_ARP - --sleep 2 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - as hv1 ovs-vsctl show - -@@ -6189,7 +6208,8 @@ ovs-vsctl -- add-port br-int hv1-vif5 -- \ - - OVN_POPULATE_ARP - --sleep 2 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - trim_zeros() { - sed 's/\(00\)\{1,\}$//' -@@ -6469,10 +6489,8 @@ ovn-nbctl lsp-add foo foo1 \ - ovn-nbctl lsp-add alice alice1 \ - -- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" - -- --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 2 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Send ip packets between foo1 and alice1 - src_mac="f00000010203" -@@ -6535,7 +6553,8 @@ ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \ - R2 static_routes @lrt - - # Wait for ovn-controller to catch up. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Send the packet again. - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -@@ -6605,11 +6624,11 @@ ovs-vsctl -- add-port br-int vif2 -- \ - options:rxq_pcap=hv1/vif2-rx.pcap \ - ofport-request=1 - -- - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -- -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ovn-nbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - - for i in 1 2; do - : > vif$i.expected -@@ -6764,10 +6783,6 @@ ovs-vsctl -- add-port br-int vif3 -- \ - options:rxq_pcap=pbr-hv/vif3-rx.pcap \ - ofport-request=1 - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -- - ls1_ro_mac=00:00:00:01:02:f1 - ls1_ro_ip=192.168.1.1 - -@@ -6952,10 +6967,6 @@ ovs-vsctl -- add-port br-int vif3 -- \ - options:rxq_pcap=pbr-hv/vif3-rx.pcap \ - ofport-request=1 - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -- - ls1_ro_mac=00:00:00:01:02:f1 - ls1_ro_ip=2001::1 - -@@ -7158,6 +7169,7 @@ ovn-nbctl lsp-del lp1 - ovn-nbctl ls-del ls1 - - # wait for earlier changes to take effect -+wait_for_ports_up - check ovn-nbctl --wait=sb sync - - # ensure OF rules are no longer present. There used to be a bug here. -@@ -7204,14 +7216,15 @@ ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-r - ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. -+# XXX The "sleep" here seems to be essential for ovn-northd-ddlog, -+# which may indicate that it needs improvement. -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - sleep 1 - --# Given the name of a logical port, prints the name of the hypervisor --# on which it is located. --vif_to_hv() { -- echo hv1${1%?} --} -+ovn-nbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) -+ - for i in 1 2; do - : > $i.expected - done -@@ -7225,11 +7238,6 @@ na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffe - as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet - echo $na_packet >> 1.expected - --echo "------ hv1 dump ------" --as hv1 ovs-vsctl show --as hv1 ovs-ofctl -O OpenFlow13 show br-int --as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int -- - for i in 1 2; do - OVN_CHECK_PACKETS([hv1/vif$i-tx.pcap], [$i.expected]) - done -@@ -7250,9 +7258,7 @@ ovn_attach n1 br-phys 192.168.0.1 - - row=`ovn-nbctl create Address_Set name=set1 addresses=\"1.1.1.1\"` - ovn-nbctl set Address_Set $row name=set1 addresses=\"1.1.1.1,1.1.1.2\" --ovn-nbctl destroy Address_Set $row -- --sleep 1 -+ovn-nbctl --wait=hv destroy Address_Set $row - - # A bug previously existed in the address set support code - # that caused ovn-controller to crash after an address set -@@ -7640,8 +7646,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ - ofport-request=3 - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Send ip packets between foo1 and foo2 - src_mac="0a0000a80103" -@@ -7848,32 +7854,11 @@ ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \ - ofport-request=2 - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -- --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 "---------------------" -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - --echo "------Flows dump-----" --as hv1 --ovs-ofctl dump-flows --echo "---------------------" -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - - src_mac="f00000000003" - dst_mac="f00000000001" -@@ -8256,18 +8241,18 @@ as hv1 - AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1]) - - # On hv1, check that there are no flows outputting bcast to tunnel --OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) -+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0]) - - # On hv2, check that no flow outputs bcast to tunnel to hv1. - as hv2 --OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) -+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0]) - - # Now bind vif2 on hv2. - AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2]) - - # At this point, the broadcast flow on vif2 should be deleted. --# because, there is now a localnet vif bound (table=32 programming logic) --OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0]) -+# because, there is now a localnet vif bound (table=37 programming logic) -+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0]) - - # Verify that the local net patch port exists on hv2. - OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1]) -@@ -8319,6 +8304,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2 - ovn-nbctl lsp-set-addresses lp1 $lp1_mac - ovn-nbctl lsp-set-addresses lp2 $lp2_mac - ovn-nbctl --wait=sb sync -+wait_for_ports_up - - ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop - ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000 'tcp.dst==81' drop -@@ -8425,6 +8411,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2 - ovn-nbctl lsp-set-addresses lp1 $lp1_mac - ovn-nbctl lsp-set-addresses lp2 $lp2_mac - ovn-nbctl --wait=sb sync -+wait_for_ports_up - - - # Add an ACL that rate-limits logs at 10 per second. -@@ -8515,6 +8502,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2 - ovn-nbctl lsp-set-addresses lp1 $lp1_mac - ovn-nbctl lsp-set-addresses lp2 $lp2_mac - ovn-nbctl --wait=sb sync -+wait_for_ports_up - - ovn-appctl -t ovn-controller vlog/set file:dbg - -@@ -8562,6 +8550,7 @@ check ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.1 - 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 -+wait_for_ports_up lp1 lp2 - - AT_CAPTURE_FILE([trace]) - ovn_trace () { -@@ -8960,8 +8949,8 @@ ovs-vsctl -- add-port br-int vm2 -- \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # 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) -@@ -8986,8 +8975,10 @@ bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2) - AT_CHECK([test -z $bar2_zoneid]) - - # Add back bar2 -+wait_for_ports_up - ovn-nbctl lsp-add bar bar2 vm2 1 \ - -- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3" -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2) -@@ -9126,6 +9117,13 @@ OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)]) - OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)]) - OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)]) - -+# Move VM1 to a new logical switch. -+ovn-nbctl ls-add mgmt2 -+ovn-nbctl lsp-del vm1 -- lsp-add mgmt2 vm1 -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)]) -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)]) -+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)]) -+ - as hv1 ovs-vsctl del-port vm1 - OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up vm1)]) - OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up foo1)]) -@@ -9267,8 +9265,8 @@ ovn-nbctl --wait=hv lsp-add bob bob1 \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - trim_zeros() { - sed 's/\(00\)\{1,\}$//' -@@ -9375,7 +9373,8 @@ ovs-vsctl -- add-port br-int hv1-vif2 -- \ - ofport-request=2 - - OVN_POPULATE_ARP --sleep 2 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - as hv1 ovs-vsctl show - - echo "*************************" -@@ -9868,6 +9867,7 @@ 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 - -+wait_for_ports_up - AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore]) - - ovn-sbctl dump-flows > sbflows -@@ -9935,13 +9935,9 @@ test_ip_packet() - fi - as ext1 reset_pcap_file ext1-vif1 ext1/vif1 - -- sleep 1 -- - # Resend packet from foo1 to outside1 - check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - -- sleep 1 -- - AT_CAPTURE_FILE([exp]) - AT_CAPTURE_FILE([rcv]) - check_packets() { -@@ -10131,6 +10127,7 @@ 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 - -+wait_for_ports_up - check ovn-nbctl --wait=sb sync - - ovn-sbctl dump-flows > sbflows -@@ -10143,8 +10140,7 @@ 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 -+check ovn-nbctl --wait=hv sync - - reset_pcap_file() { - local iface=$1 -@@ -10342,6 +10338,7 @@ 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. -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - (echo "---------NB dump-----" -@@ -10386,12 +10383,12 @@ AT_CAPTURE_FILE([hv2flows]) - - 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 -+ grep table=38 hv1flows | grep =0x3,metadata=0x1 | wc -l -+ grep table=38 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 -+ grep table=37 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l -+ grep table=37 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 -@@ -10461,6 +10458,7 @@ OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \ - grep "Port patch-br-int-to-ln-alice" | wc -l`]) - - dnl Allow some time for ovn-controller to catch up. -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - # ARP for router IP address from outside1 -@@ -10534,8 +10532,8 @@ ovn-nbctl lsp-add foo foo2 \ - -- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3" - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - : > hv1-vif2.expected - -@@ -10624,10 +10622,6 @@ AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown]) - AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) - AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1]) - --# Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 2 -- - # Expect no packets when hv2 bridge-mapping is not present - : > packets - OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [packets]) -@@ -10847,50 +10841,54 @@ 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 - --# Allow some time for ovn-northd and ovn-controller to catch up. --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). - OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \ - grep rp-foo | grep -v is_chassis_resident | grep priority=50 -c`]) - - # Set the option 'reside-on-redirect-chassis' for foo --check ovn-nbctl --wait=hv set logical_router_port foo options:reside-on-redirect-chassis=true -+check ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true -+wait_for_ports_up -+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 with the condition is_chassis_redirect. --ovn-sbctl dump-flows foo --OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \ -+ovn-sbctl dump-flows foo > sbflows -+AT_CAPTURE_FILE([sbflows]) -+OVS_WAIT_UNTIL([test 1 = `grep ls_in_l2_lkup sbflows | \ - grep rp-foo | grep is_chassis_resident | grep priority=50 -c`]) - --echo "---------NB dump-----" --ovn-nbctl show --echo "---------------------" --ovn-nbctl list logical_router --echo "---------------------" --ovn-nbctl list nat --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 --echo "---------------------" -+(echo "---------NB dump-----" -+ ovn-nbctl show -+ echo "---------------------" -+ ovn-nbctl list logical_router -+ echo "---------------------" -+ ovn-nbctl list nat -+ echo "---------------------" -+ ovn-nbctl list logical_router_port -+ echo "---------------------") > nbdump -+AT_CAPTURE_FILE([nbdump]) -+ -+(echo "---------SB dump-----" -+ ovn-sbctl list datapath_binding -+ echo "---------------------" -+ ovn-sbctl list port_binding -+ echo "---------------------" -+ ovn-sbctl list chassis -+ echo "---------------------") > sbdump -+AT_CAPTURE_FILE([sbdump]) - - for chassis in hv1 hv2 hv3; do -- as $chassis -- echo "------ $chassis dump ----------" -- ovs-vsctl show br-int -- ovs-ofctl show br-int -- ovs-ofctl dump-flows br-int -- echo "--------------------------" -+ (as $chassis -+ echo "------ $chassis dump ----------" -+ ovs-vsctl show -+ ovs-ofctl show br-int -+ ovs-ofctl dump-flows br-int -+ echo "--------------------------") > ${chassis}dump - done -+AT_CAPTURE_FILE([hv1dump]) -+AT_CAPTURE_FILE([hv2dump]) -+AT_CAPTURE_FILE([hv3dump]) - - foo1_ip=$(ip_to_hex 192 168 1 2) - gw_ip=$(ip_to_hex 172 16 1 6) -@@ -10940,8 +10938,8 @@ as hv3 reset_pcap_file hv3-vif1 hv3/vif1 - as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet - sleep 2 - --# On hv1, table 32 check that no packet goes via the tunnel port --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \ -+# On hv1, table 37 check that no packet goes via the tunnel port -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=37 \ - | grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0 - ]]) - -@@ -11083,8 +11081,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ - ofport-request=3 - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - reset_pcap_file() { - local iface=$1 -@@ -11337,8 +11335,11 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - - # Send ip packets between foo1 and alice1 - src_mac="f00000010203" -@@ -11402,6 +11403,7 @@ for i in 1 2; do - OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup]) - done - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - ovn-sbctl dump-flows - -@@ -11553,6 +11555,7 @@ ovn-nbctl lsp-set-type ln-outside localnet - ovn-nbctl lsp-set-options ln-outside network_name=phys - - # Allow some time for ovn-northd and ovn-controller to catch up. -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - echo "---------NB dump-----" -@@ -11626,20 +11629,20 @@ echo $hv2_gw1_ofport - echo $hv2_gw2_ofport - - echo "--- hv1 ---" --as hv1 ovs-ofctl dump-flows br-int table=32 -+as hv1 ovs-ofctl dump-flows br-int table=37 - - echo "--- hv2 ---" --as hv2 ovs-ofctl dump-flows br-int table=32 -+as hv2 ovs-ofctl dump-flows br-int table=37 - - 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 | \ -+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ - | wc -l], [0], [1 - ]) - --OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ - | wc -l], [0], [1 - ]) -@@ -11676,15 +11679,16 @@ ovn-nbctl --id=@gc0 create Gateway_Chassis \ - set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' - - -+wait_for_ports_up - 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 | \ -+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \ - | wc -l], [0], [1 - ]) - --OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ - | wc -l], [0], [1 - ]) -@@ -11837,12 +11841,12 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid - wait_row_count HA_Chassis_Group 1 - wait_row_count HA_Chassis 2 - --OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \ - | wc -l], [0], [1 - ]) - --OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ - | wc -l], [0], [1 - ]) -@@ -11894,12 +11898,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis - # Increase the priority of gw2 - ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40 - --OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \ - | wc -l], [0], [1 - ]) - --OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \ -+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \ - grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ - | wc -l], [0], [1 - ]) -@@ -12041,6 +12045,7 @@ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) - AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) - - # wait for earlier changes to take effect -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - reset_pcap_file() { -@@ -12241,6 +12246,7 @@ ovn-nbctl lsp-set-type ln-outside localnet - ovn-nbctl lsp-set-options ln-outside network_name=phys - - # Allow some time for ovn-northd and ovn-controller to catch up. -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - # currently when ovn-controller is restarted, the old entry is deleted -@@ -12878,6 +12884,45 @@ test_tcp_syn_packet() { - check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet - } - -+# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM -+# -+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with -+# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and SCTP_CHKSUM as specified. -+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG. -+# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT chunk generated from the ACL rule hit -+# -+# INPORT is an lport number, e.g. 11 for vif11. -+# HV is a hypervisor number. -+# ETH_SRC and ETH_DST are each 12 hex digits. -+# IPV4_SRC and IPV4_DST are each 8 hex digits. -+# SCTP_SPORT and SCTP_DPORT are 4 hex digits. -+# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits. -+# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits. -+test_sctp_init_packet() { -+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 -+ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11} -+ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13} -+ -+ local ip_ttl=ff -+ local eth_hdr=${eth_dst}${eth_src}0800 -+ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst} -+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum} -+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag} -+ -+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init} -+ -+ local sctp_abort_ttl=3f -+ local reply_eth_hdr=${eth_src}${eth_dst}0800 -+ local reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src} -+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum} -+ local reply_sctp_abort=06000004 -+ -+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort} -+ echo $reply >> vif$inport.expected -+ -+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -+} -+ - # Create hypervisors hv[123]. - # Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. - # Add all of the vifs to a single logical switch sw0. -@@ -12904,8 +12949,6 @@ for i in 1 2 3; do - done - - OVN_POPULATE_ARP --# allow some time for ovn-northd and ovn-controller to catch up. --sleep 1 - - for i in 1 2 3; do - : > vif${i}1.expected -@@ -12916,6 +12959,7 @@ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject - check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject - - # Allow some time for ovn-northd and ovn-controller to catch up. -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - ovn-sbctl dump-flows > sbflows -@@ -12931,6 +12975,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(i - test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 b85f 70e4 - test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 b854 70d9 - -+test_sctp_init_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 8b40 3039 00000001 82112601 b7e5 10fe95b6 -+test_sctp_init_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 00000002 C0379D5A b7e5 39f23aaf -+test_sctp_init_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 00000003 028E263C b7da 7124045b -+ - for i in 1 2 3; do - OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected]) - done -@@ -13062,8 +13110,8 @@ done - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... - # -@@ -13142,10 +13190,6 @@ for is in 1 2 3; do - done - done - --# Allow some time for packet forwarding. --# XXX This can be improved. --sleep 1 -- - # Now check the packets actually received against the ones expected. - for i in 1 2 3; do - for j in 1 2 3; do -@@ -13284,8 +13328,8 @@ done - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - lsp_to_mac() { - echo f0:00:00:00:0${1:0:1}:${1:1:2} -@@ -13391,10 +13435,6 @@ for is in 1 2 3; do - done - done - --# Allow some time for packet forwarding. --# XXX This can be improved. --sleep 1 -- - # Now check the packets actually received against the ones expected. - for i in 1 2 3; do - for j in 1 2 3; do -@@ -13704,6 +13744,7 @@ grep conjunction.*conjunction.*conjunction | wc -l`]) - ovn-nbctl acl-del ls1 to-lport 1001 \ - 'ip4 && ip4.src == $set1 && ip4.dst == $set1' - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.10 actions=conjunction(10,1/2) - # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(11,1/2) -@@ -13725,27 +13766,30 @@ AT_CLEANUP - AT_SETUP([ovn -- Superseding ACLs with conjunction]) - ovn_start - --ovn-nbctl ls-add ls1 -+check ovn-nbctl set nb_global . options:svc_monitor_mac=66:66:66:66:66:66 -+check ovn-nbctl ls-add ls1 - --ovn-nbctl lsp-add ls1 ls1-lp1 \ ---- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01" -+check ovn-nbctl lsp-add ls1 ls1-lp1 \ -+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01" \ -+-- set logical_switch_port ls1-lp1 options:requested-tnl-key=1 - --ovn-nbctl lsp-add ls1 ls1-lp2 \ ---- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02" -+check ovn-nbctl lsp-add ls1 ls1-lp2 \ -+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02" \ -+-- set logical_switch_port ls1-lp1 options:requested-tnl-key=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=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 \ -@@ -13765,7 +13809,8 @@ test_ip() { - 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 -+ check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -+ ovs-appctl ofproto/trace br-int in_port=hv1-vif1 "$packet" > trace - for outport; do - echo $packet >> $outport.expected - done -@@ -13774,19 +13819,51 @@ ${dst_ip}0035111100080000 - 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 - } - - # 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 -+check ovn-nbctl acl-add ls1 to-lport 2 'arp' allow -+check ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop -+check 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 -+check 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 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) -+ -+# 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 -+ -+# Trigger recompute and make sure that the traffic still works as expected. -+as hv1 ovn-appctl -t ovn-controller recompute - - # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. - for src in `seq 1 2`; do -@@ -13814,9 +13891,9 @@ 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 ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' allow -+check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow -+check ovn-nbctl --wait=hv sync - - # Check OVS flows, the less restrictive flows should have been installed. - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -@@ -13858,11 +13935,9 @@ 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 ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' -+check ovn-nbctl --wait=hv sync - - # Check OVS flows, the second less restrictive allow ACL should have been installed. - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -@@ -13878,8 +13953,8 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ - ]) - - # 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 ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1' -+check ovn-nbctl --wait=hv sync - - # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled. - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -@@ -13917,8 +13992,8 @@ $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 ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow -+check ovn-nbctl --wait=hv sync - - # Check OVS flows, the less restrictive flows should have been installed. - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -@@ -13933,6 +14008,29 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ - table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - -+# Add another ACL that overlaps with the existing less restrictive ones. -+check ovn-nbctl acl-add ls1 to-lport 3 'udp || ((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 -+check ovn-nbctl --wait=hv sync -+ -+# Check OVS flows, the same conjunctive flows as above should still be there, -+# with an additional conjunction action. -+# -+# New non-conjunctive flows should be added to match on 'udp'. -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+ grep "priority=1003" | \ -+ sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -+ table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -+ table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -+ table=45, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,46) -+ table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction() -+ table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction() -+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction() -+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=45, priority=1003,udp,metadata=0x1 actions=resubmit(,46) -+ table=45, priority=1003,udp6,metadata=0x1 actions=resubmit(,46) -+]) -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP - -@@ -13983,8 +14081,8 @@ ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:0 - OVN_POPULATE_ARP - - # Allow some time for ovn-northd and ovn-controller to catch up. --# XXX This should be more systematic. --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - # Make sure there is no attempt to adding duplicated flows by ovn-controller - AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) -@@ -14224,6 +14322,7 @@ done - - OVN_POPULATE_ARP - # allow some time for ovn-northd and ovn-controller to catch up. -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96 -@@ -14294,6 +14393,45 @@ test_tcp_syn_packet() { - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet - } - -+# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM -+# -+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with -+# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and SCTP_CHKSUM as specified. -+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG. -+# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT chunk generated by OVN logical router -+# -+# INPORT is an lport number, e.g. 1 for vif1. -+# HV is a hypervisor number. -+# ETH_SRC and ETH_DST are each 12 hex digits. -+# IPV4_SRC and IPV4_DST are each 8 hex digits. -+# SCTP_SPORT and SCTP_DPORT are 4 hex digits. -+# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits. -+# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits. -+test_sctp_init_packet() { -+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 -+ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11} -+ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13} -+ -+ local ip_ttl=ff -+ local eth_hdr=${eth_dst}${eth_src}0800 -+ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst} -+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum} -+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag} -+ -+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init} -+ -+ local sctp_abort_ttl=3e -+ local reply_eth_hdr=${eth_src}${eth_dst}0800 -+ local reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src} -+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum} -+ local reply_sctp_abort=06000004 -+ -+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort} -+ echo $reply >> vif$inport.expected -+ -+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -+} -+ - # test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_TCP_RST_CHKSUM - # - # Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn segment with -@@ -14314,6 +14452,36 @@ test_tcp6_packet() { - as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet - } - -+# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_SCTP_ABORT_CHKSUM -+# -+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with -+# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, SCTP_SPORT, SCTP_DPORT and SCTP_CHKSUM as specified. -+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG. -+# EXP_SCTP_CHKSUM is the sctp checksum of the SCTP ABORT chunk generated by OVN logical router -+test_sctp6_packet() { -+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_router=$6 -+ local sctp_sport=$7 sctp_dport=$8 sctp_init_tag=$9 sctp_chksum=${10} -+ local exp_sctp_abort_chksum=${11} -+ shift 11 -+ -+ local eth_hdr=${eth_dst}${eth_src}86dd -+ local ip_hdr=60000000002084ff${ipv6_src}${ipv6_router} -+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum} -+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag} -+ -+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init} -+ -+ local reply_eth_hdr=${eth_src}${eth_dst}86dd -+ local reply_ip_hdr=600000000010843e${ipv6_router}${ipv6_src} -+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum} -+ local reply_sctp_abort=06000004 -+ -+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort} -+ echo $reply >> vif$inport.expected -+ -+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet -+} -+ - # test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM - # - # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 -@@ -14365,16 +14533,17 @@ done - - OVN_POPULATE_ARP - # allow some time for ovn-northd and ovn-controller to catch up. -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 11 0000 f87c f485 0303 --test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 84 0000 f87c f413 0302 - test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000100000000000000000001 11 0015 dbb8303900155bac6b646f65206676676e6d66720a 0104 1d31 - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) - - test_tcp_syn_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 0000 b680 6e05 --test_ip6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 84 0004 01020304 0103 5e74 -+test_sctp_init_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 00000001 82112601 b606 10fe95b6 - test_tcp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 0000 98cd -+test_sctp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 00000002 C0379D5A 39f23aaf - OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected]) - - OVN_CLEANUP([hv1], [hv2]) -@@ -14439,7 +14608,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - - OVN_POPULATE_ARP - --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && -@@ -14632,6 +14802,8 @@ OVS_WAIT_UNTIL( - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) - -+wait_for_ports_up ls1-lp_ext1 -+ - # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1 - (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows - as hv1 ovs-ofctl dump-flows br-int > brintflows -@@ -14912,6 +15084,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv2_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - # There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2 - AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ -@@ -15026,6 +15199,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - ovs-vsctl show -@@ -15106,6 +15280,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv3_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - ovs-vsctl show -@@ -15190,11 +15365,12 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv1_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined - # to router mac. - AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \ --table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ -+table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ - grep -c "actions=drop"], [0], [1 - ]) - -@@ -15207,6 +15383,7 @@ OVS_WAIT_UNTIL( - [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=ls1-lp_ext1` - test "$chassis" = "$hv2_uuid"]) -+wait_for_ports_up ls1-lp_ext1 - - as hv1 - OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) -@@ -15357,7 +15534,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - - OVN_POPULATE_ARP - --sleep 1 -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync - - packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && - ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && -@@ -15640,6 +15818,7 @@ test_ip6_packet_larger() { - fi - } - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - ovn-nbctl show > nbdump -@@ -15789,6 +15968,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \ - - ovn-nbctl lsp-add sw0 sw0-p0 \ - -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2" -+ - ovn-nbctl lsp-add sw0 sw0-p1 \ - -- lsp-set-addresses sw0-p1 "f0:00:00:11:02:03 192.168.1.3 2001::3" - -@@ -15799,6 +15979,7 @@ ovn-nbctl lr-nat-add lr0 snat 172.16.1.1 192.168.1.0/24 - ovn-nbctl lr-nat-add lr0 snat 2002::1 2001::/64 - - OVN_POPULATE_ARP -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - ovn-sbctl dump-flows > sbflows -@@ -15847,6 +16028,14 @@ ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows > sbflows2 - AT_CAPTURE_FILE([sbflows2]) - -+# create a route policy for pkt marking -+check ovn-nbctl lr-policy-add lr0 2000 "ip4.src == 192.168.1.3" allow -+policy=$(fetch_column nb:Logical_Router_Policy _uuid priority=2000) -+check ovn-nbctl set logical_router_policy $policy options:pkt_mark=100 -+as hv2 -+# add a flow in egress pipeline to check pkt marking -+ovs-ofctl --protocols=OpenFlow13 add-flow br-int "table=37,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64 actions=resubmit(,38)" -+ - dst_ip=$(ip_to_hex 172 16 2 10) - fip_ip=$(ip_to_hex 172 16 1 2) - src_ip=$(ip_to_hex 192 168 1 3) -@@ -15857,6 +16046,8 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >> expected - send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip - echo "${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}" >> expected - -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=37 | grep pkt_mark=0x64 | grep -q n_packets=1],[0]) -+ - OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) - - OVN_CLEANUP([hv1],[hv2]) -@@ -16045,6 +16236,7 @@ for i in 1 2 3 4 5; do - done - - dnl Wait for the changes to be propagated -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - dnl Assert that each Chassis has a tunnel formed to every other Chassis -@@ -16324,6 +16516,7 @@ ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24 - ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router - ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - #ovn-sbctl dump-flows - -@@ -16500,6 +16693,7 @@ ovn-nbctl lsp-set-type sw0-vir virtual - ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 - ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - # Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline -@@ -16555,12 +16749,10 @@ spa=$(ip_to_hex 10 0 0 10) - tpa=$(ip_to_hex 10 0 0 10) - send_garp 1 1 $eth_src $eth_dst $spa $tpa - --OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ --logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) -- --AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ --logical_port=sw0-vir) = xsw0-p1]) -- -+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid -+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1 -+wait_for_ports_up sw0-vir -+check ovn-nbctl --wait=hv sync - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p1's MAC. -@@ -16578,6 +16770,8 @@ ovn-sbctl clear port_binding $pb_uuid virtual_parent - OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir - # and sw0-p1 should be its virtual_parent. - send_garp 1 1 $eth_src $eth_dst $spa $tpa -@@ -16588,6 +16782,8 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p1]) - -+wait_for_ports_up sw0-vir -+ - # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p3 should be its virtual_parent. - eth_src=505400000005 -@@ -16602,6 +16798,7 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) - OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p3]) - -+wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p2's MAC. -@@ -16627,6 +16824,7 @@ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p2]) - -+wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the - # sw0-p3's MAC. -@@ -16652,6 +16850,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p1]) - -+wait_for_ports_up sw0-vir -+ - 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 -@@ -16668,6 +16868,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to - # zero if the ip4.dst is the virtual ip. - ovn-sbctl dump-flows lr0 > lr0-flows6 -@@ -16691,6 +16893,8 @@ sleep 1 - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = xsw0-p2]) - -+wait_for_ports_up sw0-vir -+ - 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 -@@ -16705,6 +16909,8 @@ logical_port=sw0-vir) = x], [0], []) - AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ - logical_port=sw0-vir) = x]) - -+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir -+ - # 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 - -@@ -16807,22 +17013,22 @@ ovs-vsctl -- add-port br-int vif33 -- \ - options:rxq_pcap=hv$i/vif33-rx.pcap \ - ofport-request=33 - --ovn-nbctl --wait=hv set NB_Global . options:controller_event=true --ovn-nbctl lb-add lb0 192.168.1.100:80 "" -+ovn-nbctl --event lb-add lb0 192.168.1.100:80 "" - ovn-nbctl ls-lb-add sw0 lb0 - uuid_lb0=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb0) - --ovn-nbctl lb-add lb1 192.168.2.100:80 "" -+ovn-nbctl --event lb-add lb1 192.168.2.100:80 "" - ovn-nbctl lr-lb-add lr0 lb1 - uuid_lb1=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb1) - --ovn-nbctl lb-add lb2 [[2001::10]]:50051 "" -+ovn-nbctl --event lb-add lb2 [[2001::10]]:50051 "" - ovn-nbctl ls-lb-add sw0 lb2 - uuid_lb2=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb2) - - ovn-nbctl --wait=hv meter-add event-elb drop 100 pktps 10 - - OVN_POPULATE_ARP -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - ovn-sbctl lflow-list > sbflows - AT_CAPTURE_FILE([sbflows]) -@@ -16889,6 +17095,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer - "$uuid_lb2" - ]) - -+AT_CHECK_UNQUOTED([ovn-trace sw0 'inport == "sw0-p11" && eth.src == 00:00:00:00:00:11 && ip4.dst == 192.168.1.100 && tcp && tcp.dst == 80' | grep -q 'event = "empty_lb_backends"'], [0]) -+ - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP - -@@ -17159,6 +17367,7 @@ AT_CAPTURE_FILE([sbflows3]) - cp ovn-sb/ovn-sb.db ovn-sb3.db - ovn-sbctl dump-flows > sbflows3 - -+AS_BOX([IGMP traffic test 1]) - # Send traffic and make sure it gets forwarded only on the two ports that - # joined. - > expected -@@ -17207,6 +17416,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ - wait_row_count IGMP_Group 1 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - -+AS_BOX([IGMP traffic test 2]) - # Send traffic and make sure it gets forwarded only on the port that joined. - as hv1 reset_pcap_file hv1-vif1 hv1/vif1 - as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -@@ -17246,6 +17456,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ - # Check that the IGMP Group is learned. - wait_row_count IGMP_Group 1 address=224.0.0.42 - -+AS_BOX([IGMP traffic test 3]) - # Send traffic and make sure it gets flooded to all ports. - as hv1 reset_pcap_file hv1-vif1 hv1/vif1 - as hv1 reset_pcap_file hv1-vif2 hv1/vif2 -@@ -17275,6 +17486,7 @@ check ovn-nbctl set Logical_Switch sw2 \ - other_config:mcast_eth_src="00:00:00:00:02:fe" \ - other_config:mcast_ip4_src="20.0.0.254" - -+AS_BOX([IGMP traffic test 4]) - # Wait for 1 query interval (1 sec) and check that two queries are generated. - > expected - store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected -@@ -17296,6 +17508,7 @@ check ovn-nbctl set Logical_Switch sw3 \ - - check ovn-nbctl --wait=hv sync - -+AS_BOX([IGMP traffic test 5]) - # Send traffic from sw3 and make sure rtr doesn't relay it. - > expected_empty - -@@ -17345,6 +17558,7 @@ send_igmp_v3_report hv2-vif3 hv2 \ - wait_row_count IGMP_Group 2 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - -+AS_BOX([IGMP traffic test 6]) - # Send traffic from sw3 and make sure it is relayed by rtr. - # to ports that joined. - > expected_routed_sw1 -@@ -17394,6 +17608,7 @@ send_igmp_v3_report hv1-vif4 hv1 \ - wait_row_count IGMP_Group 3 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - -+AS_BOX([IGMP traffic test 7]) - # Send traffic from sw3 and make sure it is relayed by rtr - # to ports that joined. - > expected_routed_sw1 -@@ -17493,6 +17708,7 @@ send_igmp_v3_report hv1-vif2 hv1 \ - wait_row_count IGMP_Group 1 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - -+AS_BOX([IGMP traffic test 8]) - # Send traffic from sw1-p21 - send_ip_multicast_pkt hv2-vif1 hv2 \ - 000000000001 01005e000144 \ -@@ -17790,6 +18006,7 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ - ofport-request=1 - check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - AT_CAPTURE_FILE([sbflows]) -@@ -18470,6 +18687,7 @@ m4_define([DVR_N_S_ARP_HANDLING], - - # Set a hypervisor as gateway chassis, for router port 172.31.0.1 - ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 -+ wait_for_ports_up - ovn-nbctl --wait=sb sync - - wait_row_count Port_Binding 1 logical_port=cr-router-to-underlay -@@ -18689,6 +18907,7 @@ m4_define([DVR_N_S_PING], - ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 - ovn-nbctl lrp-set-redirect-type router-to-underlay bridged - -+ wait_for_ports_up - ovn-nbctl --wait=sb sync - - -@@ -18816,7 +19035,7 @@ m4_define([DVR_N_S_PING], - OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected]) - - # Confirm that packets did not go out via tunnel port. -- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=33 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0 -+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=38 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0 - ]]) - - # Confirm that packet went out via localnet port -@@ -18919,6 +19138,7 @@ ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 - ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - - OVN_POPULATE_ARP -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - as hv1 ovs-appctl -t ovn-controller vlog/set dbg -@@ -18945,7 +19165,8 @@ list mac_binding], [0], [lr0-sw0 - 50:54:00:00:00:03 - ]) - --AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | 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`]) - -@@ -18962,7 +19183,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_p - - # The packet should not be sent to ovn-controller. The packet - # count should be 1 only. --AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | 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`]) - -@@ -18975,7 +19197,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa - - # The garp packet should be sent to ovn-controller and the mac_binding entry - # should be updated. --OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - check_row_count MAC_Binding 1 - -@@ -19000,7 +19223,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa - - # The garp packet should be sent to ovn-controller and the mac_binding entry - # should be updated. --OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - OVS_WAIT_UNTIL( - [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \ -@@ -19021,7 +19245,8 @@ OVS_WAIT_UNTIL( - | grep n_packets=1 | wc -l`] - ) - --AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - # Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023 - eth_src=505400000023 -@@ -19038,7 +19263,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa - - # The garp packet should be sent to ovn-controller and the mac_binding entry - # should be updated. --OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - # Wait for an entry in table=67 for the learnt mac_binding entry. - -@@ -19054,7 +19280,8 @@ OVS_WAIT_UNTIL( - | grep n_packets=1 | wc -l`] - ) - --AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa - OVS_WAIT_UNTIL( -@@ -19062,7 +19289,8 @@ OVS_WAIT_UNTIL( - | grep n_packets=2 | wc -l`] - ) - --AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`]) -+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \ -+grep table_id=10 | wc -l`]) - - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP -@@ -19100,8 +19328,7 @@ ovn-nbctl lsp-add ls1 lp11 - ovn-nbctl lsp-set-addresses lp11 "f0:00:00:00:00:11" - ovn-nbctl lsp-set-port-security lp11 f0:00:00:00:00:11 - --OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp11` = xup]) -- -+wait_for_ports_up - ovn-nbctl --wait=sb sync - - ovn-nbctl show -@@ -19270,6 +19497,7 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 - ovn-nbctl --stateless lr-nat-add router dnat_and_snat 172.31.0.100 192.168.1.1 - ovn-nbctl lrp-set-redirect-type router-to-underlay bridged - -+wait_for_ports_up - ovn-nbctl --wait=sb sync - - -@@ -19534,6 +19762,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public - check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 - - OVN_POPULATE_ARP -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - wait_row_count Service_Monitor 2 -@@ -19542,7 +19771,7 @@ 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");) -+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) - ]) - - AT_CAPTURE_FILE([sbflows2]) -@@ -19722,6 +19951,7 @@ ovn-nbctl lsp-set-options ln-public network_name=public - ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 - - OVN_POPULATE_ARP -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - # And now for the anticlimax. We need to ensure that there is no -@@ -19861,6 +20091,7 @@ check ovs-vsctl -- add-port br-int hv1-vif2 -- \ - ofport-request=3 - - OVN_POPULATE_ARP -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - ovn-sbctl dump-flows > sbflows -@@ -20216,6 +20447,7 @@ ovn-nbctl lsp-add lsw0 lp1 - ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:01 10.0.0.1" - ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop - -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - # Trace with --ovs should see ovs flow related to the ACL -@@ -20310,6 +20542,7 @@ for az in `seq 1 $n_az`; do - done - check ovn-nbctl --wait=hv sync - ovn-sbctl list Port_Binding > az$az.ports -+ wait_for_ports_up - done - - # Pre-populate the hypervisors' ARP tables so that we don't lose any -@@ -20485,6 +20718,7 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ - - # wait for earlier changes to take effect - check ovn-nbctl --wait=hv sync -+wait_for_ports_up - - ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([sbflows]) -@@ -20672,8 +20906,9 @@ build_tcp_syn() { - - send_ipv4_pkt() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 -- local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 ip_chksum=$9 -- local l4_payload=${10} -+ local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 -+ local l4_payload=$9 -+ local hp_ip_src=${10} - local hp_l4_payload=${11} - local outfile=${12} - -@@ -20681,8 +20916,10 @@ send_ipv4_pkt() { - - local eth=${eth_dst}${eth_src}0800 - local hp_eth=${eth_src}${eth_dst}0800 -- local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_src}${ip_dst} -- local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_dst}${ip_src} -+ local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${ip_src}${ip_dst} -+ ip=$(ip4_csum_inplace $ip) -+ local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${hp_ip_src}${ip_src} -+ hp_ip=$(ip4_csum_inplace ${hp_ip}) - local packet=${eth}${ip}${l4_payload} - local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} - -@@ -20694,15 +20931,16 @@ send_ipv6_pkt() { - local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 - local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 - local l4_payload=$9 -- local hp_l4_payload=${10} -- local outfile=${11} -+ local hp_ip_src=${10} -+ local hp_l4_payload=${11} -+ local outfile=${12} - - local ip_ttl=40 - - local eth=${eth_dst}${eth_src}86dd - local hp_eth=${eth_src}${eth_dst}86dd - local ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_src}${ip_dst} -- local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_dst}${ip_src} -+ local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${hp_ip_src}${ip_src} - local packet=${eth}${ip}${l4_payload} - local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} - -@@ -20724,16 +20962,26 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ - - # One logical switch with IPv4 and IPv6 load balancers that hairpin the - # traffic. -+# Also create "duplicate" load balancers, i.e., different VIPs using the same -+# backends. - ovn-nbctl ls-add sw - ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 --ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp --ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp --ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp --ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp - ovn-nbctl ls-lb-add sw lb-ipv4-tcp -+ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv4-udp -+ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup - ovn-nbctl ls-lb-add sw lb-ipv6-tcp -+ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv6-udp -+ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup - - ovn-nbctl lr-add rtr - ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 4200::00ff/64 -@@ -20743,67 +20991,332 @@ ovn-nbctl lsp-add sw sw-rtr \ - -- lsp-set-options sw-rtr router-port=rtr-sw - - ovn-nbctl --wait=hv sync -+wait_for_ports_up - --# Inject IPv4 TCP packet from lsp. -+ovn-sbctl dump-flows > sbflows -+AT_CAPTURE_FILE([sbflows]) - > expected -+ -+AS_BOX([IPv4 TCP Hairpin]) -+ -+# Inject IPv4 TCP packets from lsp. - tcp_payload=$(build_tcp_syn 84d0 1f90 05a7) - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e) - send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -- 06 0028 35f5 \ -- ${tcp_payload} ${hp_tcp_payload} \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 88) ${hp_tcp_payload} \ - expected - --ovn-sbctl dump-flows > sbflows --AT_CAPTURE_FILE([sbflows]) -+tcp_payload=$(build_tcp_syn 84d1 1f90 05a5) -+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ -+ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp options:hairpin_snat_ip="88.88.88.87" -+# Inject IPv4 TCP packets from lsp. -+tcp_payload=$(build_tcp_syn 84d0 1f90 05a7) -+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \ -+ expected -+ -+tcp_payload=$(build_tcp_syn 84d1 1f90 05a5) -+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \ -+ 06 0028 \ -+ ${tcp_payload} \ -+ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \ -+ expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - --# Inject IPv4 UDP packet from lsp. -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv4 UDP Hairpin]) -+ -+# Inject IPv4 UDP packets from lsp. - udp_payload=$(build_udp 84d0 0fc8 6666) - hp_udp_payload=$(build_udp 84d0 07e5 6e49) - send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -- 11 001e 35f4 \ -- ${udp_payload} ${hp_udp_payload} \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \ -+ expected -+ -+udp_payload=$(build_udp 84d1 0fc8 6664) -+hp_udp_payload=$(build_udp 84d1 07e5 6e47) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, 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]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv4-udp options:hairpin_snat_ip="88.88.88.87" -+# Inject IPv4 UDP packets from lsp. -+udp_payload=$(build_udp 84d0 0fc8 6666) -+hp_udp_payload=$(build_udp 84d0 07e5 6e4a) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 87) ${hp_udp_payload} \ -+ expected -+ -+udp_payload=$(build_udp 84d1 0fc8 6664) -+hp_udp_payload=$(build_udp 84d1 07e5 6e47) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \ -+ 11 001e \ -+ ${udp_payload} \ -+ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \ - expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - --# Inject IPv6 TCP packet from lsp. -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv6 TCP Hairpin]) -+ -+# Inject IPv6 TCP packets from lsp. - tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) - hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) - send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ - 42000000000000000000000000000001 88000000000000000000000000000088 \ - 06 0014 \ -- ${tcp_payload} ${hp_tcp_payload} \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000088 ${hp_tcp_payload} \ - expected - --# Check that traffic is hairpinned. --OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -- --# Inject IPv6 UDP packet from lsp. --udp_payload=$(build_udp 84d0 0fc8 a0b8) --hp_udp_payload=$(build_udp 84d0 07e5 a89b) -+tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7) -+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe) - send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -- 42000000000000000000000000000001 88000000000000000000000000000088 \ -- 11 000a \ -- ${udp_payload} ${hp_udp_payload} \ -+ 42000000000000000000000000000001 88000000000000000000000000000089 \ -+ 06 0014 \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000089 ${hp_tcp_payload} \ - expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - --OVN_CLEANUP([hv1]) --AT_CLEANUP -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) - --AT_SETUP([ovn -- Big Load Balancer]) --ovn_start -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv6-tcp options:hairpin_snat_ip="8800::0087" - --ovn-nbctl ls-add ls1 --ovn-nbctl lsp-add ls1 lsp1 -+# Inject IPv6 TCP packets from lsp. -+tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) -+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc1) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 06 0014 \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000087 ${hp_tcp_payload} \ -+ expected -+ -+tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7) -+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000089 \ -+ 06 0014 \ -+ ${tcp_payload} \ -+ 88000000000000000000000000000089 ${hp_tcp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([IPv6 UDP Hairpin]) -+ -+# Inject IPv6 UDP packets from lsp. -+udp_payload=$(build_udp 84d0 0fc8 a0b8) -+hp_udp_payload=$(build_udp 84d0 07e5 a89b) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 11 000a \ -+ ${udp_payload} \ -+ 88000000000000000000000000000088 ${hp_udp_payload} \ -+ expected -+ -+udp_payload=$(build_udp 84d1 0fc8 a0b6) -+hp_udp_payload=$(build_udp 84d1 07e5 a899) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000089 \ -+ 11 000a \ -+ ${udp_payload} \ -+ 88000000000000000000000000000089 ${hp_udp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+# Change LB Hairpin SNAT IP. -+# Also flush conntrack to avoid reusing an existing entry. -+as hv1 ovs-appctl dpctl/flush-conntrack -+ovn-nbctl --wait=hv set load_balancer lb-ipv6-udp options:hairpin_snat_ip="8800::0087" -+ -+# Inject IPv6 UDP packets from lsp. -+udp_payload=$(build_udp 84d0 0fc8 a0b8) -+hp_udp_payload=$(build_udp 84d0 07e5 a89b) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 11 000a \ -+ ${udp_payload} \ -+ 88000000000000000000000000000087 ${hp_udp_payload} \ -+ expected -+ -+udp_payload=$(build_udp 84d1 0fc8 a0b6) -+hp_udp_payload=$(build_udp 84d1 07e5 a899) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000089 \ -+ 11 000a \ -+ ${udp_payload} \ -+ 88000000000000000000000000000089 ${hp_udp_payload} \ -+ expected -+ -+# Check learned hairpin reply flows. -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([Delete VIP]) -+check ovn-nbctl --wait=hv set Load_Balancer lb-ipv4-tcp vips='"88.88.88.88:8080"=""' -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+AS_BOX([Delete LB]) -+check ovn-nbctl --wait=hv \ -+ -- lb-del lb-ipv4-tcp \ -+ -- lb-del lb-ipv4-tcp-dup \ -+ -- lb-del lb-ipv4-udp \ -+ -- lb-del lb-ipv4-udp-dup -+ -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+check ovn-nbctl --wait=hv \ -+ -- lb-del lb-ipv6-tcp \ -+ -- lb-del lb-ipv6-tcp-dup -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]] -+]) -+ -+check ovn-nbctl --wait=hv \ -+ -- lb-del lb-ipv6-udp \ -+ -- lb-del lb-ipv6-udp-dup -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [1], [dnl -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- Big Load Balancer]) -+ovn_start -+ -+ovn-nbctl ls-add ls1 -+ovn-nbctl lsp-add ls1 lsp1 - - net_add n1 - sim_add hv1 -@@ -20936,6 +21449,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public - check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20 - check ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24 - check ovn-nbctl --wait=hv sync -+wait_for_ports_up - - wait_row_count datapath_binding 1 external-ids:name=lr0 - lr0_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding lr0) -@@ -21156,31 +21670,31 @@ AT_CHECK([ - - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(pkt.mark = 4294967295; next;) -+match=(ip6), action=(pkt.mark = 4294967295; reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=-1 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=2147483648 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(pkt.mark = 2147483648; next;) -+match=(ip6), action=(pkt.mark = 2147483648; reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=foo - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=4294967296 - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl - table=12(lr_in_policy ), priority=1001 , dnl --match=(ip6), action=(next;) -+match=(ip6), action=(reg8[[0..15]] = 0; next;) - ]) - - OVN_CLEANUP([hv1]) -@@ -21602,22 +22116,22 @@ AT_CHECK([test ! -z $p1_zoneid]) - p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g') - AT_CHECK([test ! -z $p2_zoneid]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\ - reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 1]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\ - reg15=0x${p2_dpkey} | grep "load:0x${p2_zoneid}->NXM_NX_REG13" | wc -l) -eq 1]) - - ovs-vsctl set interface hv1-vif1 external_ids:iface-id=foo - OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xdown]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0]) - - p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') -@@ -21629,16 +22143,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup]) - p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') - AT_CHECK([test ! -z $p1_zoneid]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1]) - - ovs-vsctl del-port hv1-vif2 - OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) - --AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0]) - - p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g') -@@ -21646,7 +22160,7 @@ AT_CHECK([test -z $p2_zoneid]) - - ovn-nbctl lsp-del sw0-p1 - --OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\ -+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\ - reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0]) - - p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') -@@ -21723,6 +22237,7 @@ check ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2 - check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.2 - check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.3 - -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - # Ensure ECMP symmetric reply flows are not present on any hypervisor. -@@ -21753,26 +22268,25 @@ ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1 - ovn-nbctl --wait=hv sync - - # And ensure that ECMP symmetric reply flows are present only on hv1 --AT_CHECK([ -- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \ -- grep "priority=100" | \ -- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) --]) --AT_CHECK([ -- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ -- grep "priority=200" | \ -- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) --]) -+as hv1 ovs-ofctl dump-flows br-int > hv1flows -+AT_CAPTURE_FILE([hv1flows]) -+as hv2 ovs-ofctl dump-flows br-int > hv2flows -+AT_CAPTURE_FILE([hv2flows]) - - AT_CHECK([ -- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=15 | \ -- grep "priority=100" | \ -- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) --]) --AT_CHECK([ -- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=21 | \ -- grep "priority=200" | \ -- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) -+ for hv in 1 2; do -+ grep table=15 hv${hv}flows | \ -+ grep "priority=100" | \ -+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -+ -+ grep table=22 hv${hv}flows | \ -+ grep "priority=200" | \ -+ grep -c "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -+ done; :], [0], [dnl -+1 -+1 -+0 -+0 - ]) - - OVN_CLEANUP([hv1], [hv2]) -@@ -21856,6 +22370,7 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ - # for ARP resolution). - OVN_POPULATE_ARP - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.1], [1], []) -@@ -21895,22 +22410,22 @@ as hv1 - ovs-vsctl add-br br-phys - ovn_attach n1 br-phys 192.168.0.1 - --ovn-nbctl ls-add sw0 --ovn-nbctl lsp-add sw0 sw0-p1 --ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3" --ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3" -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3" -+check ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3" - --ovn-nbctl lsp-add sw0 sw0-p2 --ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4" --ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4" -+check ovn-nbctl lsp-add sw0 sw0-p2 -+check ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4" -+check ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4" - --ovn-nbctl lsp-add sw0 sw0-p3 --ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5" --ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5" -+check ovn-nbctl lsp-add sw0 sw0-p3 -+check ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5" -+check ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5" - --ovn-nbctl lsp-add sw0 sw0-p4 --ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6" --ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6" -+check ovn-nbctl lsp-add sw0 sw0-p4 -+check ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6" -+check ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6" - - as hv1 - ovs-vsctl -- add-port br-int hv1-vif1 -- \ -@@ -21934,88 +22449,101 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \ - options:rxq_pcap=hv1/vif4-rx.pcap \ - ofport-request=4 - --OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup]) --OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup]) --OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p3) = xup]) --OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p4) = xup]) -+wait_for_ports_up - --ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 --ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow --ovn-nbctl --wait=hv sync -+check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 -+check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow -+check ovn-nbctl --wait=hv sync - --OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) -+# wait_conj_id_count COUNT ["ID COUNT [MATCH]"]... -+# -+# Waits until COUNT flows matching against conj_id appear in the -+# table 45 on hv1's br-int bridge. Makes the flows available in -+# "hv1flows", which will be logged on error. -+# -+# In addition, for each quoted "ID COUNT" or "ID COUNT MATCH", -+# verifies that there are COUNT flows in table 45 that match -+# aginst conj_id=ID and (if MATCH) is nonempty, match MATCH. -+wait_conj_id_count() { -+ AT_CAPTURE_FILE([hv1flows]) -+ local retval -+ case $1 in -+ (0) retval=1 ;; -+ (*) retval=0 ;; -+ esac -+ -+ echo "waiting for $1 conj_id flows..." -+ OVS_WAIT_FOR_OUTPUT_UNQUOTED( -+ [ovs-ofctl dump-flows br-int > hv1flows -+ grep table=45 hv1flows | grep -c conj_id], -+ [$retval], [$1 -+]) -+ -+ shift -+ for arg; do -+ set -- $arg; id=$1 count=$2 match=$3 -+ echo "checking that there are $count ${match:+$match }flows with conj_id=$id..." -+ AT_CHECK_UNQUOTED( -+ [grep table=45 hv1flows | grep "$match" | grep -c conj_id=$id], -+ [0], [$count -+]) -+ done -+} - --# Add sw0-p3 to the port group pg0. The conj_id should be 2. --ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 --OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) -+AS_BOX([Add sw0-p3 to the port group pg0. The conj_id should be 2.]) -+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 -+wait_conj_id_count 1 "2 1" - --# Add sw0p4 to the port group pg0. The conj_id should be 2. --ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4 --OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) -+AS_BOX([Add sw0p4 to the port group pg0. The conj_id should be 2.]) -+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4 -+wait_conj_id_count 1 "2 1" - --# Add another ACL with conjunction. --ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")]) -+AS_BOX([Add another ACL with conjunction.]) -+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow -+wait_conj_id_count 2 "2 1 tcp" "3 1 udp" - --# Delete tcp ACL. --ovn-nbctl acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" --OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")]) -+AS_BOX([Delete tcp ACL.]) -+check ovn-nbctl --wait=hv acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" -+wait_conj_id_count 1 "3 1 udp" - --# Add back the tcp ACL. --ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) -+AS_BOX([Add back the tcp ACL.]) -+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow -+wait_conj_id_count 2 "3 1 udp" "4 1 tcp" - AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")]) - AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")]) - --ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow --OVS_WAIT_UNTIL([test 3 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=5")]) -+AS_BOX([Add another tcp ACL.]) -+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow -+wait_conj_id_count 3 "3 1 udp" "4 1 tcp" "5 1 tcp" - --ovn-nbctl clear port_group pg0 acls --OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) -+AS_BOX([Clear ACLs.]) -+check ovn-nbctl --wait=hv clear port_group pg0 acls -+wait_conj_id_count 0 - --ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow --ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=6")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=7")]) -+AS_BOX([Add TCP ACL.]) -+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow -+check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow -+wait_conj_id_count 2 "6 1 tcp" "7 1 udp" - --# Flush the lflow cache. -+AS_BOX([Flush lflow cache.]) - as hv1 ovn-appctl -t ovn-controller flush-lflow-cache --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) -- --# Disable lflow caching. -+wait_conj_id_count 2 "2 1" "3 1" - -+AS_BOX([Disable lflow caching.]) - as hv1 ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false - --# Wait until ovn-enble-lflow-cache is processed by ovn-controller. --OVS_WAIT_UNTIL([ -- test $(ovn-sbctl get chassis hv1 other_config:ovn-enable-lflow-cache) = '"false"' --]) -- --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) -+AS_BOX([Wait until ovn-enble-lflow-cache is processed by ovn-controller.]) -+wait_row_count Chassis 1 name=hv1 other_config:ovn-enable-lflow-cache=false -+wait_conj_id_count 2 "2 1" "3 1" - --# Remove port sw0-p4 from port group. --ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=4")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=5")]) -+AS_BOX([Remove port sw0-p4 from port group.]) -+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 -+wait_conj_id_count 2 "4 1" "5 1" - -+AS_BOX([Recompute.]) - as hv1 ovn-appctl -t ovn-controller recompute - --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) -+wait_conj_id_count 2 "2 1" "3 1" - - OVN_CLEANUP([hv1]) - -@@ -22131,6 +22659,77 @@ AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl - OVN_CLEANUP([hv1]) - AT_CLEANUP - -+AT_SETUP([ovn -- Container port Incremental Processing]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.10 -+ -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 \ -+ ofport-request=1 -+ -+check ovn-nbctl ls-add ls1 \ -+ -- ls-add ls2 \ -+ -- lsp-add ls1 lsp1 \ -+ -- lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=hv sync -+ -+# 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=lsp-cont1 chassis=$ch -+ -+AS_BOX([delete OVS VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+as hv1 ovs-vsctl del-port vif1 -+ -+check ovn-nbctl --wait=sb lsp-del lsp-cont1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+check_row_count Port_Binding 1 logical_port=lsp1 chassis="[[]]" -+ -+AS_BOX([readd OVS VIF]) -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 \ -+ ofport-request=1 -+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch -+ -+AS_BOX([readd OVN container port]) -+check ovn-nbctl lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=hv sync -+check_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch -+ -+AS_BOX([delete both OVN VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl lsp-del lsp1 \ -+ -- lsp-del lsp-cont1 -+check ovn-nbctl --wait=sb sync -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+AS_BOX([readd both OVN VIF and OVN container port]) -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl lsp-add ls1 lsp1 \ -+ -- lsp-add ls2 lsp-cont1 lsp1 1 -+check ovn-nbctl --wait=sb sync -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch -+wait_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch -+ -+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 -@@ -22145,7 +22744,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router - - # Create logical port p1 in s1 - ovn-nbctl lsp-add s1 p1 \ ---- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" -+-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" \ -+-- lsp-set-port-security 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 -@@ -22165,6 +22765,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ - # for ARP resolution). - OVN_POPULATE_ARP - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1) -@@ -22208,7 +22809,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep - ]) - - # The packet should've been dropped in the lr_in_arp_resolve stage. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=22, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl - 1 - ]) - -@@ -22281,6 +22882,7 @@ check test "$hvt2" -gt 0 - # Then wait for 9 out of 10 - sleep 1 - check as hv3 ovn-appctl -t ovn-controller exit --restart -+wait_for_ports_up - ovn-nbctl --wait=sb sync - wait_row_count Chassis_Private 9 name!=hv3 nb_cfg=2 - check_row_count Chassis_Private 1 name=hv3 nb_cfg=1 -@@ -22454,6 +23056,7 @@ ovn-nbctl set logical_router gw_router options:chassis=hv3 - ovn-nbctl lr-nat-add gw_router snat 172.16.0.200 30.0.0.0/24 - ovn-nbctl lr-nat-add gw_router snat 172.16.0.201 30.0.0.3 - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - # Create an interface in br-phys in hv2 and send ARP request for 172.16.0.100 -@@ -22643,6 +23246,7 @@ check ovn-nbctl acl-add ls1 to-lport 1001 \ - check ovn-nbctl acl-add ls1 to-lport 1001 \ - 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' allow - -+wait_for_ports_up - check ovn-nbctl --wait=hv sync - - sip=`ip_to_hex 10 0 0 2` -@@ -22811,6 +23415,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ - options:rxq_pcap=hv1/vif1-rx.pcap \ - ofport-request=1 - -+wait_for_ports_up - ovn-nbctl --wait=hv sync - - # Expected conjunction flows: -@@ -22869,6 +23474,7 @@ as hv1 ovs-vsctl \ - ovn-nbctl --wait=hv sync - - # hv1 ovn-controller should not bind sw0-p2. -+wait_for_ports_up sw0-p1 - check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c - - # Trigger recompute and sw0-p2 should not be claimed. -@@ -22976,93 +23582,79 @@ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp - check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp - check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp - --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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70], [0], [dnl --NXST_FLOW reply (xid=0x8): -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl - ]) - --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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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 hv2 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 | 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 hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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 hv2 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 | 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 hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,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=68 | grep -v NXST], [1], [dnl - ]) - --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=69 | grep -v NXST], [1], [dnl - ]) - --AT_CHECK([as hv2 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=70 | grep -v NXST], [1], [dnl - ]) - - check ovn-nbctl lsp-add sw0 sw0-p2 -@@ -23070,184 +23662,159 @@ check ovn-nbctl lsp-add sw0 sw0-p2 - OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup]) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 4] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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 hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,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 | 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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 5] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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 hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,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 | 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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) - ]) - - 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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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 hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) - ]) - --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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) - ]) - - check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp -@@ -23255,65 +23822,115 @@ check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp - # Number of hairpin flows shouldn't change as it doesn't depend on how many - # datapaths the LB is applied. - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+]) -+ -+# Check backwards compatibility with ovn-northd versions that don't store the -+# original destination tuple. -+# -+# ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst(). -+as northd-backup ovn-appctl -t ovn-northd pause -+as northd ovn-appctl -t ovn-northd pause -+ -+check ovn-sbctl \ -+ -- remove load_balancer lb-ipv4-tcp options hairpin_orig_tuple \ -+ -- remove load_balancer lb-ipv6-tcp options hairpin_orig_tuple \ -+ -- remove load_balancer lb-ipv4-udp options hairpin_orig_tuple \ -+ -- remove load_balancer lb-ipv6-udp options hairpin_orig_tuple -+ -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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 hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,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]] -+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],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=69 | grep -v NXST], [1], [dnl - ]) - --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)) -+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90)) - ]) - -+# Resume ovn-northd. -+as northd ovn-appctl -t ovn-northd resume -+as northd-backup ovn-appctl -t ovn-northd resume -+check ovn-nbctl --wait=hv sync -+ - as hv2 ovs-vsctl del-port hv2-vif1 - OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) - -@@ -23321,75 +23938,73 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) - as hv2 ovn-appctl -t ovn-controller recompute - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -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=68 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]]) -+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],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 hv2 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 | 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)) -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl -+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88)) -+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::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] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0] - ) - - OVS_WAIT_UNTIL( -- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0] -+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0] - ) - - OVN_CLEANUP([hv1], [hv2]) -@@ -23541,3 +24156,680 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - AT_CLEANUP -+ -+AT_SETUP([ovn -- propagate Port_Binding.up to NB and OVS]) -+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 -+ -+check ovn-nbctl ls-add ls -+ -+AS_BOX([add OVS port for existing LSP]) -+check ovn-nbctl lsp-add ls lsp1 -+check ovn-nbctl --wait=hv sync -+check_column "false" Port_Binding up logical_port=lsp1 -+ -+check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1 -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp1 external_ids:ovn-installed` = '"true"']) -+ -+AS_BOX([add LSP for existing OVS port]) -+check ovs-vsctl add-port br-int lsp2 -- set Interface lsp2 external-ids:iface-id=lsp2 -+check ovn-nbctl lsp-add ls lsp2 -+check ovn-nbctl --wait=hv sync -+check_column "true" Port_Binding up logical_port=lsp2 -+wait_column "true" nb:Logical_Switch_Port up name=lsp2 -+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp2 external_ids:ovn-installed` = '"true"']) -+ -+AS_BOX([ovn-controller should not reset Port_Binding.up without northd]) -+# Pause northd and clear the "up" field to simulate older ovn-northd -+# versions writing to the Southbound DB. -+as northd ovn-appctl -t ovn-northd pause -+as northd-backup ovn-appctl -t ovn-northd pause -+ -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl clear Port_Binding lsp1 up -+as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+# Forcefully release the Port_Binding so ovn-controller reclaims it. -+# Make sure the Port_Binding.up field is not updated though. -+check ovn-sbctl clear Port_Binding lsp1 chassis -+hv1_uuid=$(fetch_column Chassis _uuid name=hv1) -+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp1 -+check_column "" Port_Binding up logical_port=lsp1 -+ -+# Once northd should explicitly set the Port_Binding.up field to 'false' and -+# ovn-controller sets it to 'true' as soon as the update is processed. -+as northd ovn-appctl -t ovn-northd resume -+as northd-backup ovn-appctl -t ovn-northd resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ -+AS_BOX([ovn-controller should reset Port_Binding.up - from NULL]) -+# If Port_Binding.up is cleared externally, ovn-northd resets it to 'false' -+# and ovn-controller finally sets it to 'true' once the update is processed. -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl clear Port_Binding lsp1 up -+check ovn-nbctl --wait=sb sync -+wait_column "false" nb:Logical_Switch_Port up name=lsp1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ -+AS_BOX([ovn-controller should reset Port_Binding.up - from false]) -+# If Port_Binding.up is externally set to 'false', ovn-controller should sets -+# it to 'true' once the update is processed. -+as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-sbctl set Port_Binding lsp1 up=false -+check ovn-nbctl --wait=sb sync -+wait_column "false" nb:Logical_Switch_Port up name=lsp1 -+as hv1 ovn-appctl -t ovn-controller debug/resume -+wait_column "true" Port_Binding up logical_port=lsp1 -+wait_column "true" nb:Logical_Switch_Port up name=lsp1 -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Test case to check that ovn-controller doesn't assert when -+# handling port group updates. -+AT_SETUP([ovn -- No ovn-controller assert for port group updates]) -+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 -+ -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=sw0-port1 \ -+ ofport-request=1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0-port1 -+check ovn-nbctl lsp-set-addresses sw0-port1 "10:14:00:00:00:01 192.168.0.2" -+ -+check ovn-nbctl lsp-add sw0 sw0-port2 -+check ovn-nbctl lsp-add sw0 sw0-port3 -+check ovn-nbctl lsp-add sw0 sw0-port4 -+check ovn-nbctl lsp-add sw0 sw0-port5 -+check ovn-nbctl lsp-add sw0 sw0-port6 -+check ovn-nbctl lsp-add sw0 sw0-port7 -+ -+ovn-nbctl create address_set name=as1 -+ovn-nbctl set address_set . addresses="10.0.0.10,10.0.0.11,10.0.0.12" -+ -+ovn-nbctl pg-add pg1 sw0-port1 sw0-port2 sw0-port3 -+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && icmp4" drop -+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && tcp && tcp.dst >=10000 && tcp.dst <= 20000" drop -+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && udp && udp.dst >=10000 && udp.dst <= 20000" drop -+ -+ovn-nbctl pg-add pg2 sw0-port2 sw0-port3 sw0-port4 sw0-port5 -+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && icmp4" allow-related -+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && tcp && tcp.dst >=30000 && tcp.dst <= 40000" drop -+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && udp && udp.dst >=30000 && udp.dst <= 40000" drop -+ -+ovn-nbctl pg-add pg3 sw0-port1 sw0-port5 -+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && icmp4" allow-related -+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && tcp && tcp.dst >=20000 && tcp.dst <= 30000" allow-related -+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && udp && udp.dst >=20000 && udp.dst <= 30000" allow-related -+ -+AS_BOX([Delete and add the port groups multiple times]) -+ -+for i in $(seq 1 10) -+do -+ check ovn-nbctl --wait=hv clear port_Group pg1 ports -+ check ovn-nbctl --wait=hv clear port_Group pg2 ports -+ check ovn-nbctl --wait=hv clear port_Group pg3 ports -+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1 -+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1 sw0-port4 -+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1 sw0-port4 sw0-port5 -+ -+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2 -+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2 sw0-port6 -+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2 sw0-port6 sw0-port7 -+ -+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1 -+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1 sw0-port3 -+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1 sw0-port3 sw0-port6 -+ -+ # Make sure that ovn-controller has not asserted. -+ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+done -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Test case to check that ovn-controller doesn't assert when -+# handling conjunction flows. When ovn-controller claims -+# the first port of a logical switch datapath, it programs the flows -+# for this datapath incrementally (without full recompute). If -+# suppose, in the same SB update from ovsdb-server, a logical flow is added -+# which results in conjunction action, then this logical flow is also -+# handled incrementally. The newly added logical flow is processed -+# twice which results in wrong oflows and it results in an assertion -+# in ovn-controller. Test this ovn-controller handles this scenario -+# properly and doesn't assert. -+AT_SETUP([ovn -- No ovn-controller assert when generating conjunction 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.10 -+ -+as hv1 -+ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=sw0-p1 \ -+ ofport-request=1 -+ -+check as hv1 -+ovs-vsctl set open . external_ids:ovn-monitor-all=true -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl pg-add pg1 -+check ovn-nbctl pg-add pg2 -+check ovn-nbctl lsp-add sw0 sw0-p2 -+check ovn-nbctl lsp-set-addresses sw0-p2 "00:00:00:00:00:02 192.168.47.2" -+check ovn-nbctl lsp-add sw0 sw0-p3 -+check ovn-nbctl lsp-set-addresses sw0-p3 "00:00:00:00:00:03 192.168.47.3" -+ -+# Pause ovn-northd. When it is resumed, all the below NB updates -+# will be sent in one transaction. -+ -+check as northd ovn-appctl -t ovn-northd pause -+check as northd-backup ovn-appctl -t ovn-northd pause -+ -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:01 192.168.47.1" -+check ovn-nbctl pg-set-ports pg1 sw0-p1 sw0-p2 -+check ovn-nbctl pg-set-ports pg2 sw0-p3 -+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == \$pg2_ip4 && udp && udp.dst >= 1 && udp.dst <= 65535" allow-related -+ -+# resume ovn-northd now. This should result in a single update message -+# from SB ovsdb-server to ovn-controller for all the above NB updates. -+check as northd ovn-appctl -t ovn-northd resume -+ -+AS_BOX([Wait for sw0-p1 to be up]) -+wait_for_ports_up sw0-p1 -+ -+# When the port group pg1 is updated, it should not result in -+# any assert in ovn-controller. -+ovn-nbctl --wait=hv pg-set-ports pg1 sw0-p1 sw0-p2 sw0-p3 -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+check ovn-nbctl --wait=hv sync -+ -+# Check OVS flows are installed properly. -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+ grep "priority=2002" | grep conjunction | \ -+ sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=45, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- OVN FDB (MAC learning) - 2 HVs, 2 LS, 1 LR ]) -+ovn_start -+ -+# Create the first logical switch with one port -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" unknown -+ -+check ovn-nbctl lsp-add sw0 sw0-p2 -+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+# Port security is set for sw0-p2 -+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+ -+# sw0-p1 and sw0-p3 have unknown address and no port security. -+# FDB should be enabled for these lports. -+check ovn-nbctl lsp-add sw0 sw0-p3 -+check ovn-nbctl lsp-set-addresses sw0-p3 unknown -+ -+# Create the second logical switch with one port -+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 11.0.0.3" unknown -+ -+check ovn-nbctl lsp-add sw1 sw1-p2 -+check ovn-nbctl lsp-set-addresses sw1-p2 "40:54:00:00:00:04 11.0.0.4" -+check ovn-nbctl lsp-set-port-security sw1-p2 "40:54:00:00:00:04 11.0.0.4" -+ -+# Create a logical router and attach both logical switches -+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 11.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 -+ovn-nbctl --wait=hv sync -+ -+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-p2 \ -+ 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 -- \ -+ set interface hv1-vif3 external-ids:iface-id=sw0-p3 \ -+ options:tx_pcap=hv1/vif3-tx.pcap \ -+ options:rxq_pcap=hv1/vif3-rx.pcap \ -+ ofport-request=3 -+ -+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 hv2-vif2 -- \ -+ set interface hv2-vif2 external-ids:iface-id=sw1-p1 \ -+ options:tx_pcap=hv2/vif2-tx.pcap \ -+ options:rxq_pcap=hv2/vif2-rx.pcap \ -+ ofport-request=2 -+ -+OVN_POPULATE_ARP -+ -+ip_to_hex() { -+ printf "%02x%02x%02x%02x" "$@" -+} -+ -+send_icmp_packet() { -+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8 -+ shift 8 -+ -+ local ip_ttl=ff -+ local ip_len=001c -+ local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data} -+ echo $packet > expected -+ as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet -+} -+ -+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 -+} -+ -+trim_zeros() { -+ sed 's/\(00\)\{1,\}$//' -+} -+ -+AS_BOX([Wait for all ports to be up]) -+wait_for_ports_up -+ -+# Check that there is put_fdb() flow added by ovn-northd for sw0-p1 -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep "ls_in_lookup_fdb" sw0flows | sort], [0], [dnl -+ table=3 (ls_in_lookup_fdb ), priority=0 , dnl -+match=(1), action=(next;) -+ table=3 (ls_in_lookup_fdb ), priority=100 , dnl -+match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;) -+ table=3 (ls_in_lookup_fdb ), priority=100 , dnl -+match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;) -+]) -+ -+AT_CHECK([grep "ls_in_put_fdb" sw0flows | sort], [0], [dnl -+ table=4 (ls_in_put_fdb ), priority=0 , dnl -+match=(1), action=(next;) -+ table=4 (ls_in_put_fdb ), priority=100 , dnl -+match=(inport == "sw0-p1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;) -+ table=4 (ls_in_put_fdb ), priority=100 , dnl -+match=(inport == "sw0-p3" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;) -+]) -+ -+# Send a packet from sw0-p1 with a different mac not present -+# in it's addresses. -+AS_BOX([Send a pkt from sw0-p1 with a different mac address]) -+ -+# Use the src mac 50:54:00:00:00:13 instead of 50:54:00:00:00:03 -+src_mac=505400000013 -+src_ip=$(ip_to_hex 10 0 0 13) -+ -+# send the packet to sw0-p2 -+dst_mac=505400000004 -+dst_ip=$(ip_to_hex 10 0 0 4) -+ -+data=0800bee4391a0001 -+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -+ -+# There should be one row in fdb -+AS_BOX([Check that the FDB entry is created]) -+wait_row_count FDB 1 -+ -+sw0_dpkey=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0) -+sw0p1_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p1) -+sw0p3_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p3) -+ -+check_column '50:54:00:00:00:13' fdb mac -+check_column $sw0_dpkey fdb dp_key -+check_column $sw0p1_dpkey fdb port_key -+ -+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2. -+AS_BOX([Check that ovn-controller programs the flows for FDB]) -+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt -+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table71.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table71.txt]) -+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]] -+]) -+ -+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]] -+]) -+ -+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt -+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table72.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table72.txt]) -+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]] -+]) -+ -+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]] -+]) -+ -+# Use the src mac 50:54:00:00:00:14 instead of 50:54:00:00:00:03 -+src_mac=505400000014 -+src_ip=$(ip_to_hex 10 0 0 14) -+ -+as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -+ -+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -+ -+# There should be two rows in fdb -+wait_row_count FDB 2 -+ -+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac -+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key -+check_column "$sw0p1_dpkey $sw0p1_dpkey" fdb port_key -+ -+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2. -+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt -+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table71.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table71.txt]) -+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]] -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]] -+]) -+ -+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]] -+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]] -+]) -+ -+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt -+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table72.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table72.txt]) -+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]] -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]] -+]) -+ -+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]] -+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]] -+]) -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -+ -+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:13 -+src_mac=505400000004 -+src_ip=$(ip_to_hex 10 0 0 4) -+ -+dst_mac=505400000013 -+dst_ip=$(ip_to_hex 10 0 0 13) -+ -+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+dst_mac=505400000014 -+dst_ip=$(ip_to_hex 10 0 0 14) -+ -+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ -+# Send a packet from sw0-p2 to an unknown mac. Should be received -+# by both sw0-p1 and sw0-p3 (as unknown is set). -+AS_BOX([Send pkt from sw0-p2 to an unknown mac]) -+ -+src_mac=505400000004 -+src_ip=$(ip_to_hex 10 0 0 4) -+ -+dst_mac=505400000023 -+dst_ip=$(ip_to_hex 10 0 0 23) -+ -+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+ -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -+ -+AS_BOX([Flip the mac - 50:54:00:00:00:13 from sw0-p1 to sw0-p3]) -+ -+# Use the src mac 50:54:00:00:00:13 -+src_mac=505400000013 -+src_ip=$(ip_to_hex 10 0 0 23) -+ -+# send the packet to sw0-p2 -+dst_mac=505400000004 -+dst_ip=$(ip_to_hex 10 0 0 4) -+ -+data=0800bee4391a0001 -+ -+as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -+as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ -+# Send the pkt from sw0-p3 to sw0-p2. -+send_icmp_packet 3 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) -+ -+# fdb row count should be still 2. But the mac 50:54:00:00:00:13 -+# should be learnt on sw0-p3. -+ -+wait_row_count FDB 2 -+ -+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac -+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key -+check_column "$sw0p1_dpkey $sw0p3_dpkey" fdb port_key -+ -+check_column "$sw0p3_dpkey" fdb port_key mac="50\:54\:00\:00\:00\:13" -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ -+# Send the packet from sw0-p2 to sw0-p3 with the dst mac 50:54:00:00:00:13 -+src_mac=505400000004 -+src_ip=$(ip_to_hex 10 0 0 4) -+ -+dst_mac=505400000013 -+dst_ip=$(ip_to_hex 10 0 0 13) -+ -+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -+ -+# sw0-p1 should not receive the packet. -+: > expected -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+AS_BOX([Test routing]) -+ -+# Test the routing. -+# Send the packet from sw1-p2 (hv1) to sw0-p1 (hv1) with dst ip 10.0.0.14 -+# The packet should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14 -+# Before sending add mac_binding entry for 10.0.0.14 -+ -+lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0) -+ -+ovn-sbctl create mac_binding ip=10.0.0.14 logical_port=lr0-sw0 \ -+mac="50\:54\:00\:00\:00\:14" datapath=$lr0_dp_uuid -+ -+# Wait till the mac_binding flows appear in hv1 -+OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=66 \ -+| grep -c reg0=0xa00000e)]) -+ -+src_mac=405400000004 -+src_ip=$(ip_to_hex 11 0 0 4) -+ -+dst_mac=00000000ff02 # lr0-sw1 mac -+dst_ip=$(ip_to_hex 10 0 0 14) -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ -+send_icmp_packet 2 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+ -+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data} -+echo $exp_packet > expected -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+# sw0-p3 should not receive the packet. -+: > expected -+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -+ -+# Now the send the packet from sw1-p1 (hv2) to sw0-p1 (hv1) with dst ip 10.0.0.14 -+# The acket should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14 -+ -+src_mac=405400000003 -+src_ip=$(ip_to_hex 11 0 0 3) -+ -+dst_mac=00000000ff02 # lr0-sw1 mac -+dst_ip=$(ip_to_hex 10 0 0 14) -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+send_icmp_packet 2 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+ -+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data} -+echo $exp_packet > expected -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ -+AS_BOX([Clear the FDB rows]) -+ -+# Clear the fdb rows. -+check ovn-sbctl --all destroy fdb -+ovn-sbctl list fdb -+ -+as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ -+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:14 -+# It should be delivered to both sw0-p1 and sw0-p3 since we have cleared the -+# FDB table. -+src_mac=505400000004 -+src_ip=$(ip_to_hex 10 0 0 4) -+ -+dst_mac=505400000014 -+dst_ip=$(ip_to_hex 10 0 0 13) -+ -+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data -+ -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected]) -+ -+# Make sure that OVS tables 71 and 72 are empty. -+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt -+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table71.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table71.txt]) -+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST], [1], [dnl -+]) -+ -+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt -+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt -+ -+AT_CAPTURE_FILE([hv1_offlows_table72.txt]) -+AT_CAPTURE_FILE([hv2_offlows_table72.txt]) -+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST], [1], [dnl -+]) -+ -+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl -+]) -+ -+OVN_CLEANUP([hv1], [hv2]) -+AT_CLEANUP -diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at -index 05b17ebce..8b1b03e24 100644 ---- a/tests/ovs-macros.at -+++ b/tests/ovs-macros.at -@@ -266,14 +266,16 @@ m4_define([OVS_WAIT_UNTIL], - [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])]) - - dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR) -+dnl OVS_WAIT_FOR_OUTPUT_UNQUOTED(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 -+dnl -+dnl The UNQUOTED version expands shell $variables, $(command)s, and so on. -+dnl The plain version does not -+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 - } -@@ -293,6 +295,18 @@ ovs_wait_failed () { - } - ovs_wait "AS_ESCAPE([AT_LINE])" "for output from AS_ESCAPE([$1])" - ]) -+m4_define([OVS_WAIT_FOR_OUTPUT], [dnl -+AT_DATA([wait-expected-stdout], [$3]) -+AT_DATA([wait-expected-stderr], [$4]) -+OVS_WAIT_FOR_OUTPUT__([$1], [$2]) -+]) -+m4_define([OVS_WAIT_FOR_OUTPUT_UNQUOTED], [dnl -+cat > wait-expected-stdout < wait-expected-stderr < rst.pcap &]) -+sleep 1 -+NS_CHECK_EXEC([foo1], [wget -q 30.0.0.10],[4]) -+ -+OVS_WAIT_UNTIL([ -+ n_reset=$(cat rst.pcap | wc -l) -+ test "${n_reset}" = "1" -+]) -+ - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -2212,6 +2224,144 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply= - - OVS_WAIT_UNTIL([check_est_flows], [check established flows]) - -+ovn-nbctl set logical_router R2 options:lb_force_snat_ip=router_ip -+ -+# Destroy the load balancer and create again. ovn-controller will -+# clear the OF flows and re add again and clears the n_packets -+# for these flows. -+ovn-nbctl destroy load_balancer $uuid -+uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"` -+ovn-nbctl set logical_router R2 load_balancer=$uuid -+ -+# Config OVN load-balancer with another VIP (this time with ports). -+ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"' -+ -+ovn-nbctl list load_balancer -+ovn-sbctl dump-flows R2 -+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=41 | \ -+grep 'nat(src=20.0.0.2)']) -+ -+rm -f wget*.log -+ -+dnl Test load-balancing that includes L4 ports in NAT. -+for i in `seq 1 20`; do -+ echo Request $i -+ NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done -+ -+dnl Each server should have at least one connection. -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | -+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl -+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) -+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) -+]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | -+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl -+tcp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) -+]) -+ -+OVS_WAIT_UNTIL([check_est_flows], [check established flows]) -+ -+OVS_APP_EXIT_AND_WAIT([ovn-controller]) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -+/connection dropped.*/d"]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- load balancing in gateway router hairpin scenario]) -+AT_KEYWORDS([ovnlb]) -+ -+CHECK_CONNTRACK() -+CHECK_CONNTRACK_NAT() -+ovn_start -+OVS_TRAFFIC_VSWITCHD_START() -+ADD_BR([br-int]) -+check ovs-vsctl add-br br-ext -+ -+ -+# Set external-ids in br-int needed for ovn-controller -+ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+# Start ovn-controller -+start_daemon ovn-controller -+ -+check ovn-nbctl lr-add R1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add public -+ -+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 -+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 -+ -+check ovn-nbctl set logical_router R1 options:chassis=hv1 -+ -+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ -+ type=router options:router-port=rp-sw0 \ -+ -- lsp-set-addresses sw0-rp router -+ -+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ -+ type=router options:router-port=rp-public \ -+ -- lsp-set-addresses public-rp router -+ -+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext -+ -+check ovn-nbctl lsp-add public public1 \ -+ -- lsp-set-addresses public1 unknown \ -+ -- lsp-set-type public1 localnet \ -+ -- lsp-set-options public1 network_name=phynet -+ -+ADD_NAMESPACES(server) -+ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \ -+ "172.16.1.1") -+ -+OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""]) -+ -+ADD_NAMESPACES(client) -+ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \ -+ "172.16.1.1") -+ -+OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""]) -+ -+# Start webservers in 'server'. -+OVS_START_L7([server], [http]) -+ -+# Create a load balancer and associate to R1 -+check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80 -+check ovn-nbctl lr-lb-add R1 lb1 -+ -+check ovn-nbctl --wait=hv sync -+ -+for i in $(seq 1 5); do -+ echo Request $i -+ NS_CHECK_EXEC([client], [wget 172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done -+ -+# Now send the traffic from client to the VIP - 172.16.1.150 -+check ovn-nbctl set logical_router R1 options:lb_force_snat_ip=router_ip -+check ovn-nbctl --wait=hv sync -+ -+for i in $(seq 1 5); do -+ echo Request $i -+ NS_CHECK_EXEC([client], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done -+ - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -2225,6 +2375,7 @@ OVS_APP_EXIT_AND_WAIT([ovn-northd]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -+/Failed to acquire.*/d - /connection dropped.*/d"]) - AT_CLEANUP - -@@ -4151,7 +4302,7 @@ ovn-nbctl lsp-set-type sw1-lr0 router - ovn-nbctl lsp-set-addresses sw1-lr0 router - ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - --ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 -+ovn-nbctl --reject lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 - - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -@@ -4266,6 +4417,20 @@ ovn-sbctl list service_monitor - OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ - service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`]) - -+# Stop webserer in sw1-p1 -+pid_file=$(cat l7_pid_file) -+NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)]) -+ -+NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &]) -+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ -+service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`]) -+NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4]) -+ -+OVS_WAIT_UNTIL([ -+ n_reset=$(cat rst.pcap | wc -l) -+ test "${n_reset}" = "1" -+]) -+ - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -4309,10 +4474,14 @@ start_daemon ovn-controller - # One logical switch with IPv4 load balancers that hairpin the traffic. - ovn-nbctl ls-add sw - ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 --ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp --ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp - ovn-nbctl ls-lb-add sw lb-ipv4-tcp -+ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv4-udp -+ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup - - ovn-nbctl lr-add rtr - ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 -@@ -4328,24 +4497,26 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \ - ovn-nbctl --wait=hv -t 3 sync - - # Start IPv4 TCP server on lsp. --NS_CHECK_EXEC([lsp], [timeout 2s nc -l 42.42.42.1 4041 &], [0]) -+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0]) - --# Check that IPv4 TCP hairpin connection succeeds. -+# Check that IPv4 TCP hairpin connection succeeds on both VIPs. - NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0]) -+NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0]) - - # Capture IPv4 UDP hairpinned packets. --filter="src 88.88.88.88 and dst 42.42.42.1 and dst port 2021 and udp" --NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp ${filter} > lsp.pcap &]) -+filter="dst 42.42.42.1 and dst port 2021 and udp" -+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp ${filter} > lsp.pcap &]) - - sleep 1 - - # Generate IPv4 UDP hairpin traffic. - NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -z &], [0]) -+NS_CHECK_EXEC([lsp], [nc -u 88.88.88.89 4040 -z &], [0]) - - # Check hairpin traffic. - OVS_WAIT_UNTIL([ - total_pkts=$(cat lsp.pcap | wc -l) -- test "${total_pkts}" = "1" -+ test "${total_pkts}" = "2" - ]) - - OVS_APP_EXIT_AND_WAIT([ovn-controller]) -@@ -4388,10 +4559,14 @@ start_daemon ovn-controller - # One logical switch with IPv6 load balancers that hairpin the traffic. - ovn-nbctl ls-add sw - ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 --ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp --ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp - ovn-nbctl ls-lb-add sw lb-ipv6-tcp -+ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv6-udp -+ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup - - ovn-nbctl lr-add rtr - ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64 -@@ -4406,24 +4581,26 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" - ovn-nbctl --wait=hv -t 3 sync - - # Start IPv6 TCP server on lsp. --NS_CHECK_EXEC([lsp], [timeout 2s nc -l 4200::1 4041 &], [0]) -+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0]) - --# Check that IPv6 TCP hairpin connection succeeds. -+# Check that IPv6 TCP hairpin connection succeeds on both VIPs. - NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0]) -+NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0]) - - # Capture IPv4 UDP hairpinned packets. --filter="src 8800::0088 and dst 4200::1 and dst port 2021 and udp" --NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp $filter > lsp.pcap &]) -+filter="dst 4200::1 and dst port 2021 and udp" -+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp $filter > lsp.pcap &]) - - sleep 1 - - # Generate IPv6 UDP hairpin traffic. - NS_CHECK_EXEC([lsp], [nc -u 8800::0088 4040 -z &], [0]) -+NS_CHECK_EXEC([lsp], [nc -u 8800::0089 4040 -z &], [0]) - - # Check hairpin traffic. - OVS_WAIT_UNTIL([ - total_pkts=$(cat lsp.pcap | wc -l) -- test "${total_pkts}" = "1" -+ test "${total_pkts}" = "2" - ]) - - OVS_APP_EXIT_AND_WAIT([ovn-controller]) -@@ -5505,3 +5682,152 @@ as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d - /.*terminating with signal 15.*/d"]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- BFD]) -+AT_SKIP_IF([test $HAVE_BFDD_BEACON = no]) -+AT_SKIP_IF([test $HAVE_TCPDUMP = no]) -+AT_KEYWORDS([ovn-bfd]) -+ -+ovn_start -+OVS_TRAFFIC_VSWITCHD_START() -+ -+ADD_BR([br-int]) -+ADD_BR([br-ext]) -+ -+check ovs-ofctl add-flow br-ext action=normal -+# Set external-ids in br-int needed for ovn-controller -+check ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+# Start ovn-controller -+start_daemon ovn-controller -+ -+check ovn-nbctl lr-add R1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl ls-add public -+ -+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 -+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 -+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 1000::a/64 \ -+ -- lrp-set-gateway-chassis rp-public 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 -+ -+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ -+ type=router options:router-port=rp-public \ -+ -- lsp-set-addresses public-rp router -+ -+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" -+ -+ADD_NAMESPACES(server) -+NS_CHECK_EXEC([server], [ip link set dev lo up]) -+ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \ -+ "172.16.1.1") -+NS_CHECK_EXEC([server], [ip addr add 1000::b/64 dev s1]) -+ -+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) -+check ovn-nbctl lsp-add public public1 \ -+ -- lsp-set-addresses public1 unknown \ -+ -- lsp-set-type public1 localnet \ -+ -- lsp-set-options public1 network_name=phynet -+ -+NS_CHECK_EXEC([server], [bfdd-beacon --listen=172.16.1.50], [0]) -+NS_CHECK_EXEC([server], [bfdd-control allow 172.16.1.1], [0], [dnl -+Allowing connections from 172.16.1.1 -+]) -+ -+check ovn-nbctl --bfd lr-route-add R1 100.0.0.0/8 172.16.1.50 rp-public -+uuid=$(fetch_column nb:bfd _uuid logical_port="rp-public") -+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") -+check ovn-nbctl --wait=hv sync -+ -+wait_column "up" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep -q 172.16.1.50]) -+ -+# un-associate the bfd connection and the static route -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+wait_column "admin_down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down]) -+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &]) -+sleep 5 -+kill $(pidof tcpdump) -+AT_CHECK([grep -qi bfd bfd.pcap],[1]) -+ -+# restart the connection -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+wait_column "up" nb:bfd status logical_port=rp-public -+ -+# switch to gw router configuration -+check ovn-nbctl clear logical_router_static_route $route_uuid bfd -+wait_column "admin_down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down]) -+check ovn-nbctl clear logical_router_port rp-public gateway_chassis -+check ovn-nbctl set logical_router R1 options:chassis=hv1 -+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid -+wait_column "up" nb:bfd status logical_port=rp-public -+ -+# stop bfd endpoint -+NS_CHECK_EXEC([server], [bfdd-control stop], [0], [dnl -+stopping -+]) -+ -+wait_column "down" nb:bfd status logical_port=rp-public -+OVS_WAIT_UNTIL([test "$(ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep 172.16.1.50)" = ""]) -+ -+# remove bfd entry -+ovn-nbctl destroy bfd $uuid -+check_row_count bfd 0 -+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &]) -+sleep 5 -+kill $(pidof tcpdump) -+AT_CHECK([grep -qi bfd bfd.pcap],[1]) -+ -+uuid_v6=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=\"1000::b\") -+check ovn-nbctl lr-route-add R1 2000::/64 1000::b -+route_uuid_v6=$(fetch_column nb:logical_router_static_route _uuid ip_prefix=\"2000::/64\") -+ovn-nbctl set logical_router_static_route $route_uuid_v6 bfd=$uuid_v6 -+check ovn-nbctl --wait=hv sync -+NS_CHECK_EXEC([server], [bfdd-beacon --listen=1000::b], [0]) -+NS_CHECK_EXEC([server], [bfdd-control allow 1000::a], [0], [dnl -+Allowing connections from 1000::a -+]) -+ -+wait_column "up" nb:bfd status logical_port=rp-public -+ovn-nbctl destroy bfd $uuid_v6 -+ -+kill $(pidof ovn-controller) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -+/.*terminating with signal 15.*/d"]) -+AT_CLEANUP -diff --git a/tests/test-ovn.c b/tests/test-ovn.c -index 49a1947f6..3fbe90b32 100644 ---- a/tests/test-ovn.c -+++ b/tests/test-ovn.c -@@ -1346,6 +1346,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) - .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, -+ .fdb_ptable = OFTABLE_GET_FDB, -+ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB, - }; - struct ofpbuf ofpacts; - ofpbuf_init(&ofpacts, 0); -diff --git a/tests/testsuite.at b/tests/testsuite.at -index 960227dcc..3eba785c6 100644 ---- a/tests/testsuite.at -+++ b/tests/testsuite.at -@@ -26,6 +26,7 @@ m4_include([tests/ovn.at]) - m4_include([tests/ovn-performance.at]) - m4_include([tests/ovn-northd.at]) - m4_include([tests/ovn-nbctl.at]) -+m4_include([tests/ovn-ofctrl-seqno.at]) - m4_include([tests/ovn-sbctl.at]) - m4_include([tests/ovn-ic-nbctl.at]) - m4_include([tests/ovn-ic-sbctl.at]) -diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py -index 981a433be..fb96fd66b 100755 ---- a/utilities/checkpatch.py -+++ b/utilities/checkpatch.py -@@ -189,7 +189,8 @@ line_length_blacklist = re.compile( - # Don't enforce a requirement that leading whitespace be all spaces on - # files that include these characters in their name, since these kinds - # of files need lines with leading tabs. --leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules') -+leading_whitespace_blacklist = re.compile( -+ r'\.(mk|am|at)$|debian/rules|\.gitmodules$') - - - def is_subtracted_line(line): -diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl -index c44201ccf..211c764a6 100755 ---- a/utilities/ovn-ctl -+++ b/utilities/ovn-ctl -@@ -251,6 +251,11 @@ $cluster_remote_port - - [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER" - -+ if test X"$OVSDB_DISABLE_FILE_COLUMN_DIFF" = Xyes; then -+ (ovsdb-server --help | grep -q disable-file-column-diff) \ -+ && set "$@" --disable-file-column-diff -+ fi -+ - if test X"$detach" != Xno; then - set "$@" --detach --monitor - else -@@ -715,6 +720,8 @@ set_defaults () { - OVSDB_NB_WRAPPER= - OVSDB_SB_WRAPPER= - -+ OVSDB_DISABLE_FILE_COLUMN_DIFF=no -+ - OVN_USER= - - OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info" -@@ -932,6 +939,11 @@ Options: - --ovs-user="user[:group]" pass the --user flag to ovs daemons - --ovsdb-nb-wrapper=WRAPPER run with a wrapper like valgrind for debugging - --ovsdb-sb-wrapper=WRAPPER run with a wrapper like valgrind for debugging -+ --ovsdb-disable-file-column-diff=no|yes -+ Specifies whether or not ovsdb-server -+ processes should be started with -+ --disable-file-column-diff. -+ More details in ovsdb(7). (default: no) - -h, --help display this help message - - File location options: -diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml -index 59302296b..2cab592ce 100644 ---- a/utilities/ovn-nbctl.8.xml -+++ b/utilities/ovn-nbctl.8.xml -@@ -659,6 +659,7 @@ -

    -
    [--may-exist] [--policy=POLICY] - [--ecmp] [--ecmp-symmetric-reply] -+ [--bfd[=UUID]] - lr-route-add router - prefix nexthop [port]
    -
    -@@ -695,6 +696,16 @@ - it is not necessary to set both. -

    - -+

    -+ --bfd option is used to link a BFD session to the -+ OVN route. If the BFD session UUID is provided, it will be used -+ for the OVN route otherwise the next-hop will be used to perform -+ a lookup in the OVN BFD table. -+ If the lookup fails and port is specified, a new entry -+ in the BFD table will be created using the nexthop as -+ dst_ip and port as logical_port. -+

    -+ -

    - It is an error if a route with prefix and - POLICY already exists, unless --may-exist, -@@ -739,7 +750,7 @@ -

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

    -@@ -748,10 +759,12 @@ - are similar to OVN ACLs, but exist on the logical-router. Reroute - policies are needed for service-insertion and service-chaining. - nexthop is an optional parameter. It needs to be provided -- only when action is reroute. A policy is -- uniquely identified by priority and match. -- Multiple policies can have the same priority. -- options sets the router policy options as key-value pair. -+ only when action is reroute. Multiple -+ nexthops can be specified for ECMP routing. -+ A policy is uniquely identified by priority and -+ match. Multiple policies can have the same -+ priority. options sets the router policy -+ options as key-value pair. - The supported option is : pkt_mark. -

    - -@@ -903,7 +916,7 @@ - -

    Load Balancer Commands

    -
    --
    [--may-exist | --add-duplicate] lb-add lb vip ips [protocol]
    -+
    [--may-exist | --add-duplicate | --reject | --event] lb-add lb vip ips [protocol]
    -
    -

    - Creates a new load balancer named lb with the provided -@@ -936,6 +949,23 @@ - creates a new load balancer with a duplicate name. -

    - -+

    -+ If the load balancer is created with --reject option and -+ it has no active backends, a TCP reset segment (for tcp) or an ICMP -+ port unreachable packet (for all other kind of traffic) will be sent -+ whenever an incoming packet is received for this load-balancer. -+ Please note using --reject option will disable -+ empty_lb SB controller event for this load balancer. -+

    -+ -+

    -+ If the load balancer is created with --event option and -+ it has no active backends, whenever the lb receives traffic, the event -+ is reported in the Controller_Event table in the SB db. -+ Please note --event option can't be specified with -+ --reject one. -+

    -+ -

    - The following example adds a load balancer. -

    -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index d19e1b6c6..dc0c50854 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -125,6 +125,65 @@ static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args, - const struct timer *); - static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]); - -+/* A context for keeping track of which switch/router certain ports are -+ * connected to. -+ * -+ * It is required to track changes that we did within current set of commands -+ * because partial updates of sets in database are not reflected in the idl -+ * until transaction is committed and updates received from the server. */ -+struct nbctl_context { -+ struct ctl_context base; -+ struct shash lsp_to_ls_map; -+ struct shash lrp_to_lr_map; -+ bool context_valid; -+}; -+ -+static void -+nbctl_context_init(struct nbctl_context *nbctx) -+{ -+ nbctx->context_valid = false; -+ shash_init(&nbctx->lsp_to_ls_map); -+ shash_init(&nbctx->lrp_to_lr_map); -+} -+ -+static void -+nbctl_context_destroy(struct nbctl_context *nbctx) -+{ -+ nbctx->context_valid = false; -+ shash_destroy(&nbctx->lsp_to_ls_map); -+ shash_destroy(&nbctx->lrp_to_lr_map); -+} -+ -+/* Casts 'base' into 'struct nbctl_context' and initializes it if needed. */ -+static struct nbctl_context * -+nbctl_context_get(struct ctl_context *base) -+{ -+ struct nbctl_context *nbctx; -+ -+ nbctx = CONTAINER_OF(base, struct nbctl_context, base); -+ -+ if (nbctx->context_valid) { -+ return nbctx; -+ } -+ -+ const struct nbrec_logical_switch *ls; -+ NBREC_LOGICAL_SWITCH_FOR_EACH (ls, base->idl) { -+ for (size_t i = 0; i < ls->n_ports; i++) { -+ shash_add_once(&nbctx->lsp_to_ls_map, ls->ports[i]->name, ls); -+ } -+ } -+ -+ const struct nbrec_logical_router *lr; -+ NBREC_LOGICAL_ROUTER_FOR_EACH (lr, base->idl) { -+ for (size_t i = 0; i < lr->n_ports; i++) { -+ shash_add_once(&nbctx->lrp_to_lr_map, lr->ports[i]->name, lr); -+ } -+ } -+ -+ nbctx->context_valid = true; -+ return nbctx; -+} -+ - int - main(int argc, char *argv[]) - { -@@ -707,7 +766,7 @@ Route commands:\n\ - lr-route-list ROUTER print routes for ROUTER\n\ - \n\ - Policy commands:\n\ -- lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP] \ -+ lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP,[NEXTHOP,...]] \ - [OPTIONS KEY=VALUE ...] \n\ - add a policy to router\n\ - lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\ -@@ -1249,6 +1308,7 @@ static void - nbctl_ls_del(struct ctl_context *ctx) - { - bool must_exist = !shash_find(&ctx->options, "--if-exists"); -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const char *id = ctx->argv[1]; - const struct nbrec_logical_switch *ls = NULL; - -@@ -1261,6 +1321,11 @@ nbctl_ls_del(struct ctl_context *ctx) - return; - } - -+ /* Updating runtime cache. */ -+ for (size_t i = 0; i < ls->n_ports; i++) { -+ shash_find_and_delete(&nbctx->lsp_to_ls_map, ls->ports[i]->name); -+ } -+ - nbrec_logical_switch_delete(ls); - } - -@@ -1317,22 +1382,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, - - /* Returns the logical switch that contains 'lsp'. */ - static char * OVS_WARN_UNUSED_RESULT --lsp_to_ls(const struct ovsdb_idl *idl, -+lsp_to_ls(struct ctl_context *ctx, - const struct nbrec_logical_switch_port *lsp, - const struct nbrec_logical_switch **ls_p) - { -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const struct nbrec_logical_switch *ls; - *ls_p = NULL; - -- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, idl) { -- for (size_t i = 0; i < ls->n_ports; i++) { -- if (ls->ports[i] == lsp) { -- *ls_p = ls; -- return NULL; -- } -- } -+ ls = shash_find_data(&nbctx->lsp_to_ls_map, lsp->name); -+ if (ls) { -+ *ls_p = ls; -+ return NULL; - } -- - /* Can't happen because of the database schema */ - return xasprintf("logical port %s is not part of any logical switch", - lsp->name); -@@ -1353,6 +1415,7 @@ static void - nbctl_lsp_add(struct ctl_context *ctx) - { - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - - const struct nbrec_logical_switch *ls = NULL; - char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls); -@@ -1395,7 +1458,7 @@ nbctl_lsp_add(struct ctl_context *ctx) - } - - const struct nbrec_logical_switch *lsw; -- error = lsp_to_ls(ctx->idl, lsp, &lsw); -+ error = lsp_to_ls(ctx, lsp, &lsw); - if (error) { - ctx->error = error; - return; -@@ -1448,31 +1511,27 @@ nbctl_lsp_add(struct ctl_context *ctx) - } - - /* Insert the logical port into the logical switch. */ -- nbrec_logical_switch_verify_ports(ls); -- struct nbrec_logical_switch_port **new_ports = xmalloc(sizeof *new_ports * -- (ls->n_ports + 1)); -- nullable_memcpy(new_ports, ls->ports, sizeof *new_ports * ls->n_ports); -- new_ports[ls->n_ports] = CONST_CAST(struct nbrec_logical_switch_port *, -- lsp); -- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1); -- free(new_ports); -+ nbrec_logical_switch_update_ports_addvalue(ls, lsp); -+ -+ /* Updating runtime cache. */ -+ shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls); - } - --/* Removes logical switch port 'ls->ports[idx]'. */ -+/* Removes logical switch port 'lsp' from the logical switch 'ls'. */ - static void --remove_lsp(const struct nbrec_logical_switch *ls, size_t idx) -+remove_lsp(struct ctl_context *ctx, -+ const struct nbrec_logical_switch *ls, -+ const struct nbrec_logical_switch_port *lsp) - { -- const struct nbrec_logical_switch_port *lsp = ls->ports[idx]; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); -+ -+ /* Updating runtime cache. */ -+ shash_find_and_delete(&nbctx->lsp_to_ls_map, lsp->name); - - /* First remove 'lsp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- struct nbrec_logical_switch_port **new_ports -- = xmemdup(ls->ports, sizeof *new_ports * ls->n_ports); -- new_ports[idx] = new_ports[ls->n_ports - 1]; -- nbrec_logical_switch_verify_ports(ls); -- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports - 1); -- free(new_ports); -+ nbrec_logical_switch_update_ports_delvalue(ls, lsp); - - /* Delete 'lsp' from the IDL. This won't have a real effect on the - * database server (the IDL will suppress it in fact) but it means that it -@@ -1498,18 +1557,13 @@ nbctl_lsp_del(struct ctl_context *ctx) - - /* Find the switch that contains 'lsp', then delete it. */ - const struct nbrec_logical_switch *ls; -- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) { -- for (size_t i = 0; i < ls->n_ports; i++) { -- if (ls->ports[i] == lsp) { -- remove_lsp(ls, i); -- return; -- } -- } -- } - -- /* Can't happen because of the database schema. */ -- ctl_error(ctx, "logical port %s is not part of any logical switch", -- ctx->argv[1]); -+ error = lsp_to_ls(ctx, lsp, &ls); -+ if (error) { -+ ctx->error = error; -+ return; -+ } -+ remove_lsp(ctx, ls, lsp); - } - - static void -@@ -1658,7 +1712,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx) - } - - const struct nbrec_logical_switch *ls; -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return; -@@ -2299,17 +2353,11 @@ nbctl_acl_add(struct ctl_context *ctx) - } - - /* Insert the acl into the logical switch/port group. */ -- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * (n_acls + 1)); -- nullable_memcpy(new_acls, acls, sizeof *new_acls * n_acls); -- new_acls[n_acls] = acl; - if (pg) { -- nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, n_acls + 1); -+ nbrec_port_group_update_acls_addvalue(pg, acl); - } else { -- nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, n_acls + 1); -+ nbrec_logical_switch_update_acls_addvalue(ls, acl); - } -- free(new_acls); - } - - static void -@@ -2349,23 +2397,15 @@ nbctl_acl_del(struct ctl_context *ctx) - /* If priority and match are not specified, delete all ACLs with the - * specified direction. */ - if (ctx->argc == 3) { -- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * n_acls); -- -- int n_new_acls = 0; - for (size_t i = 0; i < n_acls; i++) { -- if (strcmp(direction, acls[i]->direction)) { -- new_acls[n_new_acls++] = acls[i]; -+ if (!strcmp(direction, acls[i]->direction)) { -+ if (pg) { -+ nbrec_port_group_update_acls_delvalue(pg, acls[i]); -+ } else { -+ nbrec_logical_switch_update_acls_delvalue(ls, acls[i]); -+ } - } - } -- -- if (pg) { -- nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, n_new_acls); -- } else { -- nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, n_new_acls); -- } -- free(new_acls); - return; - } - -@@ -2387,19 +2427,11 @@ nbctl_acl_del(struct ctl_context *ctx) - - if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) && - !strcmp(direction, acl->direction)) { -- struct nbrec_acl **new_acls -- = xmemdup(acls, sizeof *new_acls * n_acls); -- new_acls[i] = acls[n_acls - 1]; - if (pg) { -- nbrec_port_group_verify_acls(pg); -- nbrec_port_group_set_acls(pg, new_acls, -- n_acls - 1); -+ nbrec_port_group_update_acls_delvalue(pg, acl); - } else { -- nbrec_logical_switch_verify_acls(ls); -- nbrec_logical_switch_set_acls(ls, new_acls, -- n_acls - 1); -+ nbrec_logical_switch_update_acls_delvalue(ls, acl); - } -- free(new_acls); - return; - } - } -@@ -2552,15 +2584,7 @@ nbctl_qos_add(struct ctl_context *ctx) - } - - /* Insert the qos rule the logical switch. */ -- nbrec_logical_switch_verify_qos_rules(ls); -- struct nbrec_qos **new_qos_rules -- = xmalloc(sizeof *new_qos_rules * (ls->n_qos_rules + 1)); -- nullable_memcpy(new_qos_rules, -- ls->qos_rules, sizeof *new_qos_rules * ls->n_qos_rules); -- new_qos_rules[ls->n_qos_rules] = qos; -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, -- ls->n_qos_rules + 1); -- free(new_qos_rules); -+ nbrec_logical_switch_update_qos_rules_addvalue(ls, qos); - } - - static void -@@ -2597,34 +2621,31 @@ nbctl_qos_del(struct ctl_context *ctx) - /* If uuid was specified, delete qos_rule with the - * specified uuid. */ - if (ctx->argc == 3) { -- struct nbrec_qos **new_qos_rules -- = xmalloc(sizeof *new_qos_rules * ls->n_qos_rules); -+ size_t i; - -- int n_qos_rules = 0; - if (qos_rule_uuid) { -- for (size_t i = 0; i < ls->n_qos_rules; i++) { -- if (!uuid_equals(qos_rule_uuid, -- &(ls->qos_rules[i]->header_.uuid))) { -- new_qos_rules[n_qos_rules++] = ls->qos_rules[i]; -+ for (i = 0; i < ls->n_qos_rules; i++) { -+ if (uuid_equals(qos_rule_uuid, -+ &(ls->qos_rules[i]->header_.uuid))) { -+ nbrec_logical_switch_update_qos_rules_delvalue( -+ ls, ls->qos_rules[i]); -+ break; - } - } -- if (n_qos_rules == ls->n_qos_rules) { -+ if (i == ls->n_qos_rules) { - ctl_error(ctx, "uuid is not found"); - } - - /* If priority and match are not specified, delete all qos_rules - * with the specified direction. */ - } else { -- for (size_t i = 0; i < ls->n_qos_rules; i++) { -- if (strcmp(direction, ls->qos_rules[i]->direction)) { -- new_qos_rules[n_qos_rules++] = ls->qos_rules[i]; -+ for (i = 0; i < ls->n_qos_rules; i++) { -+ if (!strcmp(direction, ls->qos_rules[i]->direction)) { -+ nbrec_logical_switch_update_qos_rules_delvalue( -+ ls, ls->qos_rules[i]); - } - } - } -- -- nbrec_logical_switch_verify_qos_rules(ls); -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, n_qos_rules); -- free(new_qos_rules); - return; - } - -@@ -2651,14 +2672,7 @@ nbctl_qos_del(struct ctl_context *ctx) - - if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) && - !strcmp(direction, qos->direction)) { -- struct nbrec_qos **new_qos_rules -- = xmemdup(ls->qos_rules, -- sizeof *new_qos_rules * ls->n_qos_rules); -- new_qos_rules[i] = ls->qos_rules[ls->n_qos_rules - 1]; -- nbrec_logical_switch_verify_qos_rules(ls); -- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, -- ls->n_qos_rules - 1); -- free(new_qos_rules); -+ nbrec_logical_switch_update_qos_rules_delvalue(ls, qos); - return; - } - } -@@ -2821,6 +2835,14 @@ nbctl_lb_add(struct ctl_context *ctx) - - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL; -+ bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL; -+ bool empty_backend_event = shash_find(&ctx->options, "--event") != NULL; -+ -+ if (empty_backend_event && empty_backend_rej) { -+ ctl_error(ctx, -+ "--reject and --event can't specified at the same time"); -+ return; -+ } - - const char *lb_proto; - bool is_update_proto = false; -@@ -2934,6 +2956,14 @@ nbctl_lb_add(struct ctl_context *ctx) - smap_add(CONST_CAST(struct smap *, &lb->vips), - lb_vip_normalized, ds_cstr(&lb_ips_new)); - nbrec_load_balancer_set_vips(lb, &lb->vips); -+ if (empty_backend_rej) { -+ const struct smap options = SMAP_CONST1(&options, "reject", "true"); -+ nbrec_load_balancer_set_options(lb, &options); -+ } -+ if (empty_backend_event) { -+ const struct smap options = SMAP_CONST1(&options, "event", "true"); -+ nbrec_load_balancer_set_options(lb, &options); -+ } - out: - ds_destroy(&lb_ips_new); - -@@ -3115,17 +3145,7 @@ nbctl_lr_lb_add(struct ctl_context *ctx) - } - - /* Insert the load balancer into the logical router. */ -- nbrec_logical_router_verify_load_balancer(lr); -- struct nbrec_load_balancer **new_lbs -- = xmalloc(sizeof *new_lbs * (lr->n_load_balancer + 1)); -- -- nullable_memcpy(new_lbs, lr->load_balancer, -- sizeof *new_lbs * lr->n_load_balancer); -- new_lbs[lr->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *, -- new_lb); -- nbrec_logical_router_set_load_balancer(lr, new_lbs, -- lr->n_load_balancer + 1); -- free(new_lbs); -+ nbrec_logical_router_update_load_balancer_addvalue(lr, new_lb); - } - - static void -@@ -3158,15 +3178,7 @@ nbctl_lr_lb_del(struct ctl_context *ctx) - - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ -- nbrec_logical_router_verify_load_balancer(lr); -- -- struct nbrec_load_balancer **new_lbs -- = xmemdup(lr->load_balancer, -- sizeof *new_lbs * lr->n_load_balancer); -- new_lbs[i] = lr->load_balancer[lr->n_load_balancer - 1]; -- nbrec_logical_router_set_load_balancer(lr, new_lbs, -- lr->n_load_balancer - 1); -- free(new_lbs); -+ nbrec_logical_router_update_load_balancer_delvalue(lr, lb); - return; - } - } -@@ -3240,17 +3252,7 @@ nbctl_ls_lb_add(struct ctl_context *ctx) - } - - /* Insert the load balancer into the logical switch. */ -- nbrec_logical_switch_verify_load_balancer(ls); -- struct nbrec_load_balancer **new_lbs -- = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1)); -- -- nullable_memcpy(new_lbs, ls->load_balancer, -- sizeof *new_lbs * ls->n_load_balancer); -- new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *, -- new_lb); -- nbrec_logical_switch_set_load_balancer(ls, new_lbs, -- ls->n_load_balancer + 1); -- free(new_lbs); -+ nbrec_logical_switch_update_load_balancer_addvalue(ls, new_lb); - } - - static void -@@ -3283,15 +3285,7 @@ nbctl_ls_lb_del(struct ctl_context *ctx) - - if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { - /* Remove the matching rule. */ -- nbrec_logical_switch_verify_load_balancer(ls); -- -- struct nbrec_load_balancer **new_lbs -- = xmemdup(ls->load_balancer, -- sizeof *new_lbs * ls->n_load_balancer); -- new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1]; -- nbrec_logical_switch_set_load_balancer(ls, new_lbs, -- ls->n_load_balancer - 1); -- free(new_lbs); -+ nbrec_logical_switch_update_load_balancer_delvalue(ls, lb); - return; - } - } -@@ -3378,6 +3372,7 @@ static void - nbctl_lr_del(struct ctl_context *ctx) - { - bool must_exist = !shash_find(&ctx->options, "--if-exists"); -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const char *id = ctx->argv[1]; - const struct nbrec_logical_router *lr = NULL; - -@@ -3390,6 +3385,11 @@ nbctl_lr_del(struct ctl_context *ctx) - return; - } - -+ /* Updating runtime cache. */ -+ for (size_t i = 0; i < lr->n_ports; i++) { -+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lr->ports[i]->name); -+ } -+ - nbrec_logical_router_delete(lr); - } - -@@ -3645,7 +3645,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - return; - } - const char *action = ctx->argv[4]; -- char *next_hop = NULL; -+ size_t n_nexthops = 0; -+ char **nexthops = NULL; - - bool reroute = false; - /* Validate action. */ -@@ -3665,7 +3666,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - /* Check if same routing policy already exists. - * A policy is uniquely identified by priority and match */ - bool may_exist = !!shash_find(&ctx->options, "--may-exist"); -- for (int i = 0; i < lr->n_policies; i++) { -+ size_t i; -+ for (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])) { -@@ -3676,12 +3678,53 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - return; - } - } -+ - if (reroute) { -- next_hop = normalize_prefix_str(ctx->argv[5]); -- if (!next_hop) { -- ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]); -- return; -+ char *nexthops_arg = xstrdup(ctx->argv[5]); -+ char *save_ptr, *next_hop, *token; -+ -+ n_nexthops = 0; -+ size_t n_allocs = 0; -+ -+ bool nexthops_is_ipv4 = true; -+ for (token = strtok_r(nexthops_arg, ",", &save_ptr); -+ token != NULL; token = strtok_r(NULL, ",", &save_ptr)) { -+ next_hop = normalize_addr_str(token); -+ -+ if (!next_hop) { -+ ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]); -+ free(nexthops_arg); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); -+ return; -+ } -+ if (n_nexthops == n_allocs) { -+ nexthops = x2nrealloc(nexthops, &n_allocs, sizeof *nexthops); -+ } -+ -+ bool is_ipv4 = strchr(next_hop, '.') ? true : false; -+ if (n_nexthops == 0) { -+ nexthops_is_ipv4 = is_ipv4; -+ } -+ -+ if (is_ipv4 != nexthops_is_ipv4) { -+ ctl_error(ctx, "bad next hops argument, not in the same " -+ "addr family : %s", ctx->argv[5]); -+ free(nexthops_arg); -+ free(next_hop); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); -+ return; -+ } -+ nexthops[n_nexthops] = next_hop; -+ n_nexthops++; - } -+ -+ free(nexthops_arg); - } - - struct nbrec_logical_router_policy *policy; -@@ -3690,12 +3733,13 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - nbrec_logical_router_policy_set_match(policy, ctx->argv[3]); - nbrec_logical_router_policy_set_action(policy, action); - if (reroute) { -- nbrec_logical_router_policy_set_nexthop(policy, next_hop); -+ nbrec_logical_router_policy_set_nexthops( -+ policy, (const char **)nexthops, n_nexthops); - } - - /* Parse the options. */ - struct smap options = SMAP_INITIALIZER(&options); -- for (size_t i = reroute ? 6 : 5; i < ctx->argc; i++) { -+ for (i = reroute ? 6 : 5; i < ctx->argc; i++) { - char *key, *value; - value = xstrdup(ctx->argv[i]); - key = strsep(&value, "="); -@@ -3705,7 +3749,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - ctl_error(ctx, "No value specified for the option : %s", key); - smap_destroy(&options); - free(key); -- free(next_hop); -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); -+ } -+ free(nexthops); - return; - } - free(key); -@@ -3713,18 +3760,12 @@ nbctl_lr_policy_add(struct ctl_context *ctx) - nbrec_logical_router_policy_set_options(policy, &options); - smap_destroy(&options); - -- nbrec_logical_router_verify_policies(lr); -- struct nbrec_logical_router_policy **new_policies -- = xmalloc(sizeof *new_policies * (lr->n_policies + 1)); -- memcpy(new_policies, lr->policies, -- sizeof *new_policies * lr->n_policies); -- new_policies[lr->n_policies] = policy; -- nbrec_logical_router_set_policies(lr, new_policies, -- lr->n_policies + 1); -- free(new_policies); -- if (next_hop != NULL) { -- free(next_hop); -+ nbrec_logical_router_update_policies_addvalue(lr, policy); -+ -+ for (i = 0; i < n_nexthops; i++) { -+ free(nexthops[i]); - } -+ free(nexthops); - } - - static void -@@ -3758,38 +3799,34 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - /* If uuid was specified, delete routing policy with the - * specified uuid. */ - if (ctx->argc == 3) { -- struct nbrec_logical_router_policy **new_policies -- = xmemdup(lr->policies, -- sizeof *new_policies * lr->n_policies); -- int n_policies = 0; -+ size_t i; - - if (lr_policy_uuid) { -- for (size_t i = 0; i < lr->n_policies; i++) { -- if (!uuid_equals(lr_policy_uuid, -- &(lr->policies[i]->header_.uuid))) { -- new_policies[n_policies++] = lr->policies[i]; -+ for (i = 0; i < lr->n_policies; i++) { -+ if (uuid_equals(lr_policy_uuid, -+ &(lr->policies[i]->header_.uuid))) { -+ nbrec_logical_router_update_policies_delvalue( -+ lr, lr->policies[i]); -+ break; - } - } -- if (n_policies == lr->n_policies) { -+ if (i == lr->n_policies) { - if (!shash_find(&ctx->options, "--if-exists")) { - ctl_error(ctx, "Logical router policy uuid is not found."); - } -- free(new_policies); - return; - } - -- /* If match is not specified, delete all routing policies with the -- * specified priority. */ -+ /* If match is not specified, delete all routing policies with the -+ * specified priority. */ - } else { -- for (int i = 0; i < lr->n_policies; i++) { -- if (priority != lr->policies[i]->priority) { -- new_policies[n_policies++] = lr->policies[i]; -+ for (i = 0; i < lr->n_policies; i++) { -+ if (priority == lr->policies[i]->priority) { -+ nbrec_logical_router_update_policies_delvalue( -+ lr, lr->policies[i]); - } - } - } -- nbrec_logical_router_verify_policies(lr); -- nbrec_logical_router_set_policies(lr, new_policies, n_policies); -- free(new_policies); - return; - } - -@@ -3798,14 +3835,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx) - struct nbrec_logical_router_policy *routing_policy = lr->policies[i]; - if (priority == routing_policy->priority && - !strcmp(ctx->argv[3], routing_policy->match)) { -- struct nbrec_logical_router_policy **new_policies -- = xmemdup(lr->policies, -- sizeof *new_policies * lr->n_policies); -- new_policies[i] = lr->policies[lr->n_policies - 1]; -- nbrec_logical_router_verify_policies(lr); -- nbrec_logical_router_set_policies(lr, new_policies, -- lr->n_policies - 1); -- free(new_policies); -+ nbrec_logical_router_update_policies_delvalue(lr, routing_policy); - return; - } - } -@@ -3884,6 +3914,47 @@ nbctl_lr_policy_list(struct ctl_context *ctx) - } - free(policies); - } -+ -+static struct nbrec_logical_router_static_route * -+nbctl_lr_get_route(const struct nbrec_logical_router *lr, char *prefix, -+ char *next_hop, bool is_src_route, bool ecmp) -+{ -+ for (int i = 0; i < lr->n_static_routes; i++) { -+ struct nbrec_logical_router_static_route *route = lr->static_routes[i]; -+ -+ /* Compare route policy. */ -+ char *nb_policy = route->policy; -+ bool nb_is_src_route = false; -+ if (nb_policy && !strcmp(nb_policy, "src-ip")) { -+ nb_is_src_route = true; -+ } -+ if (is_src_route != nb_is_src_route) { -+ continue; -+ } -+ -+ /* Compare route prefix. */ -+ char *rt_prefix = normalize_prefix_str(route->ip_prefix); -+ if (!rt_prefix) { -+ /* Ignore existing prefix we couldn't parse. */ -+ continue; -+ } -+ -+ if (strcmp(rt_prefix, prefix)) { -+ free(rt_prefix); -+ continue; -+ } -+ -+ if (ecmp && strcmp(next_hop, route->nexthop)) { -+ free(rt_prefix); -+ continue; -+ } -+ -+ free(rt_prefix); -+ return route; -+ } -+ return NULL; -+} -+ - - static void - nbctl_lr_route_add(struct ctl_context *ctx) -@@ -3927,44 +3998,42 @@ nbctl_lr_route_add(struct ctl_context *ctx) - goto cleanup; - } - -+ struct shash_node *bfd = shash_find(&ctx->options, "--bfd"); -+ const struct nbrec_bfd *nb_bt = NULL; -+ if (bfd) { -+ if (bfd->data) { -+ struct uuid bfd_uuid; -+ if (uuid_from_string(&bfd_uuid, bfd->data)) { -+ nb_bt = nbrec_bfd_get_for_uuid(ctx->idl, &bfd_uuid); -+ } -+ if (!nb_bt) { -+ ctl_error(ctx, "no entry found in the BFD table"); -+ goto cleanup; -+ } -+ } else { -+ const struct nbrec_bfd *iter; -+ NBREC_BFD_FOR_EACH (iter, ctx->idl) { -+ if (!strcmp(iter->dst_ip, next_hop)) { -+ nb_bt = iter; -+ break; -+ } -+ } -+ } -+ } -+ - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool ecmp_symmetric_reply = shash_find(&ctx->options, - "--ecmp-symmetric-reply") != NULL; - bool ecmp = shash_find(&ctx->options, "--ecmp") != NULL || - ecmp_symmetric_reply; -+ struct nbrec_logical_router_static_route *route = -+ nbctl_lr_get_route(lr, prefix, next_hop, is_src_route, ecmp); - if (!ecmp) { -- for (int i = 0; i < lr->n_static_routes; i++) { -- const struct nbrec_logical_router_static_route *route -- = lr->static_routes[i]; -- char *rt_prefix; -- -- /* Compare route policy. */ -- char *nb_policy = lr->static_routes[i]->policy; -- bool nb_is_src_route = false; -- if (nb_policy && !strcmp(nb_policy, "src-ip")) { -- nb_is_src_route = true; -- } -- if (is_src_route != nb_is_src_route) { -- continue; -- } -- -- /* Compare route prefix. */ -- rt_prefix = normalize_prefix_str(lr->static_routes[i]->ip_prefix); -- if (!rt_prefix) { -- /* Ignore existing prefix we couldn't parse. */ -- continue; -- } -- -- if (strcmp(rt_prefix, prefix)) { -- free(rt_prefix); -- continue; -- } -- -+ if (route) { - if (!may_exist) { - ctl_error(ctx, "duplicate prefix: %s (policy: %s). Use option" - " --ecmp to allow this for ECMP routing.", - prefix, is_src_route ? "src-ip" : "dst-ip"); -- free(rt_prefix); - goto cleanup; - } - -@@ -3981,12 +4050,25 @@ nbctl_lr_route_add(struct ctl_context *ctx) - if (policy) { - nbrec_logical_router_static_route_set_policy(route, policy); - } -- free(rt_prefix); -+ if (bfd) { -+ if (!nb_bt) { -+ if (ctx->argc != 5) { -+ ctl_error(ctx, "insert entry in the BFD table failed"); -+ goto cleanup; -+ } -+ nb_bt = nbrec_bfd_insert(ctx->txn); -+ nbrec_bfd_set_dst_ip(nb_bt, next_hop); -+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ } -+ nbrec_logical_router_static_route_set_bfd(route, nb_bt); -+ } - goto cleanup; - } -+ } else if (route) { -+ ctl_error(ctx, "duplicate nexthop for the same ECMP route"); -+ goto cleanup; - } - -- struct nbrec_logical_router_static_route *route; - route = nbrec_logical_router_static_route_insert(ctx->txn); - nbrec_logical_router_static_route_set_ip_prefix(route, prefix); - nbrec_logical_router_static_route_set_nexthop(route, next_hop); -@@ -4004,15 +4086,19 @@ nbctl_lr_route_add(struct ctl_context *ctx) - nbrec_logical_router_static_route_set_options(route, &options); - } - -- nbrec_logical_router_verify_static_routes(lr); -- struct nbrec_logical_router_static_route **new_routes -- = xmalloc(sizeof *new_routes * (lr->n_static_routes + 1)); -- nullable_memcpy(new_routes, lr->static_routes, -- sizeof *new_routes * lr->n_static_routes); -- new_routes[lr->n_static_routes] = route; -- nbrec_logical_router_set_static_routes(lr, new_routes, -- lr->n_static_routes + 1); -- free(new_routes); -+ nbrec_logical_router_update_static_routes_addvalue(lr, route); -+ if (bfd) { -+ if (!nb_bt) { -+ if (ctx->argc != 5) { -+ ctl_error(ctx, "insert entry in the BFD table failed"); -+ goto cleanup; -+ } -+ nb_bt = nbrec_bfd_insert(ctx->txn); -+ nbrec_bfd_set_dst_ip(nb_bt, next_hop); -+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ } -+ nbrec_logical_router_static_route_set_bfd(route, nb_bt); -+ } - - cleanup: - free(next_hop); -@@ -4069,11 +4155,8 @@ nbctl_lr_route_del(struct ctl_context *ctx) - output_port = ctx->argv[4]; - } - -- struct nbrec_logical_router_static_route **new_routes -- = xmemdup(lr->static_routes, -- sizeof *new_routes * lr->n_static_routes); -- size_t n_new = 0; -- for (int i = 0; i < lr->n_static_routes; i++) { -+ size_t n_removed = 0; -+ for (size_t i = 0; i < lr->n_static_routes; i++) { - /* Compare route policy, if specified. */ - if (policy) { - char *nb_policy = lr->static_routes[i]->policy; -@@ -4082,7 +4165,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) - nb_is_src_route = true; - } - if (is_src_route != nb_is_src_route) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4093,14 +4175,12 @@ nbctl_lr_route_del(struct ctl_context *ctx) - normalize_prefix_str(lr->static_routes[i]->ip_prefix); - if (!rt_prefix) { - /* Ignore existing prefix we couldn't parse. */ -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - - int ret = strcmp(prefix, rt_prefix); - free(rt_prefix); - if (ret) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4111,13 +4191,11 @@ nbctl_lr_route_del(struct ctl_context *ctx) - normalize_prefix_str(lr->static_routes[i]->nexthop); - if (!rt_nexthop) { - /* Ignore existing nexthop we couldn't parse. */ -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - int ret = strcmp(nexthop, rt_nexthop); - free(rt_nexthop); - if (ret) { -- new_routes[n_new++] = lr->static_routes[i]; - continue; - } - } -@@ -4126,18 +4204,17 @@ nbctl_lr_route_del(struct ctl_context *ctx) - if (output_port) { - char *rt_output_port = lr->static_routes[i]->output_port; - if (!rt_output_port || strcmp(output_port, rt_output_port)) { -- new_routes[n_new++] = lr->static_routes[i]; -+ continue; - } - } -- } - -- if (n_new < lr->n_static_routes) { -- nbrec_logical_router_verify_static_routes(lr); -- nbrec_logical_router_set_static_routes(lr, new_routes, n_new); -- goto out; -+ /* Everything matched. Removing. */ -+ nbrec_logical_router_update_static_routes_delvalue( -+ lr, lr->static_routes[i]); -+ n_removed++; - } - -- if (!shash_find(&ctx->options, "--if-exists")) { -+ if (!n_removed && !shash_find(&ctx->options, "--if-exists")) { - ctl_error(ctx, "no matching route: policy '%s', prefix '%s', nexthop " - "'%s', output_port '%s'.", - policy ? policy : "any", -@@ -4146,8 +4223,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) - output_port ? output_port : "any"); - } - --out: -- free(new_routes); - free(prefix); - free(nexthop); - } -@@ -4418,12 +4493,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx) - smap_destroy(&nat_options); - - /* Insert the NAT into the logical router. */ -- nbrec_logical_router_verify_nat(lr); -- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * (lr->n_nat + 1)); -- nullable_memcpy(new_nats, lr->nat, sizeof *new_nats * lr->n_nat); -- new_nats[lr->n_nat] = nat; -- nbrec_logical_router_set_nat(lr, new_nats, lr->n_nat + 1); -- free(new_nats); -+ nbrec_logical_router_update_nat_addvalue(lr, nat); - - cleanup: - free(new_logical_ip); -@@ -4459,17 +4529,11 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - - if (ctx->argc == 3) { - /*Deletes all NATs with the specified type. */ -- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * lr->n_nat); -- int n_nat = 0; - for (size_t i = 0; i < lr->n_nat; i++) { -- if (strcmp(nat_type, lr->nat[i]->type)) { -- new_nats[n_nat++] = lr->nat[i]; -+ if (!strcmp(nat_type, lr->nat[i]->type)) { -+ nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]); - } - } -- -- nbrec_logical_router_verify_nat(lr); -- nbrec_logical_router_set_nat(lr, new_nats, n_nat); -- free(new_nats); - return; - } - -@@ -4491,13 +4555,7 @@ nbctl_lr_nat_del(struct ctl_context *ctx) - continue; - } - if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) { -- struct nbrec_nat **new_nats -- = xmemdup(lr->nat, sizeof *new_nats * lr->n_nat); -- new_nats[i] = lr->nat[lr->n_nat - 1]; -- nbrec_logical_router_verify_nat(lr); -- nbrec_logical_router_set_nat(lr, new_nats, -- lr->n_nat - 1); -- free(new_nats); -+ nbrec_logical_router_update_nat_delvalue(lr, nat); - should_return = true; - } - free(old_ip); -@@ -4667,20 +4725,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, - - /* Returns the logical router that contains 'lrp'. */ - static char * OVS_WARN_UNUSED_RESULT --lrp_to_lr(const struct ovsdb_idl *idl, -+lrp_to_lr(struct ctl_context *ctx, - const struct nbrec_logical_router_port *lrp, - const struct nbrec_logical_router **lr_p) - { -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - const struct nbrec_logical_router *lr; - *lr_p = NULL; - -- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, idl) { -- for (size_t i = 0; i < lr->n_ports; i++) { -- if (lr->ports[i] == lrp) { -- *lr_p = lr; -- return NULL; -- } -- } -+ lr = shash_find_data(&nbctx->lrp_to_lr_map, lrp->name); -+ if (lr) { -+ *lr_p = lr; -+ return NULL; - } - - /* Can't happen because of the database schema */ -@@ -4777,15 +4833,7 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx) - nbrec_gateway_chassis_set_priority(gc, priority); - - /* Insert the logical gateway chassis into the logical router port. */ -- nbrec_logical_router_port_verify_gateway_chassis(lrp); -- struct nbrec_gateway_chassis **new_gc = xmalloc( -- sizeof *new_gc * (lrp->n_gateway_chassis + 1)); -- nullable_memcpy(new_gc, lrp->gateway_chassis, -- sizeof *new_gc * lrp->n_gateway_chassis); -- new_gc[lrp->n_gateway_chassis] = gc; -- nbrec_logical_router_port_set_gateway_chassis( -- lrp, new_gc, lrp->n_gateway_chassis + 1); -- free(new_gc); -+ nbrec_logical_router_port_update_gateway_chassis_addvalue(lrp, gc); - free(gc_name); - } - -@@ -4802,14 +4850,7 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) - * will actually cause the gateway chassis to be deleted when the - * transaction is sent to the database server (due to garbage - * collection). */ -- struct nbrec_gateway_chassis **new_gc -- = xmemdup(lrp->gateway_chassis, -- sizeof *new_gc * lrp->n_gateway_chassis); -- new_gc[idx] = new_gc[lrp->n_gateway_chassis - 1]; -- nbrec_logical_router_port_verify_gateway_chassis(lrp); -- nbrec_logical_router_port_set_gateway_chassis( -- lrp, new_gc, lrp->n_gateway_chassis - 1); -- free(new_gc); -+ nbrec_logical_router_port_update_gateway_chassis_delvalue(lrp, gc); - } - - /* Delete 'gc' from the IDL. This won't have a real effect on -@@ -4893,6 +4934,7 @@ static void - nbctl_lrp_add(struct ctl_context *ctx) - { - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); - - const struct nbrec_logical_router *lr = NULL; - char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); -@@ -4942,7 +4984,7 @@ nbctl_lrp_add(struct ctl_context *ctx) - } - - const struct nbrec_logical_router *bound_lr; -- error = lrp_to_lr(ctx->idl, lrp, &bound_lr); -+ error = lrp_to_lr(ctx, lrp, &bound_lr); - if (error) { - ctx->error = error; - return; -@@ -5040,31 +5082,27 @@ nbctl_lrp_add(struct ctl_context *ctx) - } - - /* Insert the logical port into the logical router. */ -- nbrec_logical_router_verify_ports(lr); -- struct nbrec_logical_router_port **new_ports = xmalloc(sizeof *new_ports * -- (lr->n_ports + 1)); -- nullable_memcpy(new_ports, lr->ports, sizeof *new_ports * lr->n_ports); -- new_ports[lr->n_ports] = CONST_CAST(struct nbrec_logical_router_port *, -- lrp); -- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1); -- free(new_ports); -+ nbrec_logical_router_update_ports_addvalue(lr, lrp); -+ -+ /* Updating runtime cache. */ -+ shash_add(&nbctx->lrp_to_lr_map, lrp->name, lr); - } - --/* Removes logical router port 'lr->ports[idx]'. */ -+/* Removes logical router port 'lrp' from logical router 'lr'. */ - static void --remove_lrp(const struct nbrec_logical_router *lr, size_t idx) -+remove_lrp(struct ctl_context *ctx, -+ const struct nbrec_logical_router *lr, -+ const struct nbrec_logical_router_port *lrp) - { -- const struct nbrec_logical_router_port *lrp = lr->ports[idx]; -+ struct nbctl_context *nbctx = nbctl_context_get(ctx); -+ -+ /* Updating runtime cache. */ -+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lrp->name); - - /* First remove 'lrp' from the array of ports. This is what will - * actually cause the logical port to be deleted when the transaction is - * sent to the database server (due to garbage collection). */ -- struct nbrec_logical_router_port **new_ports -- = xmemdup(lr->ports, sizeof *new_ports * lr->n_ports); -- new_ports[idx] = new_ports[lr->n_ports - 1]; -- nbrec_logical_router_verify_ports(lr); -- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports - 1); -- free(new_ports); -+ nbrec_logical_router_update_ports_delvalue(lr, lrp); - - /* Delete 'lrp' from the IDL. This won't have a real effect on - * the database server (the IDL will suppress it in fact) but it -@@ -5090,18 +5128,13 @@ nbctl_lrp_del(struct ctl_context *ctx) - - /* Find the router that contains 'lrp', then delete it. */ - const struct nbrec_logical_router *lr; -- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) { -- for (size_t i = 0; i < lr->n_ports; i++) { -- if (lr->ports[i] == lrp) { -- remove_lrp(lr, i); -- return; -- } -- } -- } - -- /* Can't happen because of the database schema. */ -- ctl_error(ctx, "logical port %s is not part of any logical router", -- ctx->argv[1]); -+ error = lrp_to_lr(ctx, lrp, &lr); -+ if (error) { -+ ctx->error = error; -+ return; -+ } -+ remove_lrp(ctx, lr, lrp); - } - - /* Print a list of logical router ports. */ -@@ -5275,7 +5308,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx, - } - - const struct nbrec_logical_switch *ls; -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return NULL; -@@ -5350,7 +5383,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) - return; - } - if (lsp) { -- error = lsp_to_ls(ctx->idl, lsp, &ls); -+ error = lsp_to_ls(ctx, lsp, &ls); - if (error) { - ctx->error = error; - return; -@@ -5373,15 +5406,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) - nbrec_forwarding_group_set_liveness(fwd_group, true); - } - -- struct nbrec_forwarding_group **new_fwd_groups = -- xmalloc(sizeof(*new_fwd_groups) * (ls->n_forwarding_groups + 1)); -- memcpy(new_fwd_groups, ls->forwarding_groups, -- sizeof *new_fwd_groups * ls->n_forwarding_groups); -- new_fwd_groups[ls->n_forwarding_groups] = fwd_group; -- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups, -- (ls->n_forwarding_groups + 1)); -- free(new_fwd_groups); -- -+ nbrec_logical_switch_update_forwarding_groups_addvalue(ls, fwd_group); - } - - static void -@@ -5403,14 +5428,8 @@ nbctl_fwd_group_del(struct ctl_context *ctx) - - for (int i = 0; i < ls->n_forwarding_groups; ++i) { - if (!strcmp(ls->forwarding_groups[i]->name, fwd_group->name)) { -- struct nbrec_forwarding_group **new_fwd_groups = -- xmemdup(ls->forwarding_groups, -- sizeof *new_fwd_groups * ls->n_forwarding_groups); -- new_fwd_groups[i] = -- ls->forwarding_groups[ls->n_forwarding_groups - 1]; -- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups, -- (ls->n_forwarding_groups - 1)); -- free(new_fwd_groups); -+ nbrec_logical_switch_update_forwarding_groups_delvalue( -+ ls, ls->forwarding_groups[i]); - nbrec_forwarding_group_delete(fwd_group); - return; - } -@@ -5498,17 +5517,27 @@ struct ipv4_route { - const struct nbrec_logical_router_static_route *route; - }; - -+static int -+__ipv4_route_cmp(const struct ipv4_route *r1, const struct ipv4_route *r2) -+{ -+ if (r1->priority != r2->priority) { -+ return r1->priority > r2->priority ? -1 : 1; -+ } -+ if (r1->addr != r2->addr) { -+ return ntohl(r1->addr) < ntohl(r2->addr) ? -1 : 1; -+ } -+ return 0; -+} -+ - static int - ipv4_route_cmp(const void *route1_, const void *route2_) - { - const struct ipv4_route *route1p = route1_; - const struct ipv4_route *route2p = route2_; - -- if (route1p->priority != route2p->priority) { -- return route1p->priority > route2p->priority ? -1 : 1; -- } -- if (route1p->addr != route2p->addr) { -- return ntohl(route1p->addr) < ntohl(route2p->addr) ? -1 : 1; -+ int ret = __ipv4_route_cmp(route1p, route2p); -+ if (ret) { -+ return ret; - } - return route_cmp_details(route1p->route, route2p->route); - } -@@ -5519,16 +5548,22 @@ struct ipv6_route { - const struct nbrec_logical_router_static_route *route; - }; - -+static int -+__ipv6_route_cmp(const struct ipv6_route *r1, const struct ipv6_route *r2) -+{ -+ if (r1->priority != r2->priority) { -+ return r1->priority > r2->priority ? -1 : 1; -+ } -+ return memcmp(&r1->addr, &r2->addr, sizeof(r1->addr)); -+} -+ - static int - ipv6_route_cmp(const void *route1_, const void *route2_) - { - const struct ipv6_route *route1p = route1_; - const struct ipv6_route *route2p = route2_; - -- if (route1p->priority != route2p->priority) { -- return route1p->priority > route2p->priority ? -1 : 1; -- } -- int ret = memcmp(&route1p->addr, &route2p->addr, sizeof(route1p->addr)); -+ int ret = __ipv6_route_cmp(route1p, route2p); - if (ret) { - return ret; - } -@@ -5536,7 +5571,8 @@ ipv6_route_cmp(const void *route1_, const void *route2_) - } - - static void --print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) -+print_route(const struct nbrec_logical_router_static_route *route, -+ struct ds *s, bool ecmp) - { - - char *prefix = normalize_prefix_str(route->ip_prefix); -@@ -5558,6 +5594,19 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) - if (smap_get(&route->external_ids, "ic-learned-route")) { - ds_put_format(s, " (learned)"); - } -+ -+ if (ecmp) { -+ ds_put_cstr(s, " ecmp"); -+ } -+ -+ if (smap_get_bool(&route->options, "ecmp_symmetric_reply", false)) { -+ ds_put_cstr(s, " ecmp-symmetric-reply"); -+ } -+ -+ if (route->bfd) { -+ ds_put_cstr(s, " bfd"); -+ } -+ - ds_put_char(s, '\n'); - } - -@@ -5623,7 +5672,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) - ds_put_cstr(&ctx->output, "IPv4 Routes\n"); - } - for (int i = 0; i < n_ipv4_routes; i++) { -- print_route(ipv4_routes[i].route, &ctx->output); -+ bool ecmp = false; -+ if (i < n_ipv4_routes - 1 && -+ !__ipv4_route_cmp(&ipv4_routes[i], &ipv4_routes[i + 1])) { -+ ecmp = true; -+ } else if (i > 0 && -+ !__ipv4_route_cmp(&ipv4_routes[i], -+ &ipv4_routes[i - 1])) { -+ ecmp = true; -+ } -+ print_route(ipv4_routes[i].route, &ctx->output, ecmp); - } - - if (n_ipv6_routes) { -@@ -5631,7 +5689,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) - n_ipv4_routes ? "\n" : ""); - } - for (int i = 0; i < n_ipv6_routes; i++) { -- print_route(ipv6_routes[i].route, &ctx->output); -+ bool ecmp = false; -+ if (i < n_ipv6_routes - 1 && -+ !__ipv6_route_cmp(&ipv6_routes[i], &ipv6_routes[i + 1])) { -+ ecmp = true; -+ } else if (i > 0 && -+ !__ipv6_route_cmp(&ipv6_routes[i], -+ &ipv6_routes[i - 1])) { -+ ecmp = true; -+ } -+ print_route(ipv6_routes[i].route, &ctx->output, ecmp); - } - - free(ipv4_routes); -@@ -6007,17 +6074,7 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx) - nbrec_ha_chassis_set_chassis_name(ha_chassis, chassis_name); - nbrec_ha_chassis_set_priority(ha_chassis, priority); - -- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); -- -- struct nbrec_ha_chassis **new_ha_chs = -- xmalloc(sizeof *new_ha_chs * (ha_ch_grp->n_ha_chassis + 1)); -- nullable_memcpy(new_ha_chs, ha_ch_grp->ha_chassis, -- sizeof *new_ha_chs * ha_ch_grp->n_ha_chassis); -- new_ha_chs[ha_ch_grp->n_ha_chassis] = -- CONST_CAST(struct nbrec_ha_chassis *, ha_chassis); -- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_chs, -- ha_ch_grp->n_ha_chassis + 1); -- free(new_ha_chs); -+ nbrec_ha_chassis_group_update_ha_chassis_addvalue(ha_ch_grp, ha_chassis); - } - - static void -@@ -6032,11 +6089,9 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) - - const char *chassis_name = ctx->argv[2]; - struct nbrec_ha_chassis *ha_chassis = NULL; -- size_t idx = 0; - for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) { - if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) { - ha_chassis = ha_ch_grp->ha_chassis[i]; -- idx = i; - break; - } - } -@@ -6047,14 +6102,7 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) - return; - } - -- struct nbrec_ha_chassis **new_ha_ch -- = xmemdup(ha_ch_grp->ha_chassis, -- sizeof *new_ha_ch * ha_ch_grp->n_ha_chassis); -- new_ha_ch[idx] = new_ha_ch[ha_ch_grp->n_ha_chassis - 1]; -- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp); -- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_ch, -- ha_ch_grp->n_ha_chassis - 1); -- free(new_ha_ch); -+ nbrec_ha_chassis_group_update_ha_chassis_delvalue(ha_ch_grp, ha_chassis); - nbrec_ha_chassis_delete(ha_chassis); - } - -@@ -6231,7 +6279,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - struct ovsdb_idl_txn *txn; - enum ovsdb_idl_txn_status status; - struct ovsdb_symbol_table *symtab; -- struct ctl_context ctx; -+ struct nbctl_context ctx; - struct ctl_command *c; - struct shash_node *node; - int64_t next_cfg = 0; -@@ -6268,25 +6316,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - ds_init(&c->output); - c->table = NULL; - } -- ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL); -+ nbctl_context_init(&ctx); -+ ctl_context_init(&ctx.base, NULL, idl, txn, symtab, NULL); - for (c = commands; c < &commands[n_commands]; c++) { -- ctl_context_init_command(&ctx, c); -+ ctl_context_init_command(&ctx.base, c); - if (c->syntax->run) { -- (c->syntax->run)(&ctx); -+ (c->syntax->run)(&ctx.base); - } -- if (ctx.error) { -- error = xstrdup(ctx.error); -- ctl_context_done(&ctx, c); -+ if (ctx.base.error) { -+ error = xstrdup(ctx.base.error); -+ ctl_context_done(&ctx.base, c); - goto out_error; - } -- ctl_context_done_command(&ctx, c); -+ ctl_context_done_command(&ctx.base, c); - -- if (ctx.try_again) { -- ctl_context_done(&ctx, NULL); -+ if (ctx.base.try_again) { -+ ctl_context_done(&ctx.base, NULL); - goto try_again; - } - } -- ctl_context_done(&ctx, NULL); -+ ctl_context_done(&ctx.base, NULL); - - SHASH_FOR_EACH (node, &symtab->sh) { - struct ovsdb_symbol *symbol = node->data; -@@ -6317,14 +6366,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - if (status == TXN_UNCHANGED || status == TXN_SUCCESS) { - for (c = commands; c < &commands[n_commands]; c++) { - if (c->syntax->postprocess) { -- ctl_context_init(&ctx, c, idl, txn, symtab, NULL); -- (c->syntax->postprocess)(&ctx); -- if (ctx.error) { -- error = xstrdup(ctx.error); -- ctl_context_done(&ctx, c); -+ ctl_context_init(&ctx.base, c, idl, txn, symtab, NULL); -+ (c->syntax->postprocess)(&ctx.base); -+ if (ctx.base.error) { -+ error = xstrdup(ctx.base.error); -+ ctl_context_done(&ctx.base, c); - goto out_error; - } -- ctl_context_done(&ctx, c); -+ ctl_context_done(&ctx.base, c); - } - } - } -@@ -6412,6 +6461,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, - done: ; - } - -+ nbctl_context_destroy(&ctx); - ovsdb_symbol_table_destroy(symtab); - ovsdb_idl_txn_destroy(txn); - the_idl_txn = NULL; -@@ -6429,6 +6479,7 @@ out_error: - ovsdb_idl_txn_destroy(txn); - the_idl_txn = NULL; - -+ nbctl_context_destroy(&ctx); - ovsdb_symbol_table_destroy(symtab); - return error; - } -@@ -6561,7 +6612,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { - /* logical router route commands. */ - { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL, - nbctl_lr_route_add, NULL, "--may-exist,--ecmp,--ecmp-symmetric-reply," -- "--policy=", RW }, -+ "--policy=,--bfd?", RW }, - { "lr-route-del", 1, 4, "ROUTER [PREFIX [NEXTHOP [PORT]]]", NULL, - nbctl_lr_route_del, NULL, "--if-exists,--policy=", RW }, - { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL, -@@ -6588,7 +6639,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { - nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, - /* load balancer commands. */ - { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, -- nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW }, -+ nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject,--event", RW }, - { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL, - "--if-exists", RW }, - { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO }, -diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c -index 0a1b9ffdc..c38e8ec3b 100644 ---- a/utilities/ovn-sbctl.c -+++ b/utilities/ovn-sbctl.c -@@ -526,6 +526,7 @@ pre_get_info(struct ctl_context *ctx) - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_tunnel_key); - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis); - ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath); -+ ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up); - - 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); -@@ -665,6 +666,7 @@ cmd_lsp_bind(struct ctl_context *ctx) - struct sbctl_chassis *sbctl_ch; - struct sbctl_port_binding *sbctl_bd; - char *lport_name, *ch_name; -+ bool up = true; - - /* port_binding must exist, chassis must exist! */ - lport_name = ctx->argv[1]; -@@ -683,6 +685,7 @@ cmd_lsp_bind(struct ctl_context *ctx) - } - } - sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg); -+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, &up, 1); - sbctl_context_invalidate_cache(ctx); - } - -@@ -699,6 +702,7 @@ cmd_lsp_unbind(struct ctl_context *ctx) - sbctl_bd = find_port_binding(sbctl_ctx, lport_name, must_exist); - if (sbctl_bd) { - sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL); -+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, NULL, 0); - } - } - -diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c -index 6fad36512..fb88bc06c 100644 ---- a/utilities/ovn-trace.c -+++ b/utilities/ovn-trace.c -@@ -405,6 +405,7 @@ struct ovntrace_datapath { - size_t n_flows, allocated_flows; - - struct hmap mac_bindings; /* Contains "struct ovntrace_mac_binding"s. */ -+ struct hmap fdbs; /* Contains "struct ovntrace_fdb"s. */ - - bool has_local_l3gateway; - }; -@@ -453,12 +454,24 @@ struct ovntrace_mac_binding { - struct eth_addr mac; - }; - -+struct ovntrace_fdb { -+ struct hmap_node node; -+ uint16_t port_key; -+ struct eth_addr mac; -+}; -+ - static inline uint32_t - hash_mac_binding(uint16_t port_key, const struct in6_addr *ip) - { - return hash_bytes(ip, sizeof *ip, port_key); - } - -+static inline uint32_t -+hash_fdb(const struct eth_addr *mac) -+{ -+ return hash_bytes(mac, sizeof *mac, 0); -+} -+ - /* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */ - static struct hmap datapaths; - -@@ -478,6 +491,7 @@ static struct shash port_groups; - static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */ - static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */ - static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */ -+static struct controller_event_options event_opts; - - static struct ovntrace_datapath * - ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) -@@ -517,6 +531,18 @@ ovntrace_datapath_find_by_name(const char *name) - return match; - } - -+static struct ovntrace_datapath * -+ovntrace_datapath_find_by_key(uint32_t tunnel_key) -+{ -+ struct ovntrace_datapath *dp; -+ HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { -+ if (dp->tunnel_key == tunnel_key) { -+ return dp; -+ } -+ } -+ return NULL; -+} -+ - static const struct ovntrace_port * - ovntrace_port_find_by_key(const struct ovntrace_datapath *dp, - uint16_t tunnel_key) -@@ -597,6 +623,20 @@ ovntrace_mac_binding_find_mac_ip(const struct ovntrace_datapath *dp, - return NULL; - } - -+static const struct ovntrace_fdb * -+ovntrace_fdb_find(const struct ovntrace_datapath *dp, -+ const struct eth_addr *mac) -+{ -+ const struct ovntrace_fdb *fdb; -+ HMAP_FOR_EACH_WITH_HASH (fdb, node, hash_fdb(mac), -+ &dp->fdbs) { -+ if (eth_addr_equals(fdb->mac, *mac)) { -+ return fdb; -+ } -+ } -+ return NULL; -+} -+ - /* If 's' ends with a UUID, returns a copy of it with the UUID truncated to - * just the first 6 characters; otherwise, returns a copy of 's'. */ - static char * -@@ -637,7 +677,7 @@ read_datapaths(void) - - ovs_list_init(&dp->mcgroups); - hmap_init(&dp->mac_bindings); -- -+ hmap_init(&dp->fdbs); - hmap_insert(&datapaths, &dp->sb_uuid_node, uuid_hash(&dp->sb_uuid)); - } - } -@@ -901,10 +941,11 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf, - .dhcp_opts = &dhcp_opts, - .dhcpv6_opts = &dhcpv6_opts, - .nd_ra_opts = &nd_ra_opts, -+ .controller_event_opts = &event_opts, - .pipeline = (!strcmp(sblf->pipeline, "ingress") - ? OVNACT_P_INGRESS - : OVNACT_P_EGRESS), -- .n_tables = 24, -+ .n_tables = LOG_PIPELINE_LEN, - .cur_ltable = sblf->table_id, - }; - uint64_t stub[1024 / 8]; -@@ -1006,6 +1047,8 @@ read_gen_opts(void) - - hmap_init(&nd_ra_opts); - nd_ra_opts_init(&nd_ra_opts); -+ -+ controller_event_opts_init(&event_opts); - } - - static void -@@ -1049,6 +1092,30 @@ read_mac_bindings(void) - } - } - -+static void -+read_fdbs(void) -+{ -+ const struct sbrec_fdb *fdb; -+ SBREC_FDB_FOR_EACH (fdb, ovnsb_idl) { -+ struct eth_addr mac; -+ if (!eth_addr_from_string(fdb->mac, &mac)) { -+ VLOG_WARN("%s: bad Ethernet address", fdb->mac); -+ continue; -+ } -+ -+ struct ovntrace_datapath *dp = -+ ovntrace_datapath_find_by_key(fdb->dp_key); -+ if (!dp) { -+ continue; -+ } -+ -+ struct ovntrace_fdb *fdb_t = xmalloc(sizeof *fdb_t); -+ fdb_t->mac = mac; -+ fdb_t->port_key = fdb->port_key; -+ hmap_insert(&dp->fdbs, &fdb_t->node, hash_fdb(&mac)); -+ } -+} -+ - static void - read_db(void) - { -@@ -1060,6 +1127,7 @@ read_db(void) - read_gen_opts(); - read_flows(); - read_mac_bindings(); -+ read_fdbs(); - } - - static const struct ovntrace_port * -@@ -1116,6 +1184,11 @@ ovntrace_lookup_port(const void *dp_, const char *port_name, - return true; - } - -+ if (!strcmp(port_name, "none")) { -+ *portp = 0; -+ return true; -+ } -+ - const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name); - if (port) { - if (port->dp == dp) { -@@ -1802,6 +1875,91 @@ execute_tcp_reset(const struct ovnact_nest *on, - execute_tcp6_reset(on, dp, uflow, table_id, loopback, pipeline, super); - } - } -+ -+static void -+execute_sctp4_abort(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 sctp_flow = *uflow; -+ -+ /* Update fields for TCP SCTP. */ -+ if (loopback) { -+ sctp_flow.dl_dst = uflow->dl_src; -+ sctp_flow.dl_src = uflow->dl_dst; -+ sctp_flow.nw_dst = uflow->nw_src; -+ sctp_flow.nw_src = uflow->nw_dst; -+ } else { -+ sctp_flow.dl_dst = uflow->dl_dst; -+ sctp_flow.dl_src = uflow->dl_src; -+ sctp_flow.nw_dst = uflow->nw_dst; -+ sctp_flow.nw_src = uflow->nw_src; -+ } -+ sctp_flow.nw_proto = IPPROTO_SCTP; -+ sctp_flow.nw_ttl = 255; -+ sctp_flow.tp_src = uflow->tp_src; -+ sctp_flow.tp_dst = uflow->tp_dst; -+ -+ struct ovntrace_node *node = ovntrace_node_append( -+ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort"); -+ -+ trace_actions(on->nested, on->nested_len, dp, &sctp_flow, -+ table_id, pipeline, &node->subs); -+} -+ -+static void -+execute_sctp6_abort(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 sctp_flow = *uflow; -+ -+ /* Update fields for SCTP. */ -+ if (loopback) { -+ sctp_flow.dl_dst = uflow->dl_src; -+ sctp_flow.dl_src = uflow->dl_dst; -+ sctp_flow.ipv6_dst = uflow->ipv6_src; -+ sctp_flow.ipv6_src = uflow->ipv6_dst; -+ } else { -+ sctp_flow.dl_dst = uflow->dl_dst; -+ sctp_flow.dl_src = uflow->dl_src; -+ sctp_flow.ipv6_dst = uflow->ipv6_dst; -+ sctp_flow.ipv6_src = uflow->ipv6_src; -+ } -+ sctp_flow.nw_proto = IPPROTO_TCP; -+ sctp_flow.nw_ttl = 255; -+ sctp_flow.tp_src = uflow->tp_src; -+ sctp_flow.tp_dst = uflow->tp_dst; -+ sctp_flow.tcp_flags = htons(TCP_RST); -+ -+ struct ovntrace_node *node = ovntrace_node_append( -+ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort"); -+ -+ trace_actions(on->nested, on->nested_len, dp, &sctp_flow, -+ table_id, pipeline, &node->subs); -+} -+ -+static void -+execute_sctp_abort(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_sctp4_abort(on, dp, uflow, table_id, loopback, -+ pipeline, super); -+ } else { -+ execute_sctp6_abort(on, dp, uflow, table_id, loopback, -+ pipeline, super); -+ } -+} -+ -+ - static void - execute_reject(const struct ovnact_nest *on, - const struct ovntrace_datapath *dp, -@@ -1810,6 +1968,8 @@ execute_reject(const struct ovnact_nest *on, - { - if (uflow->nw_proto == IPPROTO_TCP) { - execute_tcp_reset(on, dp, uflow, table_id, true, pipeline, super); -+ } else if (uflow->nw_proto == IPPROTO_SCTP) { -+ execute_sctp_abort(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); -@@ -1938,6 +2098,66 @@ execute_lookup_mac_bind_ip(const struct ovnact_lookup_mac_bind_ip *bind, - mf_write_subfield_flow(&dst, &sv, uflow); - } - -+static void -+execute_lookup_fdb(const struct ovnact_lookup_fdb *lookup_fdb, -+ const struct ovntrace_datapath *dp, -+ struct flow *uflow, -+ struct ovs_list *super) -+{ -+ /* Get logical port number.*/ -+ struct mf_subfield port_sf = expr_resolve_field(&lookup_fdb->port); -+ ovs_assert(port_sf.n_bits == 32); -+ uint32_t port_key = mf_get_subfield(&port_sf, uflow); -+ -+ /* Get MAC. */ -+ struct mf_subfield mac_sf = expr_resolve_field(&lookup_fdb->mac); -+ ovs_assert(mac_sf.n_bits == 48); -+ union mf_subvalue mac_sv; -+ mf_read_subfield(&mac_sf, uflow, &mac_sv); -+ -+ const struct ovntrace_fdb *fdb_t -+ = ovntrace_fdb_find(dp, &mac_sv.mac); -+ -+ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst); -+ uint8_t val = 0; -+ -+ if (fdb_t && fdb_t->port_key == port_key) { -+ val = 1; -+ ovntrace_node_append(super, OVNTRACE_NODE_ACTION, -+ "/* MAC lookup for "ETH_ADDR_FMT" found in " -+ "FDB. */", ETH_ADDR_ARGS(uflow->dl_dst)); -+ } else { -+ ovntrace_node_append(super, OVNTRACE_NODE_ACTION, -+ "/* lookup mac failed in mac learning table. */"); -+ } -+ union mf_subvalue sv = { .u8_val = val }; -+ mf_write_subfield_flow(&dst, &sv, uflow); -+} -+ -+static void -+execute_get_fdb(const struct ovnact_get_fdb *get_fdb, -+ const struct ovntrace_datapath *dp, -+ struct flow *uflow) -+{ -+ /* Get MAC. */ -+ struct mf_subfield mac_sf = expr_resolve_field(&get_fdb->mac); -+ ovs_assert(mac_sf.n_bits == 48); -+ union mf_subvalue mac_sv; -+ mf_read_subfield(&mac_sf, uflow, &mac_sv); -+ -+ const struct ovntrace_fdb *fdb_t -+ = ovntrace_fdb_find(dp, &mac_sv.mac); -+ -+ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst); -+ uint32_t val = 0; -+ if (fdb_t) { -+ val = fdb_t->port_key; -+ } -+ -+ union mf_subvalue sv = { .be32_int = htonl(val) }; -+ mf_write_subfield_flow(&dst, &sv, uflow); -+} -+ - static void - execute_put_opts(const struct ovnact_put_opts *po, - const char *name, struct flow *uflow, -@@ -2503,6 +2723,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - false, pipeline, super); - break; - -+ case OVNACT_SCTP_ABORT: -+ execute_sctp_abort(ovnact_get_SCTP_ABORT(a), dp, uflow, table_id, -+ false, pipeline, super); -+ break; -+ - case OVNACT_OVNFIELD_LOAD: - execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super); - break; -@@ -2540,6 +2765,20 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, - break; - case OVNACT_DHCP6_REPLY: - break; -+ case OVNACT_BFD_MSG: -+ break; -+ -+ case OVNACT_PUT_FDB: -+ /* Nothing to do for tracing. */ -+ break; -+ -+ case OVNACT_GET_FDB: -+ execute_get_fdb(ovnact_get_GET_FDB(a), dp, uflow); -+ break; -+ -+ case OVNACT_LOOKUP_FDB: -+ execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super); -+ break; - } - } - ds_destroy(&s); diff --git a/SOURCES/ovn-2021.patch b/SOURCES/ovn-2021.patch new file mode 100644 index 0000000..adf62ad --- /dev/null +++ b/SOURCES/ovn-2021.patch @@ -0,0 +1,652 @@ +diff --git a/NEWS b/NEWS +index 75f26ddb7..4043ecf20 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,3 +1,6 @@ ++OVN v21.12.1 - xx xxx xxxx ++-------------------------- ++ + OVN v21.12.0 - 22 Dec 2021 + -------------------------- + - Set ignore_lsp_down to true as default, so that ARP responder flows are +diff --git a/configure.ac b/configure.ac +index 48b4662f0..e44c86c74 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -13,7 +13,7 @@ + # limitations under the License. + + AC_PREREQ(2.63) +-AC_INIT(ovn, 21.12.0, bugs@openvswitch.org) ++AC_INIT(ovn, 21.12.1, bugs@openvswitch.org) + AC_CONFIG_MACRO_DIR([m4]) + AC_CONFIG_AUX_DIR([build-aux]) + AC_CONFIG_HEADERS([config.h]) +diff --git a/controller/physical.c b/controller/physical.c +index 836fc769a..aa651b876 100644 +--- a/controller/physical.c ++++ b/controller/physical.c +@@ -1477,10 +1477,12 @@ consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name, + put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, + &remote_ofpacts); + put_resubmit(OFTABLE_CHECK_LOOPBACK, &remote_ofpacts); +- } else if (local_binding_get_primary_pb(local_bindings, lport_name) +- || simap_contains(patch_ofports, port->logical_port) +- || (!strcmp(port->type, "l3gateway") +- && port->chassis == chassis)) { ++ } else if (port->chassis == chassis ++ && (local_binding_get_primary_pb(local_bindings, lport_name) ++ || !strcmp(port->type, "l3gateway"))) { ++ put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); ++ put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts); ++ } else if (simap_contains(patch_ofports, port->logical_port)) { + put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); + put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts); + } else if (!strcmp(port->type, "chassisredirect") +diff --git a/controller/pinctrl.c b/controller/pinctrl.c +index f0667807e..d2bb7f441 100644 +--- a/controller/pinctrl.c ++++ b/controller/pinctrl.c +@@ -1624,12 +1624,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, + struct dp_packet packet; + + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); +- dp_packet_clear(&packet); +- packet.packet_type = htonl(PT_ETH); +- +- struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh); +- eh->eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst; +- eh->eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src; ++ 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_IP)) { + struct ip_header *in_ip = dp_packet_l3(pkt_in); +@@ -1642,27 +1638,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, + return; + } + +- struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh); +- +- eh->eth_type = htons(ETH_TYPE_IP); +- dp_packet_set_l3(&packet, nh); +- nh->ip_ihl_ver = IP_IHL_VER(5, 4); +- nh->ip_tot_len = htons(sizeof(struct ip_header) + +- sizeof(struct icmp_header)); +- nh->ip_proto = IPPROTO_ICMP; +- nh->ip_frag_off = htons(IP_DF); + 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) { +- icmp_code = 3; +- } +- +- struct icmp_header *ih = dp_packet_put_zeros(&packet, sizeof *ih); +- dp_packet_set_l4(&packet, ih); +- packet_set_icmp(&packet, ICMP4_DST_UNREACH, icmp_code); + + /* RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes + * of header. MAY send more. +@@ -1671,51 +1648,40 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, + * So, lets return as much as we can. */ + + /* Calculate available room to include the original IP + data. */ +- nh = dp_packet_l3(&packet); +- uint16_t room = 576 - (sizeof *eh + ntohs(nh->ip_tot_len)); ++ uint16_t room = 576 - (sizeof(struct eth_header) ++ + sizeof(struct ip_header) ++ + sizeof(struct icmp_header)); + if (in_ip_len > room) { + in_ip_len = room; + } +- dp_packet_put(&packet, in_ip, in_ip_len); + +- /* dp_packet_put may reallocate the buffer. Get the l3 and l4 +- * header pointers again. */ +- nh = dp_packet_l3(&packet); +- ih = dp_packet_l4(&packet); +- uint16_t ip_total_len = ntohs(nh->ip_tot_len) + in_ip_len; +- nh->ip_tot_len = htons(ip_total_len); ++ uint16_t ip_total_len = sizeof(struct icmp_header) + in_ip_len; ++ ++ pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst, ++ IPPROTO_ICMP, 255, ip_total_len); ++ ++ uint8_t icmp_code = 1; ++ if (set_icmp_code && in_ip->ip_proto == IPPROTO_UDP) { ++ icmp_code = 3; ++ } ++ ++ struct icmp_header *ih = dp_packet_l4(&packet); ++ packet_set_icmp(&packet, ICMP4_DST_UNREACH, icmp_code); ++ ++ /* Include original IP + data. */ ++ void *data = ih + 1; ++ memcpy(data, in_ip, in_ip_len); ++ + ih->icmp_csum = 0; + ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len); +- nh->ip_csum = 0; +- nh->ip_csum = csum(nh, sizeof *nh); +- + } else { +- struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh); +- struct icmp6_data_header *ih; +- uint32_t icmpv6_csum; +- struct ip6_hdr *in_ip = dp_packet_l3(pkt_in); +- +- eh->eth_type = htons(ETH_TYPE_IPV6); +- dp_packet_set_l3(&packet, nh); +- nh->ip6_vfc = 0x60; +- nh->ip6_nxt = IPPROTO_ICMPV6; +- nh->ip6_plen = htons(ICMP6_DATA_HEADER_LEN); ++ struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in); ++ uint16_t in_ip_len = (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen); ++ + 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); +- ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH; +- ih->icmp6_base.icmp6_code = 1; +- +- if (set_icmp_code && in_ip->ip6_nxt == IPPROTO_UDP) { +- ih->icmp6_base.icmp6_code = ICMP6_DST_UNREACH_NOPORT; +- } +- ih->icmp6_base.icmp6_cksum = 0; + + /* RFC 4443: 3.1. + * +@@ -1730,20 +1696,33 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow, + * | exceeding the minimum IPv6 MTU [IPv6] | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +- +- uint16_t room = 1280 - (sizeof *eh + sizeof *nh + +- ICMP6_DATA_HEADER_LEN); +- uint16_t in_ip_len = (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen); ++ uint16_t room = 1280 - (sizeof(struct eth_header) ++ + sizeof(struct ip6_hdr) ++ + ICMP6_DATA_HEADER_LEN); + if (in_ip_len > room) { + in_ip_len = room; + } + +- dp_packet_put(&packet, in_ip, in_ip_len); +- nh = dp_packet_l3(&packet); +- nh->ip6_plen = htons(ICMP6_DATA_HEADER_LEN + in_ip_len); ++ uint16_t ip_total_len = ++ sizeof(struct icmp6_data_header) + in_ip_len; ++ pinctrl_compose_ipv6(&packet, eth_src, eth_dst, ++ CONST_CAST(struct in6_addr *, ip6_src), ++ CONST_CAST(struct in6_addr *, ip6_dst), ++ IPPROTO_ICMPV6, 255, ip_total_len); + +- icmpv6_csum = packet_csum_pseudoheader6(dp_packet_l3(&packet)); +- ih = dp_packet_l4(&packet); ++ struct icmp6_data_header *ih = dp_packet_l4(&packet); ++ ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH; ++ ih->icmp6_base.icmp6_code = 1; ++ ++ if (set_icmp_code && in_ip->ip6_nxt == IPPROTO_UDP) { ++ ih->icmp6_base.icmp6_code = ICMP6_DST_UNREACH_NOPORT; ++ } ++ ih->icmp6_base.icmp6_cksum = 0; ++ ++ void *data = ih + 1; ++ memcpy(data, in_ip, in_ip_len); ++ uint32_t icmpv6_csum = ++ packet_csum_pseudoheader6(dp_packet_l3(&packet)); + ih->icmp6_base.icmp6_cksum = csum_finish( + csum_continue(icmpv6_csum, ih, + in_ip_len + ICMP6_DATA_HEADER_LEN)); +@@ -1777,7 +1756,6 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, + struct dp_packet packet; + + 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; +@@ -1798,8 +1776,8 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, + IPPROTO_TCP, 63, TCP_HEADER_LEN); + } + +- struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th); +- dp_packet_set_l4(&packet, th); ++ struct tcp_header *th = dp_packet_l4(&packet); ++ + th->tcp_dst = ip_flow->tp_src; + th->tcp_src = ip_flow->tp_dst; + +@@ -1825,18 +1803,6 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, + dp_packet_uninit(&packet); + } + +-static void dp_packet_put_sctp_abort(struct dp_packet *packet, +- bool reflect_tag) +-{ +- struct sctp_chunk_header abort = { +- .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT, +- .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0, +- .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN), +- }; +- +- dp_packet_put(packet, &abort, sizeof abort); +-} +- + static void + pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + struct dp_packet *pkt_in, +@@ -1870,7 +1836,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + return; + } + +- const struct sctp_init_chunk *sh_in_init = NULL; ++ const struct sctp_16aligned_init_chunk *sh_in_init = NULL; + if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs + +@@ -1909,8 +1875,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + SCTP_CHUNK_HEADER_LEN); + } + +- struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh); +- dp_packet_set_l4(&packet, sh); ++ struct sctp_header *sh = dp_packet_l4(&packet); + sh->sctp_dst = ip_flow->tp_src; + sh->sctp_src = ip_flow->tp_dst; + put_16aligned_be32(&sh->sctp_csum, 0); +@@ -1918,7 +1883,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + bool tag_reflected; + if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) { + /* See RFC 4960 Section 8.4, item 3. */ +- put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag); ++ sh->sctp_vtag = sh_in_init->initiate_tag; + tag_reflected = false; + } else { + /* See RFC 4960 Section 8.4, item 8. */ +@@ -1926,7 +1891,11 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + tag_reflected = true; + } + +- dp_packet_put_sctp_abort(&packet, tag_reflected); ++ struct sctp_chunk_header *ah = ++ ALIGNED_CAST(struct sctp_chunk_header *, sh + 1); ++ ah->sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT; ++ ah->sctp_chunk_flags = tag_reflected ? SCTP_ABORT_CHUNK_FLAG_T : 0, ++ ah->sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN), + + put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh, + dp_packet_l4_size(&packet))); +@@ -2942,7 +2911,7 @@ pinctrl_handle_dns_lookup( + goto exit; + } + +- uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data)); ++ uint16_t query_type = ntohs(get_unaligned_be16((void *) in_dns_data)); + /* Supported query types - A, AAAA, ANY and PTR */ + if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA + || query_type == DNS_QUERY_TYPE_ANY +@@ -4564,16 +4533,15 @@ pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src, + ovs_be32 ipv4_dst, uint8_t ip_proto, uint8_t ttl, + uint16_t ip_payload_len) + { +- dp_packet_clear(packet); +- packet->packet_type = htonl(PT_ETH); +- +- struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh); +- struct ip_header *nh = dp_packet_put_zeros(packet, sizeof *nh); ++ struct ip_header *nh; ++ size_t ip_tot_len = sizeof *nh + ip_payload_len; + +- eh->eth_dst = eth_dst; +- eh->eth_src = eth_src; +- eh->eth_type = htons(ETH_TYPE_IP); +- dp_packet_set_l3(packet, nh); ++ /* Need to deal with odd-sized IP length while making sure that the ++ * packet is still aligned. Reserve 2 bytes for potential VLAN tags. ++ */ ++ dp_packet_reserve(packet, ++ sizeof(struct eth_header) + ROUND_UP(ip_tot_len, 2) + 2); ++ nh = eth_compose(packet, eth_dst, eth_src, ETH_TYPE_IP, ip_tot_len); + nh->ip_ihl_ver = IP_IHL_VER(5, 4); + nh->ip_tot_len = htons(sizeof *nh + ip_payload_len); + nh->ip_tos = IP_DSCP_CS6; +@@ -4584,6 +4552,7 @@ pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src, + + nh->ip_csum = 0; + nh->ip_csum = csum(nh, sizeof *nh); ++ dp_packet_set_l4(packet, nh + 1); + } + + static void +@@ -4592,23 +4561,21 @@ pinctrl_compose_ipv6(struct dp_packet *packet, struct eth_addr eth_src, + struct in6_addr *ipv6_dst, uint8_t ip_proto, uint8_t ttl, + uint16_t ip_payload_len) + { +- dp_packet_clear(packet); +- packet->packet_type = htonl(PT_ETH); +- +- struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh); +- struct ip6_hdr *nh = dp_packet_put_zeros(packet, sizeof *nh); +- +- eh->eth_dst = eth_dst; +- eh->eth_src = eth_src; +- eh->eth_type = htons(ETH_TYPE_IPV6); +- dp_packet_set_l3(packet, nh); +- dp_packet_set_l4(packet, nh + 1); ++ struct ip6_hdr *nh; ++ size_t ip_tot_len = sizeof *nh + ip_payload_len; + ++ /* Need to deal with odd-sized IP length while making sure that the ++ * packet is still aligned. Reserve 2 bytes for potential VLAN tags. ++ */ ++ dp_packet_reserve(packet, ++ sizeof(struct eth_header) + ROUND_UP(ip_tot_len, 2) + 2); ++ nh = eth_compose(packet, eth_dst, eth_src, ETH_TYPE_IPV6, ip_tot_len); + nh->ip6_vfc = 0x60; + nh->ip6_nxt = ip_proto; + nh->ip6_plen = htons(ip_payload_len); + + packet_set_ipv6(packet, ipv6_src, ipv6_dst, 0, 0, ttl); ++ dp_packet_set_l4(packet, nh + 1); + } + + /* +@@ -5400,10 +5367,6 @@ ip_mcast_querier_send_igmp(struct rconn *swconn, struct ip_mcast_snoop *ip_ms) + ip_ms->cfg.query_ipv4_dst, + IPPROTO_IGMP, 1, sizeof(struct igmpv3_query_header)); + +- struct igmpv3_query_header *igh = +- dp_packet_put_zeros(&packet, sizeof *igh); +- dp_packet_set_l4(&packet, igh); +- + /* IGMP query max-response in tenths of seconds. */ + uint8_t max_response = ip_ms->cfg.query_max_resp_s * 10; + uint8_t qqic = max_response; +@@ -5449,15 +5412,10 @@ ip_mcast_querier_send_mld(struct rconn *swconn, struct ip_mcast_snoop *ip_ms) + IPPROTO_HOPOPTS, 1, + IPV6_EXT_HEADER_LEN + MLD_QUERY_HEADER_LEN); + +- struct ipv6_ext_header *ext_hdr = +- dp_packet_put_zeros(&packet, IPV6_EXT_HEADER_LEN); ++ struct ipv6_ext_header *ext_hdr = dp_packet_l4(&packet); + packet_set_ipv6_ext_header(ext_hdr, IPPROTO_ICMPV6, 0, mld_router_alert, + ARRAY_SIZE(mld_router_alert)); + +- struct mld_header *mh = +- dp_packet_put_zeros(&packet, MLD_QUERY_HEADER_LEN); +- dp_packet_set_l4(&packet, mh); +- + /* MLD query max-response in milliseconds. */ + uint16_t max_response = ip_ms->cfg.query_max_resp_s * 1000; + uint8_t qqic = ip_ms->cfg.query_max_resp_s; +@@ -6033,6 +5991,8 @@ pinctrl_handle_put_nd_ra_opts( + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); ++ /* Properly align after the ethernet header */ ++ dp_packet_reserve(&pkt_out, 2); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + +@@ -6155,23 +6115,26 @@ wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn) + static bool + pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata) + { +- struct controller_event_opt_header *userdata_opt; ++ struct controller_event_opt_header opt_hdr; ++ void *userdata_opt; + uint32_t hash = 0; + char *vip = NULL; + char *protocol = NULL; + char *load_balancer = NULL; + + while (userdata->size) { +- userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt); ++ userdata_opt = ofpbuf_try_pull(userdata, sizeof opt_hdr); + if (!userdata_opt) { + return false; + } +- size_t size = ntohs(userdata_opt->size); ++ memcpy(&opt_hdr, userdata_opt, sizeof opt_hdr); ++ ++ size_t size = ntohs(opt_hdr.size); + char *userdata_opt_data = ofpbuf_try_pull(userdata, size); + if (!userdata_opt_data) { + return false; + } +- switch (ntohs(userdata_opt->opt_code)) { ++ switch (ntohs(opt_hdr.opt_code)) { + case EMPTY_LB_VIP: + vip = xmemdup0(userdata_opt_data, size); + break; +@@ -6820,8 +6783,6 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet, + { + int payload_len = sizeof(struct udp_header) + sizeof(struct bfd_msg); + +- /* Properly align after the ethernet header */ +- dp_packet_reserve(packet, 2); + if (IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) { + ovs_be32 ip_src = in6_addr_get_mapped_ipv4(&entry->ip_src); + ovs_be32 ip_dst = in6_addr_get_mapped_ipv4(&entry->ip_dst); +@@ -6833,13 +6794,13 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet, + MAXTTL, payload_len); + } + +- struct udp_header *udp = dp_packet_put_zeros(packet, sizeof *udp); ++ struct udp_header *udp = dp_packet_l4(packet); + udp->udp_len = htons(payload_len); + udp->udp_csum = 0; + udp->udp_src = htons(entry->udp_src); + udp->udp_dst = htons(BFD_DEST_PORT); + +- struct bfd_msg *msg = dp_packet_put_zeros(packet, sizeof *msg); ++ struct bfd_msg *msg = ALIGNED_CAST(struct bfd_msg *, udp + 1); + msg->vers_diag = (BFD_VERSION << 5); + msg->mult = entry->local_mult; + msg->length = BFD_PACKET_LEN; +@@ -7383,7 +7344,7 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn, + ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip), + IPPROTO_TCP, 63, TCP_HEADER_LEN); + +- struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th); ++ struct tcp_header *th = dp_packet_l4(&packet); + dp_packet_set_l4(&packet, th); + th->tcp_dst = htons(svc_mon->proto_port); + th->tcp_src = tcp_src; +@@ -7446,13 +7407,12 @@ svc_monitor_send_udp_health_check(struct rconn *swconn, + ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip), + IPPROTO_UDP, 63, UDP_HEADER_LEN + 8); + +- struct udp_header *uh = dp_packet_put_zeros(&packet, sizeof *uh); ++ struct udp_header *uh = dp_packet_l4(&packet); + dp_packet_set_l4(&packet, uh); + uh->udp_dst = htons(svc_mon->proto_port); + uh->udp_src = udp_src; + uh->udp_len = htons(UDP_HEADER_LEN + 8); + uh->udp_csum = 0; +- dp_packet_put_zeros(&packet, 8); + + uint64_t ofpacts_stub[4096 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); +diff --git a/debian/changelog b/debian/changelog +index 0cc5f14ac..0d8593083 100644 +--- a/debian/changelog ++++ b/debian/changelog +@@ -1,3 +1,9 @@ ++OVN (21.12.1-1) unstable; urgency=low ++ [ OVN team ] ++ * New upstream version ++ ++ -- OVN team Wed, 22 Dec 2021 09:45:52 -0500 ++ + ovn (21.12.0-1) unstable; urgency=low + + * New upstream version +diff --git a/lib/actions.c b/lib/actions.c +index da00ee349..a78d01198 100644 +--- a/lib/actions.c ++++ b/lib/actions.c +@@ -1842,19 +1842,20 @@ encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts, + { + for (const struct ovnact_gen_option *o = event->options; + o < &event->options[event->n_options]; o++) { +- struct controller_event_opt_header *hdr = +- ofpbuf_put_uninit(ofpacts, sizeof *hdr); ++ ++ /* All empty_lb_backends fields are of type 'str' */ ++ ovs_assert(!strcmp(o->option->type, "str")); ++ + const union expr_constant *c = o->value.values; +- size_t size; +- hdr->opt_code = htons(o->option->code); +- if (!strcmp(o->option->type, "str")) { +- size = strlen(c->string); +- hdr->size = htons(size); +- ofpbuf_put(ofpacts, c->string, size); +- } else { +- /* All empty_lb_backends fields are of type 'str' */ +- OVS_NOT_REACHED(); +- } ++ size_t size = strlen(c->string); ++ struct controller_event_opt_header hdr = ++ (struct controller_event_opt_header) { ++ .opt_code = htons(o->option->code), ++ .size = htons(size), ++ }; ++ ++ memcpy(ofpbuf_put_uninit(ofpacts, sizeof hdr), &hdr, sizeof hdr); ++ ofpbuf_put(ofpacts, c->string, size); + } + } + +diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h +index 9a33f5cda..256e963d9 100644 +--- a/lib/ovn-l7.h ++++ b/lib/ovn-l7.h +@@ -502,7 +502,7 @@ struct mld_query_header { + ovs_be16 csum; + ovs_be16 max_resp; + ovs_be16 rsvd; +- struct in6_addr group; ++ union ovs_16aligned_in6_addr group; + uint8_t srs_qrv; + uint8_t qqic; + ovs_be16 nsrcs; +@@ -518,7 +518,9 @@ packet_set_mld_query(struct dp_packet *packet, uint16_t max_resp, + const struct in6_addr *group, + bool srs, uint8_t qrv, uint8_t qqic) + { +- struct mld_query_header *mqh = dp_packet_l4(packet); ++ struct ipv6_ext_header *ext_hdr = dp_packet_l4(packet); ++ struct mld_query_header *mqh = ALIGNED_CAST(struct mld_query_header *, ++ ext_hdr + 1); + mqh->type = MLD_QUERY; + mqh->code = 0; + mqh->max_resp = htons(max_resp); +diff --git a/lib/ovn-util.h b/lib/ovn-util.h +index a923c3b65..b212c64b7 100644 +--- a/lib/ovn-util.h ++++ b/lib/ovn-util.h +@@ -261,14 +261,16 @@ struct sctp_chunk_header { + BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header)); + + #define SCTP_INIT_CHUNK_LEN 16 +-struct sctp_init_chunk { +- ovs_be32 initiate_tag; +- ovs_be32 a_rwnd; ++struct sctp_16aligned_init_chunk { ++ ovs_16aligned_be32 initiate_tag; ++ ovs_16aligned_be32 a_rwnd; + ovs_be16 num_outbound_streams; + ovs_be16 num_inbound_streams; +- ovs_be32 initial_tsn; ++ ovs_16aligned_be32 initial_tsn; + }; +-BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk)); ++BUILD_ASSERT_DECL( ++ SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_16aligned_init_chunk) ++); + + /* These are the only SCTP chunk types that OVN cares about. + * There is no need to define the other chunk types until they are +diff --git a/tests/ovn.at b/tests/ovn.at +index 9ec62e321..92e284e8a 100644 +--- a/tests/ovn.at ++++ b/tests/ovn.at +@@ -9658,8 +9658,8 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet + + # expected packet at foo2 + packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +-echo $packet > expected +-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected]) ++echo $packet > expected2 ++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2]) + + # Send ip packets between foo1 and bar2 (different switch, different HV) + src_mac="f00000010205" +@@ -9673,8 +9673,8 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet + src_mac="000000010204" + dst_mac="f00000010208" + packet=${dst_mac}${src_mac}8100000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 +-echo $packet >> expected +-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected]) ++echo $packet >> expected2 ++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2]) + + # Send ip packets between foo1 and bar1 + # (different switch, loopback to same vm but different tag) +@@ -9703,11 +9703,11 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet + + # expected packet at bar3 + packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +-echo $packet > expected +-OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected]) ++echo $packet > expected3 ++OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected3]) + + # Send ip packets between foo1 and vm1. +-(different switch, container to the VM hosting it.) ++# (different switch, container to the VM hosting it.) + src_mac="f00000010205" + dst_mac="000000010203" + src_ip=`ip_to_hex 192 168 1 2` +@@ -9723,7 +9723,7 @@ echo $packet >> expected1 + OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) + + # Send packets from vm1 to bar1. +-(different switch, A hosting VM to a container inside it) ++# (different switch, A hosting VM to a container inside it) + src_mac="f00000010203" + dst_mac="000000010202" + src_ip=`ip_to_hex 172 16 1 2` +@@ -9739,6 +9739,7 @@ echo $packet >> expected1 + OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) + + # Send broadcast packet from foo1. foo1 should not receive the same packet. ++# But foo2 should. + src_mac="f00000010205" + dst_mac="ffffffffffff" + src_ip=`ip_to_hex 192 168 1 2` +@@ -9749,6 +9750,11 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet + # expected packet at VM1 + OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1]) + ++# expected packet at foo2 ++packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 ++echo $packet >> expected2 ++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2]) ++ + # Test binding of parent and container ports. + ovn-nbctl lsp-set-options vm1 requested-chassis=foo + diff --git a/SOURCES/ovn-21.03.0.patch b/SOURCES/ovn-21.03.0.patch deleted file mode 100644 index 57474ee..0000000 --- a/SOURCES/ovn-21.03.0.patch +++ /dev/null @@ -1,8444 +0,0 @@ -diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh -index 0bb0ff096..83ad3958b 100755 ---- a/.ci/linux-prepare.sh -+++ b/.ci/linux-prepare.sh -@@ -12,5 +12,5 @@ set -ev - git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git - cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd .. - --pip install --disable-pip-version-check --user six flake8 hacking --pip install --user --upgrade docutils -+pip3 install --disable-pip-version-check --user flake8 hacking sphinx pyOpenSSL -+pip3 install --upgrade --user docutils -diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml -index f3a53a8b6..91bd1e538 100644 ---- a/.github/workflows/test.yml -+++ b/.github/workflows/test.yml -@@ -13,7 +13,6 @@ jobs: - dependencies: | - automake libtool gcc bc libjemalloc1 libjemalloc-dev \ - libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \ -- python3-openssl python3-pip python3-sphinx \ - selinux-policy-dev - m32_dependecies: gcc-multilib - CC: ${{ matrix.compiler }} -@@ -88,11 +87,21 @@ jobs: - if: matrix.m32 != '' - run: sudo apt install -y ${{ env.m32_dependecies }} - -+ - name: update PATH -+ run: | -+ echo "$HOME/bin" >> $GITHUB_PATH -+ echo "$HOME/.local/bin" >> $GITHUB_PATH -+ -+ - name: set up python -+ uses: actions/setup-python@v2 -+ with: -+ python-version: '3.x' -+ - - name: prepare - run: ./.ci/linux-prepare.sh - - - name: build -- run: PATH="$PATH:$HOME/bin" ./.ci/linux-build.sh -+ run: ./.ci/linux-build.sh - - - name: copy logs on failure - if: failure() || cancelled() -@@ -145,10 +154,18 @@ jobs: - ref: 'master' - - name: install dependencies - run: brew install automake libtool -+ - name: update PATH -+ run: | -+ echo "$HOME/bin" >> $GITHUB_PATH -+ echo "$HOME/.local/bin" >> $GITHUB_PATH -+ - name: set up python -+ uses: actions/setup-python@v2 -+ with: -+ python-version: '3.x' - - name: prepare - run: ./.ci/osx-prepare.sh - - name: build -- run: PATH="$PATH:$HOME/bin" ./.ci/osx-build.sh -+ run: ./.ci/osx-build.sh - - name: upload logs on failure - if: failure() - uses: actions/upload-artifact@v2 -diff --git a/Makefile.am b/Makefile.am -index 80247b62d..1fe730dc4 100644 ---- a/Makefile.am -+++ b/Makefile.am -@@ -221,6 +221,7 @@ dist-hook-git: distfiles - grep -v '\.gitattributes$$' | \ - grep -v '\.gitmodules$$' | \ - grep -v "$(submodules)" | \ -+ grep -v 'redhat' | \ - LC_ALL=C sort -u > all-gitfiles; \ - LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \ - if test -s missing-distfiles; then \ -@@ -332,7 +333,7 @@ check-tabs: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ - grep -ln "^ " \ -- `git ls-files | grep -v $(submodules) \ -+ `git ls-files | grep -v $(submodules) | grep -v redhat \ - | grep -v -f build-aux/initial-tab-whitelist` /dev/null \ - | $(EGREP) -v ':[ ]*/?\*'; \ - then \ -diff --git a/NEWS b/NEWS -index 5372668bf..530c5d42f 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,13 @@ -+Post-v21.03.0 -+------------------------- -+ - ovn-northd-ddlog: New implementation of northd, based on DDlog. This -+ implementation is incremental, meaning that it only recalculates what is -+ needed for the southbound database when northbound changes occur. It is -+ expected to scale better than the C implementation, for large deployments. -+ (This may take testing and tuning to be effective.) This version of OVN -+ requires DDLog 0.36. -+ - Introduce ovn-controller incremetal processing engine statistics -+ - OVN v21.03.0 - 12 Mar 2021 - ------------------------- - - Support ECMP multiple nexthops for reroute router policies. -diff --git a/configure.ac b/configure.ac -index 37b476d53..f3de6fef2 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -13,7 +13,7 @@ - # limitations under the License. - - AC_PREREQ(2.63) --AC_INIT(ovn, 21.03.0, bugs@openvswitch.org) -+AC_INIT(ovn, 21.03.1, bugs@openvswitch.org) - AC_CONFIG_MACRO_DIR([m4]) - AC_CONFIG_AUX_DIR([build-aux]) - AC_CONFIG_HEADERS([config.h]) -diff --git a/controller/automake.mk b/controller/automake.mk -index e664f1980..2f6c50890 100644 ---- a/controller/automake.mk -+++ b/controller/automake.mk -@@ -10,6 +10,8 @@ controller_ovn_controller_SOURCES = \ - controller/encaps.h \ - controller/ha-chassis.c \ - controller/ha-chassis.h \ -+ controller/if-status.c \ -+ controller/if-status.h \ - controller/ip-mcast.c \ - controller/ip-mcast.h \ - controller/lflow.c \ -diff --git a/controller/binding.c b/controller/binding.c -index 4e6c75696..31f3a210f 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -16,9 +16,9 @@ - #include - #include "binding.h" - #include "ha-chassis.h" -+#include "if-status.h" - #include "lflow.h" - #include "lport.h" --#include "ofctrl-seqno.h" - #include "patch.h" - - #include "lib/bitmap.h" -@@ -41,32 +41,6 @@ VLOG_DEFINE_THIS_MODULE(binding); - */ - #define OVN_INSTALLED_EXT_ID "ovn-installed" - --/* Set of OVS interface IDs that have been released in the most recent -- * processing iterations. This gets updated in release_lport() and is -- * periodically emptied in binding_seqno_run(). -- */ --static struct sset binding_iface_released_set = -- SSET_INITIALIZER(&binding_iface_released_set); -- --/* Set of OVS interface IDs that have been bound in the most recent -- * processing iterations. This gets updated in release_lport() and is -- * periodically emptied in binding_seqno_run(). -- */ --static struct sset binding_iface_bound_set = -- SSET_INITIALIZER(&binding_iface_bound_set); -- --static void --binding_iface_released_add(const char *iface_id) --{ -- sset_add(&binding_iface_released_set, iface_id); --} -- --static void --binding_iface_bound_add(const char *iface_id) --{ -- sset_add(&binding_iface_bound_set, iface_id); --} -- - #define OVN_QOS_TYPE "linux-htb" - - struct qos_queue { -@@ -597,6 +571,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, - } - } - -+/* Corresponds to each Port_Binding.type. */ -+enum en_lport_type { -+ LP_UNKNOWN, -+ LP_VIF, -+ LP_CONTAINER, -+ LP_PATCH, -+ LP_L3GATEWAY, -+ LP_LOCALNET, -+ LP_LOCALPORT, -+ LP_L2GATEWAY, -+ LP_VTEP, -+ LP_CHASSISREDIRECT, -+ LP_VIRTUAL, -+ LP_EXTERNAL, -+ LP_REMOTE -+}; -+ - /* Local bindings. binding.c module binds the logical port (represented by - * Port_Binding rows) and sets the 'chassis' column when it sees the - * OVS interface row (of type "" or "internal") with the -@@ -608,134 +599,270 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, - * 'struct local_binding' is used. A shash of these local bindings is - * maintained with the 'external_ids:iface-id' as the key to the shash. - * -- * struct local_binding (defined in binding.h) has 3 main fields: -- * - type -- * - OVS interface row object -- * - Port_Binding row object -- * -- * An instance of 'struct local_binding' can be one of 3 types. -- * -- * BT_VIF: Represent a local binding for an OVS interface of -- * type "" or "internal" with the external_ids:iface-id -- * set. -- * -- * This can be a -- * * probable local binding - external_ids:iface-id is -- * set, but the corresponding Port_Binding row is not -- * created or is not visible to the local ovn-controller -- * instance. -- * -- * * a local binding - external_ids:iface-id is set and -- * which is already bound to the corresponding Port_Binding -- * row. -- * -- * It maintains a list of children -- * (of type BT_CONTAINER/BT_VIRTUAL) if any. -- * -- * BT_CONTAINER: Represents a local binding which has a parent of type -- * BT_VIF. Its Port_Binding row's 'parent' column is set to -- * its parent's Port_Binding. It shares the OVS interface row -- * with the parent. -- * Each ovn-controller when it sees a container Port_Binding, -- * it creates 'struct local_binding' for the parent -- * Port_Binding and for its even if the OVS interface row for -- * the parent is not present. -- * -- * BT_VIRTUAL: Represents a local binding which has a parent of type BT_VIF. -- * Its Port_Binding type is "virtual" and it shares the OVS -- * interface row with the parent. -- * Port_Binding of type "virtual" is claimed by pinctrl module -- * when it sees the ARP packet from the parent's VIF. -- * -+ * struct local_binding has 3 main fields: -+ * - name : 'external_ids:iface-id' of the OVS interface (key). -+ * - OVS interface row object. -+ * - List of 'binding_lport' objects with the primary lport -+ * in the front of the list (if present). - * - * An object of 'struct local_binding' is created: -- * - For each interface that has iface-id configured with the type - BT_VIF. -+ * - For each interface that has external_ids:iface-id configured. -+ * -+ * - For each port binding (also referred as lport) of type 'LP_VIF' -+ * if it is a parent lport of container lports even if there is no -+ * corresponding OVS interface. -+ */ -+struct local_binding { -+ char *name; -+ const struct ovsrec_interface *iface; -+ struct ovs_list binding_lports; -+}; -+ -+/* This structure represents a logical port (or port binding) -+ * which is associated with 'struct local_binding'. - * -- * - For each container Port Binding (of type BT_CONTAINER) and its -- * parent Port_Binding (of type BT_VIF), no matter if -- * they are bound to this chassis i.e even if OVS interface row for the -- * parent is not present. -+ * An instance of 'struct binding_lport' is created for a logical port -+ * - If the OVS interface's iface-id corresponds to the logical port. -+ * - If it is a container or virtual logical port and its parent -+ * has a 'local binding'. - * -- * - For each 'virtual' Port Binding (of type BT_VIRTUAL) provided its parent -- * is bound to this chassis. - */ -+struct binding_lport { -+ struct ovs_list list_node; /* Node in local_binding.binding_lports. */ - --static struct local_binding * --local_binding_create(const char *name, const struct ovsrec_interface *iface, -- const struct sbrec_port_binding *pb, -- enum local_binding_type type) -+ char *name; -+ const struct sbrec_port_binding *pb; -+ struct local_binding *lbinding; -+ enum en_lport_type type; -+}; -+ -+static struct local_binding *local_binding_create( -+ const char *name, const struct ovsrec_interface *); -+static void local_binding_add(struct shash *local_bindings, -+ struct local_binding *); -+static struct local_binding *local_binding_find( -+ struct shash *local_bindings, const char *name); -+static void local_binding_destroy(struct local_binding *, -+ struct shash *binding_lports); -+static void local_binding_delete(struct local_binding *, -+ struct shash *local_bindings, -+ struct shash *binding_lports, -+ struct if_status_mgr *if_mgr); -+static struct binding_lport *local_binding_add_lport( -+ struct shash *binding_lports, -+ struct local_binding *, -+ const struct sbrec_port_binding *, -+ enum en_lport_type); -+static struct binding_lport *local_binding_get_primary_lport( -+ struct local_binding *); -+static bool local_binding_handle_stale_binding_lports( -+ struct local_binding *lbinding, struct binding_ctx_in *b_ctx_in, -+ struct binding_ctx_out *b_ctx_out, struct hmap *qos_map); -+ -+static struct binding_lport *binding_lport_create( -+ const struct sbrec_port_binding *, -+ struct local_binding *, enum en_lport_type); -+static void binding_lport_destroy(struct binding_lport *); -+static void binding_lport_delete(struct shash *binding_lports, -+ struct binding_lport *); -+static void binding_lport_add(struct shash *binding_lports, -+ struct binding_lport *); -+static void binding_lport_set_up(struct binding_lport *, bool sb_readonly); -+static void binding_lport_set_down(struct binding_lport *, bool sb_readonly); -+static struct binding_lport *binding_lport_find( -+ struct shash *binding_lports, const char *lport_name); -+static const struct sbrec_port_binding *binding_lport_get_parent_pb( -+ struct binding_lport *b_lprt); -+static struct binding_lport *binding_lport_check_and_cleanup( -+ struct binding_lport *, struct shash *b_lports); -+ -+static char *get_lport_type_str(enum en_lport_type lport_type); -+ -+void -+local_binding_data_init(struct local_binding_data *lbinding_data) - { -- struct local_binding *lbinding = xzalloc(sizeof *lbinding); -- lbinding->name = xstrdup(name); -- lbinding->type = type; -- lbinding->pb = pb; -- lbinding->iface = iface; -- shash_init(&lbinding->children); -- return lbinding; -+ shash_init(&lbinding_data->bindings); -+ shash_init(&lbinding_data->lports); - } - --static void --local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) -+void -+local_binding_data_destroy(struct local_binding_data *lbinding_data) - { -- shash_add(local_bindings, lbinding->name, lbinding); -+ struct shash_node *node, *next; -+ -+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->lports) { -+ struct binding_lport *b_lport = node->data; -+ binding_lport_destroy(b_lport); -+ shash_delete(&lbinding_data->lports, node); -+ } -+ -+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->bindings) { -+ struct local_binding *lbinding = node->data; -+ local_binding_destroy(lbinding, &lbinding_data->lports); -+ shash_delete(&lbinding_data->bindings, node); -+ } -+ -+ shash_destroy(&lbinding_data->lports); -+ shash_destroy(&lbinding_data->bindings); - } - --static void --local_binding_destroy(struct local_binding *lbinding) -+const struct sbrec_port_binding * -+local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name) - { -- local_bindings_destroy(&lbinding->children); -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); - -- free(lbinding->name); -- free(lbinding); -+ return b_lport ? b_lport->pb : NULL; - } - --void --local_bindings_init(struct shash *local_bindings) -+bool -+local_binding_is_up(struct shash *local_bindings, const char *pb_name) - { -- shash_init(local_bindings); -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ if (lbinding && b_lport && lbinding->iface) { -+ if (b_lport->pb->n_up && !b_lport->pb->up[0]) { -+ return false; -+ } -+ return smap_get_bool(&lbinding->iface->external_ids, -+ OVN_INSTALLED_EXT_ID, false); -+ } -+ return false; - } - --void --local_bindings_destroy(struct shash *local_bindings) -+bool -+local_binding_is_down(struct shash *local_bindings, const char *pb_name) - { -- struct shash_node *node, *next; -- SHASH_FOR_EACH_SAFE (node, next, local_bindings) { -- struct local_binding *lbinding = node->data; -- local_binding_destroy(lbinding); -- shash_delete(local_bindings, node); -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ -+ if (!lbinding) { -+ return true; - } - -- shash_destroy(local_bindings); --} -+ if (lbinding->iface && smap_get_bool(&lbinding->iface->external_ids, -+ OVN_INSTALLED_EXT_ID, false)) { -+ return false; -+ } - --static --void local_binding_delete(struct shash *local_bindings, -- struct local_binding *lbinding) --{ -- shash_find_and_delete(local_bindings, lbinding->name); -- local_binding_destroy(lbinding); -+ if (b_lport && b_lport->pb->n_up && b_lport->pb->up[0]) { -+ return false; -+ } -+ -+ return true; - } - --static void --local_binding_add_child(struct local_binding *lbinding, -- struct local_binding *child) -+void -+local_binding_set_up(struct shash *local_bindings, const char *pb_name, -+ bool sb_readonly, bool ovs_readonly) - { -- local_binding_add(&lbinding->children, child); -- child->parent = lbinding; -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ -+ if (!ovs_readonly && lbinding && lbinding->iface -+ && !smap_get_bool(&lbinding->iface->external_ids, -+ OVN_INSTALLED_EXT_ID, false)) { -+ ovsrec_interface_update_external_ids_setkey(lbinding->iface, -+ OVN_INSTALLED_EXT_ID, -+ "true"); -+ } -+ -+ if (!sb_readonly && lbinding && b_lport && b_lport->pb->n_up) { -+ binding_lport_set_up(b_lport, sb_readonly); -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ binding_lport_set_up(b_lport, sb_readonly); -+ } -+ } - } - --static struct local_binding * --local_binding_find_child(struct local_binding *lbinding, -- const char *child_name) -+void -+local_binding_set_down(struct shash *local_bindings, const char *pb_name, -+ bool sb_readonly, bool ovs_readonly) - { -- return local_binding_find(&lbinding->children, child_name); -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ -+ if (!ovs_readonly && lbinding && lbinding->iface -+ && smap_get_bool(&lbinding->iface->external_ids, -+ OVN_INSTALLED_EXT_ID, false)) { -+ ovsrec_interface_update_external_ids_delkey(lbinding->iface, -+ OVN_INSTALLED_EXT_ID); -+ } -+ -+ if (!sb_readonly && b_lport && b_lport->pb->n_up) { -+ binding_lport_set_down(b_lport, sb_readonly); -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ binding_lport_set_down(b_lport, sb_readonly); -+ } -+ } - } - --static void --local_binding_delete_child(struct local_binding *lbinding, -- struct local_binding *child) -+void -+binding_dump_local_bindings(struct local_binding_data *lbinding_data, -+ struct ds *out_data) - { -- shash_find_and_delete(&lbinding->children, child->name); -+ const struct shash_node **nodes; -+ -+ nodes = shash_sort(&lbinding_data->bindings); -+ size_t n = shash_count(&lbinding_data->bindings); -+ -+ ds_put_cstr(out_data, "Local bindings:\n"); -+ for (size_t i = 0; i < n; i++) { -+ const struct shash_node *node = nodes[i]; -+ struct local_binding *lbinding = node->data; -+ size_t num_lports = ovs_list_size(&lbinding->binding_lports); -+ ds_put_format(out_data, "name: [%s], OVS interface name : [%s], " -+ "num binding lports : [%"PRIuSIZE"]\n", -+ lbinding->name, -+ lbinding->iface ? lbinding->iface->name : "NULL", -+ num_lports); -+ -+ if (num_lports) { -+ struct shash child_lports = SHASH_INITIALIZER(&child_lports); -+ struct binding_lport *primary_lport = NULL; -+ struct binding_lport *b_lport; -+ bool first_elem = true; -+ -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (first_elem && b_lport->type == LP_VIF) { -+ primary_lport = b_lport; -+ } else { -+ shash_add(&child_lports, b_lport->name, b_lport); -+ } -+ first_elem = false; -+ } -+ -+ if (primary_lport) { -+ ds_put_format(out_data, "primary lport : [%s]\n", -+ primary_lport->name); -+ } else { -+ ds_put_format(out_data, "no primary lport\n"); -+ } -+ -+ if (!shash_is_empty(&child_lports)) { -+ const struct shash_node **c_nodes = -+ shash_sort(&child_lports); -+ for (size_t j = 0; j < shash_count(&child_lports); j++) { -+ b_lport = c_nodes[j]->data; -+ ds_put_format(out_data, "child lport[%"PRIuSIZE"] : [%s], " -+ "type : [%s]\n", j + 1, b_lport->name, -+ get_lport_type_str(b_lport->type)); -+ } -+ free(c_nodes); -+ } -+ shash_destroy(&child_lports); -+ } -+ -+ ds_put_cstr(out_data, "----------------------------------------\n"); -+ } -+ -+ free(nodes); - } - - static bool -@@ -744,12 +871,6 @@ is_lport_vif(const struct sbrec_port_binding *pb) - return !pb->type[0]; - } - --static bool --is_lport_container(const struct sbrec_port_binding *pb) --{ -- return is_lport_vif(pb) && pb->parent_port && pb->parent_port[0]; --} -- - static struct tracked_binding_datapath * - tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp, - bool is_new, -@@ -818,26 +939,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths) - hmap_destroy(tracked_datapaths); - } - --/* Corresponds to each Port_Binding.type. */ --enum en_lport_type { -- LP_UNKNOWN, -- LP_VIF, -- LP_PATCH, -- LP_L3GATEWAY, -- LP_LOCALNET, -- LP_LOCALPORT, -- LP_L2GATEWAY, -- LP_VTEP, -- LP_CHASSISREDIRECT, -- LP_VIRTUAL, -- LP_EXTERNAL, -- LP_REMOTE --}; -- - static enum en_lport_type - get_lport_type(const struct sbrec_port_binding *pb) - { - if (is_lport_vif(pb)) { -+ if (pb->parent_port && pb->parent_port[0]) { -+ return LP_CONTAINER; -+ } - return LP_VIF; - } else if (!strcmp(pb->type, "patch")) { - return LP_PATCH; -@@ -864,6 +972,41 @@ get_lport_type(const struct sbrec_port_binding *pb) - return LP_UNKNOWN; - } - -+static char * -+get_lport_type_str(enum en_lport_type lport_type) -+{ -+ switch (lport_type) { -+ case LP_VIF: -+ return "VIF"; -+ case LP_CONTAINER: -+ return "CONTAINER"; -+ case LP_VIRTUAL: -+ return "VIRTUAL"; -+ case LP_PATCH: -+ return "PATCH"; -+ case LP_CHASSISREDIRECT: -+ return "CHASSISREDIRECT"; -+ case LP_L3GATEWAY: -+ return "L3GATEWAT"; -+ case LP_LOCALNET: -+ return "PATCH"; -+ case LP_LOCALPORT: -+ return "LOCALPORT"; -+ case LP_L2GATEWAY: -+ return "L2GATEWAY"; -+ case LP_EXTERNAL: -+ return "EXTERNAL"; -+ case LP_REMOTE: -+ return "REMOTE"; -+ case LP_VTEP: -+ return "VTEP"; -+ case LP_UNKNOWN: -+ return "UNKNOWN"; -+ } -+ -+ OVS_NOT_REACHED(); -+} -+ - /* For newly claimed ports, if 'notify_up' is 'false': - * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'. - * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for -@@ -880,7 +1023,7 @@ static void - claimed_lport_set_up(const struct sbrec_port_binding *pb, - const struct sbrec_port_binding *parent_pb, - const struct sbrec_chassis *chassis_rec, -- bool notify_up) -+ bool notify_up, struct if_status_mgr *if_mgr) - { - if (!notify_up) { - bool up = true; -@@ -891,7 +1034,7 @@ claimed_lport_set_up(const struct sbrec_port_binding *pb, - } - - if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) { -- binding_iface_bound_add(pb->logical_port); -+ if_status_mgr_claim_iface(if_mgr, pb->logical_port); - } - } - -@@ -904,10 +1047,11 @@ claim_lport(const struct sbrec_port_binding *pb, - const struct sbrec_chassis *chassis_rec, - const struct ovsrec_interface *iface_rec, - bool sb_readonly, bool notify_up, -- struct hmap *tracked_datapaths) -+ struct hmap *tracked_datapaths, -+ struct if_status_mgr *if_mgr) - { - if (!sb_readonly) { -- claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up); -+ claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up, if_mgr); - } - - if (pb->chassis != chassis_rec) { -@@ -955,7 +1099,7 @@ claim_lport(const struct sbrec_port_binding *pb, - */ - static bool - release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, -- struct hmap *tracked_datapaths) -+ struct hmap *tracked_datapaths, struct if_status_mgr *if_mgr) - { - if (pb->encap) { - if (sb_readonly) { -@@ -978,12 +1122,8 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - sbrec_port_binding_set_virtual_parent(pb, NULL); - } - -- if (pb->n_up) { -- bool up = false; -- sbrec_port_binding_set_up(pb, &up, 1); -- } - update_lport_tracking(pb, tracked_datapaths); -- binding_iface_released_add(pb->logical_port); -+ if_status_mgr_release_iface(if_mgr, pb->logical_port); - VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); - return true; - } -@@ -991,14 +1131,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - static bool - is_lbinding_set(struct local_binding *lbinding) - { -- return lbinding && lbinding->pb && lbinding->iface; -+ return lbinding && lbinding->iface; - } - - static bool --is_lbinding_this_chassis(struct local_binding *lbinding, -- const struct sbrec_chassis *chassis) -+is_binding_lport_this_chassis(struct binding_lport *b_lport, -+ const struct sbrec_chassis *chassis) - { -- return lbinding && lbinding->pb && lbinding->pb->chassis == chassis; -+ return (b_lport && b_lport->pb && chassis && -+ b_lport->pb->chassis == chassis); - } - - static bool -@@ -1010,15 +1151,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec, - || !strcmp(requested_chassis, chassis_rec->hostname); - } - --/* Returns 'true' if the 'lbinding' has children of type BT_CONTAINER, -+/* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER, - * 'false' otherwise. */ - static bool - is_lbinding_container_parent(struct local_binding *lbinding) - { -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *l = node->data; -- if (l->type == BT_CONTAINER) { -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { - return true; - } - } -@@ -1027,66 +1167,44 @@ is_lbinding_container_parent(struct local_binding *lbinding) - } - - static bool --release_local_binding_children(const struct sbrec_chassis *chassis_rec, -- struct local_binding *lbinding, -- bool sb_readonly, -- struct hmap *tracked_dp_bindings) --{ -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *l = node->data; -- if (is_lbinding_this_chassis(l, chassis_rec)) { -- if (!release_lport(l->pb, sb_readonly, tracked_dp_bindings)) { -- return false; -- } -+release_binding_lport(const struct sbrec_chassis *chassis_rec, -+ struct binding_lport *b_lport, bool sb_readonly, -+ struct binding_ctx_out *b_ctx_out) -+{ -+ if (is_binding_lport_this_chassis(b_lport, chassis_rec)) { -+ remove_local_lport_ids(b_lport->pb, b_ctx_out); -+ if (!release_lport(b_lport->pb, sb_readonly, -+ b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr)) { -+ return false; - } -- -- /* Clear the local bindings' 'iface'. */ -- l->iface = NULL; -+ binding_lport_set_down(b_lport, sb_readonly); - } - - return true; - } - --static bool --release_local_binding(const struct sbrec_chassis *chassis_rec, -- struct local_binding *lbinding, bool sb_readonly, -- struct hmap *tracked_dp_bindings) --{ -- if (!release_local_binding_children(chassis_rec, lbinding, -- sb_readonly, tracked_dp_bindings)) { -- return false; -- } -- -- bool retval = true; -- if (is_lbinding_this_chassis(lbinding, chassis_rec)) { -- retval = release_lport(lbinding->pb, sb_readonly, tracked_dp_bindings); -- } -- -- lbinding->pb = NULL; -- lbinding->iface = NULL; -- return retval; --} -- - static bool - consider_vif_lport_(const struct sbrec_port_binding *pb, - bool can_bind, const char *vif_chassis, - struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out, -- struct local_binding *lbinding, -+ struct binding_lport *b_lport, - struct hmap *qos_map) - { -- bool lbinding_set = is_lbinding_set(lbinding); -+ bool lbinding_set = b_lport && is_lbinding_set(b_lport->lbinding); -+ - if (lbinding_set) { - if (can_bind) { - /* We can claim the lport. */ - const struct sbrec_port_binding *parent_pb = -- lbinding->parent ? lbinding->parent->pb : NULL; -+ binding_lport_get_parent_pb(b_lport); - - if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec, -- lbinding->iface, !b_ctx_in->ovnsb_idl_txn, -- !lbinding->parent, -- b_ctx_out->tracked_dp_bindings)){ -+ b_lport->lbinding->iface, -+ !b_ctx_in->ovnsb_idl_txn, -+ !parent_pb, b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr)){ - return false; - } - -@@ -1098,7 +1216,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - b_ctx_out->tracked_dp_bindings); - update_local_lport_ids(pb, b_ctx_out); - update_local_lports(pb->logical_port, b_ctx_out); -- if (lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { -+ if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { - get_qos_params(pb, qos_map); - } - } else { -@@ -1117,7 +1235,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - /* Release the lport if there is no lbinding. */ - if (!lbinding_set || !can_bind) { - return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings); -+ b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr); - } - } - -@@ -1136,16 +1255,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb, - vif_chassis); - - if (!lbinding) { -- lbinding = local_binding_find(b_ctx_out->local_bindings, -+ lbinding = local_binding_find(&b_ctx_out->lbinding_data->bindings, - pb->logical_port); - } - -+ struct binding_lport *b_lport = NULL; - if (lbinding) { -- lbinding->pb = pb; -+ struct shash *binding_lports = -+ &b_ctx_out->lbinding_data->lports; -+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, LP_VIF); - } - - return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, -- b_ctx_out, lbinding, qos_map); -+ b_ctx_out, b_lport, qos_map); - } - - static bool -@@ -1154,9 +1276,9 @@ consider_container_lport(const struct sbrec_port_binding *pb, - struct binding_ctx_out *b_ctx_out, - struct hmap *qos_map) - { -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; - struct local_binding *parent_lbinding; -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->parent_port); -+ parent_lbinding = local_binding_find(local_bindings, pb->parent_port); - - if (!parent_lbinding) { - /* There is no local_binding for parent port. Create it -@@ -1171,54 +1293,62 @@ consider_container_lport(const struct sbrec_port_binding *pb, - * we want the these container ports also be claimed by the - * chassis. - * */ -- parent_lbinding = local_binding_create(pb->parent_port, NULL, NULL, -- BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, parent_lbinding); -+ parent_lbinding = local_binding_create(pb->parent_port, NULL); -+ local_binding_add(local_bindings, parent_lbinding); - } - -- struct local_binding *container_lbinding = -- local_binding_find_child(parent_lbinding, pb->logical_port); -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct binding_lport *container_b_lport = -+ local_binding_add_lport(binding_lports, parent_lbinding, pb, -+ LP_CONTAINER); - -- if (!container_lbinding) { -- container_lbinding = local_binding_create(pb->logical_port, -- parent_lbinding->iface, -- pb, BT_CONTAINER); -- local_binding_add_child(parent_lbinding, container_lbinding); -- } else { -- ovs_assert(container_lbinding->type == BT_CONTAINER); -- container_lbinding->pb = pb; -- container_lbinding->iface = parent_lbinding->iface; -- } -+ struct binding_lport *parent_b_lport = -+ binding_lport_find(binding_lports, pb->parent_port); - -- if (!parent_lbinding->pb) { -- parent_lbinding->pb = lport_lookup_by_name( -+ bool can_consider_c_lport = true; -+ if (!parent_b_lport || !parent_b_lport->pb) { -+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( - b_ctx_in->sbrec_port_binding_by_name, pb->parent_port); - -- if (parent_lbinding->pb) { -+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { - /* Its possible that the parent lport is not considered yet. - * So call consider_vif_lport() to process it first. */ -- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, -+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, - parent_lbinding, qos_map); -+ parent_b_lport = binding_lport_find(binding_lports, -+ pb->parent_port); - } else { -- /* The parent lport doesn't exist. Call release_lport() to -- * release the container lport, if it was bound earlier. */ -- if (is_lbinding_this_chassis(container_lbinding, -- b_ctx_in->chassis_rec)) { -- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings); -- } -+ /* The parent lport doesn't exist. Cannot consider the container -+ * lport for binding. */ -+ can_consider_c_lport = false; -+ } -+ } - -- return true; -+ if (parent_b_lport && parent_b_lport->type != LP_VIF) { -+ can_consider_c_lport = false; -+ } -+ -+ if (!can_consider_c_lport) { -+ /* Call release_lport() to release the container lport, -+ * if it was bound earlier. */ -+ if (is_binding_lport_this_chassis(container_b_lport, -+ b_ctx_in->chassis_rec)) { -+ return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr); - } -+ -+ return true; - } - -- const char *vif_chassis = smap_get(&parent_lbinding->pb->options, -+ ovs_assert(parent_b_lport && parent_b_lport->pb); -+ const char *vif_chassis = smap_get(&parent_b_lport->pb->options, - "requested-chassis"); - bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec, - vif_chassis); - - return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out, -- container_lbinding, qos_map); -+ container_b_lport, qos_map); - } - - static bool -@@ -1227,46 +1357,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, - struct binding_ctx_out *b_ctx_out, - struct hmap *qos_map) - { -- struct local_binding * parent_lbinding = -- pb->virtual_parent ? local_binding_find(b_ctx_out->local_bindings, -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct local_binding *parent_lbinding = -+ pb->virtual_parent ? local_binding_find(local_bindings, - pb->virtual_parent) - : NULL; - -- if (parent_lbinding && !parent_lbinding->pb) { -- parent_lbinding->pb = lport_lookup_by_name( -- b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); -- -- if (parent_lbinding->pb) { -- /* Its possible that the parent lport is not considered yet. -- * So call consider_vif_lport() to process it first. */ -- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, -- parent_lbinding, qos_map); -- } -- } -- -+ struct binding_lport *virtual_b_lport = NULL; - /* Unlike container lports, we don't have to create parent_lbinding if - * it is NULL. This is because, if parent_lbinding is not present, it - * means the virtual port can't bind in this chassis. - * Note: pinctrl module binds the virtual lport when it sees ARP - * packet from the parent lport. */ -- struct local_binding *virtual_lbinding = NULL; -- if (is_lbinding_this_chassis(parent_lbinding, b_ctx_in->chassis_rec)) { -- virtual_lbinding = -- local_binding_find_child(parent_lbinding, pb->logical_port); -- if (!virtual_lbinding) { -- virtual_lbinding = local_binding_create(pb->logical_port, -- parent_lbinding->iface, -- pb, BT_VIRTUAL); -- local_binding_add_child(parent_lbinding, virtual_lbinding); -- } else { -- ovs_assert(virtual_lbinding->type == BT_VIRTUAL); -- virtual_lbinding->pb = pb; -- virtual_lbinding->iface = parent_lbinding->iface; -+ if (parent_lbinding) { -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ -+ struct binding_lport *parent_b_lport = -+ binding_lport_find(binding_lports, pb->virtual_parent); -+ -+ if (!parent_b_lport || !parent_b_lport->pb) { -+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( -+ b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); -+ -+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { -+ /* Its possible that the parent lport is not considered yet. -+ * So call consider_vif_lport() to process it first. */ -+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, -+ parent_lbinding, qos_map); -+ } -+ } -+ -+ parent_b_lport = local_binding_get_primary_lport(parent_lbinding); -+ if (is_binding_lport_this_chassis(parent_b_lport, -+ b_ctx_in->chassis_rec)) { -+ virtual_b_lport = -+ local_binding_add_lport(binding_lports, parent_lbinding, pb, -+ LP_VIRTUAL); - } - } - -- return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, -- virtual_lbinding, qos_map); -+ if (!consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, -+ virtual_b_lport, qos_map)) { -+ return false; -+ } -+ -+ /* If the virtual lport is not bound to this chassis, then remove -+ * its entry from the local_lport_ids if present. This is required -+ * when a virtual port moves from one chassis to other.*/ -+ if (!virtual_b_lport) { -+ remove_local_lport_ids(pb, b_ctx_out); -+ } -+ -+ return true; - } - - /* Considers either claiming the lport or releasing the lport -@@ -1291,10 +1433,12 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, - update_local_lport_ids(pb, b_ctx_out); - return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL, - !b_ctx_in->ovnsb_idl_txn, false, -- b_ctx_out->tracked_dp_bindings); -+ b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr); - } else if (pb->chassis == b_ctx_in->chassis_rec) { - return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings); -+ b_ctx_out->tracked_dp_bindings, -+ b_ctx_out->if_mgr); - } - - return true; -@@ -1407,6 +1551,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - continue; - } - -+ struct shash *local_bindings = -+ &b_ctx_out->lbinding_data->bindings; - for (j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec; - -@@ -1416,11 +1562,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - - if (iface_id && ofport > 0) { - struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, iface_id); -+ local_binding_find(local_bindings, iface_id); - if (!lbinding) { -- lbinding = local_binding_create(iface_id, iface_rec, NULL, -- BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, lbinding); -+ lbinding = local_binding_create(iface_id, iface_rec); -+ local_binding_add(local_bindings, lbinding); - } else { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 5); -@@ -1431,7 +1576,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - "configuration on interface [%s]", - lbinding->iface->name, iface_rec->name, - iface_rec->name); -- ovs_assert(lbinding->type == BT_VIF); - } - - update_local_lports(iface_id, b_ctx_out); -@@ -1494,11 +1638,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - break; - - case LP_VIF: -- if (is_lport_container(pb)) { -- consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); -- } else { -- consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); -- } -+ consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); -+ break; -+ -+ case LP_CONTAINER: -+ consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); - break; - - case LP_VIRTUAL: -@@ -1799,39 +1943,44 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec, - update_local_lports(iface_id, b_ctx_out); - smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id); - -- struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, iface_id); -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct local_binding *lbinding = local_binding_find(local_bindings, -+ iface_id); - - if (!lbinding) { -- lbinding = local_binding_create(iface_id, iface_rec, NULL, BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, lbinding); -+ lbinding = local_binding_create(iface_id, iface_rec); -+ local_binding_add(local_bindings, lbinding); - } else { - lbinding->iface = iface_rec; - } - -- if (!lbinding->pb || strcmp(lbinding->name, lbinding->pb->logical_port)) { -- lbinding->pb = lport_lookup_by_name( -- b_ctx_in->sbrec_port_binding_by_name, lbinding->name); -- if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) { -- lbinding->pb = NULL; -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ const struct sbrec_port_binding *pb = NULL; -+ if (!b_lport) { -+ pb = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name, -+ lbinding->name); -+ if (pb && get_lport_type(pb) == LP_VIF) { -+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, -+ LP_VIF); - } - } - -- if (lbinding->pb) { -- if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out, -- lbinding, qos_map)) { -- return false; -- } -+ if (!b_lport) { -+ /* There is no binding lport for this local binding. */ -+ return true; -+ } -+ -+ if (!consider_vif_lport(b_lport->pb, b_ctx_in, b_ctx_out, -+ lbinding, qos_map)) { -+ return false; - } - - /* Update the child local_binding's iface (if any children) and try to - * claim the container lbindings. */ -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *child = node->data; -- child->iface = iface_rec; -- if (child->type == BT_CONTAINER) { -- if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out, -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { -+ if (!consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, - qos_map)) { - return false; - } -@@ -1862,32 +2011,43 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, - struct binding_ctx_out *b_ctx_out) - { - struct local_binding *lbinding; -- lbinding = local_binding_find(b_ctx_out->local_bindings, -- iface_id); -- if (is_lbinding_this_chassis(lbinding, b_ctx_in->chassis_rec)) { -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ -+ lbinding = local_binding_find(local_bindings, iface_id); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) { - struct local_datapath *ld = - get_local_datapath(b_ctx_out->local_datapaths, -- lbinding->pb->datapath->tunnel_key); -+ b_lport->pb->datapath->tunnel_key); - if (ld) { -- remove_pb_from_local_datapath(lbinding->pb, -- b_ctx_in->chassis_rec, -- b_ctx_out, ld); -+ remove_pb_from_local_datapath(b_lport->pb, -+ b_ctx_in->chassis_rec, -+ b_ctx_out, ld); - } - -- /* Note: release_local_binding() resets lbinding->pb and -- * lbinding->iface. -- * Cannot access these members of lbinding after this call. */ -- if (!release_local_binding(b_ctx_in->chassis_rec, lbinding, -- !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings)) { -- return false; -+ /* Release the primary binding lport and other children lports if -+ * any. */ -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out)) { -+ return false; -+ } - } -+ -+ } -+ -+ if (lbinding) { -+ /* Clear the iface of the local binding. */ -+ lbinding->iface = NULL; - } - - /* Check if the lbinding has children of type PB_CONTAINER. - * If so, don't delete the local_binding. */ - if (lbinding && !is_lbinding_container_parent(lbinding)) { -- local_binding_delete(b_ctx_out->local_bindings, lbinding); -+ local_binding_delete(lbinding, local_bindings, binding_lports, -+ b_ctx_out->if_mgr); - } - - remove_local_lports(iface_id, b_ctx_out); -@@ -2088,56 +2248,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb, - } - } - --static struct local_binding * --get_lbinding_for_lport(const struct sbrec_port_binding *pb, -- enum en_lport_type lport_type, -- struct binding_ctx_out *b_ctx_out) --{ -- ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL); -- -- if (lport_type == LP_VIF && !is_lport_container(pb)) { -- return local_binding_find(b_ctx_out->local_bindings, pb->logical_port); -- } -- -- struct local_binding *parent_lbinding = NULL; -- -- if (lport_type == LP_VIRTUAL) { -- if (pb->virtual_parent) { -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->virtual_parent); -- } -- } else { -- if (pb->parent_port) { -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->parent_port); -- } -- } -- -- return parent_lbinding -- ? local_binding_find(&parent_lbinding->children, pb->logical_port) -- : NULL; --} -- - static bool - handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - enum en_lport_type lport_type, - struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out) - { -- struct local_binding *lbinding = -- get_lbinding_for_lport(pb, lport_type, b_ctx_out); -+ struct local_binding *lbinding = NULL; -+ bool bound = false; - -- if (lbinding) { -- lbinding->pb = NULL; -- /* The port_binding 'pb' is deleted. So there is no need to -- * clear the 'chassis' column of 'pb'. But we need to do -- * for the local_binding's children. */ -- if (lbinding->type == BT_VIF && -- !release_local_binding_children( -- b_ctx_in->chassis_rec, lbinding, -- !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings)) { -- return false; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct binding_lport *b_lport = binding_lport_find(binding_lports, pb->logical_port); -+ if (b_lport) { -+ lbinding = b_lport->lbinding; -+ bound = is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec); -+ -+ /* Remove b_lport from local_binding. */ -+ binding_lport_delete(binding_lports, b_lport); -+ } -+ -+ if (bound && lbinding && lport_type == LP_VIF) { -+ /* We need to release the container/virtual binding lports (if any) if -+ * deleted 'pb' type is LP_VIF. */ -+ struct binding_lport *c_lport; -+ LIST_FOR_EACH (c_lport, list_node, &lbinding->binding_lports) { -+ if (!release_binding_lport(b_ctx_in->chassis_rec, c_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out)) { -+ return false; -+ } - } - } - -@@ -2147,18 +2286,8 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - * it from local_lports if there is a VIF entry. - * consider_iface_release() takes care of removing from the local_lports - * when the interface change happens. */ -- if (is_lport_container(pb)) { -+ if (lport_type == LP_CONTAINER) { - remove_local_lports(pb->logical_port, b_ctx_out); -- -- /* If the container port is removed we should also remove it from -- * its parent's children set. -- */ -- if (lbinding) { -- if (lbinding->parent) { -- local_binding_delete_child(lbinding->parent, lbinding); -- } -- local_binding_destroy(lbinding); -- } - } - - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -@@ -2177,7 +2306,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - - if (lport_type == LP_VIRTUAL) { - handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); -- } else if (lport_type == LP_VIF && is_lport_container(pb)) { -+ } else if (lport_type == LP_CONTAINER) { - handled = consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map); - } else { - handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map); -@@ -2189,14 +2318,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - - bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec); - -- if (lport_type == LP_VIRTUAL || -- (lport_type == LP_VIF && is_lport_container(pb)) || -+ if (lport_type == LP_VIRTUAL || lport_type == LP_CONTAINER || - claimed == now_claimed) { - return true; - } - -- struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, pb->logical_port); -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct local_binding *lbinding = local_binding_find(local_bindings, -+ pb->logical_port); - - /* If the ovs port backing this binding previously was removed in the - * meantime, we won't have a local_binding for it. -@@ -2206,12 +2335,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - return true; - } - -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *child = node->data; -- if (child->type == BT_CONTAINER) { -- handled = consider_container_lport(child->pb, b_ctx_in, b_ctx_out, -- qos_map); -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { -+ handled = consider_container_lport(b_lport->pb, b_ctx_in, -+ b_ctx_out, qos_map); - if (!handled) { - return false; - } -@@ -2256,12 +2384,25 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - - enum en_lport_type lport_type = get_lport_type(pb); - -- if (lport_type == LP_VIF) { -- if (is_lport_container(pb)) { -- shash_add(&deleted_container_pbs, pb->logical_port, pb); -- } else { -- shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ struct binding_lport *b_lport = -+ binding_lport_find(&b_ctx_out->lbinding_data->lports, -+ pb->logical_port); -+ if (b_lport) { -+ /* If the 'b_lport->type' and 'lport_type' don't match, then update -+ * the b_lport->type to the updated 'lport_type'. The function -+ * binding_lport_check_and_cleanup() will cleanup the 'b_lport' -+ * if required. */ -+ if (b_lport->type != lport_type) { -+ b_lport->type = lport_type; - } -+ b_lport = binding_lport_check_and_cleanup( -+ b_lport, &b_ctx_out->lbinding_data->lports); -+ } -+ -+ if (lport_type == LP_VIF) { -+ shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ } else if (lport_type == LP_CONTAINER) { -+ shash_add(&deleted_container_pbs, pb->logical_port, pb); - } else if (lport_type == LP_VIRTUAL) { - shash_add(&deleted_virtual_pbs, pb->logical_port, pb); - } else { -@@ -2272,7 +2413,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - struct shash_node *node; - struct shash_node *node_next; - SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) { -- handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ handled = handle_deleted_vif_lport(node->data, LP_CONTAINER, b_ctx_in, - b_ctx_out); - shash_delete(&deleted_container_pbs, node); - if (!handled) { -@@ -2326,12 +2467,33 @@ delete_done: - - enum en_lport_type lport_type = get_lport_type(pb); - -+ struct binding_lport *b_lport = -+ binding_lport_find(&b_ctx_out->lbinding_data->lports, -+ pb->logical_port); -+ if (b_lport) { -+ ovs_assert(b_lport->pb == pb); -+ -+ if (b_lport->type != lport_type) { -+ b_lport->type = lport_type; -+ } -+ -+ if (b_lport->lbinding) { -+ handled = local_binding_handle_stale_binding_lports( -+ b_lport->lbinding, b_ctx_in, b_ctx_out, qos_map_ptr); -+ if (!handled) { -+ /* Backout from the handling. */ -+ break; -+ } -+ } -+ } -+ - struct local_datapath *ld = - get_local_datapath(b_ctx_out->local_datapaths, - pb->datapath->tunnel_key); - - switch (lport_type) { - case LP_VIF: -+ case LP_CONTAINER: - case LP_VIRTUAL: - handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in, - b_ctx_out, qos_map_ptr); -@@ -2440,154 +2602,327 @@ delete_done: - return handled; - } - --/* Registered ofctrl seqno type for port_binding flow installation. */ --static size_t binding_seq_type_pb_cfg; -+/* Static functions for local_lbindind and binding_lport. */ -+static struct local_binding * -+local_binding_create(const char *name, const struct ovsrec_interface *iface) -+{ -+ struct local_binding *lbinding = xzalloc(sizeof *lbinding); -+ lbinding->name = xstrdup(name); -+ lbinding->iface = iface; -+ ovs_list_init(&lbinding->binding_lports); -+ -+ return lbinding; -+} - --/* Binding specific seqno to be acked by ofctrl when flows for new interfaces -- * have been installed. -- */ --static uint32_t binding_iface_seqno = 0; -+static struct local_binding * -+local_binding_find(struct shash *local_bindings, const char *name) -+{ -+ return shash_find_data(local_bindings, name); -+} - --/* Map indexed by iface-id containing the sequence numbers that when acked -- * indicate that the OVS flows for the iface-id have been installed. -- */ --static struct simap binding_iface_seqno_map = -- SIMAP_INITIALIZER(&binding_iface_seqno_map); -+static void -+local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) -+{ -+ shash_add(local_bindings, lbinding->name, lbinding); -+} - --void --binding_init(void) -+static void -+local_binding_destroy(struct local_binding *lbinding, -+ struct shash *binding_lports) - { -- binding_seq_type_pb_cfg = ofctrl_seqno_add_type(); -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH_POP (b_lport, list_node, &lbinding->binding_lports) { -+ b_lport->lbinding = NULL; -+ binding_lport_delete(binding_lports, b_lport); -+ } -+ -+ free(lbinding->name); -+ free(lbinding); - } - --/* Processes new release/bind operations OVN ports. For newly bound ports -- * it creates ofctrl seqno update requests that will be acked when -- * corresponding OVS flows have been installed. -- * -- * NOTE: Should be called only when valid SB and OVS transactions are -- * available. -+static void -+local_binding_delete(struct local_binding *lbinding, -+ struct shash *local_bindings, -+ struct shash *binding_lports, -+ struct if_status_mgr *if_mgr) -+{ -+ shash_find_and_delete(local_bindings, lbinding->name); -+ if_status_mgr_delete_iface(if_mgr, lbinding->name); -+ local_binding_destroy(lbinding, binding_lports); -+} -+ -+/* Returns the primary binding lport if present in lbinding's -+ * binding lports list. A binding lport is considered primary -+ * if binding lport's type is LP_VIF and the name matches -+ * with the 'lbinding'. - */ --void --binding_seqno_run(struct shash *local_bindings) -+static struct binding_lport * -+local_binding_get_primary_lport(struct local_binding *lbinding) - { -- const char *iface_id; -- const char *iface_id_next; -+ if (!lbinding) { -+ return NULL; -+ } - -- SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) { -- struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ if (!ovs_list_is_empty(&lbinding->binding_lports)) { -+ struct binding_lport *b_lport = NULL; -+ b_lport = CONTAINER_OF(ovs_list_front(&lbinding->binding_lports), -+ struct binding_lport, list_node); - -- /* If the local binding still exists (i.e., the OVS interface is -- * still configured locally) then remove the external id and remove -- * it from the in-flight seqno map. -- */ -- if (lb_node) { -- struct local_binding *lb = lb_node->data; -+ if (b_lport->type == LP_VIF && -+ !strcmp(lbinding->name, b_lport->name)) { -+ return b_lport; -+ } -+ } - -- if (lb->iface && smap_get(&lb->iface->external_ids, -- OVN_INSTALLED_EXT_ID)) { -- ovsrec_interface_update_external_ids_delkey( -- lb->iface, OVN_INSTALLED_EXT_ID); -- } -+ return NULL; -+} -+ -+static struct binding_lport * -+local_binding_add_lport(struct shash *binding_lports, -+ struct local_binding *lbinding, -+ const struct sbrec_port_binding *pb, -+ enum en_lport_type b_type) -+{ -+ struct binding_lport *b_lport = -+ binding_lport_find(binding_lports, pb->logical_port); -+ bool add_to_lport_list = false; -+ if (!b_lport) { -+ b_lport = binding_lport_create(pb, lbinding, b_type); -+ binding_lport_add(binding_lports, b_lport); -+ add_to_lport_list = true; -+ } else if (b_lport->lbinding != lbinding) { -+ add_to_lport_list = true; -+ if (!ovs_list_is_empty(&b_lport->list_node)) { -+ ovs_list_remove(&b_lport->list_node); - } -- simap_find_and_delete(&binding_iface_seqno_map, iface_id); -- sset_delete(&binding_iface_released_set, -- SSET_NODE_FROM_NAME(iface_id)); -+ b_lport->lbinding = lbinding; -+ b_lport->type = b_type; - } - -- bool new_ifaces = false; -- uint32_t new_seqno = binding_iface_seqno + 1; -+ if (add_to_lport_list) { -+ if (b_type == LP_VIF) { -+ ovs_list_push_front(&lbinding->binding_lports, &b_lport->list_node); -+ } else { -+ ovs_list_push_back(&lbinding->binding_lports, &b_lport->list_node); -+ } -+ } - -- SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_bound_set) { -- struct shash_node *lb_node = shash_find(local_bindings, iface_id); -+ return b_lport; -+} - -- struct local_binding *lb = lb_node ? lb_node->data : NULL; -+/* This function handles the stale binding lports of 'lbinding' if 'lbinding' -+ * doesn't have a primary binding lport. -+ */ -+static bool -+local_binding_handle_stale_binding_lports(struct local_binding *lbinding, -+ struct binding_ctx_in *b_ctx_in, -+ struct binding_ctx_out *b_ctx_out, -+ struct hmap *qos_map) -+{ -+ /* Check if this lbinding has a primary binding_lport or not. */ -+ struct binding_lport *p_lport = local_binding_get_primary_lport(lbinding); -+ if (p_lport) { -+ /* Nothing to be done. */ -+ return true; -+ } - -- /* Make sure the binding is still complete, i.e., both SB port_binding -- * and OVS interface still exist. -- * -- * If so, then this is a newly bound interface, make sure we reset the -- * Port_Binding 'up' field and the OVS Interface 'external-id'. -- */ -- if (lb && lb->pb && lb->iface) { -- new_ifaces = true; -- -- if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) { -- ovsrec_interface_update_external_ids_delkey( -- lb->iface, OVN_INSTALLED_EXT_ID); -- } -- if (lb->pb->n_up) { -- bool up = false; -- sbrec_port_binding_set_up(lb->pb, &up, 1); -- } -- simap_put(&binding_iface_seqno_map, lb->name, new_seqno); -+ bool handled = true; -+ struct binding_lport *b_lport, *next; -+ const struct sbrec_port_binding *pb; -+ LIST_FOR_EACH_SAFE (b_lport, next, list_node, &lbinding->binding_lports) { -+ /* Get the lport type again from the pb. Its possible that the -+ * pb type has changed. */ -+ enum en_lport_type pb_lport_type = get_lport_type(b_lport->pb); -+ if (b_lport->type == LP_VIRTUAL && pb_lport_type == LP_VIRTUAL) { -+ pb = b_lport->pb; -+ binding_lport_delete(&b_ctx_out->lbinding_data->lports, -+ b_lport); -+ handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); -+ } else if (b_lport->type == LP_CONTAINER && -+ pb_lport_type == LP_CONTAINER) { -+ /* For container lport, binding_lport is preserved so that when -+ * the parent port is created, it can be considered. -+ * consider_container_lport() creates the binding_lport for the parent -+ * port (with iface set to NULL). */ -+ handled = consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, qos_map); -+ } else { -+ /* This can happen when the lport type changes from one type -+ * to another. Eg. from normal lport to external. Release the -+ * lport if it was claimed earlier and delete the b_lport. */ -+ handled = release_binding_lport(b_ctx_in->chassis_rec, b_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out); -+ binding_lport_delete(&b_ctx_out->lbinding_data->lports, -+ b_lport); -+ } -+ -+ if (!handled) { -+ return false; - } -- sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id)); - } - -- /* Request a seqno update when the flows for new interfaces have been -- * installed in OVS. -- */ -- if (new_ifaces) { -- binding_iface_seqno = new_seqno; -- ofctrl_seqno_update_create(binding_seq_type_pb_cfg, new_seqno); -+ return handled; -+} -+ -+static struct binding_lport * -+binding_lport_create(const struct sbrec_port_binding *pb, -+ struct local_binding *lbinding, -+ enum en_lport_type type) -+{ -+ struct binding_lport *b_lport = xzalloc(sizeof *b_lport); -+ b_lport->name = xstrdup(pb->logical_port); -+ b_lport->pb = pb; -+ b_lport->type = type; -+ b_lport->lbinding = lbinding; -+ ovs_list_init(&b_lport->list_node); -+ -+ return b_lport; -+} -+ -+static void -+binding_lport_add(struct shash *binding_lports, struct binding_lport *b_lport) -+{ -+ shash_add(binding_lports, b_lport->pb->logical_port, b_lport); -+} -+ -+static struct binding_lport * -+binding_lport_find(struct shash *binding_lports, const char *lport_name) -+{ -+ if (!lport_name) { -+ return NULL; - } -+ -+ return shash_find_data(binding_lports, lport_name); - } - --/* Processes ofctrl seqno ACKs for new bindings. Sets the -- * 'OVN_INSTALLED_EXT_ID' external-id in the OVS interface and the -- * Port_Binding.up field for all ports for which OVS flows have been -- * installed. -+static void -+binding_lport_destroy(struct binding_lport *b_lport) -+{ -+ if (!ovs_list_is_empty(&b_lport->list_node)) { -+ ovs_list_remove(&b_lport->list_node); -+ } -+ -+ free(b_lport->name); -+ free(b_lport); -+} -+ -+static void -+binding_lport_delete(struct shash *binding_lports, -+ struct binding_lport *b_lport) -+{ -+ shash_find_and_delete(binding_lports, b_lport->name); -+ binding_lport_destroy(b_lport); -+} -+ -+static void -+binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly) -+{ -+ if (sb_readonly || !b_lport || !b_lport->pb->n_up || b_lport->pb->up[0]) { -+ return; -+ } -+ -+ bool up = true; -+ sbrec_port_binding_set_up(b_lport->pb, &up, 1); -+} -+ -+static void -+binding_lport_set_down(struct binding_lport *b_lport, bool sb_readonly) -+{ -+ if (sb_readonly || !b_lport || !b_lport->pb->n_up || !b_lport->pb->up[0]) { -+ return; -+ } -+ -+ bool up = false; -+ sbrec_port_binding_set_up(b_lport->pb, &up, 1); -+} -+ -+static const struct sbrec_port_binding * -+binding_lport_get_parent_pb(struct binding_lport *b_lport) -+{ -+ if (!b_lport) { -+ return NULL; -+ } -+ -+ if (b_lport->type == LP_VIF) { -+ return NULL; -+ } -+ -+ struct local_binding *lbinding = b_lport->lbinding; -+ ovs_assert(lbinding); -+ -+ struct binding_lport *parent_b_lport = -+ local_binding_get_primary_lport(lbinding); -+ -+ return parent_b_lport ? parent_b_lport->pb : NULL; -+} -+ -+/* This function checks and cleans up the 'b_lport' if it is -+ * not in the correct state. -+ * -+ * If the 'b_lport' type is LP_VIF, then its name and its lbinding->name -+ * should match. Otherwise this should be cleaned up. - * -- * NOTE: Should be called only when valid SB and OVS transactions are -- * available. -+ * If the 'b_lport' type is LP_CONTAINER, then its parent_port name should -+ * be the same as its lbinding's name. Otherwise this should be -+ * cleaned up. -+ * -+ * If the 'b_lport' type is LP_VIRTUAL, then its virtual parent name -+ * should be the same as its lbinding's name. Otherwise this -+ * should be cleaned up. -+ * -+ * If the 'b_lport' type is not LP_VIF, LP_CONTAINER or LP_VIRTUAL, it -+ * should be cleaned up. This can happen if the CMS changes -+ * the port binding type. - */ --void --binding_seqno_install(struct shash *local_bindings) -+static struct binding_lport * -+binding_lport_check_and_cleanup(struct binding_lport *b_lport, -+ struct shash *binding_lports) - { -- struct ofctrl_acked_seqnos *acked_seqnos = -- ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg); -- struct simap_node *node; -- struct simap_node *node_next; -+ bool cleanup_blport = false; - -- SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { -- struct shash_node *lb_node = shash_find(local_bindings, node->name); -- -- if (!lb_node) { -- goto del_seqno; -- } -+ if (!b_lport->lbinding) { -+ cleanup_blport = true; -+ goto cleanup; -+ } - -- struct local_binding *lb = lb_node->data; -- if (!lb->pb || !lb->iface) { -- goto del_seqno; -+ switch (b_lport->type) { -+ case LP_VIF: -+ if (strcmp(b_lport->name, b_lport->lbinding->name)) { -+ cleanup_blport = true; - } -+ break; - -- if (!ofctrl_acked_seqnos_contains(acked_seqnos, node->data)) { -- continue; -+ case LP_CONTAINER: -+ if (strcmp(b_lport->pb->parent_port, b_lport->lbinding->name)) { -+ cleanup_blport = true; - } -+ break; - -- ovsrec_interface_update_external_ids_setkey(lb->iface, -- OVN_INSTALLED_EXT_ID, -- "true"); -- if (lb->pb->n_up) { -- bool up = true; -- -- sbrec_port_binding_set_up(lb->pb, &up, 1); -- struct shash_node *child_node; -- SHASH_FOR_EACH (child_node, &lb->children) { -- struct local_binding *lb_child = child_node->data; -- sbrec_port_binding_set_up(lb_child->pb, &up, 1); -- } -+ case LP_VIRTUAL: -+ if (!b_lport->pb->virtual_parent || -+ strcmp(b_lport->pb->virtual_parent, b_lport->lbinding->name)) { -+ cleanup_blport = true; - } -+ break; - --del_seqno: -- simap_delete(&binding_iface_seqno_map, node); -+ case LP_PATCH: -+ case LP_LOCALPORT: -+ case LP_VTEP: -+ case LP_L2GATEWAY: -+ case LP_L3GATEWAY: -+ case LP_CHASSISREDIRECT: -+ case LP_EXTERNAL: -+ case LP_LOCALNET: -+ case LP_REMOTE: -+ case LP_UNKNOWN: -+ cleanup_blport = true; - } - -- ofctrl_acked_seqnos_destroy(acked_seqnos); --} -+cleanup: -+ if (cleanup_blport) { -+ binding_lport_delete(binding_lports, b_lport); -+ return NULL; -+ } - --void --binding_seqno_flush(void) --{ -- simap_clear(&binding_iface_seqno_map); -+ return b_lport; - } -diff --git a/controller/binding.h b/controller/binding.h -index c9ebef4b1..7a6495320 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -36,6 +36,8 @@ struct sbrec_chassis; - struct sbrec_port_binding_table; - struct sset; - struct sbrec_port_binding; -+struct ds; -+struct if_status_mgr; - - struct binding_ctx_in { - struct ovsdb_idl_txn *ovnsb_idl_txn; -@@ -56,7 +58,7 @@ struct binding_ctx_in { - - struct binding_ctx_out { - struct hmap *local_datapaths; -- struct shash *local_bindings; -+ struct local_binding_data *lbinding_data; - - /* sset of (potential) local lports. */ - struct sset *local_lports; -@@ -84,30 +86,26 @@ struct binding_ctx_out { - * binding_handle_port_binding_changes) fills in for - * the changed datapaths and port bindings. */ - struct hmap *tracked_dp_bindings; --}; - --enum local_binding_type { -- BT_VIF, -- BT_CONTAINER, -- BT_VIRTUAL -+ struct if_status_mgr *if_mgr; - }; - --struct local_binding { -- char *name; -- enum local_binding_type type; -- const struct ovsrec_interface *iface; -- const struct sbrec_port_binding *pb; -- -- /* shash of 'struct local_binding' representing children. */ -- struct shash children; -- struct local_binding *parent; -+struct local_binding_data { -+ struct shash bindings; -+ struct shash lports; - }; - --static inline struct local_binding * --local_binding_find(struct shash *local_bindings, const char *name) --{ -- return shash_find_data(local_bindings, name); --} -+void local_binding_data_init(struct local_binding_data *); -+void local_binding_data_destroy(struct local_binding_data *); -+ -+const struct sbrec_port_binding *local_binding_get_primary_pb( -+ struct shash *local_bindings, const char *pb_name); -+bool local_binding_is_up(struct shash *local_bindings, const char *pb_name); -+bool local_binding_is_down(struct shash *local_bindings, const char *pb_name); -+void local_binding_set_up(struct shash *local_bindings, const char *pb_name, -+ bool sb_readonly, bool ovs_readonly); -+void local_binding_set_down(struct shash *local_bindings, const char *pb_name, -+ bool sb_readonly, bool ovs_readonly); - - /* Represents a tracked binding logical port. */ - struct tracked_binding_lport { -@@ -128,16 +126,11 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_port_binding_table *, - const struct sbrec_chassis *); - --void local_bindings_init(struct shash *local_bindings); --void local_bindings_destroy(struct shash *local_bindings); - bool binding_handle_ovs_interface_changes(struct binding_ctx_in *, - struct binding_ctx_out *); - bool binding_handle_port_binding_changes(struct binding_ctx_in *, - struct binding_ctx_out *); - void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); - --void binding_init(void); --void binding_seqno_run(struct shash *local_bindings); --void binding_seqno_install(struct shash *local_bindings); --void binding_seqno_flush(void); -+void binding_dump_local_bindings(struct local_binding_data *, struct ds *); - #endif /* controller/binding.h */ -diff --git a/controller/if-status.c b/controller/if-status.c -new file mode 100644 -index 000000000..8d8c8d436 ---- /dev/null -+++ b/controller/if-status.c -@@ -0,0 +1,415 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "binding.h" -+#include "if-status.h" -+#include "ofctrl-seqno.h" -+ -+#include "lib/hmapx.h" -+#include "lib/util.h" -+#include "openvswitch/vlog.h" -+ -+VLOG_DEFINE_THIS_MODULE(if_status); -+ -+/* This module implements an interface manager that maintains the state of -+ * the interfaces wrt. their flows being completely installed in OVS and -+ * their corresponding bindings being marked up/down. -+ * -+ * A state machine is maintained for each interface. -+ * -+ * Transitions are triggered between states by three types of events: -+ * A. Events received from the binding module: -+ * - interface is claimed: if_status_mgr_claim_iface() -+ * - interface is released: if_status_mgr_release_iface() -+ * - interface is deleted: if_status_mgr_delete_iface() -+ * -+ * B. At every iteration, based on SB/OVS updates, handled in -+ * if_status_mgr_update(): -+ * - an interface binding has been marked "up" both in the Southbound and OVS -+ * databases. -+ * - an interface binding has been marked "down" both in the Southbound and OVS -+ * databases. -+ * - new interface has been claimed. -+ * -+ * C. At every iteration, based on ofctrl_seqno updates, handled in -+ * if_status_mgr_run(): -+ * - the flows for a previously claimed interface have been installed in OVS. -+ */ -+ -+enum if_state { -+ OIF_CLAIMED, /* Newly claimed interface. */ -+ OIF_INSTALL_FLOWS, /* Already claimed interface for which flows are still -+ * being installed. -+ */ -+ OIF_MARK_UP, /* Interface with flows successfully installed in OVS -+ * but not yet marked "up" in the binding module (in -+ * SB and OVS databases). -+ */ -+ OIF_MARK_DOWN, /* Released interface but not yet marked "down" in the -+ * binding module (in SB and/or OVS databases). -+ */ -+ OIF_INSTALLED, /* Interface flows programmed in OVS and binding marked -+ * "up" in the binding module. -+ */ -+ OIF_MAX, -+}; -+ -+static const char *if_state_names[] = { -+ [OIF_CLAIMED] = "CLAIMED", -+ [OIF_INSTALL_FLOWS] = "INSTALL_FLOWS", -+ [OIF_MARK_UP] = "MARK_UP", -+ [OIF_MARK_DOWN] = "MARK_DOWN", -+ [OIF_INSTALLED] = "INSTALLED", -+}; -+ -+struct ovs_iface { -+ char *id; /* Extracted from OVS external_ids.iface_id. */ -+ enum if_state state; /* State of the interface in the state machine. */ -+ uint32_t install_seqno; /* Seqno at which this interface is expected to -+ * be fully programmed in OVS. Only used in state -+ * OIF_INSTALL_FLOWS. -+ */ -+}; -+ -+/* State machine manager for all local OVS interfaces. */ -+struct if_status_mgr { -+ /* All local interfaces, mapping from 'iface-id' to 'struct ovs_iface'. */ -+ struct shash ifaces; -+ -+ /* All local interfaces, stored per state. */ -+ struct hmapx ifaces_per_state[OIF_MAX]; -+ -+ /* Registered ofctrl seqno type for port_binding flow installation. */ -+ size_t iface_seq_type_pb_cfg; -+ -+ /* Interface specific seqno to be acked by ofctrl when flows for new -+ * interfaces have been installed. -+ */ -+ uint32_t iface_seqno; -+}; -+ -+static struct ovs_iface *ovs_iface_create(struct if_status_mgr *, -+ const char *iface_id, -+ enum if_state ); -+static void ovs_iface_destroy(struct if_status_mgr *, struct ovs_iface *); -+static void ovs_iface_set_state(struct if_status_mgr *, struct ovs_iface *, -+ enum if_state); -+ -+static void if_status_mgr_update_bindings( -+ struct if_status_mgr *mgr, struct local_binding_data *binding_data, -+ bool sb_readonly, bool ovs_readonly); -+ -+struct if_status_mgr * -+if_status_mgr_create(void) -+{ -+ struct if_status_mgr *mgr = xzalloc(sizeof *mgr); -+ -+ mgr->iface_seq_type_pb_cfg = ofctrl_seqno_add_type(); -+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) { -+ hmapx_init(&mgr->ifaces_per_state[i]); -+ } -+ shash_init(&mgr->ifaces); -+ return mgr; -+} -+ -+void -+if_status_mgr_clear(struct if_status_mgr *mgr) -+{ -+ struct shash_node *node_next; -+ struct shash_node *node; -+ -+ SHASH_FOR_EACH_SAFE (node, node_next, &mgr->ifaces) { -+ ovs_iface_destroy(mgr, node->data); -+ } -+ ovs_assert(shash_is_empty(&mgr->ifaces)); -+ -+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) { -+ ovs_assert(hmapx_is_empty(&mgr->ifaces_per_state[i])); -+ } -+} -+ -+void -+if_status_mgr_destroy(struct if_status_mgr *mgr) -+{ -+ if_status_mgr_clear(mgr); -+ shash_destroy(&mgr->ifaces); -+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) { -+ hmapx_destroy(&mgr->ifaces_per_state[i]); -+ } -+ free(mgr); -+} -+ -+void -+if_status_mgr_claim_iface(struct if_status_mgr *mgr, const char *iface_id) -+{ -+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id); -+ -+ if (!iface) { -+ iface = ovs_iface_create(mgr, iface_id, OIF_CLAIMED); -+ } -+ -+ switch (iface->state) { -+ case OIF_CLAIMED: -+ case OIF_INSTALL_FLOWS: -+ case OIF_MARK_UP: -+ /* Nothing to do here. */ -+ break; -+ case OIF_INSTALLED: -+ case OIF_MARK_DOWN: -+ ovs_iface_set_state(mgr, iface, OIF_CLAIMED); -+ break; -+ case OIF_MAX: -+ OVS_NOT_REACHED(); -+ break; -+ } -+} -+ -+void -+if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id) -+{ -+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id); -+ -+ if (!iface) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "Trying to release unknown interface %s", iface_id); -+ return; -+ } -+ -+ switch (iface->state) { -+ case OIF_CLAIMED: -+ case OIF_INSTALL_FLOWS: -+ /* Not yet fully installed interfaces can be safely deleted. */ -+ ovs_iface_destroy(mgr, iface); -+ break; -+ case OIF_MARK_UP: -+ case OIF_INSTALLED: -+ /* Properly mark interfaces "down" if their flows were already -+ * programmed in OVS. -+ */ -+ ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN); -+ break; -+ case OIF_MARK_DOWN: -+ /* Nothing to do here. */ -+ break; -+ case OIF_MAX: -+ OVS_NOT_REACHED(); -+ break; -+ } -+} -+ -+void -+if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id) -+{ -+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id); -+ -+ if (!iface) { -+ return; -+ } -+ -+ switch (iface->state) { -+ case OIF_CLAIMED: -+ case OIF_INSTALL_FLOWS: -+ /* Not yet fully installed interfaces can be safely deleted. */ -+ ovs_iface_destroy(mgr, iface); -+ break; -+ case OIF_MARK_UP: -+ case OIF_INSTALLED: -+ /* Properly mark interfaces "down" if their flows were already -+ * programmed in OVS. -+ */ -+ ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN); -+ break; -+ case OIF_MARK_DOWN: -+ /* Nothing to do here. */ -+ break; -+ case OIF_MAX: -+ OVS_NOT_REACHED(); -+ break; -+ } -+} -+ -+void -+if_status_mgr_update(struct if_status_mgr *mgr, -+ struct local_binding_data *binding_data) -+{ -+ if (!binding_data) { -+ return; -+ } -+ -+ struct shash *bindings = &binding_data->bindings; -+ struct hmapx_node *node_next; -+ struct hmapx_node *node; -+ -+ /* Move all interfaces that have been confirmed "up" by the binding module, -+ * from OIF_MARK_UP to OIF_INSTALLED. -+ */ -+ HMAPX_FOR_EACH_SAFE (node, node_next, -+ &mgr->ifaces_per_state[OIF_MARK_UP]) { -+ struct ovs_iface *iface = node->data; -+ -+ if (local_binding_is_up(bindings, iface->id)) { -+ ovs_iface_set_state(mgr, iface, OIF_INSTALLED); -+ } -+ } -+ -+ /* Cleanup all interfaces that have been confirmed "down" by the binding -+ * module. -+ */ -+ HMAPX_FOR_EACH_SAFE (node, node_next, -+ &mgr->ifaces_per_state[OIF_MARK_DOWN]) { -+ struct ovs_iface *iface = node->data; -+ -+ if (local_binding_is_down(bindings, iface->id)) { -+ ovs_iface_destroy(mgr, iface); -+ } -+ } -+ -+ /* Register for a notification about flows being installed in OVS for all -+ * newly claimed interfaces. -+ * -+ * Move them from OIF_CLAIMED to OIF_INSTALL_FLOWS. -+ */ -+ bool new_ifaces = false; -+ HMAPX_FOR_EACH_SAFE (node, node_next, -+ &mgr->ifaces_per_state[OIF_CLAIMED]) { -+ struct ovs_iface *iface = node->data; -+ -+ ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS); -+ iface->install_seqno = mgr->iface_seqno + 1; -+ new_ifaces = true; -+ } -+ -+ /* Request a seqno update when the flows for new interfaces have been -+ * installed in OVS. -+ */ -+ if (new_ifaces) { -+ mgr->iface_seqno++; -+ ofctrl_seqno_update_create(mgr->iface_seq_type_pb_cfg, -+ mgr->iface_seqno); -+ VLOG_DBG("Seqno requested: %"PRIu32, mgr->iface_seqno); -+ } -+} -+ -+void -+if_status_mgr_run(struct if_status_mgr *mgr, -+ struct local_binding_data *binding_data, -+ bool sb_readonly, bool ovs_readonly) -+{ -+ struct ofctrl_acked_seqnos *acked_seqnos = -+ ofctrl_acked_seqnos_get(mgr->iface_seq_type_pb_cfg); -+ struct hmapx_node *node_next; -+ struct hmapx_node *node; -+ -+ /* Move interfaces from state OIF_INSTALL_FLOWS to OIF_MARK_UP if a -+ * notification has been received aabout their flows being installed -+ * in OVS. -+ */ -+ HMAPX_FOR_EACH_SAFE (node, node_next, -+ &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) { -+ struct ovs_iface *iface = node->data; -+ -+ if (!ofctrl_acked_seqnos_contains(acked_seqnos, -+ iface->install_seqno)) { -+ continue; -+ } -+ ovs_iface_set_state(mgr, iface, OIF_MARK_UP); -+ } -+ ofctrl_acked_seqnos_destroy(acked_seqnos); -+ -+ /* Update binding states. */ -+ if_status_mgr_update_bindings(mgr, binding_data, sb_readonly, -+ ovs_readonly); -+} -+ -+static struct ovs_iface * -+ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id, -+ enum if_state state) -+{ -+ struct ovs_iface *iface = xzalloc(sizeof *iface); -+ -+ VLOG_DBG("Interface %s create.", iface->id); -+ iface->id = xstrdup(iface_id); -+ shash_add(&mgr->ifaces, iface_id, iface); -+ ovs_iface_set_state(mgr, iface, state); -+ return iface; -+} -+ -+static void -+ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface) -+{ -+ VLOG_DBG("Interface %s destroy: state %s", iface->id, -+ if_state_names[iface->state]); -+ hmapx_find_and_delete(&mgr->ifaces_per_state[iface->state], iface); -+ shash_find_and_delete(&mgr->ifaces, iface->id); -+ free(iface->id); -+ free(iface); -+} -+ -+static void -+ovs_iface_set_state(struct if_status_mgr *mgr, struct ovs_iface *iface, -+ enum if_state state) -+{ -+ VLOG_DBG("Interface %s set state: old %s, new %s", iface->id, -+ if_state_names[iface->state], -+ if_state_names[state]); -+ -+ hmapx_find_and_delete(&mgr->ifaces_per_state[iface->state], iface); -+ iface->state = state; -+ hmapx_add(&mgr->ifaces_per_state[iface->state], iface); -+ iface->install_seqno = 0; -+} -+ -+static void -+if_status_mgr_update_bindings(struct if_status_mgr *mgr, -+ struct local_binding_data *binding_data, -+ bool sb_readonly, bool ovs_readonly) -+{ -+ if (!binding_data) { -+ return; -+ } -+ -+ struct shash *bindings = &binding_data->bindings; -+ struct hmapx_node *node; -+ -+ /* Notify the binding module to set "down" all bindings that are still -+ * in the process of being installed in OVS, i.e., are not yet instsalled. -+ */ -+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) { -+ struct ovs_iface *iface = node->data; -+ -+ local_binding_set_down(bindings, iface->id, sb_readonly, ovs_readonly); -+ } -+ -+ /* Notifiy the binding module to set "up" all bindings that have had -+ * their flows installed but are not yet marked "up" in the binding -+ * module. -+ */ -+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_UP]) { -+ struct ovs_iface *iface = node->data; -+ -+ local_binding_set_up(bindings, iface->id, sb_readonly, ovs_readonly); -+ } -+ -+ /* Notify the binding module to set "down" all bindings that have been -+ * released but are not yet marked as "down" in the binding module. -+ */ -+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_DOWN]) { -+ struct ovs_iface *iface = node->data; -+ -+ local_binding_set_down(bindings, iface->id, sb_readonly, ovs_readonly); -+ } -+} -diff --git a/controller/if-status.h b/controller/if-status.h -new file mode 100644 -index 000000000..51fe7c684 ---- /dev/null -+++ b/controller/if-status.h -@@ -0,0 +1,37 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#ifndef IF_STATUS_H -+#define IF_STATUS_H 1 -+ -+#include "openvswitch/shash.h" -+ -+#include "binding.h" -+ -+struct if_status_mgr; -+ -+struct if_status_mgr *if_status_mgr_create(void); -+void if_status_mgr_clear(struct if_status_mgr *); -+void if_status_mgr_destroy(struct if_status_mgr *); -+ -+void if_status_mgr_claim_iface(struct if_status_mgr *, const char *iface_id); -+void if_status_mgr_release_iface(struct if_status_mgr *, const char *iface_id); -+void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id); -+ -+void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *); -+void if_status_mgr_run(struct if_status_mgr *mgr, struct local_binding_data *, -+ bool sb_readonly, bool ovs_readonly); -+ -+# endif /* controller/if-status.h */ -diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml -index 51c0c372c..8886df568 100644 ---- a/controller/ovn-controller.8.xml -+++ b/controller/ovn-controller.8.xml -@@ -578,6 +578,28 @@ - Displays logical flow cache statistics: enabled/disabled, per cache - type entry counts. -
    -+ -+
    inc-engine/show-stats
    -+
    -+ Display ovn-controller engine counters. For each engine -+ node the following counters have been added: -+
      -+
    • -+ recompute -+
    • -+
    • -+ compute -+
    • -+
    • -+ abort -+
    • -+
    -+
    -+ -+
    inc-engine/clear-stats
    -+
    -+ Reset ovn-controller engine counters. -+
    -
    -

    - -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 5dd643f52..b4eee4848 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -33,6 +33,7 @@ - #include "openvswitch/dynamic-string.h" - #include "encaps.h" - #include "fatal-signal.h" -+#include "if-status.h" - #include "ip-mcast.h" - #include "openvswitch/hmap.h" - #include "lflow.h" -@@ -81,6 +82,7 @@ static unixctl_cb_func cluster_state_reset_cmd; - static unixctl_cb_func debug_pause_execution; - static unixctl_cb_func debug_resume_execution; - static unixctl_cb_func debug_status_execution; -+static unixctl_cb_func debug_dump_local_bindings; - static unixctl_cb_func lflow_cache_flush_cmd; - static unixctl_cb_func lflow_cache_show_stats_cmd; - static unixctl_cb_func debug_delay_nb_cfg_report; -@@ -102,6 +104,7 @@ OVS_NO_RETURN static void usage(void); - - struct controller_engine_ctx { - struct lflow_cache *lflow_cache; -+ struct if_status_mgr *if_mgr; - }; - - /* Pending packet to be injected into connected OVS. */ -@@ -258,23 +261,15 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, - uuid); - } - -- /* Updating conditions to receive logical flows that references -- * datapath groups containing local datapaths. */ -- const struct sbrec_logical_dp_group *group; -- SBREC_LOGICAL_DP_GROUP_FOR_EACH (group, ovnsb_idl) { -- struct uuid *uuid = CONST_CAST(struct uuid *, -- &group->header_.uuid); -- size_t i; -- -- for (i = 0; i < group->n_datapaths; i++) { -- if (get_local_datapath(local_datapaths, -- group->datapaths[i]->tunnel_key)) { -- sbrec_logical_flow_add_clause_logical_dp_group( -- &lf, OVSDB_F_EQ, uuid); -- break; -- } -- } -- } -+ /* Datapath groups are immutable, which means a new group record is -+ * created when a datapath is added to a group. The logical flows -+ * referencing a datapath group are also updated in such cases but the -+ * new group UUID is not known by ovn-controller until the SB update -+ * is received. To avoid unnecessarily removing and adding lflows -+ * that reference datapath groups, set the monitor condition to always -+ * request all of them. -+ */ -+ sbrec_logical_flow_add_clause_logical_dp_group(&lf, OVSDB_F_NE, NULL); - } - - out:; -@@ -420,6 +415,10 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) { - ovsrec_bridge_set_datapath_type(br_int, datapath_type); - } -+ if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) { -+ ovsrec_bridge_set_fail_mode(br_int, "secure"); -+ VLOG_WARN("Integration bridge fail-mode changed to 'secure'."); -+ } - } - return br_int; - } -@@ -1003,6 +1002,7 @@ en_ofctrl_is_connected_cleanup(void *data OVS_UNUSED) - static void - en_ofctrl_is_connected_run(struct engine_node *node, void *data) - { -+ struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx; - struct ed_type_ofctrl_is_connected *of_data = data; - if (of_data->connected != ofctrl_is_connected()) { - of_data->connected = !of_data->connected; -@@ -1010,7 +1010,7 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data) - /* Flush ofctrl seqno requests when the ofctrl connection goes down. */ - if (!of_data->connected) { - ofctrl_seqno_flush(); -- binding_seqno_flush(); -+ if_status_mgr_clear(ctrl_ctx->if_mgr); - } - engine_set_node_state(node, EN_UPDATED); - return; -@@ -1182,8 +1182,7 @@ struct ed_type_runtime_data { - /* Contains "struct local_datapath" nodes. */ - struct hmap local_datapaths; - -- /* Contains "struct local_binding" nodes. */ -- struct shash local_bindings; -+ struct local_binding_data lbinding_data; - - /* Contains the name of each logical port resident on the local - * hypervisor. These logical ports include the VIFs (and their child -@@ -1222,9 +1221,9 @@ struct ed_type_runtime_data { - * | | Interface and Port Binding changes store the | - * | @tracked_dp_bindings | changed datapaths (datapaths added/removed from | - * | | local_datapaths) and changed port bindings | -- * | | (added/updated/deleted in 'local_bindings'). | -+ * | | (added/updated/deleted in 'lbinding_data'). | - * | | So any changes to the runtime data - | -- * | | local_datapaths and local_bindings is captured | -+ * | | local_datapaths and lbinding_data is captured | - * | | here. | - * ------------------------------------------------------------------------ - * | | This is a bool which represents if the runtime | -@@ -1251,7 +1250,7 @@ struct ed_type_runtime_data { - * - * --------------------------------------------------------------------- - * | local_datapaths | The changes to these runtime data is captured in | -- * | local_bindings | the @tracked_dp_bindings indirectly and hence it | -+ * | lbinding_data | the @tracked_dp_bindings indirectly and hence it | - * | local_lport_ids | is not tracked explicitly. | - * --------------------------------------------------------------------- - * | local_iface_ids | This is used internally within the runtime data | -@@ -1294,7 +1293,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, - sset_init(&data->active_tunnels); - sset_init(&data->egress_ifaces); - smap_init(&data->local_iface_ids); -- local_bindings_init(&data->local_bindings); -+ local_binding_data_init(&data->lbinding_data); - - /* Init the tracked data. */ - hmap_init(&data->tracked_dp_bindings); -@@ -1322,7 +1321,7 @@ en_runtime_data_cleanup(void *data) - free(cur_node); - } - hmap_destroy(&rt_data->local_datapaths); -- local_bindings_destroy(&rt_data->local_bindings); -+ local_binding_data_destroy(&rt_data->lbinding_data); - hmapx_destroy(&rt_data->ct_updated_datapaths); - } - -@@ -1383,6 +1382,8 @@ init_binding_ctx(struct engine_node *node, - engine_get_input("SB_port_binding", node), - "datapath"); - -+ struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx; -+ - b_ctx_in->ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn; - b_ctx_in->ovs_idl_txn = engine_get_context()->ovs_idl_txn; - b_ctx_in->sbrec_datapath_binding_by_key = sbrec_datapath_binding_by_key; -@@ -1405,10 +1406,10 @@ init_binding_ctx(struct engine_node *node, - b_ctx_out->local_lport_ids_changed = false; - b_ctx_out->non_vif_ports_changed = false; - b_ctx_out->egress_ifaces = &rt_data->egress_ifaces; -- b_ctx_out->local_bindings = &rt_data->local_bindings; -+ b_ctx_out->lbinding_data = &rt_data->lbinding_data; - b_ctx_out->local_iface_ids = &rt_data->local_iface_ids; - b_ctx_out->tracked_dp_bindings = NULL; -- b_ctx_out->local_lports_changed = NULL; -+ b_ctx_out->if_mgr = ctrl_ctx->if_mgr; - } - - static void -@@ -1449,7 +1450,7 @@ en_runtime_data_run(struct engine_node *node, void *data) - free(cur_node); - } - hmap_clear(local_datapaths); -- local_bindings_destroy(&rt_data->local_bindings); -+ local_binding_data_destroy(&rt_data->lbinding_data); - sset_destroy(local_lports); - sset_destroy(local_lport_ids); - sset_destroy(active_tunnels); -@@ -1460,7 +1461,7 @@ en_runtime_data_run(struct engine_node *node, void *data) - sset_init(active_tunnels); - sset_init(&rt_data->egress_ifaces); - smap_init(&rt_data->local_iface_ids); -- local_bindings_init(&rt_data->local_bindings); -+ local_binding_data_init(&rt_data->lbinding_data); - hmapx_clear(&rt_data->ct_updated_datapaths); - } - -@@ -1715,6 +1716,7 @@ en_physical_flow_changes_run(struct engine_node *node, void *data) - { - struct ed_type_pfc_data *pfc_tdata = data; - pfc_tdata->recompute_physical_flows = true; -+ pfc_tdata->ovs_ifaces_changed = true; - engine_set_node_state(node, EN_UPDATED); - } - -@@ -1822,7 +1824,7 @@ static void init_physical_ctx(struct engine_node *node, - p_ctx->local_lports = &rt_data->local_lports; - p_ctx->ct_zones = ct_zones; - p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; -- p_ctx->local_bindings = &rt_data->local_bindings; -+ p_ctx->local_bindings = &rt_data->lbinding_data.bindings; - p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths; - } - -@@ -2448,7 +2450,6 @@ main(int argc, char *argv[]) - /* Register ofctrl seqno types. */ - ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type(); - -- binding_init(); - patch_init(); - pinctrl_init(); - lflow_init(); -@@ -2685,7 +2686,8 @@ main(int argc, char *argv[]) - engine_get_internal_data(&en_flow_output); - struct ed_type_ct_zones *ct_zones_data = - engine_get_internal_data(&en_ct_zones); -- struct ed_type_runtime_data *runtime_data = NULL; -+ struct ed_type_runtime_data *runtime_data = -+ engine_get_internal_data(&en_runtime_data); - - ofctrl_init(&flow_output_data->group_table, - &flow_output_data->meter_table, -@@ -2738,13 +2740,19 @@ main(int argc, char *argv[]) - unixctl_command_register("debug/delay-nb-cfg-report", "SECONDS", 1, 1, - debug_delay_nb_cfg_report, &delay_nb_cfg_report); - -+ unixctl_command_register("debug/dump-local-bindings", "", 0, 0, -+ debug_dump_local_bindings, -+ &runtime_data->lbinding_data); -+ - unsigned int ovs_cond_seqno = UINT_MAX; - unsigned int ovnsb_cond_seqno = UINT_MAX; - unsigned int ovnsb_expected_cond_seqno = UINT_MAX; - - struct controller_engine_ctx ctrl_engine_ctx = { - .lflow_cache = lflow_cache_create(), -+ .if_mgr = if_status_mgr_create(), - }; -+ struct if_status_mgr *if_mgr = ctrl_engine_ctx.if_mgr; - - char *ovn_version = ovn_get_internal_version(); - VLOG_INFO("OVN internal version is : [%s]", ovn_version); -@@ -2954,9 +2962,10 @@ main(int argc, char *argv[]) - ovnsb_idl_loop.idl), - ovnsb_cond_seqno, - ovnsb_expected_cond_seqno)); -- if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -- binding_seqno_run(&runtime_data->local_bindings); -- } -+ -+ struct local_binding_data *binding_data = -+ runtime_data ? &runtime_data->lbinding_data : NULL; -+ if_status_mgr_update(if_mgr, binding_data); - - flow_output_data = engine_get_data(&en_flow_output); - if (flow_output_data && ct_zones_data) { -@@ -2967,9 +2976,8 @@ main(int argc, char *argv[]) - engine_node_changed(&en_flow_output)); - } - ofctrl_seqno_run(ofctrl_get_cur_cfg()); -- if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -- binding_seqno_install(&runtime_data->local_bindings); -- } -+ if_status_mgr_run(if_mgr, binding_data, !ovnsb_idl_txn, -+ !ovs_idl_txn); - } - - } -@@ -3135,6 +3143,7 @@ loop_done: - ofctrl_destroy(); - pinctrl_destroy(); - patch_destroy(); -+ if_status_mgr_destroy(if_mgr); - - ovsdb_idl_loop_destroy(&ovs_idl_loop); - ovsdb_idl_loop_destroy(&ovnsb_idl_loop); -@@ -3408,3 +3417,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED, - unixctl_command_reply(conn, "no delay for nb_cfg report."); - } - } -+ -+static void -+debug_dump_local_bindings(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *local_bindings) -+{ -+ struct ds binding_data = DS_EMPTY_INITIALIZER; -+ binding_dump_local_bindings(local_bindings, &binding_data); -+ unixctl_command_reply(conn, ds_cstr(&binding_data)); -+ ds_destroy(&binding_data); -+} -diff --git a/controller/physical.c b/controller/physical.c -index fa5d0d692..c7090b351 100644 ---- a/controller/physical.c -+++ b/controller/physical.c -@@ -1160,6 +1160,11 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, - - load_logical_ingress_metadata(binding, &zone_ids, ofpacts_p); - -+ if (!strcmp(binding->type, "localport")) { -+ /* mark the packet as incoming from a localport */ -+ put_load(1, MFF_LOG_FLAGS, MLF_LOCALPORT_BIT, 1, ofpacts_p); -+ } -+ - /* Resubmit to first logical ingress pipeline table. */ - put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, -@@ -1219,6 +1224,24 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, - ofport, flow_table); - } - -+ /* Table 39, priority 160. -+ * ======================= -+ * -+ * Do not forward local traffic from a localport to a localnet port. -+ */ -+ if (!strcmp(binding->type, "localnet")) { -+ /* do not forward traffic from localport to localnet port */ -+ match_init_catchall(&match); -+ ofpbuf_clear(ofpacts_p); -+ match_set_metadata(&match, htonll(dp_key)); -+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key); -+ match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, -+ MLF_LOCALPORT, MLF_LOCALPORT); -+ ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 160, -+ binding->header_.uuid.parts[0], &match, -+ ofpacts_p, &binding->header_.uuid); -+ } -+ - } else if (!tun && !is_ha_remote) { - /* Remote port connected by localnet port */ - /* Table 33, priority 100. -@@ -1839,20 +1862,29 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, - continue; - } - -- const struct local_binding *lb = -- local_binding_find(p_ctx->local_bindings, iface_id); -- -- if (!lb || !lb->pb) { -- continue; -+ const struct sbrec_port_binding *lb_pb = -+ local_binding_get_primary_pb(p_ctx->local_bindings, iface_id); -+ if (!lb_pb) { -+ /* For regular VIFs (e.g. lsp) the upcoming port-binding update -+ * will remove lfows related to the unclaimed ovs port. -+ * Localport is a special case and it needs to be managed here -+ * since the port is not binded and otherwise the related lfows -+ * will not be cleared removing the ovs port. -+ */ -+ lb_pb = lport_lookup_by_name(p_ctx->sbrec_port_binding_by_name, -+ iface_id); -+ if (!lb_pb || strcmp(lb_pb->type, "localport")) { -+ continue; -+ } - } - - int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0; - if (ovsrec_interface_is_deleted(iface_rec)) { -- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); -+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); - simap_find_and_delete(&localvif_to_ofport, iface_id); - } else { - if (!ovsrec_interface_is_new(iface_rec)) { -- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); -+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); - } - - simap_put(&localvif_to_ofport, iface_id, ofport); -@@ -1860,7 +1892,7 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, - p_ctx->mff_ovn_geneve, p_ctx->ct_zones, - p_ctx->active_tunnels, - p_ctx->local_datapaths, -- lb->pb, p_ctx->chassis, -+ lb_pb, p_ctx->chassis, - flow_table, &ofpacts); - } - } -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index b42288ea5..523a45b9a 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -4240,6 +4240,12 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct shash *nat_addresses) - { - volatile struct garp_rarp_data *garp_rarp = NULL; -+ -+ /* Skip localports as they don't need to be announced */ -+ if (!strcmp(binding_rec->type, "localport")) { -+ return; -+ } -+ - /* Update GARP for NAT IP if it exists. Consider port bindings with type - * "l3gateway" for logical switch ports attached to gateway routers, and - * port bindings with type "patch" for logical switch ports attached to -diff --git a/debian/changelog b/debian/changelog -index 51f9bcc91..25a04f8ae 100644 ---- a/debian/changelog -+++ b/debian/changelog -@@ -1,3 +1,9 @@ -+ovn (21.03.1-1) unstable; urgency=low -+ -+ * New upstream version -+ -+ -- OVN team Fri, 12 Mar 2021 12:00:00 -0500 -+ - ovn (21.03.0-1) unstable; urgency=low - - * New upstream version -diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h -index 017176f98..ef97117b9 100644 ---- a/include/ovn/logical-fields.h -+++ b/include/ovn/logical-fields.h -@@ -66,6 +66,8 @@ enum mff_log_flags_bits { - MLF_LOOKUP_MAC_BIT = 6, - MLF_LOOKUP_LB_HAIRPIN_BIT = 7, - MLF_LOOKUP_FDB_BIT = 8, -+ MLF_SKIP_SNAT_FOR_LB_BIT = 9, -+ MLF_LOCALPORT_BIT = 10, - }; - - /* MFF_LOG_FLAGS_REG flag assignments */ -@@ -102,6 +104,13 @@ enum mff_log_flags { - - /* Indicate that the lookup in the fdb table was successful. */ - MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT), -+ -+ /* Indicate that a packet must not SNAT in the gateway router when -+ * load-balancing has taken place. */ -+ MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT), -+ -+ /* Indicate the packet has been received from a localport */ -+ MLF_LOCALPORT = (1 << MLF_LOCALPORT_BIT), - }; - - /* OVN logical fields -diff --git a/lib/expr.c b/lib/expr.c -index f061a8fbe..7b3d3ddb3 100644 ---- a/lib/expr.c -+++ b/lib/expr.c -@@ -2452,7 +2452,7 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol) - free(or); - return cmp; - } else { -- return or; -+ return crush_cmps(or, symbol); - } - } else { - /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into -diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c -index 916dbbe39..a6337a1d9 100644 ---- a/lib/inc-proc-eng.c -+++ b/lib/inc-proc-eng.c -@@ -27,6 +27,7 @@ - #include "openvswitch/hmap.h" - #include "openvswitch/vlog.h" - #include "inc-proc-eng.h" -+#include "unixctl.h" - - VLOG_DEFINE_THIS_MODULE(inc_proc_eng); - -@@ -102,6 +103,40 @@ engine_get_nodes(struct engine_node *node, size_t *n_count) - return engine_topo_sort(node, NULL, n_count, &n_size); - } - -+static void -+engine_clear_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) -+{ -+ for (size_t i = 0; i < engine_n_nodes; i++) { -+ struct engine_node *node = engine_nodes[i]; -+ -+ memset(&node->stats, 0, sizeof node->stats); -+ } -+ unixctl_command_reply(conn, NULL); -+} -+ -+static void -+engine_dump_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) -+{ -+ struct ds dump = DS_EMPTY_INITIALIZER; -+ -+ for (size_t i = 0; i < engine_n_nodes; i++) { -+ struct engine_node *node = engine_nodes[i]; -+ -+ ds_put_format(&dump, -+ "Node: %s\n" -+ "- recompute: %12"PRIu64"\n" -+ "- compute: %12"PRIu64"\n" -+ "- abort: %12"PRIu64"\n", -+ node->name, node->stats.recompute, -+ node->stats.compute, node->stats.abort); -+ } -+ unixctl_command_reply(conn, ds_cstr(&dump)); -+ -+ ds_destroy(&dump); -+} -+ - void - engine_init(struct engine_node *node, struct engine_arg *arg) - { -@@ -115,6 +150,11 @@ engine_init(struct engine_node *node, struct engine_arg *arg) - engine_nodes[i]->data = NULL; - } - } -+ -+ unixctl_command_register("inc-engine/show-stats", "", 0, 0, -+ engine_dump_stats, NULL); -+ unixctl_command_register("inc-engine/clear-stats", "", 0, 0, -+ engine_clear_stats, NULL); - } - - void -@@ -288,6 +328,7 @@ engine_recompute(struct engine_node *node, bool forced, bool allowed) - - /* Run the node handler which might change state. */ - node->run(node, node->data); -+ node->stats.recompute++; - } - - /* Return true if the node could be computed, false otherwise. */ -@@ -312,6 +353,8 @@ engine_compute(struct engine_node *node, bool recompute_allowed) - } - } - } -+ node->stats.compute++; -+ - return true; - } - -@@ -321,6 +364,7 @@ engine_run_node(struct engine_node *node, bool recompute_allowed) - if (!node->n_inputs) { - /* Run the node handler which might change state. */ - node->run(node, node->data); -+ node->stats.recompute++; - return; - } - -@@ -377,6 +421,7 @@ engine_run(bool recompute_allowed) - engine_run_node(engine_nodes[i], recompute_allowed); - - if (engine_nodes[i]->state == EN_ABORTED) { -+ engine_nodes[i]->stats.abort++; - engine_run_aborted = true; - return; - } -@@ -393,6 +438,7 @@ engine_need_run(void) - } - - engine_nodes[i]->run(engine_nodes[i], engine_nodes[i]->data); -+ engine_nodes[i]->stats.recompute++; - VLOG_DBG("input node: %s, state: %s", engine_nodes[i]->name, - engine_node_state_name[engine_nodes[i]->state]); - if (engine_nodes[i]->state == EN_UPDATED) { -diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h -index 857234677..7e9f5bb70 100644 ---- a/lib/inc-proc-eng.h -+++ b/lib/inc-proc-eng.h -@@ -107,6 +107,12 @@ enum engine_node_state { - EN_STATE_MAX, - }; - -+struct engine_stats { -+ uint64_t recompute; -+ uint64_t compute; -+ uint64_t abort; -+}; -+ - struct engine_node { - /* A unique name for each node. */ - char *name; -@@ -154,6 +160,9 @@ struct engine_node { - /* Method to clear up tracked data maintained by the engine node in the - * engine 'data'. It may be NULL. */ - void (*clear_tracked_data)(void *tracked_data); -+ -+ /* Engine stats. */ -+ struct engine_stats stats; - }; - - /* Initialize the data for the engine nodes. It calls each node's -diff --git a/lib/logical-fields.c b/lib/logical-fields.c -index 9d08b44c2..72853013e 100644 ---- a/lib/logical-fields.c -+++ b/lib/logical-fields.c -@@ -121,6 +121,10 @@ ovn_init_symtab(struct shash *symtab) - MLF_FORCE_SNAT_FOR_LB_BIT); - expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL, - flags_str); -+ snprintf(flags_str, sizeof flags_str, "flags[%d]", -+ MLF_SKIP_SNAT_FOR_LB_BIT); -+ expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL, -+ flags_str); - - /* Connection tracking state. */ - expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false, -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index c272cc922..37d1728b8 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -407,12 +407,13 @@ - it contains a priority-110 flow to move IPv6 Neighbor Discovery and MLD - traffic to the next table. If load balancing rules with virtual IP - addresses (and ports) are configured in OVN_Northbound -- database for alogical switch datapath, a priority-100 flow is added -+ database for a logical switch datapath, a priority-100 flow is added - with the match ip to match on IP packets and sets the action -- reg0[0] = 1; next; to act as a hint for table -+ reg0[2] = 1; next; to act as a hint for table - Pre-stateful to send IP packets to the connection tracker -- for packet de-fragmentation before eventually advancing to ingress -- table LB. -+ for packet de-fragmentation (and to possibly do DNAT for already -+ established load balanced traffic) before eventually advancing to ingress -+ table Stateful. - If controller_event has been enabled and load balancing rules with - empty backends have been added in OVN_Northbound, a 130 flow - is added to trigger ovn-controller events whenever the chassis receives a -@@ -470,11 +471,38 @@ -

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

    -+
      -+
    • -+ Priority-120 flows that send the packets to connection tracker using -+ ct_lb; as the action so that the already established -+ traffic destined to the load balancer VIP gets DNATted based on a hint -+ provided by the previous tables (with a match -+ for reg0[2] == 1 and on supported load balancer protocols -+ and address families). For IPv4 traffic the flows also load the -+ original destination IP and transport port in registers -+ reg1 and reg2. For IPv6 traffic the flows -+ also load the original destination IP and transport port in -+ registers xxreg1 and reg2. -+
    • -+ -+
    • -+ A priority-110 flow sends the packets to connection tracker based -+ on a hint provided by the previous tables -+ (with a match for reg0[2] == 1) by using the -+ ct_lb; action. This flow is added to handle -+ the traffic for load balancer VIPs whose protocol is not defined -+ (mainly for ICMP traffic). -+
    • -+ -+
    • -+ A priority-100 flow sends the packets to connection tracker based -+ on a hint provided by the previous tables -+ (with a match for reg0[0] == 1) by using the -+ ct_next; action. -+
    • -+
    - -

    Ingress Table 8: from-lport ACL hints

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

    - The table contains the following flows: -

    -+
      -+
    • -+ A priority-65535 flow to advance to the next table if the logical -+ switch has no ACLs configured, otherwise a -+ priority-0 flow to advance to the next table. -+
    • -+
    -+ -
      -
    • - A priority-7 flow that matches on packets that initiate a new session. -@@ -551,9 +587,6 @@ - This flow sets reg0[10] and then advances to the next - table. -
    • --
    • -- A priority-0 flow to advance to the next table. --
    • -
    - -

    Ingress table 9: from-lport ACLs

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

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

    -+ -+

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

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

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

    -+ -+
      -
    • - A priority 34000 logical flow is added for each logical switch datapath - with the match eth.dst = E to allow the service -@@ -709,33 +754,7 @@ -
    • -
    - --

    Ingress Table 12: LB

    -- --

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

    -- --

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

    -- --

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

    -- --

    Ingress Table 13: Stateful

    -+

    Ingress Table 12: Stateful

    - -
      -
    • -@@ -792,23 +811,12 @@ - ct_commit; next; action based on a hint provided by - the previous tables (with a match for reg0[1] == 1). -
    • --
    • -- Priority-100 flows that send the packets to connection tracker using -- ct_lb; as the action based on a hint provided by the -- previous tables (with a match for reg0[2] == 1 and -- on supported load balancer protocols and address families). -- For IPv4 traffic the flows also load the original destination -- IP and transport port in registers reg1 and -- reg2. For IPv6 traffic the flows also load the original -- destination IP and transport port in registers xxreg1 and -- reg2. --
    • -
    • - A priority-0 flow that simply moves traffic to the next table. -
    • -
    - --

    Ingress Table 14: Pre-Hairpin

    -+

    Ingress Table 13: Pre-Hairpin

    -
      -
    • - If the logical switch has load balancer(s) configured, then a -@@ -826,7 +834,7 @@ -
    • -
    - --

    Ingress Table 15: Nat-Hairpin

    -+

    Ingress Table 14: Nat-Hairpin

    -
      -
    • - If the logical switch has load balancer(s) configured, then a -@@ -861,7 +869,7 @@ -
    • -
    - --

    Ingress Table 16: Hairpin

    -+

    Ingress Table 15: Hairpin

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

    Ingress Table 17: ARP/ND responder

    -+

    Ingress Table 16: ARP/ND responder

    - -

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

    Ingress Table 18: DHCP option processing

    -+

    Ingress Table 17: DHCP option processing

    - -

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

    Ingress Table 19: DHCP responses

    -+

    Ingress Table 18: DHCP responses

    - -

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

    Ingress Table 20 DNS Lookup

    -+

    Ingress Table 19 DNS Lookup

    - -

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

    Ingress Table 21 DNS Responses

    -+

    Ingress Table 20 DNS Responses

    - -

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

    Ingress table 22 External ports

    -+

    Ingress table 21 External ports

    - -

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

    Ingress Table 23 Destination Lookup

    -+

    Ingress Table 22 Destination Lookup

    - -

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

    - -

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

    Egress Table 2: Pre-stateful

    - -

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

    - --

    Egress Table 3: LB

    --

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

    -+
      -+
    • -+ A Priority-120 flow that send the packets to connection tracker using -+ ct_lb; as the action so that the already established -+ traffic gets unDNATted from the backend IP to the load balancer VIP -+ based on a hint provided by the previous tables with a match -+ for reg0[2] == 1. If the packet was not DNATted earlier, -+ then ct_lb functions like ct_next. -+
    • - --

      Egress Table 4: from-lport ACL hints

      -+
    • -+ A priority-100 flow sends the packets to connection tracker based -+ on a hint provided by the previous tables -+ (with a match for reg0[0] == 1) by using the -+ ct_next; action. -+
    • -+ -+
    • -+ A priority-0 flow that matches all packets to advance to the next -+ table. -+
    • -+
    -+ -+

    Egress Table 3: from-lport ACL hints

    -

    - This is similar to ingress table ACL hints. -

    - --

    Egress Table 5: to-lport ACLs

    -+

    Egress Table 4: to-lport ACLs

    - -

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

    Egress Table 6: to-lport QoS Marking

    -+

    Egress Table 5: to-lport QoS Marking

    - -

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

    - --

    Egress Table 7: to-lport QoS Meter

    -+

    Egress Table 6: to-lport QoS Meter

    - -

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

    - --

    Egress Table 8: Stateful

    -+

    Egress Table 7: Stateful

    - -

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

    - --

    Egress Table 9: Egress Port Security - IP

    -+

    Egress Table 8: Egress Port Security - IP

    - -

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

    - --

    Egress Table 10: Egress Port Security - L2

    -+

    Egress Table 9: Egress Port Security - L2

    - -

    - This is similar to the ingress port security logic in ingress table -@@ -2283,8 +2312,7 @@ eth.src = xreg0[0..47]; - arp.op = 2; /* ARP reply. */ - arp.tha = arp.sha; - arp.sha = xreg0[0..47]; --arp.tpa = arp.spa; --arp.spa = A; -+arp.tpa <-> arp.spa; - outport = inport; - flags.loopback = 1; - output; -@@ -2720,7 +2748,11 @@ icmp6 { - (and optional port numbers) to load balance to. If the router is - configured to force SNAT any load-balanced packets, the above action - will be replaced by flags.force_snat_for_lb = 1; -- ct_lb(args);. If health check is enabled, then -+ ct_lb(args);. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_lb(args);. -+ If health check is enabled, then - args will only contain those endpoints whose service - monitor status entry in OVN_Southbound db is - either online or empty. -@@ -2737,6 +2769,9 @@ icmp6 { - with an action of ct_dnat;. If the router is - configured to force SNAT any load-balanced packets, the above action - will be replaced by flags.force_snat_for_lb = 1; ct_dnat;. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_dnat;. - - -

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

    -
  • - -+
  • -+

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

    -+
  • -+ -
  • -

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

    - - -+ -+

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

    -+
    -+ - -

    - These options control how routes are advertised between OVN -@@ -1653,6 +1668,12 @@ - exactly one IPv4 and/or one IPv6 address on it, separated by a space - character. - -+ -+ -+ If the load balancing rule is configured with skip_snat -+ option, the force_snat_for_lb option configured for the router -+ pipeline will not be applied for this load balancer. -+ - - - -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 2cd3e261f..5c64fff12 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -431,3 +431,83 @@ OVS_WAIT_UNTIL([ - - OVN_CLEANUP([hv1]) - AT_CLEANUP -+ -+# Test that changes of a port binding from one type to another doesn'that -+# result in any ovn-controller asserts or crashes. -+AT_SETUP([ovn-controller - port binding type change handling]) -+AT_KEYWORDS([ovn]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 -+ -+as hv1 -+check ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 -+ -+# ovn-controller should bind the interface. -+wait_for_ports_up -+hv_uuid=$(fetch_column Chassis _uuid name=hv1) -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] -+primary lport : [[lsp1]] -+---------------------------------------- -+]) -+ -+# pause ovn-northd -+check as northd ovn-appctl -t ovn-northd pause -+check as northd-backup ovn-appctl -t ovn-northd pause -+ -+as northd ovn-appctl -t ovn-northd status -+as northd-backup ovn-appctl -t ovn-northd status -+ -+pb_types=(patch chassisredirect l3gateway localnet localport l2gateway -+ virtual external remote vtep) -+for type in ${pb_types[[@]]} -+do -+ for update_type in ${pb_types[[@]]} -+ do -+ check ovn-sbctl set port_binding lsp1 type=$type -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$type -+ OVS_WAIT_UNTIL([test $type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+ echo "Updating to $update_type from $type" -+ check ovn-sbctl set port_binding lsp1 type=$update_type -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$update_type -+ OVS_WAIT_UNTIL([test $update_type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ # Set the port binding type back to VIF. -+ check ovn-sbctl set port_binding lsp1 type=\"\" -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=foo -+ OVS_WAIT_UNTIL([test foo = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] -+primary lport : [[lsp1]] -+---------------------------------------- -+]) -+ done -+done -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index 2ba29a960..4cf14b1f2 100644 ---- a/tests/ovn-macros.at -+++ b/tests/ovn-macros.at -@@ -433,6 +433,24 @@ wait_for_ports_up() { - done - fi - } -+ -+# reset_pcap_file iface pcap_file -+# Resets the pcap file associates with OVS interface. should be used -+# with dummy datapath. -+reset_iface_pcap_file() { -+ local iface=$1 -+ local pcap_file=$2 -+ check rm -f dummy-*.pcap -+ check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -+options:rxq_pcap=dummy-rx.pcap -+ OVS_WAIT_WHILE([test 24 = $(wc -c dummy-tx.pcap | cut -d " " -f1)]) -+ check rm -f ${pcap_file}*.pcap -+ check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -+options:rxq_pcap=${pcap_file}-rx.pcap -+ -+ OVS_WAIT_WHILE([test 24 = $(wc -c ${pcap_file}-tx.pcap | cut -d " " -f1)]) -+} -+ - OVS_END_SHELL_HELPERS - - m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) -diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 6d91aa4c5..8af55161f 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1551,6 +1551,7 @@ IPv4 Routes - AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2], [1], [], - [ovn-nbctl: duplicate nexthop for the same ECMP route - ]) -+AT_CHECK([ovn-nbctl --may-exist --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2]) - - dnl Delete ecmp routes - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1]) -@@ -1614,6 +1615,7 @@ AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 200 - AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6], [1], [], - [ovn-nbctl: duplicate nexthop for the same ECMP route - ]) -+AT_CHECK([ovn-nbctl --may-exist --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6]) - - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index b78baa708..8ca915302 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -1077,7 +1077,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 - - AT_CAPTURE_FILE([sbflows]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed 's/table=..//'], 0, [dnl -+ [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' | sed 's/table=..//'], 0, [dnl - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - -@@ -1087,7 +1087,7 @@ wait_row_count Service_Monitor 0 - - AT_CAPTURE_FILE([sbflows2]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], -+ [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - -@@ -1098,7 +1098,7 @@ health_check @hc - wait_row_count Service_Monitor 2 - check ovn-nbctl --wait=sb sync - --ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt -+ovn-sbctl dump-flows sw0 | grep backends | grep priority=120 > lflows.txt - AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) -@@ -1109,7 +1109,7 @@ sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1) - - AT_CAPTURE_FILE([sbflows3]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], -+ [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - -@@ -1120,7 +1120,7 @@ check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows4]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0], -+ [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);) - ]) - -@@ -1132,7 +1132,7 @@ check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows5]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*ct_lb'], 1) -+ [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*backends'], 1) - - AT_CAPTURE_FILE([sbflows6]) - OVS_WAIT_FOR_OUTPUT( -@@ -1149,7 +1149,7 @@ check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows7]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, -+ [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep backends | grep priority=120 | sed 's/table=..//'], 0, - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - -@@ -1185,7 +1185,7 @@ wait_row_count Service_Monitor 1 port=1000 - - AT_CAPTURE_FILE([sbflows9]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], -+ [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep backends | grep priority=120 | sed 's/table=..//' | sort], - 0, - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);) - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);) -@@ -1199,7 +1199,7 @@ check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows10]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], -+ [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep backends | grep priority=120 | sed 's/table=..//' | sort], - 0, - [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) -@@ -1209,7 +1209,7 @@ AS_BOX([Associate lb1 to sw1]) - check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 - AT_CAPTURE_FILE([sbflows11]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort], -+ [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep backends | grep priority=120 | sed 's/table=..//' | sort], - 0, [dnl - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);) -@@ -1269,7 +1269,7 @@ ovn-sbctl set service_monitor $sm_sw1_p1 status=offline - AT_CAPTURE_FILE([sbflows12]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl -- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=5);};) - ]) - - AT_CLEANUP -@@ -1504,6 +1504,19 @@ ovn-nbctl lr-nat-add lr dnat_and_snat 43.43.43.4 42.42.42.4 ls-vm 00:00:00:00:00 - ovn-nbctl lr-nat-add lr snat 43.43.43.150 43.43.43.50 - ovn-nbctl lr-nat-add lr snat 43.43.43.150 43.43.43.51 - -+ovn-nbctl lb-add lb1 "192.168.2.1:8080" "10.0.0.4:8080" -+ovn-nbctl lb-add lb2 "192.168.2.4:8080" "10.0.0.5:8080" udp -+ovn-nbctl lb-add lb3 "192.168.2.5:8080" "10.0.0.6:8080" -+ovn-nbctl lb-add lb4 "192.168.2.6:8080" "10.0.0.7:8080" -+ovn-nbctl lb-add lb5 "fe80::200:ff:fe00:101:8080" "fe02::200:ff:fe00:101:8080" -+ovn-nbctl lb-add lb5 "fe80::200:ff:fe00:102:8080" "fe02::200:ff:fe00:102:8080" -+ -+ovn-nbctl lr-lb-add lr lb1 -+ovn-nbctl lr-lb-add lr lb2 -+ovn-nbctl lr-lb-add lr lb3 -+ovn-nbctl lr-lb-add lr lb4 -+ovn-nbctl lr-lb-add lr lb5 -+ - ovn-nbctl --wait=sb sync - - # Ingress router port ETH address is stored in lr_in_admission. -@@ -1526,28 +1539,46 @@ action=(xreg0[[0..47]] = 00:00:00:00:01:00; next;) - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=90" | grep "arp\|nd" | sort], [0], [dnl - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.150), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.2), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.3), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.4), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp" && arp.op == 1 && arp.tpa == 42.42.42.1 && arp.spa == 42.42.42.0/24), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 42.42.42.1; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp" && ip6.dst == {fe80::200:ff:fe00:1, ff02::1:ff00:1} && nd_ns && nd.target == fe80::200:ff:fe00:1), dnl - action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:1; nd.target = fe80::200:ff:fe00:1; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) - table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.1 && arp.spa == 43.43.43.0/24), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.1; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp-public" && ip6.dst == {fe80::200:ff:fe00:100, ff02::1:ff00:100} && nd_ns && nd.target == fe80::200:ff:fe00:100), dnl - action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:100; nd.target = fe80::200:ff:fe00:100; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) - ]) - - # xreg0[0..47] isn't used anywhere else. -@@ -1583,28 +1614,46 @@ action=(xreg0[[0..47]] = 00:00:00:00:01:00; next;) - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=90" | grep "arp\|nd" | sort], [0], [dnl - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.150), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.2), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.3), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(arp.op == 1 && arp.tpa == 43.43.43.4), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp" && arp.op == 1 && arp.tpa == 42.42.42.1 && arp.spa == 42.42.42.0/24), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 42.42.42.1; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp" && ip6.dst == {fe80::200:ff:fe00:1, ff02::1:ff00:1} && nd_ns && nd.target == fe80::200:ff:fe00:1), dnl - action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:1; nd.target = fe80::200:ff:fe00:1; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) - table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.1 && arp.spa == 43.43.43.0/24), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.1; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 } && is_chassis_resident("cr-lrp-public")), dnl -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=90 , dnl - match=(inport == "lrp-public" && ip6.dst == {fe80::200:ff:fe00:100, ff02::1:ff00:100} && nd_ns && nd.target == fe80::200:ff:fe00:100 && is_chassis_resident("cr-lrp-public")), dnl - action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:100; nd.target = fe80::200:ff:fe00:100; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080 && is_chassis_resident("cr-lrp-public")), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) -+ table=3 (lr_in_ip_input ), priority=90 , dnl -+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080 && is_chassis_resident("cr-lrp-public")), dnl -+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };) - ]) - - # Priority 91 drop flows (per distributed gw port), if port is not resident. -@@ -1626,16 +1675,16 @@ action=(drop;) - AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=92" | grep "arp\|nd" | sort], [0], [dnl - table=3 (lr_in_ip_input ), priority=92 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.150 && is_chassis_resident("cr-lrp-public")), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=92 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.2 && is_chassis_resident("cr-lrp-public")), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=92 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.3 && is_chassis_resident("cr-lrp-public")), dnl --action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - table=3 (lr_in_ip_input ), priority=92 , dnl - match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.4 && is_chassis_resident("ls-vm")), dnl --action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;) -+action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;) - ]) - - # xreg0[0..47] isn't used anywhere else. -@@ -1671,13 +1720,13 @@ AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 - ovn-nbctl ls-lb-add sw0 lb1 - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl ls-lb-add sw0 lb2 - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - lb1_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb1) -@@ -1686,7 +1735,7 @@ lb2_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb2) - ovn-nbctl clear load_balancer $lb1_uuid vips - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl clear load_balancer $lb2_uuid vips -@@ -1699,14 +1748,14 @@ ovn-nbctl set load_balancer $lb2_uuid vips:"10.0.0.11"="10.0.0.4" - - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - # Now reverse the order of clearing the vip. - ovn-nbctl clear load_balancer $lb2_uuid vips - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl clear load_balancer $lb1_uuid vips -@@ -1754,10 +1803,10 @@ AT_CAPTURE_FILE([sw1flows]) - - AT_CHECK( - [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl --sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) --sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };) -+sw0flows: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=5); };) -+sw1flows: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=5); };) - ]) - - AS_BOX([2]) -@@ -1770,10 +1819,10 @@ ovn-sbctl dump-flows sw1 > sw1flows2 - AT_CAPTURE_FILE([sw1flows2]) - - AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl --sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows2: table=4 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw0flows2: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows2: table=4 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows2: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) - ]) - - AS_BOX([3]) -@@ -1786,18 +1835,18 @@ ovn-sbctl dump-flows sw1 > sw1flows3 - AT_CAPTURE_FILE([sw1flows3]) - - AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl --sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) --sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) --sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) --sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) --sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) --sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };) -+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw0flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw0flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw0flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw0flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw1flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) -+sw1flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };) - ]) - - AT_CLEANUP -@@ -1932,17 +1981,17 @@ check ovn-nbctl --wait=sb \ - -- acl-add ls from-lport 2 "udp" allow-related \ - -- acl-add ls to-lport 2 "udp" allow-related - AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -- table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -- table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) - table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -@@ -1951,9 +2000,9 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e - table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) - table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -- table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -- table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) - ]) - - AS_BOX([Check match ct_state with load balancer]) -@@ -1963,18 +2012,25 @@ check ovn-nbctl --wait=sb \ - -- lb-add lb "10.0.0.1" "10.0.0.2" \ - -- ls-lb-add ls lb - --AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -- table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -- table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl -+ table=3 (ls_out_acl_hint ), priority=0 , match=(1), action=(next;) -+ table=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl ), priority=0 , match=(1), action=(next;) -+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;) -+ table=4 (ls_out_acl ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+ table=8 (ls_in_acl_hint ), priority=0 , match=(1), action=(next;) - table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -@@ -1982,12 +2038,28 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e - table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) - table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=9 (ls_in_acl ), priority=0 , match=(1), action=(next;) - table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -- table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -- table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;) -+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;) -+ table=9 (ls_in_acl ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+ovn-nbctl --wait=sb clear logical_switch ls acls -+ovn-nbctl --wait=sb clear logical_switch ls load_balancer -+ -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl -+ table=3 (ls_out_acl_hint ), priority=65535, match=(1), action=(next;) -+ table=4 (ls_out_acl ), priority=65535, match=(1), action=(next;) -+ table=8 (ls_in_acl_hint ), priority=65535, match=(1), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(1), action=(next;) - ]) - -+ - AT_CLEANUP - - AT_SETUP([datapath requested-tnl-key]) -@@ -2197,20 +2269,20 @@ check ovn-nbctl \ - check ovn-nbctl --wait=sb sync - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl -- table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;) -- table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;) -+ table=13(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;) -+ table=13(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl -- table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;) -- table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;) -- table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;) -- table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;) -+ table=14(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;) -+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;) -+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;) -+ table=14(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;) - ]) - - AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl -- table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;) -- table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;) -+ table=15(ls_in_hairpin ), priority=0 , match=(1), action=(next;) -+ table=15(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;) - ]) - - AT_CLEANUP -@@ -2324,6 +2396,13 @@ check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public - - check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102 - -+ovn-nbctl lr-policy-list lr0 > policy-list -+AT_CAPTURE_FILE([policy-list]) -+AT_CHECK([cat policy-list], [0], [dnl -+Routing Policies -+ 10 ip4.src == 10.0.0.3 reroute 172.168.0.101, 172.168.0.102 -+]) -+ - ovn-sbctl dump-flows lr0 > lr0flows3 - AT_CAPTURE_FILE([lr0flows3]) - -@@ -2551,7 +2630,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1 - - AT_CLEANUP - --AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers]) -+AT_SETUP([ovn -- Load Balancers and lb_force_snat_ip for Gateway Routers]) - ovn_start - - check ovn-nbctl ls-add sw0 -@@ -2589,11 +2668,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl --]) -- -- --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - - check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4" -@@ -2608,14 +2687,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);) - table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip" -@@ -2633,15 +2716,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl --wait=sb remove logical_router lr0 options chassis -@@ -2653,7 +2740,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl set logical_router lr0 options:chassis=ch1 -@@ -2670,16 +2759,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080 -+check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true -+check ovn-nbctl lr-lb-add lr0 lb2 -+check ovn-nbctl --wait=sb lb-del lb1 -+ovn-sbctl dump-flows lr0 > lr0flows -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=120 , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;) - ]) - - AT_CLEANUP -@@ -2783,3 +2899,206 @@ wait_row_count FDB 0 - ovn-sbctl list FDB - - AT_CLEANUP -+ -+AT_SETUP([ovn -- LS load balancer logical flows]) -+ovn_start -+ -+check ovn-nbctl \ -+ -- ls-add sw0 \ -+ -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \ -+ -- ls-lb-add sw0 lb0 -+ -+check ovn-nbctl lr-add lr0 -+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 -+check ovn-nbctl lsp-add sw0 sw0-lr0 -+check ovn-nbctl lsp-set-type sw0-lr0 router -+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 -+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 -+ -+check ovn-nbctl --wait=sb sync -+ -+check_stateful_flows() { -+ ovn-sbctl dump-flows sw0 > sw0flows -+ AT_CAPTURE_FILE([sw0flows]) -+ -+ AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl -+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+ AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+]) -+ -+ AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl -+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;) -+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+ table=12(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.4:8080);) -+]) -+ -+ AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl -+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+ AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl -+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;) -+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+]) -+ -+ AT_CHECK([grep "ls_out_lb" sw0flows | sort], [0], []) -+ -+ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+} -+ -+check_stateful_flows -+ -+# Add few ACLs -+check ovn-nbctl --wait=sb acl-add sw0 from-lport 1002 "ip4 && tcp && tcp.dst == 80" allow-related -+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 "ip4 && tcp && tcp.src == 80" drop -+ -+check_stateful_flows -+ -+# Remove load balancer from sw0 -+check ovn-nbctl --wait=sb ls-lb-del sw0 lb0 -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl -+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+]) -+ -+AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl -+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;) -+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+ -+AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl -+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl -+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;) -+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+]) -+ -+AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+ -+AT_CLEANUP -+]) -+ -+AT_SETUP([ovn -- ct.inv usage]) -+ovn_start -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0p1 -+ -+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 ip allow-related -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+# Disable ct.inv usage. -+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=false -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -c "ct.inv" sw0flows], [1], [dnl -+0 -+]) -+ -+# Enable ct.inv usage. -+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=true -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -c "ct.inv" sw0flows], [0], [dnl -+6 -+]) -+ -+AT_CLEANUP -diff --git a/tests/ovn.at b/tests/ovn.at -index b465784cd..0377b75c3 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -693,6 +693,11 @@ ip,nw_src=4.0.0.0/4.0.0.0 - ip,nw_src=64.0.0.0/64.0.0.0 - ip,nw_src=8.0.0.0/8.0.0.0 - ]) -+AT_CHECK([expr_to_flow 'ip4.dst == 172.27.0.65 && ip4.src == $set1 && ip4.dst != 10.128.0.0/14'], [0], [dnl -+ip,nw_src=10.0.0.1,nw_dst=172.27.0.65 -+ip,nw_src=10.0.0.2,nw_dst=172.27.0.65 -+ip,nw_src=10.0.0.3,nw_dst=172.27.0.65 -+]) - AT_CLEANUP - - AT_SETUP([ovn -- converting expressions to flows -- port groups]) -@@ -9878,15 +9883,12 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore]) - ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([sbflows]) - --reset_pcap_file() { -- local iface=$1 -- local pcap_file=$2 -- check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ --options:rxq_pcap=dummy-rx.pcap -- rm -f ${pcap_file}*.pcap -- check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ --options:rxq_pcap=${pcap_file}-rx.pcap --} -+hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0) -+hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0) -+ -+OVS_WAIT_UNTIL([ -+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport") -+]) - - test_ip_packet() - { -@@ -9932,13 +9934,13 @@ test_ip_packet() - echo $expected > ext1-vif1.expected - exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101 - echo $exp_gw_ip_garp >> ext1-vif1.expected -- as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1 -+ as $active_gw reset_iface_pcap_file br-phys_n1 $active_gw/br-phys_n1 - - if test $backup_vswitchd_dead != 1; then - # Reset the file only if vswitchd in backup gw is alive -- as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1 -+ as $backup_gw reset_iface_pcap_file br-phys_n1 $backup_gw/br-phys_n1 - fi -- as ext1 reset_pcap_file ext1-vif1 ext1/vif1 -+ as ext1 reset_iface_pcap_file ext1-vif1 ext1/vif1 - - # Resend packet from foo1 to outside1 - check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -@@ -9990,6 +9992,10 @@ AT_CHECK( - <1> - ]) - -+OVS_WAIT_UNTIL([ -+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport") -+]) -+ - test_ip_packet gw2 gw1 0 - - # Get the claim count of both gw1 and gw2. -@@ -10010,6 +10016,12 @@ OVS_WAIT_UNTIL([test $gw1_claim_ct = `cat gw1/ovn-controller.log \ - AT_CHECK([test $gw2_claim_ct = `cat gw2/ovn-controller.log | \ - grep -c "cr-alice: Claiming"`]) - -+OVS_WAIT_UNTIL([ -+ bfd_status=$(as hv1 ovs-vsctl get interface ovn-gw2-0 bfd_status:state) -+ echo "bfd status = $bfd_status" -+ test "$bfd_status" = "down" -+]) -+ - test_ip_packet gw1 gw2 1 - - as gw2 -@@ -11490,10 +11502,100 @@ for i in 1 2; do - done - done - -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl -+10 -+11 -+]) -+ -+# remove the localport from br-int and re-create it -+as hv1 -+check ovs-vsctl del-port vif01 -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl -+11 -+]) -+ -+as hv1 -+check ovs-vsctl add-port br-int vif01 \ -+ -- set Interface vif01 external-ids:iface-id=lp01 -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl -+2 -+11 -+]) -+ - OVN_CLEANUP([hv1],[hv2]) - - AT_CLEANUP - -+AT_SETUP([ovn -- localport suppress gARP]) -+ovn_start -+ -+send_garp() { -+ local inport=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 -+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa} -+ as hv1 ovs-appctl netdev-dummy/receive vif$inport $request -+} -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+check ovs-vsctl add-br br-phys -+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -+ -+check ovn-nbctl ls-add ls \ -+ -- lsp-add ls lp \ -+ -- lsp-set-type lp localport \ -+ -- lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" \ -+ -- lsp-add ls ln \ -+ -- lsp-set-type ln localnet \ -+ -- lsp-set-addresses ln unknown \ -+ -- lsp-set-options ln network_name=phys \ -+ -- lsp-add ls lsp \ -+ -- lsp-set-addresses lsp "00:00:00:00:00:02 10.0.0.2" -+ -+dnl First bind the localport. -+check ovs-vsctl add-port br-int vif1 \ -+ -- set Interface vif1 external-ids:iface-id=lp -+check ovn-nbctl --wait=hv sync -+ -+dnl Then bind the regular vif. -+check ovs-vsctl add-port br-int vif2 \ -+ -- set Interface vif2 external-ids:iface-id=lsp \ -+ options:tx_pcap=hv1/vif2-tx.pcap \ -+ options:rxq_pcap=hv1/vif2-rx.pcap -+ -+wait_for_ports_up lsp -+check ovn-nbctl --wait=hv sync -+ -+dnl Wait for at least two gARPs from lsp (10.0.0.2). -+lsp_garp=ffffffffffff000000000002080600010800060400010000000000020a0000020000000000000a000002 -+OVS_WAIT_UNTIL([ -+ garps=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep ${lsp_garp} -c` -+ test $garps -ge 2 -+]) -+ -+dnl At this point it's safe to assume that ovn-controller skipped sending gARP -+dnl for the localport. Check that there are no other packets than the gARPs -+dnl for the regular vif. -+AT_CHECK([ -+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep -v ${lsp_garp} -c` -+ test 0 -eq $pkts -+]) -+ -+spa=$(ip_to_hex 10 0 0 1) -+tpa=$(ip_to_hex 10 0 0 100) -+send_garp 1 000000000001 ffffffffffff $spa $tpa -+ -+dnl traffic from localport should not be sent to localnet -+AT_CHECK([tcpdump -r hv1/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000064 | wc -l],[0],[dnl -+0 -+],[ignore]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ - AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) - ovn_start - -@@ -13901,16 +14003,16 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow - check ovn-nbctl --wait=hv sync - - # Check OVS flows, the less restrictive flows should have been installed. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -13945,16 +14047,16 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' - check ovn-nbctl --wait=hv sync - - # Check OVS flows, the second less restrictive allow ACL should have been installed. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Remove the less restrictive allow ACL. -@@ -13962,16 +14064,16 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1' - check ovn-nbctl --wait=hv sync - - # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -14001,16 +14103,16 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow - check ovn-nbctl --wait=hv sync - - # Check OVS flows, the less restrictive flows should have been installed. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Add another ACL that overlaps with the existing less restrictive ones. -@@ -14021,19 +14123,19 @@ check ovn-nbctl --wait=hv sync - # with an additional conjunction action. - # - # New non-conjunctive flows should be added to match on 'udp'. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -- table=45, priority=1003,udp,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,udp6,metadata=0x1 actions=resubmit(,46) -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,udp,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,udp6,metadata=0x1 actions=resubmit(,45) - ]) - - OVN_CLEANUP([hv1]) -@@ -15375,7 +15477,7 @@ wait_for_ports_up ls1-lp_ext1 - # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined - # to router mac. - AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \ --table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ -+table=29,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ - grep -c "actions=drop"], [0], [1 - ]) - -@@ -16647,56 +16749,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \ - - ovn-nbctl ls-add sw0 - --ovn-nbctl lsp-add sw0 sw0-vir --ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-type sw0-vir virtual --ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 --ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 -+check ovn-nbctl lsp-add sw0 sw0-vir -+check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" -+check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" -+check ovn-nbctl lsp-set-type sw0-vir virtual -+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 -+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 - --ovn-nbctl lsp-add sw0 sw0-p1 --ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" --ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" -+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" - --ovn-nbctl lsp-add sw0 sw0-p2 --ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" --ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" -+check ovn-nbctl lsp-add sw0 sw0-p2 -+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" - --ovn-nbctl lsp-add sw0 sw0-p3 --ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" --ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" -+check ovn-nbctl lsp-add sw0 sw0-p3 -+check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" -+check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" - - # Create the second logical switch with one port --ovn-nbctl ls-add sw1 --ovn-nbctl lsp-add sw1 sw1-p1 --ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" --ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl lsp-add sw1 sw1-p1 -+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" -+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" - - # Create a logical router and attach both logical switches --ovn-nbctl lr-add lr0 --ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 --ovn-nbctl lsp-add sw0 sw0-lr0 --ovn-nbctl lsp-set-type sw0-lr0 router --ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 --ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 -+check ovn-nbctl lr-add lr0 -+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 -+check ovn-nbctl lsp-add sw0 sw0-lr0 -+check ovn-nbctl lsp-set-type sw0-lr0 router -+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 -+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 - --ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 --ovn-nbctl lsp-add sw1 sw1-lr0 --ovn-nbctl lsp-set-type sw1-lr0 router --ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 --ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 -+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 -+check ovn-nbctl lsp-add sw1 sw1-lr0 -+check ovn-nbctl lsp-set-type sw1-lr0 router -+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 -+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - --OVN_POPULATE_ARP -+# Add an ACL that matches on sw0-vir being bound locally. -+check ovn-nbctl acl-add sw0 to-lport 1000 'is_chassis_resident("sw0-vir") && ip' allow - --# Delete sw0-vir and add again. --ovn-nbctl lsp-del sw0-vir -+check ovn-nbctl ls-add public -+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 -+check ovn-nbctl lsp-add public public-lr0 -+check ovn-nbctl lsp-set-type public-lr0 router -+check ovn-nbctl lsp-set-addresses public-lr0 router -+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public - --ovn-nbctl lsp-add sw0 sw0-vir --ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-type sw0-vir virtual --ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 --ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 -+# localnet port -+check ovn-nbctl lsp-add public ln-public -+check ovn-nbctl lsp-set-type ln-public localnet -+check ovn-nbctl lsp-set-addresses ln-public unknown -+check ovn-nbctl lsp-set-options ln-public network_name=public -+ -+# schedule the gw router port to a chassis. Change the name of the chassis -+check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 -+ -+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.10 sw0-vir 10:54:00:00:00:10 -+ -+OVN_POPULATE_ARP - - wait_for_ports_up - ovn-nbctl --wait=hv sync -@@ -16746,6 +16859,30 @@ ovs-vsctl del-port hv1-vif3 - AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=sw0-vir) = x], [0], []) - -+check_virtual_offlows_present() { -+ hv=$1 -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl -+ table=44, priority=2000,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=2000,ipv6,metadata=0x1 actions=resubmit(,45) -+]) -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ -+ grep "priority=92" | grep 172.168.0.50], [0], [dnl -+ table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37) -+]) -+} -+ -+check_virtual_offlows_not_present() { -+ hv=$1 -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl -+]) -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ -+ grep "priority=92" | grep 172.168.0.50], [1], [dnl -+]) -+} -+ - # From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p1 should be its virtual_parent. - eth_src=505400000003 -@@ -16767,6 +16904,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) - ]) - -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # Forcibly clear virtual_parent. ovn-controller should release the binding - # gracefully. - pb_uuid=$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=sw0-vir) -@@ -16777,6 +16921,13 @@ logical_port=sw0-vir) = x]) - - wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir - -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the flow for ACL. -+check_virtual_offlows_not_present hv2 -+ - # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir - # and sw0-p1 should be its virtual_parent. - send_garp 1 1 $eth_src $eth_dst $spa $tpa -@@ -16789,6 +16940,58 @@ logical_port=sw0-vir) = xsw0-p1]) - - wait_for_ports_up sw0-vir - -+check ovn-nbctl --wait=hv sync -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# Release sw0-p1. -+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-px -+wait_column "false" nb:Logical_Switch_Port up name=sw0-p1 -+wait_column "false" nb:Logical_Switch_Port up name=sw0-vir -+ -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# Claim sw0-p1 again. -+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-p1 -+wait_for_ports_up sw0-p1 -+ -+# hv1 should not have the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir -+# and sw0-p1 should be its virtual_parent. -+eth_src=505400000003 -+eth_dst=ffffffffffff -+spa=$(ip_to_hex 10 0 0 10) -+tpa=$(ip_to_hex 10 0 0 10) -+send_garp 1 1 $eth_src $eth_dst $spa $tpa -+ -+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid -+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1 -+wait_for_ports_up sw0-vir -+check ovn-nbctl --wait=hv sync -+ -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p3 should be its virtual_parent. - eth_src=505400000005 -@@ -16806,8 +17009,8 @@ logical_port=sw0-vir) = xsw0-p3]) - wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the --# sw0-p2's MAC. --sleep 1 -+# sw0-p3's MAC. -+check ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows lr0 > lr0-flows3 - AT_CAPTURE_FILE([lr0-flows3]) - cp ovn-sb/ovn-sb.db lr0-flows3.db -@@ -16815,6 +17018,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;) - ]) - -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir - # and sw0-p2 shpuld be its virtual_parent. - eth_src=505400000004 -@@ -16832,14 +17042,21 @@ logical_port=sw0-vir) = xsw0-p2]) - wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the --# sw0-p3's MAC. --sleep 1 -+# sw0-p2's MAC. -+check ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows lr0 > lr0-flows4 - AT_CAPTURE_FILE([lr0-flows4]) - AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) - ]) - -+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv1 -+ - # Now send arp reply from sw0-p1. hv1 should claim sw0-vir - # and sw0-p1 shpuld be its virtual_parent. - eth_src=505400000003 -@@ -16863,6 +17080,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # Delete hv1-vif1 port. hv1 should release sw0-vir - as hv1 ovs-vsctl del-port hv1-vif1 - -@@ -16883,6 +17108,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+ - # Now send arp reply from sw0-p2. hv2 should claim sw0-vir - # and sw0-p2 should be its virtual_parent. - eth_src=505400000004 -@@ -16906,6 +17140,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv1 -+ - # Delete sw0-p2 logical port - ovn-nbctl lsp-del sw0-p2 - -@@ -16933,6 +17175,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl - table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv2 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents - ovn-sbctl dump-flows sw0 > sw0-flows4 - AT_CAPTURE_FILE([sw0-flows4]) -@@ -16942,6 +17192,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8 - AT_CAPTURE_FILE([lr0-flows8]) - AT_CHECK([grep lr_in_arp_resolve lr0-flows8 | grep "reg0 == 10.0.0.10"], [1]) - -+# Delete sw0-vir and add again. -+ovn-nbctl lsp-del sw0-vir -+ -+ovn-nbctl lsp-add sw0 sw0-vir -+ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" -+ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" -+ovn-nbctl lsp-set-type sw0-vir virtual -+ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 -+ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 -+ -+ovn-nbctl --wait=hv sync -+ -+# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline -+# with bind_vport action. -+ -+ovn-sbctl dump-flows sw0 > sw0-flows -+AT_CAPTURE_FILE([sw0-flows]) -+ -+AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl -+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) -+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) -+]) -+ -+ovn-sbctl dump-flows lr0 > lr0-flows -+AT_CAPTURE_FILE([lr0-flows]) -+ -+# Since the sw0-vir is not claimed by any chassis, eth.dst should be set to -+# zero if the ip4.dst is the virtual ip in the router pipeline. -+AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl -+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) -+]) -+ - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP - -@@ -17321,6 +17603,27 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ - ofport-request=1 - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -+AT_CAPTURE_FILE([exp]) -+AT_CAPTURE_FILE([rcv]) -+check_packets() { -+ > exp -+ > rcv -+ if test "$1" = --uniq; then -+ sort="sort -u"; shift -+ else -+ sort=sort -+ fi -+ for tuple in "$@"; do -+ set $tuple; pcap=$1 type=$2 -+ echo "--- $pcap" | tee -a exp >> rcv -+ $sort "$type" >> exp -+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv -+ echo | tee -a exp >> rcv -+ done -+ -+ $at_diff exp rcv >/dev/null -+} -+ - OVN_POPULATE_ARP - - # Enable IGMP snooping on sw1. -@@ -17337,21 +17640,16 @@ ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([expected]) - AT_CAPTURE_FILE([received]) - > expected --> received --for i in 1 2; do -- for j in 1 2; do -- pcap=hv$i/vif$j-tx.pcap -- echo "--- $pcap" | tee -a expected >> received -- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received -- echo | tee -a expected >> received -- done --done --check $at_diff -F'^---' expected received -+OVS_WAIT_UNTIL( -+ [check_packets 'hv1/vif1-tx.pcap expected' \ -+ 'hv1/vif2-tx.pcap expected' \ -+ 'hv2/vif1-tx.pcap expected' \ -+ 'hv2/vif2-tx.pcap expected'], -+ [$at_diff -F'^---' exp rcv]) - - check ovn-nbctl --wait=hv sync - - AT_CAPTURE_FILE([sbflows2]) --cp ovn-sb/ovn-sb.db ovn-sb2.db - ovn-sbctl dump-flows > sbflows2 - - # Inject IGMP Join for 239.0.1.68 on sw1-p11. -@@ -17369,7 +17667,6 @@ wait_row_count IGMP_Group 2 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - - AT_CAPTURE_FILE([sbflows3]) --cp ovn-sb/ovn-sb.db ovn-sb3.db - ovn-sbctl dump-flows > sbflows3 - - AS_BOX([IGMP traffic test 1]) -@@ -17386,22 +17683,6 @@ store_ip_multicast_pkt \ - $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ - e518e518000a3b3a0000 expected - --AT_CAPTURE_FILE([exp]) --AT_CAPTURE_FILE([rcv]) --check_packets() { -- > exp -- > rcv -- for tuple in "$@"; do -- set $tuple; pcap=$1 type=$2 -- echo "--- $pcap" | tee -a exp >> rcv -- sort "$type" >> exp -- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> rcv -- echo | tee -a exp >> rcv -- done -- -- $at_diff exp rcv >/dev/null --} -- - OVS_WAIT_UNTIL( - [check_packets 'hv1/vif1-tx.pcap expected' \ - 'hv2/vif1-tx.pcap expected' \ -@@ -17492,15 +17773,26 @@ check ovn-nbctl set Logical_Switch sw2 \ - other_config:mcast_ip4_src="20.0.0.254" - - AS_BOX([IGMP traffic test 4]) --# Wait for 1 query interval (1 sec) and check that two queries are generated. -+# Check that multiple queries are generated over time. - > expected - store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected - store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected - --OVS_WAIT_UNTIL( -- [check_packets 'hv1/vif3-tx.pcap expected' \ -- 'hv2/vif3-tx.pcap expected'], -- [$at_diff -F'^---' exp rcv]) -+for count in 1 2 3; do -+ as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+ as hv1 reset_pcap_file hv1-vif2 hv1/vif2 -+ as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ as hv1 reset_pcap_file hv1-vif4 hv1/vif4 -+ as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -+ as hv2 reset_pcap_file hv2-vif2 hv2/vif2 -+ as hv2 reset_pcap_file hv2-vif3 hv2/vif3 -+ as hv2 reset_pcap_file hv2-vif4 hv2/vif4 -+ OVS_WAIT_UNTIL( -+ [check_packets --uniq \ -+ 'hv1/vif3-tx.pcap expected' \ -+ 'hv2/vif3-tx.pcap expected'], -+ [$at_diff -F'^---' exp rcv]) -+done - - # Disable IGMP querier on sw2. - check ovn-nbctl set Logical_Switch sw2 \ -@@ -19776,7 +20068,14 @@ AT_CAPTURE_FILE([sbflows]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows > sbflows - ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0, -- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) -+ [dnl -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) - ]) - - AT_CAPTURE_FILE([sbflows2]) -@@ -22463,7 +22762,7 @@ check ovn-nbctl --wait=hv sync - # wait_conj_id_count COUNT ["ID COUNT [MATCH]"]... - # - # Waits until COUNT flows matching against conj_id appear in the --# table 45 on hv1's br-int bridge. Makes the flows available in -+# table 44 on hv1's br-int bridge. Makes the flows available in - # "hv1flows", which will be logged on error. - # - # In addition, for each quoted "ID COUNT" or "ID COUNT MATCH", -@@ -22480,7 +22779,7 @@ wait_conj_id_count() { - echo "waiting for $1 conj_id flows..." - OVS_WAIT_FOR_OUTPUT_UNQUOTED( - [ovs-ofctl dump-flows br-int > hv1flows -- grep table=45 hv1flows | grep -c conj_id], -+ grep table=44 hv1flows | grep -c conj_id], - [$retval], [$1 - ]) - -@@ -22489,7 +22788,7 @@ wait_conj_id_count() { - set -- $arg; id=$1 count=$2 match=$3 - echo "checking that there are $count ${match:+$match }flows with conj_id=$id..." - AT_CHECK_UNQUOTED( -- [grep table=45 hv1flows | grep "$match" | grep -c conj_id=$id], -+ [grep table=44 hv1flows | grep "$match" | grep -c conj_id=$id], - [0], [$count - ]) - done -@@ -22514,8 +22813,8 @@ wait_conj_id_count 1 "3 1 udp" - AS_BOX([Add back the tcp ACL.]) - check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow - wait_conj_id_count 2 "3 1 udp" "4 1 tcp" --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")]) -+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep udp | grep -c "conj_id=3")]) -+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep tcp | grep -c "conj_id=4")]) - - AS_BOX([Add another tcp ACL.]) - check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow -@@ -24317,6 +24616,14 @@ as hv1 ovn-appctl -t ovn-controller debug/resume - wait_column "true" Port_Binding up logical_port=lsp1 - wait_column "true" nb:Logical_Switch_Port up name=lsp1 - -+AS_BOX([ovn-controller should set Port_Binding.up - to false when OVS port is released]) -+check ovs-vsctl remove Interface lsp1 external_ids iface-id -+check ovs-vsctl remove Interface lsp2 external_ids iface-id -+wait_column "false" Port_Binding up logical_port=lsp1 -+wait_column "false" Port_Binding up logical_port=lsp2 -+wait_column "false" Port_Binding up logical_port=lsp1 -+wait_column "false" nb:Logical_Switch_Port up name=lsp1 -+ - OVN_CLEANUP([hv1]) - AT_CLEANUP - -@@ -24454,43 +24761,43 @@ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) - check ovn-nbctl --wait=hv sync - - # Check OVS flows are installed properly. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=2002" | grep conjunction | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() - ]) - - OVN_CLEANUP([hv1]) -@@ -24918,3 +25225,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl - - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- container port changed to normal port and then deleted]) -+ovn_start -+ -+net_add n1 -+ -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ovs-vsctl -- add-port br-int vm1 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+check ovn-nbctl lsp-del vm-cont -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+ -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+check ovn-nbctl --wait=sb lsp-del vm1 -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl clear logical_switch_port vm-cont2 parent_name -+ -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-del vm1 -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl --wait=sb lsp-del vm-cont2 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl lsp-del vm-cont2 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+ -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm-cont1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "true" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm-cont1 -+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=vm-cont1 -+ -+wait_for_ports_up -+ -+# Delete vm1, vm-cont1 and vm-cont2 and recreate again. -+check ovn-nbctl lsp-del vm1 -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl --wait=hv lsp-del vm-cont2 -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+# Make vm1 as a child port of some non existent lport - foo. vm1, vm1-cont1 and -+# vm1-cont2 should be released. -+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=bar -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- container port changed from one parent to another]) -+ovn_start -+ -+net_add n1 -+ -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ovs-vsctl -- add-port br-int vm1 -- set interface vm1 ofport-request=1 -+ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm1-cont vm1 1 -+check ovn-nbctl lsp-add ls vm2 -+check ovn-nbctl lsp-add ls vm2-cont vm2 2 -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check as hv1 ovs-vsctl set Interface vm2 external_ids:iface-id=vm2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [0], [dnl -+1 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl -+1 -+]) -+ -+# change the parent of vm1-cont to vm2. -+as hv1 ovn-appctl -t ovn-controller vlog/set dbg -+check ovn-nbctl --wait=sb set logical_switch_port vm1-cont parent_name=vm2 \ -+-- set logical_switch_port vm1-cont tag_request=3 -+ -+wait_for_ports_up -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [1], [dnl -+0 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl -+1 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=3], [0], [dnl -+1 -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- container port use-after-free test]) -+ovn_start -+ -+net_add n1 -+ -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ovs-vsctl -- add-port br-int vm1 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+check ovn-nbctl lsp-del vm-cont -+check ovn-nbctl ls-del ls -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check ovn-nbctl lsp-del vm-cont -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+ -+ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Test that OVS.external_ids:iface-id doesn't affect non-VIF port bindings. -+AT_SETUP([ovn -- Non-VIF ports incremental processing]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.10 -+ -+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 -+ -+as hv1 -+check ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 -+ -+# ovn-controller should bind the interface. -+wait_for_ports_up -+hv_uuid=$(fetch_column Chassis _uuid name=hv1) -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to router, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 router -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localnet, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 localnet -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localport, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 localport -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localnet and then delete it. -+# ovn-controller should handle this properly. -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-set-type lsp1 localport -+check ovn-nbctl --wait=sb lsp-del lsp1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls1 lsp1 -+wait_for_ports_up -+ -+# Change the port type to virtual and then delete it. -+# ovn-controller should handle this properly. -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-set-type lsp1 virtual -+check ovn-nbctl --wait=sb lsp-del lsp1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Tests that ovn-controller creates local bindings correctly by running -+# ovn-appctl -t ovn-controller debug/dump-local-bindings. -+# Ideally this test case should have been a unit test case. -+AT_SETUP([ovn -- ovn-controller local bindings]) -+ovn_start -+ -+net_add n1 -+ -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ovs-vsctl -- add-port br-int hv1-vm1 -+ -+sim_add hv2 -+as hv2 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.2 -+ovs-vsctl -- add-port br-int hv2-vm1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0p1 -+check ovn-nbctl lsp-add sw0 sw0p2 -+ -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Create an ovs interface in hv1 -+check as hv1 ovs-vsctl add-port br-int hv1-vm2 -- set interface hv1-vm2 external_ids:iface-id=sw1p1 -+check ovn-nbctl --wait=hv sync -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# Create lport sw1p1 -+check ovn-nbctl ls-add sw1 -- lsp-add sw1 sw1p1 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+# Swap sw0p1 and sw0p2. -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p2 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p1 -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+]) -+ -+# Create child port for sw0p1 -+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c1 sw0p1 1 -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[2]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+---------------------------------------- -+]) -+ -+# Create another child port for sw0p1 -+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c2 sw0p1 2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+]) -+ -+# Swap sw0p1 and sw0p2 again. -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Make sw0p1 as child port of non existent lport - foo -+check ovn-nbctl --wait=hv set logical_switch_port sw0p1 parent_name=foo -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Change the lport type of sw0p2 to different types and make sure that -+# local bindings are correct. -+ -+hv2_uuid=$(fetch_column Chassis _uuid name=hv2) -+check_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 -+ -+# Change the port type to router, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 router -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to external from router. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 external -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to localnet from external. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to localport from localnet. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type back to vif. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 "" -+wait_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+OVN_CLEANUP([hv1], [hv2]) -+AT_CLEANUP -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 9819573bb..bd27b01a0 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -4722,7 +4722,7 @@ OVS_WAIT_UNTIL([ - ]) - - OVS_WAIT_UNTIL([ -- n_pkt=$(ovs-ofctl dump-flows br-int table=45 | grep -v n_packets=0 | \ -+ n_pkt=$(ovs-ofctl dump-flows br-int table=44 | grep -v n_packets=0 | \ - grep controller | grep tp_dst=84 -c) - test $n_pkt -eq 1 - ]) -@@ -5831,3 +5831,131 @@ as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d - /.*terminating with signal 15.*/d"]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- No ct_state matches in dp flows when no ACLs in an LS]) -+AT_KEYWORDS([no ct_state match]) -+ovn_start -+ -+OVS_TRAFFIC_VSWITCHD_START() -+ADD_BR([br-int]) -+ -+# Set external-ids in br-int needed for ovn-controller -+ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+# Start ovn-controller -+start_daemon ovn-controller -+ -+check ovn-nbctl ls-add sw0 -+ -+check ovn-nbctl lsp-add sw0 sw0-p1 -+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03" -+ -+check ovn-nbctl lsp-add sw0 sw0-p2 -+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+ -+ -+# Create the second logical switch with one port and configure some ACLs. -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl lsp-add sw1 sw1-p1 -+ -+# Create port group and ACLs for sw1 ports. -+check ovn-nbctl pg-add pg1 sw1-p1 -+check ovn-nbctl acl-add pg1 from-lport 1002 "ip" allow-related -+check ovn-nbctl acl-add pg1 to-lport 1002 "ip" allow-related -+ -+ -+OVN_POPULATE_ARP -+ovn-nbctl --wait=hv sync -+ -+ADD_NAMESPACES(sw0-p1) -+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \ -+ "10.0.0.1") -+ -+ -+ADD_NAMESPACES(sw0-p2) -+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \ -+ "10.0.0.1") -+ -+ADD_NAMESPACES(sw1-p1) -+ADD_VETH(sw1-p1, sw1-p1, br-int, "20.0.0.4/24", "30:54:00:00:00:04", \ -+ "20.0.0.1") -+ -+wait_for_ports_up -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+# sw1-p1 may send IPv6 traffic. So filter this out. Since sw1-p1 has -+# ACLs configured, the datapath flows for the packets from sw1-p1 will have -+# matches on ct_state and ct_label fields. -+# Since sw0 doesn't have any ACLs, there should be no match on ct fields. -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+# Add an ACL to sw0. -+check ovn-nbctl --wait=hv acl-add sw0 to-lport 1002 ip allow-related -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [0], [ignore]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [0], [ignore]) -+ -+# Clear ACL for sw0 -+check ovn-nbctl --wait=hv clear logical_switch sw0 acls -+ -+check ovs-appctl dpctl/del-flows -+ -+check ovn-nbctl --wait=hv sync -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+OVS_APP_EXIT_AND_WAIT([ovn-controller]) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -+/connection dropped.*/d"]) -+AT_CLEANUP -diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl -index 967db6d6c..c52c17ee0 100755 ---- a/utilities/ovn-ctl -+++ b/utilities/ovn-ctl -@@ -45,18 +45,12 @@ pidfile_is_running () { - test -e "$pidfile" && [ -s "$pidfile" ] && pid=`cat "$pidfile"` && pid_exists "$pid" - } >/dev/null 2>&1 - --stop_xx_ovsdb() { -- if pidfile_is_running $1; then -- ovn-appctl -t $OVN_RUNDIR/$2 exit -- fi --} -- - stop_nb_ovsdb() { -- stop_xx_ovsdb $DB_NB_PID ovnnb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnnb_db $DB_NB_PID $OVN_RUNDIR/ovnnb_db.ctl - } - - stop_sb_ovsdb() { -- stop_xx_ovsdb $DB_SB_PID ovnsb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnsb_db $DB_SB_PID $OVN_RUNDIR/ovnsb_db.ctl - } - - stop_ovsdb () { -@@ -65,11 +59,11 @@ stop_ovsdb () { - } - - stop_ic_nb_ovsdb() { -- stop_xx_ovsdb $DB_IC_NB_PID ovn_ic_nb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_nb_db $DB_IC_NB_PID $OVN_RUNDIR/ovn_ic_nb_db.ctl - } - - stop_ic_sb_ovsdb() { -- stop_xx_ovsdb $DB_IC_SB_PID ovn_ic_sb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_sb_db $DB_IC_SB_PID $OVN_RUNDIR/ovn_ic_sb_db.ctl - } - - stop_ic_ovsdb () { -@@ -590,7 +584,7 @@ stop_ic () { - } - - stop_controller () { -- OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "$@" -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "" "" "$@" - } - - stop_controller_vtep () { -diff --git a/utilities/ovn-lib.in b/utilities/ovn-lib.in -index 016815626..301cc5712 100644 ---- a/utilities/ovn-lib.in -+++ b/utilities/ovn-lib.in -@@ -137,10 +137,22 @@ start_ovn_daemon () { - } - - stop_ovn_daemon () { -- if test -e "$ovn_rundir/$1.pid"; then -- if pid=`cat "$ovn_rundir/$1.pid"`; then -+ local pid_file=$2 -+ local ctl_file=$3 -+ local other_args=$4 -+ -+ if [ -z "$pid_file" ]; then -+ pid_file="$ovn_rundir/$1.pid" -+ fi -+ -+ if test -e "$pid_file"; then -+ if pid=`cat "$pid_file"`; then -+ if [ -z "$ctl_file" ]; then -+ ctl_file="$ovn_rundir/$1.$pid.ctl" -+ fi -+ - if pid_exists "$pid" >/dev/null 2>&1; then :; else -- rm -f $ovn_rundir/$1.$pid.ctl $ovn_rundir/$1.$pid -+ rm -f $ctl_file $pid_file - return 0 - fi - -@@ -148,7 +160,7 @@ stop_ovn_daemon () { - actions="TERM .1 .25 .65 1 1 1 1 \ - KILL 1 1 1 2 10 15 30 \ - FAIL" -- version=`ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl version \ -+ version=`ovs-appctl -T 1 -t $ctl_file version \ - | awk 'NR==1{print $NF}'` - - # Use `ovs-appctl exit` only if the running daemon version -@@ -159,20 +171,36 @@ stop_ovn_daemon () { - if version_geq "$version" "2.5.90"; then - actions="$graceful $actions" - fi -+ actiontype="" - for action in $actions; do - if pid_exists "$pid" >/dev/null 2>&1; then :; else -- return 0 -+ # pid does not exist. -+ if [ -n "$actiontype" ]; then -+ return 0 -+ fi -+ # But, does the file exist? We may have had a daemon -+ # segfault with `ovs-appctl exit`. Check one more time -+ # before deciding that the daemon is dead. -+ [ -e "$pid_file" ] && sleep 2 && pid=`cat "$pid_file"` 2>/dev/null -+ if pid_exists "$pid" >/dev/null 2>&1; then :; else -+ return 0 -+ fi - fi - case $action in - EXIT) - action "Exiting $1 ($pid)" \ -- ${bindir}/ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl exit $2 -+ ${bindir}/ovs-appctl -T 1 -t $ctl_file exit $other_args -+ # The above command could have resulted in delayed -+ # daemon segfault. And if a monitor is running, it -+ # would restart the daemon giving it a new pid. - ;; - TERM) - action "Killing $1 ($pid)" kill $pid -+ actiontype="force" - ;; - KILL) - action "Killing $1 ($pid) with SIGKILL" kill -9 $pid -+ actiontype="force" - ;; - FAIL) - log_failure_msg "Killing $1 ($pid) failed" -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 2c77f4ba7..51af138c6 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3866,11 +3866,15 @@ static void - print_routing_policy(const struct nbrec_logical_router_policy *policy, - struct ds *s) - { -- if (policy->nexthop != NULL) { -- char *next_hop = normalize_prefix_str(policy->nexthop); -- ds_put_format(s, "%10"PRId64" %50s %15s %25s", policy->priority, -- policy->match, policy->action, next_hop); -- free(next_hop); -+ if (policy->n_nexthops) { -+ ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority, -+ policy->match, policy->action); -+ for (int i = 0; i < policy->n_nexthops; i++) { -+ char *next_hop = normalize_prefix_str(policy->nexthops[i]); -+ char *fmt = i ? ", %s" : " %25s"; -+ ds_put_format(s, fmt, next_hop); -+ free(next_hop); -+ } - } else { - ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority, - policy->match, policy->action); -@@ -4068,7 +4072,9 @@ nbctl_lr_route_add(struct ctl_context *ctx) - goto cleanup; - } - } else if (route) { -- ctl_error(ctx, "duplicate nexthop for the same ECMP route"); -+ if (!may_exist) { -+ ctl_error(ctx, "duplicate nexthop for the same ECMP route"); -+ } - goto cleanup; - } - -diff --git a/utilities/ovndb-servers.ocf b/utilities/ovndb-servers.ocf -index 7351c7d64..eba9c97a1 100755 ---- a/utilities/ovndb-servers.ocf -+++ b/utilities/ovndb-servers.ocf -@@ -259,6 +259,9 @@ ovsdb_server_notify() { - ovn-nbctl -- --id=@conn_uuid create Connection \ - target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" \ - inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid -+ else -+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) -+ ovn-nbctl set connection "${CONN_UID}" target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" - fi - - conn=`ovn-sbctl get SB_global . connections` -@@ -267,6 +270,9 @@ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid - ovn-sbctl -- --id=@conn_uuid create Connection \ - target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" \ - inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid -+ else -+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) -+ ovn-sbctl set connection "${CONN_UID}" target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" - fi - - else diff --git a/SOURCES/ovn-21.06.0.patch b/SOURCES/ovn-21.06.0.patch deleted file mode 100644 index a7d4f84..0000000 --- a/SOURCES/ovn-21.06.0.patch +++ /dev/null @@ -1,4279 +0,0 @@ -diff --git a/AUTHORS.rst b/AUTHORS.rst -index 9f9b4fbaa..c243c5358 100644 ---- a/AUTHORS.rst -+++ b/AUTHORS.rst -@@ -271,6 +271,7 @@ Miguel Angel Ajo majopela@redhat.com - Mijo Safradin mijo@linux.vnet.ibm.com - Mika Vaisanen mika.vaisanen@gmail.com - Minoru TAKAHASHI takahashi.minoru7@gmail.com -+Mohammad Heib mheib@redhat.com - Moshe Levi moshele@mellanox.com - Murphy McCauley murphy.mccauley@gmail.com - Natasha Gude -diff --git a/NEWS b/NEWS -index 839ab2cfe..237a9d8f6 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,7 @@ -+OVN v21.06.1 - xx xxx xxxx -+-------------------------- -+ - Allow static routes without nexthops. -+ - OVN v21.06.0 - 18 Jun 2021 - ------------------------- - - ovn-northd-ddlog: New implementation of northd, based on DDlog. This -diff --git a/TODO.rst b/TODO.rst -index c89fe203e..618ea4844 100644 ---- a/TODO.rst -+++ b/TODO.rst -@@ -164,3 +164,9 @@ OVN To-do List - 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. -+ -+* ovn-controller Incremental processing -+ -+ * physical.c has a global simap -localvif_to_ofport which stores the -+ local OVS interfaces and the ofport numbers. Move this to the engine data -+ of the engine data node - ed_type_pflow_output. -diff --git a/configure.ac b/configure.ac -index 53034388a..a1cdcb7a9 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -13,7 +13,7 @@ - # limitations under the License. - - AC_PREREQ(2.63) --AC_INIT(ovn, 21.06.0, bugs@openvswitch.org) -+AC_INIT(ovn, 21.06.1, bugs@openvswitch.org) - AC_CONFIG_MACRO_DIR([m4]) - AC_CONFIG_AUX_DIR([build-aux]) - AC_CONFIG_HEADERS([config.h]) -diff --git a/controller/binding.c b/controller/binding.c -index 7fde0fdbb..ba558efdb 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -22,6 +22,7 @@ - #include "patch.h" - - #include "lib/bitmap.h" -+#include "lib/hmapx.h" - #include "openvswitch/poll-loop.h" - #include "lib/sset.h" - #include "lib/util.h" -@@ -108,6 +109,7 @@ add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, - hmap_insert(local_datapaths, &ld->hmap_node, dp_key); - ld->datapath = datapath; - ld->localnet_port = NULL; -+ shash_init(&ld->external_ports); - ld->has_local_l3gateway = has_local_l3gateway; - - if (tracked_datapaths) { -@@ -474,6 +476,18 @@ is_network_plugged(const struct sbrec_port_binding *binding_rec, - return network ? !!shash_find_data(bridge_mappings, network) : false; - } - -+static void -+update_ld_external_ports(const struct sbrec_port_binding *binding_rec, -+ struct hmap *local_datapaths) -+{ -+ struct local_datapath *ld = get_local_datapath( -+ local_datapaths, binding_rec->datapath->tunnel_key); -+ if (ld) { -+ shash_replace(&ld->external_ports, binding_rec->logical_port, -+ binding_rec); -+ } -+} -+ - static void - update_ld_localnet_port(const struct sbrec_port_binding *binding_rec, - struct shash *bridge_mappings, -@@ -531,38 +545,41 @@ remove_local_lports(const char *iface_id, struct binding_ctx_out *b_ctx) - } - } - --/* Add a port binding ID (of the form "dp-key"_"port-key") to the set of local -- * lport IDs. Also track if the set has changed. -+/* Add a port binding to the set of locally relevant lports. -+ * Also track if the set has changed. - */ - static void --update_local_lport_ids(const struct sbrec_port_binding *pb, -- struct binding_ctx_out *b_ctx) -+update_related_lport(const struct sbrec_port_binding *pb, -+ struct binding_ctx_out *b_ctx) - { - char buf[16]; - get_unique_lport_key(pb->datapath->tunnel_key, pb->tunnel_key, - buf, sizeof(buf)); -- if (sset_add(b_ctx->local_lport_ids, buf) != NULL) { -- b_ctx->local_lport_ids_changed = true; -+ if (sset_add(&b_ctx->related_lports->lport_ids, buf) != NULL) { -+ b_ctx->related_lports_changed = true; - - if (b_ctx->tracked_dp_bindings) { - /* Add the 'pb' to the tracked_datapaths. */ - tracked_binding_datapath_lport_add(pb, b_ctx->tracked_dp_bindings); - } - } -+ sset_add(&b_ctx->related_lports->lport_names, pb->logical_port); - } - --/* Remove a port binding id from the set of local lport IDs. Also track if -- * the set has changed. -+/* Remove a port binding id from the set of locally relevant lports. -+ * Also track if the set has changed. - */ - static void --remove_local_lport_ids(const struct sbrec_port_binding *pb, -- struct binding_ctx_out *b_ctx) -+remove_related_lport(const struct sbrec_port_binding *pb, -+ struct binding_ctx_out *b_ctx) - { - char buf[16]; - get_unique_lport_key(pb->datapath->tunnel_key, pb->tunnel_key, - buf, sizeof(buf)); -- if (sset_find_and_delete(b_ctx->local_lport_ids, buf)) { -- b_ctx->local_lport_ids_changed = true; -+ sset_find_and_delete(&b_ctx->related_lports->lport_names, -+ pb->logical_port); -+ if (sset_find_and_delete(&b_ctx->related_lports->lport_ids, buf)) { -+ b_ctx->related_lports_changed = true; - - if (b_ctx->tracked_dp_bindings) { - /* Add the 'pb' to the tracked_datapaths. */ -@@ -678,6 +695,20 @@ static struct binding_lport *binding_lport_check_and_cleanup( - - static char *get_lport_type_str(enum en_lport_type lport_type); - -+void -+related_lports_init(struct related_lports *rp) -+{ -+ sset_init(&rp->lport_names); -+ sset_init(&rp->lport_ids); -+} -+ -+void -+related_lports_destroy(struct related_lports *rp) -+{ -+ sset_destroy(&rp->lport_names); -+ sset_destroy(&rp->lport_ids); -+} -+ - void - local_binding_data_init(struct local_binding_data *lbinding_data) - { -@@ -1172,7 +1203,7 @@ release_binding_lport(const struct sbrec_chassis *chassis_rec, - struct binding_ctx_out *b_ctx_out) - { - if (is_binding_lport_this_chassis(b_lport, chassis_rec)) { -- remove_local_lport_ids(b_lport->pb, b_ctx_out); -+ remove_related_lport(b_lport->pb, b_ctx_out); - if (!release_lport(b_lport->pb, sb_readonly, - b_ctx_out->tracked_dp_bindings, - b_ctx_out->if_mgr)) { -@@ -1214,7 +1245,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - pb->datapath, false, - b_ctx_out->local_datapaths, - b_ctx_out->tracked_dp_bindings); -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - update_local_lports(pb->logical_port, b_ctx_out); - if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { - get_qos_params(pb, qos_map); -@@ -1405,7 +1436,7 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, - * its entry from the local_lport_ids if present. This is required - * when a virtual port moves from one chassis to other.*/ - if (!virtual_b_lport) { -- remove_local_lport_ids(pb, b_ctx_out); -+ remove_related_lport(pb, b_ctx_out); - } - - return true; -@@ -1430,7 +1461,7 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, - b_ctx_out->local_datapaths, - b_ctx_out->tracked_dp_bindings); - -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL, - !b_ctx_in->ovnsb_idl_txn, false, - b_ctx_out->tracked_dp_bindings, -@@ -1482,7 +1513,7 @@ consider_localnet_lport(const struct sbrec_port_binding *pb, - get_qos_params(pb, qos_map); - } - -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - } - - static bool -@@ -1512,7 +1543,7 @@ consider_ha_lport(const struct sbrec_port_binding *pb, - pb->datapath, false, - b_ctx_out->local_datapaths, - b_ctx_out->tracked_dp_bindings); -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - } - - return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out); -@@ -1614,8 +1645,9 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - !sset_is_empty(b_ctx_out->egress_ifaces) ? &qos_map : NULL; - - struct ovs_list localnet_lports = OVS_LIST_INITIALIZER(&localnet_lports); -+ struct ovs_list external_lports = OVS_LIST_INITIALIZER(&external_lports); - -- struct localnet_lport { -+ struct lport { - struct ovs_list list_node; - const struct sbrec_port_binding *pb; - }; -@@ -1634,7 +1666,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - case LP_PATCH: - case LP_LOCALPORT: - case LP_VTEP: -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - break; - - case LP_VIF: -@@ -1663,11 +1695,14 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - - case LP_EXTERNAL: - consider_external_lport(pb, b_ctx_in, b_ctx_out); -+ struct lport *ext_lport = xmalloc(sizeof *ext_lport); -+ ext_lport->pb = pb; -+ ovs_list_push_back(&external_lports, &ext_lport->list_node); - break; - - case LP_LOCALNET: { - consider_localnet_lport(pb, b_ctx_in, b_ctx_out, &qos_map); -- struct localnet_lport *lnet_lport = xmalloc(sizeof *lnet_lport); -+ struct lport *lnet_lport = xmalloc(sizeof *lnet_lport); - lnet_lport->pb = pb; - ovs_list_push_back(&localnet_lports, &lnet_lport->list_node); - break; -@@ -1694,7 +1729,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - /* Run through each localnet lport list to see if it is a localnet port - * on local datapaths discovered from above loop, and update the - * corresponding local datapath accordingly. */ -- struct localnet_lport *lnet_lport; -+ struct lport *lnet_lport; - LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) { - update_ld_localnet_port(lnet_lport->pb, &bridge_mappings, - b_ctx_out->egress_ifaces, -@@ -1702,6 +1737,15 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - free(lnet_lport); - } - -+ /* Run through external lport list to see if these are external ports -+ * on local datapaths discovered from above loop, and update the -+ * corresponding local datapath accordingly. */ -+ struct lport *ext_lport; -+ LIST_FOR_EACH_POP (ext_lport, list_node, &external_lports) { -+ update_ld_external_ports(ext_lport->pb, b_ctx_out->local_datapaths); -+ free(ext_lport); -+ } -+ - shash_destroy(&bridge_mappings); - - if (!sset_is_empty(b_ctx_out->egress_ifaces) -@@ -1895,7 +1939,7 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb, - struct binding_ctx_out *b_ctx_out, - struct local_datapath *ld) - { -- remove_local_lport_ids(pb, b_ctx_out); -+ remove_related_lport(pb, b_ctx_out); - if (!strcmp(pb->type, "patch") || - !strcmp(pb->type, "l3gateway")) { - remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths); -@@ -1904,6 +1948,8 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb, - pb->logical_port)) { - ld->localnet_port = NULL; - } -+ } else if (!strcmp(pb->type, "external")) { -+ shash_find_and_delete(&ld->external_ports, pb->logical_port); - } - - if (!strcmp(pb->type, "l3gateway")) { -@@ -2407,6 +2453,9 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - shash_add(&deleted_virtual_pbs, pb->logical_port, pb); - } else { - shash_add(&deleted_other_pbs, pb->logical_port, pb); -+ if (lport_type == LP_EXTERNAL) { -+ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath); -+ } - } - } - -@@ -2502,7 +2551,7 @@ delete_done: - case LP_PATCH: - case LP_LOCALPORT: - case LP_VTEP: -- update_local_lport_ids(pb, b_ctx_out); -+ update_related_lport(pb, b_ctx_out); - if (lport_type == LP_PATCH) { - if (!ld) { - /* If 'ld' for this lport is not present, then check if -@@ -2561,6 +2610,8 @@ delete_done: - - case LP_EXTERNAL: - handled = consider_external_lport(pb, b_ctx_in, b_ctx_out); -+ update_ld_external_ports(pb, b_ctx_out->local_datapaths); -+ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath); - break; - - case LP_LOCALNET: { -@@ -2926,23 +2977,3 @@ cleanup: - - return b_lport; - } -- --struct sset * --binding_collect_local_binding_lports(struct local_binding_data *lbinding_data) --{ -- struct sset *lports = xzalloc(sizeof *lports); -- sset_init(lports); -- struct shash_node *shash_node; -- SHASH_FOR_EACH (shash_node, &lbinding_data->lports) { -- struct binding_lport *b_lport = shash_node->data; -- sset_add(lports, b_lport->name); -- } -- return lports; --} -- --void --binding_destroy_local_binding_lports(struct sset *lports) --{ -- sset_destroy(lports); -- free(lports); --} -diff --git a/controller/binding.h b/controller/binding.h -index 8f3289476..8fd54092e 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -22,6 +22,7 @@ - #include "openvswitch/hmap.h" - #include "openvswitch/uuid.h" - #include "openvswitch/list.h" -+#include "sset.h" - - struct hmap; - struct ovsdb_idl; -@@ -56,6 +57,19 @@ struct binding_ctx_in { - const struct ovsrec_interface_table *iface_table; - }; - -+/* Locally relevant port bindings, e.g., VIFs that might be bound locally, -+ * patch ports. -+ */ -+struct related_lports { -+ struct sset lport_names; /* Set of port names. */ -+ struct sset lport_ids; /* Set of _ -+ * IDs for fast lookup. -+ */ -+}; -+ -+void related_lports_init(struct related_lports *); -+void related_lports_destroy(struct related_lports *); -+ - struct binding_ctx_out { - struct hmap *local_datapaths; - struct local_binding_data *lbinding_data; -@@ -65,11 +79,9 @@ struct binding_ctx_out { - /* Track if local_lports have been updated. */ - bool local_lports_changed; - -- /* sset of local lport ids in the format -- * _. */ -- struct sset *local_lport_ids; -- /* Track if local_lport_ids has been updated. */ -- bool local_lport_ids_changed; -+ /* Port bindings that are relevant to the local chassis. */ -+ struct related_lports *related_lports; -+ bool related_lports_changed; - - /* Track if non-vif port bindings (e.g., patch, external) have been - * added/deleted. -@@ -88,6 +100,8 @@ struct binding_ctx_out { - struct hmap *tracked_dp_bindings; - - struct if_status_mgr *if_mgr; -+ -+ struct hmapx *extport_updated_datapaths; - }; - - struct local_binding_data { -@@ -133,13 +147,4 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *, - void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); - - void binding_dump_local_bindings(struct local_binding_data *, struct ds *); -- --/* Generates a sset of lport names from local_binding_data. -- * Note: the caller is responsible for destroying and freeing the returned -- * sset, by calling binding_detroy_local_binding_lports(). */ --struct sset *binding_collect_local_binding_lports(struct local_binding_data *); -- --/* Destroy and free the lports sset returned by -- * binding_collect_local_binding_lports(). */ --void binding_destroy_local_binding_lports(struct sset *lports); - #endif /* controller/binding.h */ -diff --git a/controller/lflow.c b/controller/lflow.c -index 680b8cca1..4270d0a33 100644 ---- a/controller/lflow.c -+++ b/controller/lflow.c -@@ -611,7 +611,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow, - get_unique_lport_key(dp_id, port_id, buf, sizeof(buf)); - lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_PORTBINDING, buf, - &lflow->header_.uuid); -- if (!sset_contains(l_ctx_in->local_lport_ids, buf)) { -+ if (!sset_contains(l_ctx_in->related_lport_ids, buf)) { - VLOG_DBG("lflow "UUID_FMT - " port %s in match is not local, skip", - UUID_ARGS(&lflow->header_.uuid), -diff --git a/controller/lflow.h b/controller/lflow.h -index 3c929d8a6..076b05beb 100644 ---- a/controller/lflow.h -+++ b/controller/lflow.h -@@ -143,7 +143,7 @@ struct lflow_ctx_in { - const struct shash *addr_sets; - const struct shash *port_groups; - const struct sset *active_tunnels; -- const struct sset *local_lport_ids; -+ const struct sset *related_lport_ids; - }; - - struct lflow_ctx_out { -diff --git a/controller/ofctrl.c b/controller/ofctrl.c -index c29c3d180..053631590 100644 ---- a/controller/ofctrl.c -+++ b/controller/ofctrl.c -@@ -173,7 +173,7 @@ struct sb_flow_ref { - struct uuid sb_uuid; - }; - --/* A installed flow, in static variable installed_flows. -+/* An installed flow, in static variable installed_lflows/installed_pflows. - * - * Installed flows are updated in ofctrl_put for maintaining the flow - * installation to OVS. They are updated according to desired flows: either by -@@ -234,7 +234,7 @@ static struct desired_flow *desired_flow_lookup_conjunctive( - static void desired_flow_destroy(struct desired_flow *); - - static struct installed_flow *installed_flow_lookup( -- const struct ovn_flow *target); -+ const struct ovn_flow *target, struct hmap *installed_flows); - 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 *); -@@ -302,9 +302,12 @@ static ovs_be32 xid, xid2; - * zero, to avoid unbounded buffering. */ - static struct rconn_packet_counter *tx_counter; - --/* Flow table of "struct ovn_flow"s, that holds the flow table currently -- * installed in the switch. */ --static struct hmap installed_flows; -+/* Flow table of "struct ovn_flow"s, that holds the logical flow table -+ * currently installed in the switch. */ -+static struct hmap installed_lflows; -+/* Flow table of "struct ovn_flow"s, that holds the physical flow table -+ * currently installed in the switch. */ -+static struct hmap installed_pflows; - - /* A reference to the group_table. */ - static struct ovn_extend_table *groups; -@@ -343,7 +346,8 @@ ofctrl_init(struct ovn_extend_table *group_table, - swconn = rconn_create(inactivity_probe_interval, 0, - DSCP_DEFAULT, 1 << OFP15_VERSION); - tx_counter = rconn_packet_counter_create(); -- hmap_init(&installed_flows); -+ hmap_init(&installed_lflows); -+ hmap_init(&installed_pflows); - ovs_list_init(&flow_updates); - ovn_init_symtab(&symtab); - groups = group_table; -@@ -1426,11 +1430,12 @@ desired_flow_lookup_conjunctive(struct ovn_desired_flow_table *flow_table, - /* 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 * --installed_flow_lookup(const struct ovn_flow *target) -+installed_flow_lookup(const struct ovn_flow *target, -+ struct hmap *installed_flows) - { - struct installed_flow *i; - HMAP_FOR_EACH_WITH_HASH (i, match_hmap_node, target->hash, -- &installed_flows) { -+ installed_flows) { - struct ovn_flow *f = &i->flow; - if (f->table_id == target->table_id - && f->priority == target->priority -@@ -1542,8 +1547,14 @@ static void - ovn_installed_flow_table_clear(void) - { - struct installed_flow *f, *next; -- HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_flows) { -- hmap_remove(&installed_flows, &f->match_hmap_node); -+ HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_lflows) { -+ hmap_remove(&installed_lflows, &f->match_hmap_node); -+ unlink_all_refs_for_installed_flow(f); -+ installed_flow_destroy(f); -+ } -+ -+ HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_pflows) { -+ hmap_remove(&installed_pflows, &f->match_hmap_node); - unlink_all_refs_for_installed_flow(f); - installed_flow_destroy(f); - } -@@ -1553,7 +1564,8 @@ static void - ovn_installed_flow_table_destroy(void) - { - ovn_installed_flow_table_clear(); -- hmap_destroy(&installed_flows); -+ hmap_destroy(&installed_lflows); -+ hmap_destroy(&installed_pflows); - } - - /* Flow table update. */ -@@ -1829,6 +1841,7 @@ installed_flow_del(struct ovn_flow *i, - static void - update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - struct ofputil_bundle_ctrl_msg *bc, -+ struct hmap *installed_flows, - struct ovs_list *msgs) - { - ovs_assert(ovs_list_is_empty(&flow_table->tracked_flows)); -@@ -1836,7 +1849,7 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - * longer desired, delete them; if any of them should have different - * actions, update them. */ - struct installed_flow *i, *next; -- HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) { -+ 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); - if (!d) { -@@ -1845,7 +1858,7 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - installed_flow_del(&i->flow, bc, msgs); - ovn_flow_log(&i->flow, "removing installed"); - -- hmap_remove(&installed_flows, &i->match_hmap_node); -+ hmap_remove(installed_flows, &i->match_hmap_node); - installed_flow_destroy(i); - } else { - if (!ofpacts_equal(i->flow.ofpacts, i->flow.ofpacts_len, -@@ -1863,14 +1876,14 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table, - * in the installed flow table. */ - struct desired_flow *d; - HMAP_FOR_EACH (d, match_hmap_node, &flow_table->match_flow_table) { -- i = installed_flow_lookup(&d->flow); -+ i = installed_flow_lookup(&d->flow, installed_flows); - if (!i) { - ovn_flow_log(&d->flow, "adding installed"); - installed_flow_add(&d->flow, bc, msgs); - - /* Copy 'd' from 'flow_table' to installed_flows. */ - i = installed_flow_dup(d); -- hmap_insert(&installed_flows, &i->match_hmap_node, i->flow.hash); -+ 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 -@@ -1961,6 +1974,7 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table) - static void - update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - struct ofputil_bundle_ctrl_msg *bc, -+ struct hmap *installed_flows, - struct ovs_list *msgs) - { - merge_tracked_flows(flow_table); -@@ -1979,7 +1993,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - installed_flow_del(&i->flow, bc, msgs); - ovn_flow_log(&i->flow, "removing installed (tracked)"); - -- hmap_remove(&installed_flows, &i->match_hmap_node); -+ hmap_remove(installed_flows, &i->match_hmap_node); - installed_flow_destroy(i); - } else if (was_active) { - /* There are other desired flow(s) referencing this -@@ -1993,7 +2007,8 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - desired_flow_destroy(f); - } else { - /* The desired flow was added or modified. */ -- struct installed_flow *i = installed_flow_lookup(&f->flow); -+ struct installed_flow *i = installed_flow_lookup(&f->flow, -+ installed_flows); - if (!i) { - /* Adding a new flow. */ - installed_flow_add(&f->flow, bc, msgs); -@@ -2001,7 +2016,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table, - - /* Copy 'f' from 'flow_table' to installed_flows. */ - struct installed_flow *new_node = installed_flow_dup(f); -- hmap_insert(&installed_flows, &new_node->match_hmap_node, -+ hmap_insert(installed_flows, &new_node->match_hmap_node, - new_node->flow.hash); - link_installed_to_desired(new_node, f); - } else if (installed_flow_get_active(i) == f) { -@@ -2055,16 +2070,19 @@ ofctrl_can_put(void) - * - * This should be called after ofctrl_run() within the main loop. */ - void --ofctrl_put(struct ovn_desired_flow_table *flow_table, -+ofctrl_put(struct ovn_desired_flow_table *lflow_table, -+ struct ovn_desired_flow_table *pflow_table, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *meter_table, - uint64_t req_cfg, -- bool flow_changed) -+ bool lflows_changed, -+ bool pflows_changed) - { - static bool skipped_last_time = false; - static uint64_t old_req_cfg = 0; - bool need_put = false; -- if (flow_changed || skipped_last_time || need_reinstall_flows) { -+ if (lflows_changed || pflows_changed || skipped_last_time || -+ need_reinstall_flows) { - need_put = true; - old_req_cfg = req_cfg; - } else if (req_cfg != old_req_cfg) { -@@ -2093,7 +2111,6 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - return; - } - -- skipped_last_time = false; - need_reinstall_flows = false; - - /* OpenFlow messages to send to the switch to bring it up-to-date. */ -@@ -2159,12 +2176,35 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - bundle_open = ofputil_encode_bundle_ctrl_request(OFP15_VERSION, &bc); - ovs_list_push_back(&msgs, &bundle_open->list_node); - -- if (flow_table->change_tracked) { -- update_installed_flows_by_track(flow_table, &bc, &msgs); -- } else { -- update_installed_flows_by_compare(flow_table, &bc, &msgs); -+ /* If skipped last time, then process the flow table -+ * (tracked) flows even if lflows_changed is not set. -+ * Same for pflows_changed. */ -+ if (lflows_changed || skipped_last_time) { -+ if (lflow_table->change_tracked) { -+ update_installed_flows_by_track(lflow_table, &bc, -+ &installed_lflows, -+ &msgs); -+ } else { -+ update_installed_flows_by_compare(lflow_table, &bc, -+ &installed_lflows, -+ &msgs); -+ } -+ } -+ -+ if (pflows_changed || skipped_last_time) { -+ if (pflow_table->change_tracked) { -+ update_installed_flows_by_track(pflow_table, &bc, -+ &installed_pflows, -+ &msgs); -+ } else { -+ update_installed_flows_by_compare(pflow_table, &bc, -+ &installed_pflows, -+ &msgs); -+ } - } - -+ skipped_last_time = false; -+ - if (ovs_list_back(&msgs) == &bundle_open->list_node) { - /* No flow updates. Removing the bundle open request. */ - ovs_list_pop_back(&msgs); -@@ -2287,8 +2327,11 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table, - cur_cfg = req_cfg; - } - -- flow_table->change_tracked = true; -- ovs_assert(ovs_list_is_empty(&flow_table->tracked_flows)); -+ lflow_table->change_tracked = true; -+ ovs_assert(ovs_list_is_empty(&lflow_table->tracked_flows)); -+ -+ pflow_table->change_tracked = true; -+ ovs_assert(ovs_list_is_empty(&pflow_table->tracked_flows)); - } - - /* Looks up the logical port with the name 'port_name' in 'br_int_'. If -diff --git a/controller/ofctrl.h b/controller/ofctrl.h -index 88769566a..ead8088c5 100644 ---- a/controller/ofctrl.h -+++ b/controller/ofctrl.h -@@ -52,11 +52,13 @@ void ofctrl_init(struct ovn_extend_table *group_table, - void ofctrl_run(const struct ovsrec_bridge *br_int, - struct shash *pending_ct_zones); - enum mf_field_id ofctrl_get_mf_field_id(void); --void ofctrl_put(struct ovn_desired_flow_table *, -+void ofctrl_put(struct ovn_desired_flow_table *lflow_table, -+ struct ovn_desired_flow_table *pflow_table, - struct shash *pending_ct_zones, - const struct sbrec_meter_table *, - uint64_t nb_cfg, -- bool flow_changed); -+ bool lflow_changed, -+ bool pflow_changed); - bool ofctrl_can_put(void); - void ofctrl_wait(void); - void ofctrl_destroy(void); -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 07c6fcfd1..ea03638a9 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -46,6 +46,7 @@ - #include "openvswitch/vconn.h" - #include "openvswitch/vlog.h" - #include "ovn/actions.h" -+#include "ovn/features.h" - #include "lib/chassis-index.h" - #include "lib/extend-table.h" - #include "lib/ip-mcast-index.h" -@@ -88,6 +89,7 @@ static unixctl_cb_func lflow_cache_show_stats_cmd; - static unixctl_cb_func debug_delay_nb_cfg_report; - - #define DEFAULT_BRIDGE_NAME "br-int" -+#define DEFAULT_DATAPATH "system" - #define DEFAULT_PROBE_INTERVAL_MSEC 5000 - #define OFCTRL_DEFAULT_PROBE_INTERVAL_SEC 0 - -@@ -319,10 +321,6 @@ static const struct ovsrec_bridge * - create_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_open_vswitch_table *ovs_table) - { -- if (!ovs_idl_txn) { -- return NULL; -- } -- - const struct ovsrec_open_vswitch *cfg; - cfg = ovsrec_open_vswitch_table_first(ovs_table); - if (!cfg) { -@@ -386,6 +384,21 @@ create_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - return bridge; - } - -+static const struct ovsrec_datapath * -+create_br_datapath(struct ovsdb_idl_txn *ovs_idl_txn, -+ const struct ovsrec_open_vswitch *cfg, -+ const char *datapath_type) -+{ -+ ovsdb_idl_txn_add_comment(ovs_idl_txn, -+ "ovn-controller: creating bridge datapath '%s'", -+ datapath_type); -+ -+ struct ovsrec_datapath *dp = ovsrec_datapath_insert(ovs_idl_txn); -+ ovsrec_open_vswitch_verify_datapaths(cfg); -+ ovsrec_open_vswitch_update_datapaths_setkey(cfg, datapath_type, dp); -+ return dp; -+} -+ - static const struct ovsrec_bridge * - get_br_int(const struct ovsrec_bridge_table *bridge_table, - const struct ovsrec_open_vswitch_table *ovs_table) -@@ -399,33 +412,69 @@ get_br_int(const struct ovsrec_bridge_table *bridge_table, - return get_bridge(bridge_table, br_int_name(cfg)); - } - --static const struct ovsrec_bridge * -+static const struct ovsrec_datapath * -+get_br_datapath(const struct ovsrec_open_vswitch *cfg, -+ const char *datapath_type) -+{ -+ for (size_t i = 0; i < cfg->n_datapaths; i++) { -+ if (!strcmp(cfg->key_datapaths[i], datapath_type)) { -+ return cfg->value_datapaths[i]; -+ } -+ } -+ return NULL; -+} -+ -+static void - process_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - const struct ovsrec_bridge_table *bridge_table, -- const struct ovsrec_open_vswitch_table *ovs_table) -+ const struct ovsrec_open_vswitch_table *ovs_table, -+ const struct ovsrec_bridge **br_int_, -+ const struct ovsrec_datapath **br_int_dp_) - { -- const struct ovsrec_bridge *br_int = get_br_int(bridge_table, -- ovs_table); -- if (!br_int) { -- br_int = create_br_int(ovs_idl_txn, ovs_table); -- } -- if (br_int && ovs_idl_txn) { -- const struct ovsrec_open_vswitch *cfg; -- cfg = ovsrec_open_vswitch_table_first(ovs_table); -- ovs_assert(cfg); -- const char *datapath_type = smap_get(&cfg->external_ids, -- "ovn-bridge-datapath-type"); -- /* Check for the datapath_type and set it only if it is defined in -- * cfg. */ -- if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) { -- ovsrec_bridge_set_datapath_type(br_int, datapath_type); -+ const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); -+ const struct ovsrec_datapath *br_int_dp = NULL; -+ -+ ovs_assert(br_int_ && br_int_dp_); -+ if (ovs_idl_txn) { -+ if (!br_int) { -+ br_int = create_br_int(ovs_idl_txn, ovs_table); - } -- if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) { -- ovsrec_bridge_set_fail_mode(br_int, "secure"); -- VLOG_WARN("Integration bridge fail-mode changed to 'secure'."); -+ -+ if (br_int) { -+ const struct ovsrec_open_vswitch *cfg = -+ ovsrec_open_vswitch_table_first(ovs_table); -+ ovs_assert(cfg); -+ -+ /* Propagate "ovn-bridge-datapath-type" from OVS table, if any. -+ * Otherwise use the datapath-type set in br-int, if any. -+ * Finally, assume "system" datapath if none configured. -+ */ -+ const char *datapath_type = -+ smap_get(&cfg->external_ids, "ovn-bridge-datapath-type"); -+ -+ if (!datapath_type) { -+ if (br_int->datapath_type[0]) { -+ datapath_type = br_int->datapath_type; -+ } else { -+ datapath_type = DEFAULT_DATAPATH; -+ } -+ } -+ if (strcmp(br_int->datapath_type, datapath_type)) { -+ ovsrec_bridge_set_datapath_type(br_int, datapath_type); -+ } -+ if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) { -+ ovsrec_bridge_set_fail_mode(br_int, "secure"); -+ VLOG_WARN("Integration bridge fail-mode changed to 'secure'."); -+ } -+ br_int_dp = get_br_datapath(cfg, datapath_type); -+ if (!br_int_dp) { -+ br_int_dp = create_br_datapath(ovs_idl_txn, cfg, -+ datapath_type); -+ } - } - } -- return br_int; -+ *br_int_ = br_int; -+ *br_int_dp_ = br_int_dp; - } - - static const char * -@@ -563,7 +612,7 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones, - 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 hmapx *updated_dps) -+ struct shash *pending_ct_zones) - { - struct simap_node *ct_zone, *ct_zone_next; - int scan_start = 1; -@@ -653,11 +702,6 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, - - 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); -- } - } - - /* xxx This is wasteful to assign a zone to each port--even if no -@@ -686,12 +730,6 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, - - 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); -@@ -848,6 +886,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids); - ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges); -+ ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_datapaths); - ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); - ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); -@@ -870,6 +909,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate); - ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key); -+ ovsdb_idl_add_table(ovs_idl, &ovsrec_table_datapath); -+ ovsdb_idl_add_column(ovs_idl, &ovsrec_datapath_col_capabilities); - chassis_register_ovs_idl(ovs_idl); - encaps_register_ovs_idl(ovs_idl); - binding_register_ovs_idl(ovs_idl); -@@ -970,9 +1011,10 @@ struct ed_type_runtime_data { - * local hypervisor, and localnet ports. */ - struct sset local_lports; - -- /* Contains the same ports as local_lports, but in the format: -- * _ */ -- struct sset local_lport_ids; -+ /* Port bindings that are relevant to the local chassis (VIFs bound -+ * localy, patch ports). -+ */ -+ struct related_lports related_lports; - struct sset active_tunnels; - - /* runtime data engine private data. */ -@@ -986,6 +1028,9 @@ struct ed_type_runtime_data { - - /* CT zone data. Contains datapaths that had updated CT zones */ - struct hmapx ct_updated_datapaths; -+ -+ /* Contains datapaths that had updated external ports. */ -+ struct hmapx extport_updated_datapaths; - }; - - /* struct ed_type_runtime_data has the below members for tracking the -@@ -1068,7 +1113,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, - - hmap_init(&data->local_datapaths); - sset_init(&data->local_lports); -- sset_init(&data->local_lport_ids); -+ related_lports_init(&data->related_lports); - sset_init(&data->active_tunnels); - sset_init(&data->egress_ifaces); - smap_init(&data->local_iface_ids); -@@ -1078,6 +1123,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, - hmap_init(&data->tracked_dp_bindings); - - hmapx_init(&data->ct_updated_datapaths); -+ hmapx_init(&data->extport_updated_datapaths); - - return data; - } -@@ -1088,7 +1134,7 @@ en_runtime_data_cleanup(void *data) - struct ed_type_runtime_data *rt_data = data; - - sset_destroy(&rt_data->local_lports); -- sset_destroy(&rt_data->local_lport_ids); -+ related_lports_destroy(&rt_data->related_lports); - sset_destroy(&rt_data->active_tunnels); - sset_destroy(&rt_data->egress_ifaces); - smap_destroy(&rt_data->local_iface_ids); -@@ -1096,12 +1142,14 @@ en_runtime_data_cleanup(void *data) - HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, - &rt_data->local_datapaths) { - free(cur_node->peer_ports); -+ shash_destroy(&cur_node->external_ports); - hmap_remove(&rt_data->local_datapaths, &cur_node->hmap_node); - free(cur_node); - } - hmap_destroy(&rt_data->local_datapaths); - local_binding_data_destroy(&rt_data->lbinding_data); - hmapx_destroy(&rt_data->ct_updated_datapaths); -+ hmapx_destroy(&rt_data->extport_updated_datapaths); - } - - static void -@@ -1181,14 +1229,15 @@ init_binding_ctx(struct engine_node *node, - b_ctx_out->local_datapaths = &rt_data->local_datapaths; - b_ctx_out->local_lports = &rt_data->local_lports; - b_ctx_out->local_lports_changed = false; -- b_ctx_out->local_lport_ids = &rt_data->local_lport_ids; -- b_ctx_out->local_lport_ids_changed = false; -+ b_ctx_out->related_lports = &rt_data->related_lports; -+ b_ctx_out->related_lports_changed = false; - b_ctx_out->non_vif_ports_changed = false; - b_ctx_out->egress_ifaces = &rt_data->egress_ifaces; - b_ctx_out->lbinding_data = &rt_data->lbinding_data; - b_ctx_out->local_iface_ids = &rt_data->local_iface_ids; - b_ctx_out->tracked_dp_bindings = NULL; - b_ctx_out->if_mgr = ctrl_ctx->if_mgr; -+ b_ctx_out->extport_updated_datapaths = &rt_data->extport_updated_datapaths; - } - - static void -@@ -1197,7 +1246,6 @@ en_runtime_data_run(struct engine_node *node, void *data) - struct ed_type_runtime_data *rt_data = data; - struct hmap *local_datapaths = &rt_data->local_datapaths; - struct sset *local_lports = &rt_data->local_lports; -- struct sset *local_lport_ids = &rt_data->local_lport_ids; - struct sset *active_tunnels = &rt_data->active_tunnels; - - static bool first_run = true; -@@ -1208,23 +1256,25 @@ en_runtime_data_run(struct engine_node *node, void *data) - struct local_datapath *cur_node, *next_node; - HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) { - free(cur_node->peer_ports); -+ shash_destroy(&cur_node->external_ports); - hmap_remove(local_datapaths, &cur_node->hmap_node); - free(cur_node); - } - hmap_clear(local_datapaths); - local_binding_data_destroy(&rt_data->lbinding_data); - sset_destroy(local_lports); -- sset_destroy(local_lport_ids); -+ related_lports_destroy(&rt_data->related_lports); - sset_destroy(active_tunnels); - sset_destroy(&rt_data->egress_ifaces); - smap_destroy(&rt_data->local_iface_ids); - sset_init(local_lports); -- sset_init(local_lport_ids); -+ related_lports_init(&rt_data->related_lports); - sset_init(active_tunnels); - sset_init(&rt_data->egress_ifaces); - smap_init(&rt_data->local_iface_ids); - local_binding_data_init(&rt_data->lbinding_data); - hmapx_clear(&rt_data->ct_updated_datapaths); -+ hmapx_clear(&rt_data->extport_updated_datapaths); - } - - struct binding_ctx_in b_ctx_in; -@@ -1289,7 +1339,7 @@ runtime_data_sb_port_binding_handler(struct engine_node *node, void *data) - return false; - } - -- if (b_ctx_out.local_lport_ids_changed || -+ if (b_ctx_out.related_lports_changed || - b_ctx_out.non_vif_ports_changed || - !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) { - engine_set_node_state(node, EN_UPDATED); -@@ -1599,11 +1649,8 @@ en_port_groups_run(struct engine_node *node, void *data) - struct ed_type_runtime_data *rt_data = - engine_get_input_data("runtime_data", node); - -- struct sset *local_b_lports = binding_collect_local_binding_lports( -- &rt_data->lbinding_data); -- port_groups_init(pg_table, local_b_lports, &pg->port_group_ssets, -- &pg->port_groups_cs_local); -- binding_destroy_local_binding_lports(local_b_lports); -+ port_groups_init(pg_table, &rt_data->related_lports.lport_names, -+ &pg->port_group_ssets, &pg->port_groups_cs_local); - - engine_set_node_state(node, EN_UPDATED); - } -@@ -1620,12 +1667,9 @@ port_groups_sb_port_group_handler(struct engine_node *node, void *data) - struct ed_type_runtime_data *rt_data = - engine_get_input_data("runtime_data", node); - -- struct sset *local_b_lports = binding_collect_local_binding_lports( -- &rt_data->lbinding_data); -- port_groups_update(pg_table, local_b_lports, &pg->port_group_ssets, -- &pg->port_groups_cs_local, &pg->new, &pg->deleted, -- &pg->updated); -- binding_destroy_local_binding_lports(local_b_lports); -+ port_groups_update(pg_table, &rt_data->related_lports.lport_names, -+ &pg->port_group_ssets, &pg->port_groups_cs_local, -+ &pg->new, &pg->deleted, &pg->updated); - - if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) || - !sset_is_empty(&pg->updated)) { -@@ -1658,9 +1702,6 @@ port_groups_runtime_data_handler(struct engine_node *node, void *data) - goto out; - } - -- struct sset *local_b_lports = binding_collect_local_binding_lports( -- &rt_data->lbinding_data); -- - const struct sbrec_port_group *pg_sb; - SBREC_PORT_GROUP_TABLE_FOR_EACH (pg_sb, pg_table) { - struct sset *pg_lports = shash_find_data(&pg->port_group_ssets, -@@ -1687,13 +1728,12 @@ port_groups_runtime_data_handler(struct engine_node *node, void *data) - if (need_update) { - expr_const_sets_add_strings(&pg->port_groups_cs_local, pg_sb->name, - (const char *const *) pg_sb->ports, -- pg_sb->n_ports, local_b_lports); -+ pg_sb->n_ports, -+ &rt_data->related_lports.lport_names); - sset_add(&pg->updated, pg_sb->name); - } - } - -- binding_destroy_local_binding_lports(local_b_lports); -- - out: - if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) || - !sset_is_empty(&pg->updated)) { -@@ -1748,10 +1788,9 @@ en_ct_zones_run(struct engine_node *node, void *data) - 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, &rt_data->ct_updated_datapaths); -+ &ct_zones_data->pending); - - - engine_set_node_state(node, EN_UPDATED); -@@ -1794,107 +1833,13 @@ en_mff_ovn_geneve_run(struct engine_node *node, void *data) - engine_set_node_state(node, EN_UNCHANGED); - } - --/* Engine node en_physical_flow_changes indicates whether -- * there is a need to -- * - recompute only physical flows or -- * - we can incrementally process the physical flows. -- * -- * en_physical_flow_changes is an input to flow_output engine node. -- * If the engine node 'en_physical_flow_changes' gets updated during -- * engine run, it means the handler for this - -- * flow_output_physical_flow_changes_handler() will either -- * - recompute the physical flows by calling 'physical_run() or -- * - incrementlly process some of the changes for physical flow -- * calculation. Right now we handle OVS interfaces changes -- * for physical flow computation. -- * -- * When ever a port binding happens, the follow up -- * activity is the zone id allocation for that port binding. -- * With this intermediate engine node, we avoid full recomputation. -- * Instead we do physical flow computation (either full recomputation -- * by calling physical_run() or handling the changes incrementally. -- * -- * Hence this is an intermediate engine node to indicate the -- * flow_output engine to recomputes/compute the physical flows. -- * -- * TODO 1. Ideally this engine node should recompute/compute the physical -- * flows instead of relegating it to the flow_output node. -- * But this requires splitting the flow_output node to -- * logical_flow_output and physical_flow_output. -- * -- * TODO 2. We can further optimise the en_ct_zone changes to -- * compute the phsyical flows for changed zone ids. -- * -- * TODO 3: physical.c has a global simap -localvif_to_ofport which stores the -- * local OVS interfaces and the ofport numbers. Ideally this should be -- * part of the engine data. -- */ --struct ed_type_pfc_data { -- /* Both these variables are tracked and set in each engine run. */ -- bool recompute_physical_flows; -- bool ovs_ifaces_changed; --}; -- --static void --en_physical_flow_changes_clear_tracked_data(void *data_) --{ -- struct ed_type_pfc_data *data = data_; -- data->recompute_physical_flows = false; -- data->ovs_ifaces_changed = false; --} -- --static void * --en_physical_flow_changes_init(struct engine_node *node OVS_UNUSED, -- struct engine_arg *arg OVS_UNUSED) --{ -- struct ed_type_pfc_data *data = xzalloc(sizeof *data); -- return data; --} -- --static void --en_physical_flow_changes_cleanup(void *data OVS_UNUSED) --{ --} -- --/* Indicate to the flow_output engine that we need to recompute physical -- * flows. */ --static void --en_physical_flow_changes_run(struct engine_node *node, void *data) --{ -- struct ed_type_pfc_data *pfc_tdata = data; -- pfc_tdata->recompute_physical_flows = true; -- pfc_tdata->ovs_ifaces_changed = true; -- 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 --physical_flow_changes_ovs_iface_handler(struct engine_node *node, void *data) --{ -- struct ed_type_pfc_data *pfc_tdata = data; -- pfc_tdata->ovs_ifaces_changed = true; -- engine_set_node_state(node, EN_UPDATED); -- return true; --} -- --struct flow_output_persistent_data { -+struct lflow_output_persistent_data { - uint32_t conj_id_ofs; - struct lflow_cache *lflow_cache; - }; - --struct ed_type_flow_output { -- /* desired flows */ -+struct ed_type_lflow_output { -+ /* Logical flow table */ - struct ovn_desired_flow_table flow_table; - /* group ids for load balancing */ - struct ovn_extend_table group_table; -@@ -1905,81 +1850,15 @@ struct ed_type_flow_output { - - /* Data which is persistent and not cleared during - * full recompute. */ -- struct flow_output_persistent_data pd; -+ struct lflow_output_persistent_data pd; - }; - --static void init_physical_ctx(struct engine_node *node, -- struct ed_type_runtime_data *rt_data, -- struct physical_ctx *p_ctx) --{ -- struct ovsdb_idl_index *sbrec_port_binding_by_name = -- engine_ovsdb_node_get_index( -- engine_get_input("SB_port_binding", node), -- "name"); -- -- struct sbrec_multicast_group_table *multicast_group_table = -- (struct sbrec_multicast_group_table *)EN_OVSDB_GET( -- engine_get_input("SB_multicast_group", node)); -- -- struct sbrec_port_binding_table *port_binding_table = -- (struct sbrec_port_binding_table *)EN_OVSDB_GET( -- engine_get_input("SB_port_binding", node)); -- -- struct sbrec_chassis_table *chassis_table = -- (struct sbrec_chassis_table *)EN_OVSDB_GET( -- engine_get_input("SB_chassis", node)); -- -- struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve = -- engine_get_input_data("mff_ovn_geneve", node); -- -- struct ovsrec_open_vswitch_table *ovs_table = -- (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( -- engine_get_input("OVS_open_vswitch", node)); -- struct ovsrec_bridge_table *bridge_table = -- (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 = 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( -- engine_get_input("SB_chassis", node), -- "name"); -- if (chassis_id) { -- chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); -- } -- -- ovs_assert(br_int && chassis); -- -- struct ovsrec_interface_table *iface_table = -- (struct ovsrec_interface_table *)EN_OVSDB_GET( -- engine_get_input("OVS_interface", node)); -- -- struct ed_type_ct_zones *ct_zones_data = -- engine_get_input_data("ct_zones", node); -- struct simap *ct_zones = &ct_zones_data->current; -- -- p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name; -- p_ctx->port_binding_table = port_binding_table; -- p_ctx->mc_group_table = multicast_group_table; -- p_ctx->br_int = br_int; -- p_ctx->chassis_table = chassis_table; -- p_ctx->iface_table = iface_table; -- p_ctx->chassis = chassis; -- p_ctx->active_tunnels = &rt_data->active_tunnels; -- p_ctx->local_datapaths = &rt_data->local_datapaths; -- p_ctx->local_lports = &rt_data->local_lports; -- p_ctx->ct_zones = ct_zones; -- p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; -- p_ctx->local_bindings = &rt_data->lbinding_data.bindings; -- p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths; --} -- --static void init_lflow_ctx(struct engine_node *node, -- struct ed_type_runtime_data *rt_data, -- struct ed_type_flow_output *fo, -- struct lflow_ctx_in *l_ctx_in, -- struct lflow_ctx_out *l_ctx_out) -+static void -+init_lflow_ctx(struct engine_node *node, -+ struct ed_type_runtime_data *rt_data, -+ struct ed_type_lflow_output *fo, -+ struct lflow_ctx_in *l_ctx_in, -+ struct lflow_ctx_out *l_ctx_out) - { - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( -@@ -2077,7 +1956,7 @@ static void init_lflow_ctx(struct engine_node *node, - l_ctx_in->addr_sets = addr_sets; - l_ctx_in->port_groups = port_groups; - l_ctx_in->active_tunnels = &rt_data->active_tunnels; -- l_ctx_in->local_lport_ids = &rt_data->local_lport_ids; -+ l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids; - - l_ctx_out->flow_table = &fo->flow_table; - l_ctx_out->group_table = &fo->group_table; -@@ -2089,11 +1968,10 @@ static void init_lflow_ctx(struct engine_node *node, - } - - static void * --en_flow_output_init(struct engine_node *node OVS_UNUSED, -- struct engine_arg *arg OVS_UNUSED) -+en_lflow_output_init(struct engine_node *node OVS_UNUSED, -+ struct engine_arg *arg OVS_UNUSED) - { -- struct ed_type_flow_output *data = xzalloc(sizeof *data); -- -+ struct ed_type_lflow_output *data = xzalloc(sizeof *data); - ovn_desired_flow_table_init(&data->flow_table); - ovn_extend_table_init(&data->group_table); - ovn_extend_table_init(&data->meter_table); -@@ -2103,9 +1981,9 @@ en_flow_output_init(struct engine_node *node OVS_UNUSED, - } - - static void --en_flow_output_cleanup(void *data) -+en_lflow_output_cleanup(void *data) - { -- struct ed_type_flow_output *flow_output_data = data; -+ struct ed_type_lflow_output *flow_output_data = data; - ovn_desired_flow_table_destroy(&flow_output_data->flow_table); - ovn_extend_table_destroy(&flow_output_data->group_table); - ovn_extend_table_destroy(&flow_output_data->meter_table); -@@ -2114,7 +1992,7 @@ en_flow_output_cleanup(void *data) - } - - static void --en_flow_output_run(struct engine_node *node, void *data) -+en_lflow_output_run(struct engine_node *node, void *data) - { - struct ed_type_runtime_data *rt_data = - engine_get_input_data("runtime_data", node); -@@ -2140,8 +2018,8 @@ en_flow_output_run(struct engine_node *node, void *data) - - ovs_assert(br_int && chassis); - -- struct ed_type_flow_output *fo = data; -- struct ovn_desired_flow_table *flow_table = &fo->flow_table; -+ struct ed_type_lflow_output *fo = data; -+ struct ovn_desired_flow_table *lflow_table = &fo->flow_table; - struct ovn_extend_table *group_table = &fo->group_table; - struct ovn_extend_table *meter_table = &fo->meter_table; - struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref; -@@ -2150,7 +2028,7 @@ en_flow_output_run(struct engine_node *node, void *data) - if (first_run) { - first_run = false; - } else { -- ovn_desired_flow_table_clear(flow_table); -+ ovn_desired_flow_table_clear(lflow_table); - ovn_extend_table_clear(group_table, false /* desired */); - ovn_extend_table_clear(meter_table, false /* desired */); - lflow_resource_clear(lfrr); -@@ -2172,7 +2050,7 @@ en_flow_output_run(struct engine_node *node, void *data) - if (l_ctx_out.conj_id_overflow) { - /* Conjunction ids overflow. There can be many holes in between. - * Destroy lflow cache and call lflow_run() again. */ -- ovn_desired_flow_table_clear(flow_table); -+ ovn_desired_flow_table_clear(lflow_table); - ovn_extend_table_clear(group_table, false /* desired */); - ovn_extend_table_clear(meter_table, false /* desired */); - lflow_resource_clear(lfrr); -@@ -2185,16 +2063,11 @@ en_flow_output_run(struct engine_node *node, void *data) - } - } - -- struct physical_ctx p_ctx; -- init_physical_ctx(node, rt_data, &p_ctx); -- -- physical_run(&p_ctx, &fo->flow_table); -- - engine_set_node_state(node, EN_UPDATED); - } - - static bool --flow_output_sb_logical_flow_handler(struct engine_node *node, void *data) -+lflow_output_sb_logical_flow_handler(struct engine_node *node, void *data) - { - struct ed_type_runtime_data *rt_data = - engine_get_input_data("runtime_data", node); -@@ -2207,7 +2080,7 @@ flow_output_sb_logical_flow_handler(struct engine_node *node, void *data) - const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); - ovs_assert(br_int); - -- struct ed_type_flow_output *fo = data; -+ struct ed_type_lflow_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); -@@ -2219,7 +2092,7 @@ flow_output_sb_logical_flow_handler(struct engine_node *node, void *data) - } - - static bool --flow_output_sb_mac_binding_handler(struct engine_node *node, void *data) -+lflow_output_sb_mac_binding_handler(struct engine_node *node, void *data) - { - struct ovsdb_idl_index *sbrec_port_binding_by_name = - engine_ovsdb_node_get_index( -@@ -2234,60 +2107,17 @@ flow_output_sb_mac_binding_handler(struct engine_node *node, void *data) - engine_get_input_data("runtime_data", node); - const struct hmap *local_datapaths = &rt_data->local_datapaths; - -- struct ed_type_flow_output *fo = data; -- struct ovn_desired_flow_table *flow_table = &fo->flow_table; -+ struct ed_type_lflow_output *lfo = data; - - lflow_handle_changed_neighbors(sbrec_port_binding_by_name, -- mac_binding_table, local_datapaths, flow_table); -+ mac_binding_table, local_datapaths, &lfo->flow_table); - - engine_set_node_state(node, EN_UPDATED); - return true; - } - - static bool --flow_output_sb_port_binding_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 ovn_desired_flow_table *flow_table = &fo->flow_table; -- -- struct physical_ctx p_ctx; -- init_physical_ctx(node, rt_data, &p_ctx); -- -- /* We handle port-binding changes for physical flow processing -- * only. flow_output runtime data handler takes care of processing -- * logical flows for any port binding changes. -- */ -- physical_handle_port_binding_changes(&p_ctx, flow_table); -- -- engine_set_node_state(node, EN_UPDATED); -- return true; --} -- --static bool --flow_output_sb_multicast_group_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 ovn_desired_flow_table *flow_table = &fo->flow_table; -- -- struct physical_ctx p_ctx; -- init_physical_ctx(node, rt_data, &p_ctx); -- -- physical_handle_mc_group_changes(&p_ctx, flow_table); -- -- engine_set_node_state(node, EN_UPDATED); -- return true; -- --} -- --static bool --_flow_output_resource_ref_handler(struct engine_node *node, void *data, -+_lflow_output_resource_ref_handler(struct engine_node *node, void *data, - enum ref_type ref_type) - { - struct ed_type_runtime_data *rt_data = -@@ -2319,7 +2149,7 @@ _flow_output_resource_ref_handler(struct engine_node *node, void *data, - - ovs_assert(br_int && chassis); - -- struct ed_type_flow_output *fo = data; -+ struct ed_type_lflow_output *fo = data; - - struct lflow_ctx_in l_ctx_in; - struct lflow_ctx_out l_ctx_out; -@@ -2388,53 +2218,20 @@ _flow_output_resource_ref_handler(struct engine_node *node, void *data, - } - - static bool --flow_output_addr_sets_handler(struct engine_node *node, void *data) -+lflow_output_addr_sets_handler(struct engine_node *node, void *data) - { -- return _flow_output_resource_ref_handler(node, data, REF_TYPE_ADDRSET); -+ return _lflow_output_resource_ref_handler(node, data, REF_TYPE_ADDRSET); - } - - static bool --flow_output_port_groups_handler(struct engine_node *node, void *data) -+lflow_output_port_groups_handler(struct engine_node *node, void *data) - { -- return _flow_output_resource_ref_handler(node, data, REF_TYPE_PORTGROUP); -+ return _lflow_output_resource_ref_handler(node, data, REF_TYPE_PORTGROUP); - } - - static bool --flow_output_physical_flow_changes_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 physical_ctx p_ctx; -- init_physical_ctx(node, rt_data, &p_ctx); -- -- engine_set_node_state(node, EN_UPDATED); -- 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; -- } -- -- return true; --} -- --static bool --flow_output_runtime_data_handler(struct engine_node *node, -- void *data OVS_UNUSED) -+lflow_output_runtime_data_handler(struct engine_node *node, -+ void *data OVS_UNUSED) - { - struct ed_type_runtime_data *rt_data = - engine_get_input_data("runtime_data", node); -@@ -2455,12 +2252,9 @@ flow_output_runtime_data_handler(struct engine_node *node, - - struct lflow_ctx_in l_ctx_in; - struct lflow_ctx_out l_ctx_out; -- struct ed_type_flow_output *fo = data; -+ struct ed_type_lflow_output *fo = data; - init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out); - -- struct physical_ctx p_ctx; -- init_physical_ctx(node, rt_data, &p_ctx); -- - struct tracked_binding_datapath *tdp; - HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) { - if (tdp->is_new) { -@@ -2485,12 +2279,12 @@ flow_output_runtime_data_handler(struct engine_node *node, - } - - static bool --flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) -+lflow_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 ed_type_lflow_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); -@@ -2502,12 +2296,12 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) - } - - static bool --flow_output_sb_fdb_handler(struct engine_node *node, void *data) -+lflow_output_sb_fdb_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 ed_type_lflow_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); -@@ -2518,6 +2312,205 @@ flow_output_sb_fdb_handler(struct engine_node *node, void *data) - return handled; - } - -+struct ed_type_pflow_output { -+ /* Desired physical flows. */ -+ struct ovn_desired_flow_table flow_table; -+}; -+ -+static void init_physical_ctx(struct engine_node *node, -+ struct ed_type_runtime_data *rt_data, -+ struct physical_ctx *p_ctx) -+{ -+ struct ovsdb_idl_index *sbrec_port_binding_by_name = -+ engine_ovsdb_node_get_index( -+ engine_get_input("SB_port_binding", node), -+ "name"); -+ -+ struct sbrec_multicast_group_table *multicast_group_table = -+ (struct sbrec_multicast_group_table *)EN_OVSDB_GET( -+ engine_get_input("SB_multicast_group", node)); -+ -+ struct sbrec_port_binding_table *port_binding_table = -+ (struct sbrec_port_binding_table *)EN_OVSDB_GET( -+ engine_get_input("SB_port_binding", node)); -+ -+ struct sbrec_chassis_table *chassis_table = -+ (struct sbrec_chassis_table *)EN_OVSDB_GET( -+ engine_get_input("SB_chassis", node)); -+ -+ struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve = -+ engine_get_input_data("mff_ovn_geneve", node); -+ -+ struct ovsrec_open_vswitch_table *ovs_table = -+ (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( -+ engine_get_input("OVS_open_vswitch", node)); -+ struct ovsrec_bridge_table *bridge_table = -+ (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 = 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( -+ engine_get_input("SB_chassis", node), -+ "name"); -+ if (chassis_id) { -+ chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); -+ } -+ -+ ovs_assert(br_int && chassis); -+ -+ struct ovsrec_interface_table *iface_table = -+ (struct ovsrec_interface_table *)EN_OVSDB_GET( -+ engine_get_input("OVS_interface", node)); -+ -+ struct ed_type_ct_zones *ct_zones_data = -+ engine_get_input_data("ct_zones", node); -+ struct simap *ct_zones = &ct_zones_data->current; -+ -+ p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name; -+ p_ctx->port_binding_table = port_binding_table; -+ p_ctx->mc_group_table = multicast_group_table; -+ p_ctx->br_int = br_int; -+ p_ctx->chassis_table = chassis_table; -+ p_ctx->iface_table = iface_table; -+ p_ctx->chassis = chassis; -+ p_ctx->active_tunnels = &rt_data->active_tunnels; -+ p_ctx->local_datapaths = &rt_data->local_datapaths; -+ p_ctx->local_lports = &rt_data->local_lports; -+ p_ctx->ct_zones = ct_zones; -+ p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; -+ p_ctx->local_bindings = &rt_data->lbinding_data.bindings; -+} -+ -+static void * -+en_pflow_output_init(struct engine_node *node OVS_UNUSED, -+ struct engine_arg *arg OVS_UNUSED) -+{ -+ struct ed_type_pflow_output *data = xzalloc(sizeof *data); -+ ovn_desired_flow_table_init(&data->flow_table); -+ return data; -+} -+ -+static void -+en_pflow_output_cleanup(void *data OVS_UNUSED) -+{ -+ struct ed_type_pflow_output *pfo = data; -+ ovn_desired_flow_table_destroy(&pfo->flow_table); -+} -+ -+static void -+en_pflow_output_run(struct engine_node *node, void *data) -+{ -+ struct ed_type_pflow_output *pfo = data; -+ struct ovn_desired_flow_table *pflow_table = &pfo->flow_table; -+ static bool first_run = true; -+ if (first_run) { -+ first_run = false; -+ } else { -+ ovn_desired_flow_table_clear(pflow_table); -+ } -+ -+ struct ed_type_runtime_data *rt_data = -+ engine_get_input_data("runtime_data", node); -+ -+ struct physical_ctx p_ctx; -+ init_physical_ctx(node, rt_data, &p_ctx); -+ physical_run(&p_ctx, pflow_table); -+ -+ engine_set_node_state(node, EN_UPDATED); -+} -+ -+static bool -+pflow_output_sb_port_binding_handler(struct engine_node *node, -+ void *data) -+{ -+ struct ed_type_runtime_data *rt_data = -+ engine_get_input_data("runtime_data", node); -+ -+ struct ed_type_pflow_output *pfo = data; -+ -+ struct physical_ctx p_ctx; -+ init_physical_ctx(node, rt_data, &p_ctx); -+ -+ /* We handle port-binding changes for physical flow processing -+ * only. flow_output runtime data handler takes care of processing -+ * logical flows for any port binding changes. -+ */ -+ physical_handle_port_binding_changes(&p_ctx, &pfo->flow_table); -+ -+ engine_set_node_state(node, EN_UPDATED); -+ return true; -+} -+ -+static bool -+pflow_output_sb_multicast_group_handler(struct engine_node *node, void *data) -+{ -+ struct ed_type_runtime_data *rt_data = -+ engine_get_input_data("runtime_data", node); -+ -+ struct ed_type_pflow_output *pfo = data; -+ -+ struct physical_ctx p_ctx; -+ init_physical_ctx(node, rt_data, &p_ctx); -+ -+ physical_handle_mc_group_changes(&p_ctx, &pfo->flow_table); -+ -+ engine_set_node_state(node, EN_UPDATED); -+ return true; -+} -+ -+static bool -+pflow_output_ovs_iface_handler(struct engine_node *node OVS_UNUSED, -+ void *data OVS_UNUSED) -+{ -+ struct ed_type_runtime_data *rt_data = -+ engine_get_input_data("runtime_data", node); -+ -+ struct ed_type_pflow_output *pfo = data; -+ -+ struct physical_ctx p_ctx; -+ init_physical_ctx(node, rt_data, &p_ctx); -+ -+ engine_set_node_state(node, EN_UPDATED); -+ return physical_handle_ovs_iface_changes(&p_ctx, &pfo->flow_table); -+} -+ -+static void * -+en_flow_output_init(struct engine_node *node OVS_UNUSED, -+ struct engine_arg *arg OVS_UNUSED) -+{ -+ return NULL; -+} -+ -+static void -+en_flow_output_cleanup(void *data OVS_UNUSED) -+{ -+ -+} -+ -+static void -+en_flow_output_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) -+{ -+ engine_set_node_state(node, EN_UPDATED); -+} -+ -+static bool -+flow_output_pflow_output_handler(struct engine_node *node, -+ void *data OVS_UNUSED) -+{ -+ engine_set_node_state(node, EN_UPDATED); -+ return true; -+} -+ -+static bool -+flow_output_lflow_output_handler(struct engine_node *node, -+ void *data OVS_UNUSED) -+{ -+ engine_set_node_state(node, EN_UPDATED); -+ return true; -+} -+ - struct ovn_controller_exit_args { - bool *exiting; - bool *restart; -@@ -2710,8 +2703,8 @@ main(int argc, char *argv[]) - ENGINE_NODE_WITH_CLEAR_TRACK_DATA(runtime_data, "runtime_data"); - ENGINE_NODE(mff_ovn_geneve, "mff_ovn_geneve"); - ENGINE_NODE(ofctrl_is_connected, "ofctrl_is_connected"); -- ENGINE_NODE_WITH_CLEAR_TRACK_DATA(physical_flow_changes, -- "physical_flow_changes"); -+ ENGINE_NODE(pflow_output, "physical_flow_output"); -+ ENGINE_NODE(lflow_output, "logical_flow_output"); - ENGINE_NODE(flow_output, "flow_output"); - ENGINE_NODE(addr_sets, "addr_sets"); - ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups"); -@@ -2735,58 +2728,68 @@ main(int argc, char *argv[]) - engine_add_input(&en_port_groups, &en_runtime_data, - port_groups_runtime_data_handler); - -- /* 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 -+ /* 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_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); -- engine_add_input(&en_flow_output, &en_port_groups, -- flow_output_port_groups_handler); -- engine_add_input(&en_flow_output, &en_runtime_data, -- flow_output_runtime_data_handler); -- engine_add_input(&en_flow_output, &en_mff_ovn_geneve, NULL); -- engine_add_input(&en_flow_output, &en_physical_flow_changes, -- flow_output_physical_flow_changes_handler); -- -- /* We need this input nodes for only data. Hence the noop handler. */ -- engine_add_input(&en_flow_output, &en_ct_zones, engine_noop_handler); -- engine_add_input(&en_flow_output, &en_ovs_interface, engine_noop_handler); -- -- engine_add_input(&en_flow_output, &en_ovs_open_vswitch, NULL); -- engine_add_input(&en_flow_output, &en_ovs_bridge, NULL); -- -- engine_add_input(&en_flow_output, &en_sb_chassis, NULL); -- engine_add_input(&en_flow_output, &en_sb_encap, NULL); -- engine_add_input(&en_flow_output, &en_sb_multicast_group, -- flow_output_sb_multicast_group_handler); -- engine_add_input(&en_flow_output, &en_sb_port_binding, -- flow_output_sb_port_binding_handler); -- engine_add_input(&en_flow_output, &en_sb_mac_binding, -- flow_output_sb_mac_binding_handler); -- engine_add_input(&en_flow_output, &en_sb_logical_flow, -- flow_output_sb_logical_flow_handler); -+ engine_add_input(&en_pflow_output, &en_ovs_interface, -+ pflow_output_ovs_iface_handler); -+ engine_add_input(&en_pflow_output, &en_ct_zones, NULL); -+ engine_add_input(&en_pflow_output, &en_sb_chassis, NULL); -+ engine_add_input(&en_pflow_output, &en_sb_port_binding, -+ pflow_output_sb_port_binding_handler); -+ engine_add_input(&en_pflow_output, &en_sb_multicast_group, -+ pflow_output_sb_multicast_group_handler); -+ -+ engine_add_input(&en_pflow_output, &en_runtime_data, -+ NULL); -+ engine_add_input(&en_pflow_output, &en_sb_encap, NULL); -+ engine_add_input(&en_pflow_output, &en_mff_ovn_geneve, NULL); -+ engine_add_input(&en_pflow_output, &en_ovs_open_vswitch, NULL); -+ engine_add_input(&en_pflow_output, &en_ovs_bridge, NULL); -+ -+ engine_add_input(&en_lflow_output, &en_addr_sets, -+ lflow_output_addr_sets_handler); -+ engine_add_input(&en_lflow_output, &en_port_groups, -+ lflow_output_port_groups_handler); -+ engine_add_input(&en_lflow_output, &en_runtime_data, -+ lflow_output_runtime_data_handler); -+ -+ /* We need these input nodes only for the data. Hence the noop handler. -+ * Changes to en_sb_multicast_group is handled by the pflow_output engine -+ * node. -+ * */ -+ engine_add_input(&en_lflow_output, &en_sb_multicast_group, -+ engine_noop_handler); -+ -+ engine_add_input(&en_lflow_output, &en_sb_chassis, NULL); -+ -+ /* Any changes to the port binding, need not be handled -+ * for lflow_outout engine. We still need sb_port_binding -+ * as input to access the port binding data in lflow.c and -+ * hence the noop handler. */ -+ engine_add_input(&en_lflow_output, &en_sb_port_binding, -+ engine_noop_handler); -+ -+ engine_add_input(&en_lflow_output, &en_ovs_open_vswitch, NULL); -+ engine_add_input(&en_lflow_output, &en_ovs_bridge, NULL); -+ -+ engine_add_input(&en_lflow_output, &en_sb_mac_binding, -+ lflow_output_sb_mac_binding_handler); -+ engine_add_input(&en_lflow_output, &en_sb_logical_flow, -+ lflow_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_add_input(&en_lflow_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_flow_output, &en_sb_fdb, -- flow_output_sb_fdb_handler); -+ engine_add_input(&en_lflow_output, &en_sb_dhcp_options, NULL); -+ engine_add_input(&en_lflow_output, &en_sb_dhcpv6_options, NULL); -+ engine_add_input(&en_lflow_output, &en_sb_dns, NULL); -+ engine_add_input(&en_lflow_output, &en_sb_load_balancer, -+ lflow_output_sb_load_balancer_handler); -+ engine_add_input(&en_lflow_output, &en_sb_fdb, -+ lflow_output_sb_fdb_handler); - - engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL); - engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL); -@@ -2808,12 +2811,20 @@ main(int argc, char *argv[]) - /* The OVS interface handler for runtime_data changes MUST be executed - * after the sb_port_binding_handler as port_binding deletes must be - * processed first. -+ * -+ * runtime_data needs to access the OVS Port data and hence a noop -+ * handler. - */ - engine_add_input(&en_runtime_data, &en_ovs_port, - engine_noop_handler); - engine_add_input(&en_runtime_data, &en_ovs_interface, - runtime_data_ovs_interface_handler); - -+ engine_add_input(&en_flow_output, &en_lflow_output, -+ flow_output_lflow_output_handler); -+ engine_add_input(&en_flow_output, &en_pflow_output, -+ flow_output_pflow_output_handler); -+ - struct engine_arg engine_arg = { - .sb_idl = ovnsb_idl_loop.idl, - .ovs_idl = ovs_idl_loop.idl, -@@ -2836,25 +2847,27 @@ main(int argc, char *argv[]) - engine_ovsdb_node_add_index(&en_sb_datapath_binding, "key", - sbrec_datapath_binding_by_key); - -- struct ed_type_flow_output *flow_output_data = -- engine_get_internal_data(&en_flow_output); -+ struct ed_type_lflow_output *lflow_output_data = -+ engine_get_internal_data(&en_lflow_output); -+ struct ed_type_lflow_output *pflow_output_data = -+ engine_get_internal_data(&en_pflow_output); - struct ed_type_ct_zones *ct_zones_data = - engine_get_internal_data(&en_ct_zones); - struct ed_type_runtime_data *runtime_data = - engine_get_internal_data(&en_runtime_data); - -- ofctrl_init(&flow_output_data->group_table, -- &flow_output_data->meter_table, -+ ofctrl_init(&lflow_output_data->group_table, -+ &lflow_output_data->meter_table, - get_ofctrl_probe_interval(ovs_idl_loop.idl)); - ofctrl_seqno_init(); - - unixctl_command_register("group-table-list", "", 0, 0, - extend_table_list, -- &flow_output_data->group_table); -+ &lflow_output_data->group_table); - - unixctl_command_register("meter-table-list", "", 0, 0, - extend_table_list, -- &flow_output_data->meter_table); -+ &lflow_output_data->meter_table); - - unixctl_command_register("ct-zone-list", "", 0, 0, - ct_zone_list, -@@ -2868,14 +2881,14 @@ main(int argc, char *argv[]) - NULL); - unixctl_command_register("lflow-cache/flush", "", 0, 0, - lflow_cache_flush_cmd, -- &flow_output_data->pd); -+ &lflow_output_data->pd); - /* Keep deprecated 'flush-lflow-cache' command for now. */ - unixctl_command_register("flush-lflow-cache", "[deprecated]", 0, 0, - lflow_cache_flush_cmd, -- &flow_output_data->pd); -+ &lflow_output_data->pd); - unixctl_command_register("lflow-cache/show-stats", "", 0, 0, - lflow_cache_show_stats_cmd, -- &flow_output_data->pd); -+ &lflow_output_data->pd); - - bool reset_ovnsb_idl_min_index = false; - unixctl_command_register("sb-cluster-state-reset", "", 0, 0, -@@ -2981,8 +2994,10 @@ main(int argc, char *argv[]) - ovsrec_bridge_table_get(ovs_idl_loop.idl); - const struct ovsrec_open_vswitch_table *ovs_table = - ovsrec_open_vswitch_table_get(ovs_idl_loop.idl); -- const struct ovsrec_bridge *br_int = -- process_br_int(ovs_idl_txn, bridge_table, ovs_table); -+ const struct ovsrec_bridge *br_int = NULL; -+ const struct ovsrec_datapath *br_int_dp = NULL; -+ process_br_int(ovs_idl_txn, bridge_table, ovs_table, -+ &br_int, &br_int_dp); - - if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl) && - northd_version_match) { -@@ -3013,6 +3028,13 @@ main(int argc, char *argv[]) - &chassis_private); - } - -+ /* If any OVS feature support changed, force a full recompute. */ -+ if (br_int_dp -+ && ovs_feature_support_update(&br_int_dp->capabilities)) { -+ VLOG_INFO("OVS feature set changed, force recompute."); -+ engine_set_force_recompute(true); -+ } -+ - if (br_int) { - ct_zones_data = engine_get_data(&en_ct_zones); - if (ct_zones_data) { -@@ -3121,13 +3143,17 @@ main(int argc, char *argv[]) - runtime_data ? &runtime_data->lbinding_data : NULL; - if_status_mgr_update(if_mgr, binding_data); - -- flow_output_data = engine_get_data(&en_flow_output); -- if (flow_output_data && ct_zones_data) { -- ofctrl_put(&flow_output_data->flow_table, -+ lflow_output_data = engine_get_data(&en_lflow_output); -+ pflow_output_data = engine_get_data(&en_pflow_output); -+ if (lflow_output_data && pflow_output_data && -+ ct_zones_data) { -+ ofctrl_put(&lflow_output_data->flow_table, -+ &pflow_output_data->flow_table, - &ct_zones_data->pending, - sbrec_meter_table_get(ovnsb_idl_loop.idl), - ofctrl_seqno_get_req_cfg(), -- engine_node_changed(&en_flow_output)); -+ engine_node_changed(&en_lflow_output), -+ engine_node_changed(&en_pflow_output)); - } - ofctrl_seqno_run(ofctrl_get_cur_cfg()); - if_status_mgr_run(if_mgr, binding_data, !ovnsb_idl_txn, -@@ -3495,7 +3521,7 @@ lflow_cache_flush_cmd(struct unixctl_conn *conn OVS_UNUSED, - void *arg_) - { - VLOG_INFO("User triggered lflow cache flush."); -- struct flow_output_persistent_data *fo_pd = arg_; -+ struct lflow_output_persistent_data *fo_pd = arg_; - lflow_cache_flush(fo_pd->lflow_cache); - fo_pd->conj_id_ofs = 1; - engine_set_force_recompute(true); -@@ -3507,7 +3533,7 @@ static void - lflow_cache_show_stats_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *arg_) - { -- struct flow_output_persistent_data *fo_pd = arg_; -+ struct lflow_output_persistent_data *fo_pd = arg_; - struct lflow_cache *lc = fo_pd->lflow_cache; - struct ds ds = DS_EMPTY_INITIALIZER; - -diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h -index 5d9466880..2bf1fecbf 100644 ---- a/controller/ovn-controller.h -+++ b/controller/ovn-controller.h -@@ -67,6 +67,8 @@ struct local_datapath { - - size_t n_peer_ports; - size_t n_allocated_peer_ports; -+ -+ struct shash external_ports; - }; - - struct local_datapath *get_local_datapath(const struct hmap *, -diff --git a/controller/physical.c b/controller/physical.c -index 018e09540..a9a3dc720 100644 ---- a/controller/physical.c -+++ b/controller/physical.c -@@ -1272,6 +1272,52 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, - ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 160, - binding->header_.uuid.parts[0], &match, - ofpacts_p, &binding->header_.uuid); -+ -+ /* localport traffic directed to external is *not* local */ -+ struct shash_node *node; -+ SHASH_FOR_EACH (node, &ld->external_ports) { -+ const struct sbrec_port_binding *pb = node->data; -+ -+ /* skip ports that are not claimed by this chassis */ -+ if (!pb->chassis) { -+ continue; -+ } -+ if (strcmp(pb->chassis->name, chassis->name)) { -+ continue; -+ } -+ -+ ofpbuf_clear(ofpacts_p); -+ for (int i = 0; i < MFF_N_LOG_REGS; i++) { -+ put_load(0, MFF_REG0 + i, 0, 32, ofpacts_p); -+ } -+ put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, ofpacts_p); -+ -+ /* allow traffic directed to external MAC address */ -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); -+ for (int i = 0; i < pb->n_mac; i++) { -+ char *err_str; -+ struct eth_addr peer_mac; -+ if ((err_str = str_to_mac(pb->mac[i], &peer_mac))) { -+ VLOG_WARN_RL( -+ &rl, "Parsing MAC failed for external port: %s, " -+ "with error: %s", pb->logical_port, err_str); -+ free(err_str); -+ continue; -+ } -+ -+ match_init_catchall(&match); -+ match_set_metadata(&match, htonll(dp_key)); -+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, -+ port_key); -+ match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, -+ MLF_LOCALPORT, MLF_LOCALPORT); -+ match_set_dl_dst(&match, peer_mac); -+ -+ ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 170, -+ binding->header_.uuid.parts[0], &match, -+ ofpacts_p, &binding->header_.uuid); -+ } -+ } - } - - } else if (!tun && !is_ha_remote) { -@@ -1953,22 +1999,3 @@ physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *flow_table) - 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 --git a/controller/physical.h b/controller/physical.h -index 0bf13f268..feab41df4 100644 ---- a/controller/physical.h -+++ b/controller/physical.h -@@ -56,16 +56,12 @@ struct physical_ctx { - 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 --git a/controller/pinctrl.c b/controller/pinctrl.c -index 78ecfed84..1859d33d6 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -768,6 +768,13 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow, - - pfd->state = PREFIX_REQUEST; - -+ char ip6_s[INET6_ADDRSTRLEN + 1]; -+ if (ipv6_string_mapped(ip6_s, &ip_flow->ipv6_src)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); -+ VLOG_DBG_RL(&rl, "Received DHCPv6 advt from %s with aid %d" -+ " sending DHCPv6 request", ip6_s, aid); -+ } -+ - uint64_t packet_stub[256 / 8]; - struct dp_packet packet; - -@@ -936,6 +943,14 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in, - in_dhcpv6_data += opt_len; - } - if (status) { -+ char prefix[INET6_ADDRSTRLEN + 1]; -+ char ip6_s[INET6_ADDRSTRLEN + 1]; -+ if (ipv6_string_mapped(ip6_s, &ip_flow->ipv6_src) && -+ ipv6_string_mapped(prefix, &ipv6)) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); -+ VLOG_DBG_RL(&rl, "Received DHCPv6 reply from %s with prefix %s/%d" -+ " aid %d", ip6_s, prefix, prefix_len, aid); -+ } - 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); -@@ -1226,18 +1241,26 @@ fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn, - } - } else if (pfd->state == PREFIX_PENDING && ovnsb_idl_txn) { - char prefix_str[INET6_ADDRSTRLEN + 1] = {}; -- struct smap options; -+ if (!ipv6_string_mapped(prefix_str, &pfd->prefix)) { -+ goto out; -+ } -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); -+ VLOG_DBG_RL(&rl, "updating port_binding for %s with prefix %s/%d" -+ " aid %d", pb->logical_port, prefix_str, pfd->plen, -+ pfd->aid); - - pfd->state = PREFIX_DONE; - pfd->last_complete = time_msec(); - pfd->next_announce = pfd->last_complete + pfd->t1; -- ipv6_string_mapped(prefix_str, &pfd->prefix); -+ struct smap options; - smap_clone(&options, &pb->options); -+ smap_remove(&options, "ipv6_ra_pd_list"); - smap_add_format(&options, "ipv6_ra_pd_list", "%d:%s/%d", - pfd->aid, prefix_str, pfd->plen); - sbrec_port_binding_set_options(pb, &options); - smap_destroy(&options); - } -+out: - pfd->last_used = time_msec(); - destroy_lport_addresses(&c_addrs); - } -@@ -1288,7 +1311,8 @@ prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn, - sbrec_port_binding_by_name, chassis, active_tunnels, - redirect_name); - free(redirect_name); -- if (!resident && strcmp(pb->type, "l3gateway")) { -+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) && -+ !resident) { - continue; - } - -diff --git a/debian/changelog b/debian/changelog -index 9e6e5215d..42b952144 100644 ---- a/debian/changelog -+++ b/debian/changelog -@@ -1,3 +1,9 @@ -+ovn (21.06.1-1) unstable; urgency=low -+ -+ * New upstream version -+ -+ -- OVN team Fri, 18 Jun 2021 13:21:08 -0400 -+ - ovn (21.06.0-1) unstable; urgency=low - - * New upstream version -diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 040213177..f5eb01eb7 100644 ---- a/include/ovn/actions.h -+++ b/include/ovn/actions.h -@@ -25,6 +25,7 @@ - #include "openvswitch/hmap.h" - #include "openvswitch/uuid.h" - #include "util.h" -+#include "ovn/features.h" - - struct expr; - struct lexer; -diff --git a/include/ovn/features.h b/include/ovn/features.h -index 10ee46fcd..c35d59b14 100644 ---- a/include/ovn/features.h -+++ b/include/ovn/features.h -@@ -16,7 +16,25 @@ - #ifndef OVN_FEATURES_H - #define OVN_FEATURES_H 1 - -+#include -+ -+#include "smap.h" -+ - /* ovn-controller supported feature names. */ - #define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif" - -+/* OVS datapath supported features. Based on availability OVN might generate -+ * different types of openflows. -+ */ -+enum ovs_feature_support_bits { -+ OVS_CT_ZERO_SNAT_SUPPORT_BIT, -+}; -+ -+enum ovs_feature_value { -+ OVS_CT_ZERO_SNAT_SUPPORT = (1 << OVS_CT_ZERO_SNAT_SUPPORT_BIT), -+}; -+ -+bool ovs_feature_is_supported(enum ovs_feature_value feature); -+bool ovs_feature_support_update(const struct smap *ovs_capabilities); -+ - #endif -diff --git a/lib/actions.c b/lib/actions.c -index b3433f49e..7010fab2b 100644 ---- a/lib/actions.c -+++ b/lib/actions.c -@@ -742,6 +742,22 @@ encode_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc, - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - -+ /* If the datapath supports all-zero SNAT then use it to avoid tuple -+ * collisions at commit time between NATed and firewalled-only sessions. -+ */ -+ -+ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) { -+ size_t nat_offset = ofpacts->size; -+ ofpbuf_pull(ofpacts, nat_offset); -+ -+ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts); -+ nat->flags = 0; -+ nat->range_af = AF_UNSPEC; -+ nat->flags |= NX_NAT_F_SRC; -+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); -+ ct = ofpacts->header; -+ } -+ - size_t set_field_offset = ofpacts->size; - ofpbuf_pull(ofpacts, set_field_offset); - -@@ -792,6 +808,21 @@ encode_CT_COMMIT_V2(const struct ovnact_nest *on, - ct->zone_src.ofs = 0; - ct->zone_src.n_bits = 16; - -+ /* If the datapath supports all-zero SNAT then use it to avoid tuple -+ * collisions at commit time between NATed and firewalled-only sessions. -+ */ -+ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) { -+ size_t nat_offset = ofpacts->size; -+ ofpbuf_pull(ofpacts, nat_offset); -+ -+ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts); -+ nat->flags = 0; -+ nat->range_af = AF_UNSPEC; -+ nat->flags |= NX_NAT_F_SRC; -+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); -+ ct = ofpacts->header; -+ } -+ - size_t set_field_offset = ofpacts->size; - ofpbuf_pull(ofpacts, set_field_offset); - -diff --git a/lib/automake.mk b/lib/automake.mk -index 781be2109..917b28e1e 100644 ---- a/lib/automake.mk -+++ b/lib/automake.mk -@@ -13,6 +13,7 @@ lib_libovn_la_SOURCES = \ - lib/expr.c \ - lib/extend-table.h \ - lib/extend-table.c \ -+ lib/features.c \ - lib/ovn-parallel-hmap.h \ - lib/ovn-parallel-hmap.c \ - lib/ip-mcast-index.c \ -diff --git a/lib/features.c b/lib/features.c -new file mode 100644 -index 000000000..87d04ee3f ---- /dev/null -+++ b/lib/features.c -@@ -0,0 +1,84 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+#include -+#include -+ -+#include "lib/util.h" -+#include "openvswitch/vlog.h" -+#include "ovn/features.h" -+ -+VLOG_DEFINE_THIS_MODULE(features); -+ -+struct ovs_feature { -+ enum ovs_feature_value value; -+ const char *name; -+}; -+ -+static struct ovs_feature all_ovs_features[] = { -+ { -+ .value = OVS_CT_ZERO_SNAT_SUPPORT, -+ .name = "ct_zero_snat" -+ }, -+}; -+ -+/* A bitmap of OVS features that have been detected as 'supported'. */ -+static uint32_t supported_ovs_features; -+ -+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); -+ -+static bool -+ovs_feature_is_valid(enum ovs_feature_value feature) -+{ -+ switch (feature) { -+ case OVS_CT_ZERO_SNAT_SUPPORT: -+ return true; -+ default: -+ return false; -+ } -+} -+ -+bool -+ovs_feature_is_supported(enum ovs_feature_value feature) -+{ -+ ovs_assert(ovs_feature_is_valid(feature)); -+ return supported_ovs_features & feature; -+} -+ -+/* Returns 'true' if the set of tracked OVS features has been updated. */ -+bool -+ovs_feature_support_update(const struct smap *ovs_capabilities) -+{ -+ bool updated = false; -+ -+ for (size_t i = 0; i < ARRAY_SIZE(all_ovs_features); i++) { -+ enum ovs_feature_value value = all_ovs_features[i].value; -+ const char *name = all_ovs_features[i].name; -+ bool old_state = supported_ovs_features & value; -+ bool new_state = smap_get_bool(ovs_capabilities, name, false); -+ if (new_state != old_state) { -+ updated = true; -+ if (new_state) { -+ supported_ovs_features |= value; -+ } else { -+ supported_ovs_features &= ~value; -+ } -+ VLOG_INFO_RL(&rl, "OVS Feature: %s, state: %s", name, -+ new_state ? "supported" : "not supported"); -+ } -+ } -+ return updated; -+} -diff --git a/lib/test-ovn-features.c b/lib/test-ovn-features.c -new file mode 100644 -index 000000000..deb97581e ---- /dev/null -+++ b/lib/test-ovn-features.c -@@ -0,0 +1,56 @@ -+/* Copyright (c) 2021, Red Hat, Inc. -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+ -+#include -+ -+#include "ovn/features.h" -+#include "tests/ovstest.h" -+ -+static void -+test_ovn_features(struct ovs_cmdl_context *ctx OVS_UNUSED) -+{ -+ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); -+ -+ struct smap features = SMAP_INITIALIZER(&features); -+ -+ smap_add(&features, "ct_zero_snat", "false"); -+ ovs_assert(!ovs_feature_support_update(&features)); -+ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); -+ -+ smap_replace(&features, "ct_zero_snat", "true"); -+ ovs_assert(ovs_feature_support_update(&features)); -+ ovs_assert(ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); -+ -+ smap_add(&features, "unknown_feature", "true"); -+ ovs_assert(!ovs_feature_support_update(&features)); -+ -+ smap_destroy(&features); -+} -+ -+static void -+test_ovn_features_main(int argc, char *argv[]) -+{ -+ set_program_name(argv[0]); -+ static const struct ovs_cmdl_command commands[] = { -+ {"run", NULL, 0, 0, test_ovn_features, OVS_RO}, -+ {NULL, NULL, 0, 0, NULL, OVS_RO}, -+ }; -+ struct ovs_cmdl_context ctx; -+ ctx.argc = argc - 1; -+ ctx.argv = argv + 1; -+ ovs_cmdl_run_command(&ctx, commands); -+} -+ -+OVSTEST_REGISTER("test-ovn-features", test_ovn_features_main); -diff --git a/northd/lrouter.dl b/northd/lrouter.dl -index 6c25b1ca9..6805b9036 100644 ---- a/northd/lrouter.dl -+++ b/northd/lrouter.dl -@@ -692,6 +692,17 @@ relation &StaticRoute(lrsr: nb::Logical_Router_Static_Route, - }, - var esr = lrsr.options.get_bool_def("ecmp_symmetric_reply", false). - -+relation &StaticRouteEmptyNextHop(lrsr: nb::Logical_Router_Static_Route, -+ key: route_key, -+ output_port: Option) -+&StaticRouteEmptyNextHop(.lrsr = lrsr, -+ .key = RouteKey{policy, ip_prefix, plen}, -+ .output_port = lrsr.output_port) :- -+ lrsr in nb::Logical_Router_Static_Route(.nexthop = ""), -+ not StaticRouteDown(lrsr._uuid), -+ var policy = route_policy_from_string(lrsr.policy), -+ Some{(var ip_prefix, var plen)} = ip46_parse_cidr(lrsr.ip_prefix). -+ - /* Returns the IP address of the router port 'op' that - * overlaps with 'ip'. If one is not found, returns None. */ - function find_lrp_member_ip(networks: lport_addresses, ip: v46_ip): Option = -@@ -743,6 +754,19 @@ RouterStaticRoute_(.router = router, - var route_id = FlatMap(routes), - route in &StaticRoute(.lrsr = nb::Logical_Router_Static_Route{._uuid = route_id}). - -+relation RouterStaticRouteEmptyNextHop_( -+ router : Intern, -+ key : route_key, -+ output_port : Option) -+ -+RouterStaticRouteEmptyNextHop_(.router = router, -+ .key = route.key, -+ .output_port = route.output_port) :- -+ router in &Router(), -+ nb::Logical_Router(._uuid = router._uuid, .static_routes = routes), -+ var route_id = FlatMap(routes), -+ route in &StaticRouteEmptyNextHop(.lrsr = nb::Logical_Router_Static_Route{._uuid = route_id}). -+ - /* Step-2: compute output_port for each pair */ - typedef route_dst = RouteDst { - nexthop: v46_ip, -@@ -805,6 +829,42 @@ RouterStaticRoute(router, key, dsts) :- - }, - var dsts = set_singleton(RouteDst{nexthop, src_ip, port, ecmp_symmetric_reply}). - -+relation RouterStaticRouteEmptyNextHop( -+ router : Intern, -+ key : route_key, -+ dsts : Set) -+ -+RouterStaticRouteEmptyNextHop(router, key, dsts) :- -+ RouterStaticRouteEmptyNextHop_(.router = router, -+ .key = key, -+ .output_port = Some{oport}), -+ /* output_port specified */ -+ port in &RouterPort(.lrp = &nb::Logical_Router_Port{.name = oport}, -+ .networks = networks), -+ /* There are no IP networks configured on the router's port via -+ * which 'route->nexthop' is theoretically reachable. But since -+ * 'out_port' has been specified, we honor it by trying to reach -+ * 'route->nexthop' via the first IP address of 'out_port'. -+ * (There are cases, e.g in GCE, where each VM gets a /32 IP -+ * address and the default gateway is still reachable from it.) */ -+ Some{var src_ip} = match (key.ip_prefix) { -+ IPv4{_} -> match (networks.ipv4_addrs.nth(0)) { -+ Some{addr} -> Some{IPv4{addr.addr}}, -+ None -> { -+ warn("No path for static route ${key.ip_prefix}"); -+ None -+ } -+ }, -+ IPv6{_} -> match (networks.ipv6_addrs.nth(0)) { -+ Some{addr} -> Some{IPv6{addr.addr}}, -+ None -> { -+ warn("No path for static route ${key.ip_prefix}"); -+ None -+ } -+ } -+ }, -+ var dsts = set_singleton(RouteDst{src_ip, src_ip, port, false}). -+ - /* compute route-route pairs for nexthop = "discard" routes */ - relation &DiscardRoute(lrsr: nb::Logical_Router_Static_Route, - key: route_key) -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index 407464602..890775797 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -1072,8 +1072,10 @@ output; - localport ports) that are down (unless - ignore_lsp_down is configured as true in options - column of NB_Global table of the Northbound -- database), for logical ports of type virtual and for -- logical ports with 'unknown' address set. -+ database), for logical ports of type virtual, for -+ logical ports with 'unknown' address set and for logical ports of -+ a logical switch configured with -+ other_config:vlan-passthru=true. -

    -
  • - -@@ -3710,6 +3712,13 @@ icmp6 { - external ip and D is NAT external mac. - - -+
  • -+ For each NAT rule in the OVN Northbound database that can -+ be handled in a distributed manner, a priority-80 logical flow -+ with drop action if the NAT logical port is a virtual port not -+ claimed by any chassis yet. -+
  • -+ -
  • - A priority-50 logical flow with match - outport == GW has actions -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 3dae7bb1c..148e3ee21 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -7007,6 +7007,10 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op, - return; - } - -+ if (is_vlan_transparent(op->od)) { -+ return; -+ } -+ - for (size_t i = 0; i < op->n_lsp_addrs; i++) { - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { - ds_clear(match); -@@ -7371,6 +7375,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group, - - struct mcast_switch_info *mcast_sw_info = - &igmp_group->datapath->mcast_info.sw; -+ uint64_t table_size = mcast_sw_info->table_size; - - if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { - /* RFC 4541, section 2.1.2, item 2: Skip groups in the 224.0.0.X -@@ -7381,10 +7386,8 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group, - if (ip_is_local_multicast(group_address)) { - return; - } -- - if (atomic_compare_exchange_strong( -- &mcast_sw_info->active_v4_flows, -- (uint64_t *) &mcast_sw_info->table_size, -+ &mcast_sw_info->active_v4_flows, &table_size, - mcast_sw_info->table_size)) { - return; - } -@@ -7399,8 +7402,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group, - return; - } - if (atomic_compare_exchange_strong( -- &mcast_sw_info->active_v6_flows, -- (uint64_t *) &mcast_sw_info->table_size, -+ &mcast_sw_info->active_v6_flows, &table_size, - mcast_sw_info->table_size)) { - return; - } -@@ -8039,10 +8041,16 @@ route_hash(struct parsed_route *route) - - static struct ovs_mutex bfd_lock = OVS_MUTEX_INITIALIZER; - -+static bool -+find_static_route_outport(struct ovn_datapath *od, struct hmap *ports, -+ const struct nbrec_logical_router_static_route *route, bool is_ipv4, -+ const char **p_lrp_addr_s, struct ovn_port **p_out_port); -+ - /* Parse and validate the route. Return the parsed route if successful. - * Otherwise return NULL. */ - static struct parsed_route * --parsed_routes_add(struct ovs_list *routes, -+parsed_routes_add(struct ovn_datapath *od, struct hmap *ports, -+ struct ovs_list *routes, - const struct nbrec_logical_router_static_route *route, - struct hmap *bfd_connections) - { -@@ -8050,7 +8058,8 @@ parsed_routes_add(struct ovs_list *routes, - struct in6_addr nexthop; - unsigned int plen; - bool is_discard_route = !strcmp(route->nexthop, "discard"); -- if (!is_discard_route) { -+ bool valid_nexthop = strlen(route->nexthop) && !is_discard_route; -+ if (valid_nexthop) { - if (!ip46_parse_cidr(route->nexthop, &nexthop, &plen)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'nexthop' %s in static route" -@@ -8079,7 +8088,7 @@ parsed_routes_add(struct ovs_list *routes, - } - - /* Verify that ip_prefix and nexthop have same address familiy. */ -- if (!is_discard_route) { -+ if (valid_nexthop) { - 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'" -@@ -8090,6 +8099,14 @@ parsed_routes_add(struct ovs_list *routes, - } - } - -+ /* Verify that ip_prefix and nexthop are on the same network. */ -+ if (!is_discard_route && -+ !find_static_route_outport(od, ports, route, -+ IN6_IS_ADDR_V4MAPPED(&prefix), -+ NULL, NULL)) { -+ return NULL; -+ } -+ - const struct nbrec_bfd *nb_bt = route->bfd; - if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) { - struct bfd_entry *bfd_e; -@@ -8364,8 +8381,12 @@ find_static_route_outport(struct ovn_datapath *od, struct hmap *ports, - route->ip_prefix, route->nexthop); - return false; - } -- *p_out_port = out_port; -- *p_lrp_addr_s = lrp_addr_s; -+ if (p_out_port) { -+ *p_out_port = out_port; -+ } -+ if (p_lrp_addr_s) { -+ *p_lrp_addr_s = lrp_addr_s; -+ } - - return true; - } -@@ -8563,7 +8584,7 @@ add_route(struct hmap *lflows, struct ovn_datapath *od, - } else { - ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ", - is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6); -- if (gateway) { -+ if (gateway && strlen(gateway)) { - ds_put_cstr(&common_actions, gateway); - } else { - ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6"); -@@ -9892,8 +9913,8 @@ build_static_route_flows_for_lrouter( - struct ecmp_groups_node *group; - for (int i = 0; i < od->nbr->n_static_routes; i++) { - struct parsed_route *route = -- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i], -- bfd_connections); -+ parsed_routes_add(od, ports, &parsed_routes, -+ od->nbr->static_routes[i], bfd_connections); - if (!route) { - continue; - } -@@ -11656,6 +11677,7 @@ lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat, - static void - build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, - struct hmap *lflows, -+ struct hmap *ports, - struct shash *meter_groups, - struct hmap *lbs, - struct ds *match, struct ds *actions) -@@ -11763,10 +11785,21 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, - ds_clear(match); - ds_clear(actions); - ds_put_format(match, -- "ip%s.src == %s && outport == %s && " -- "is_chassis_resident(\"%s\")", -+ "ip%s.src == %s && outport == %s", - is_v6 ? "6" : "4", nat->logical_ip, -- od->l3dgw_port->json_key, nat->logical_port); -+ od->l3dgw_port->json_key); -+ /* Add a rule to drop traffic from a distributed NAT if -+ * the virtual port has not claimed yet becaused otherwise -+ * the traffic will be centralized misconfiguring the TOR switch. -+ */ -+ struct ovn_port *op = ovn_port_find(ports, nat->logical_port); -+ if (op && op->nbsp && !strcmp(op->nbsp->type, "virtual")) { -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -+ 80, ds_cstr(match), "drop;", -+ &nat->header_); -+ } -+ ds_put_format(match, " && is_chassis_resident(\"%s\")", -+ nat->logical_port); - ds_put_format(actions, "eth.src = %s; %s = %s; next;", - nat->external_mac, - is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -@@ -11800,6 +11833,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, - ds_put_format(actions, - "clone { ct_clear; " - "inport = outport; outport = \"\"; " -+ "eth.dst <-> eth.src; " - "flags = 0; flags.loopback = 1; "); - for (int j = 0; j < MFF_N_LOG_REGS; j++) { - ds_put_format(actions, "reg%d = 0; ", j); -@@ -11925,8 +11959,9 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, - &lsi->actions); - build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); - build_lrouter_arp_nd_for_datapath(od, lsi->lflows); -- build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups, -- lsi->lbs, &lsi->match, &lsi->actions); -+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports, -+ lsi->meter_groups, lsi->lbs, &lsi->match, -+ &lsi->actions); - } - - /* Helper function to combine all lflow generation which is iterated by port. -@@ -13271,7 +13306,7 @@ ovnnb_db_run(struct northd_context *ctx, - struct smap options; - smap_clone(&options, &nb->options); - -- smap_add(&options, "mac_prefix", mac_addr_prefix); -+ smap_replace(&options, "mac_prefix", mac_addr_prefix); - - if (!monitor_mac) { - eth_addr_random(&svc_monitor_mac_ea); -@@ -13286,8 +13321,10 @@ ovnnb_db_run(struct northd_context *ctx, - - smap_replace(&options, "northd_internal_version", ovn_internal_version); - -- nbrec_nb_global_verify_options(nb); -- nbrec_nb_global_set_options(nb, &options); -+ if (!smap_equal(&nb->options, &options)) { -+ nbrec_nb_global_verify_options(nb); -+ nbrec_nb_global_set_options(nb, &options); -+ } - - smap_destroy(&options); - -diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl -index 3afa80a3b..de6a0652e 100644 ---- a/northd/ovn_northd.dl -+++ b/northd/ovn_northd.dl -@@ -3309,7 +3309,8 @@ for (CheckLspIsUp[check_lsp_is_up]) { - ((lsp_is_up(lsp) or not check_lsp_is_up) - or lsp.__type == "router" or lsp.__type == "localport") and - lsp.__type != "external" and lsp.__type != "virtual" and -- not lsp.addresses.contains("unknown")) -+ not lsp.addresses.contains("unknown") and -+ not sw.is_vlan_transparent) - { - var __match = "arp.tpa == ${addr.addr} && arp.op == 1" in - { -@@ -3359,7 +3360,8 @@ for (SwitchPortIPv6Address(.port = &SwitchPort{.lsp = lsp, .json_name = json_nam - .ea = ea, .addr = addr) - if lsp.is_enabled() and - (lsp_is_up(lsp) or lsp.__type == "router" or lsp.__type == "localport") and -- lsp.__type != "external" and lsp.__type != "virtual") -+ lsp.__type != "external" and lsp.__type != "virtual" and -+ not sw.is_vlan_transparent) - { - var __match = "nd_ns && ip6.dst == {${addr.addr}, ${addr.solicited_node()}} && nd.target == ${addr.addr}" in - var actions = "${if (lsp.__type == \"router\") \"nd_na_router\" else \"nd_na\"} { " -@@ -5555,6 +5557,10 @@ for (rp in &RouterPort(.router = &Router{._uuid = lr_uuid, .options = lr_options - } - } - -+relation VirtualLogicalPort(logical_port: Option) -+VirtualLogicalPort(Some{logical_port}) :- -+ lsp in &nb::Logical_Switch_Port(.name = logical_port, .__type = "virtual"). -+ - /* NAT rules are only valid on Gateway routers and routers with - * l3dgw_port (router has a port with "redirect-chassis" - * specified). */ -@@ -5649,7 +5655,7 @@ for (r in &Router(._uuid = lr_uuid, - } in - if (nat.nat.__type == "dnat" or nat.nat.__type == "dnat_and_snat") { - None = l3dgw_port in -- var __match = "ip && ip4.dst == ${nat.nat.external_ip}" in -+ var __match = "ip && ${ipX}.dst == ${nat.nat.external_ip}" in - (var ext_ip_match, var ext_flow) = lrouter_nat_add_ext_ip_match( - r, nat, __match, ipX, true, mask) in - { -@@ -5900,6 +5906,17 @@ for (r in &Router(._uuid = lr_uuid, - .actions = actions, - .external_ids = stage_hint(nat.nat._uuid)); - -+ for (VirtualLogicalPort(nat.nat.logical_port)) { -+ Some{var gwport} = l3dgw_port in -+ Flow(.logical_datapath = lr_uuid, -+ .stage = s_ROUTER_IN_GW_REDIRECT(), -+ .priority = 80, -+ .__match = "${ipX}.src == ${nat.nat.logical_ip} && " -+ "outport == ${json_string_escape(gwport.name)}", -+ .actions = "drop;", -+ .external_ids = stage_hint(nat.nat._uuid)) -+ }; -+ - /* Egress Loopback table: For NAT on a distributed router. - * If packets in the egress pipeline on the distributed - * gateway port have ip.dst matching a NAT external IP, then -@@ -5925,6 +5942,7 @@ for (r in &Router(._uuid = lr_uuid, - var actions = - "clone { ct_clear; " - "inport = outport; outport = \"\"; " -+ "eth.dst <-> eth.src; " - "flags = 0; flags.loopback = 1; " ++ - regs.join("") ++ - "${rEGBIT_EGRESS_LOOPBACK()} = 1; " -@@ -6468,6 +6486,11 @@ Route(key, dst.port, dst.src_ip, Some{dst.nexthop}) :- - dsts.size() == 1, - Some{var dst} = dsts.nth(0). - -+Route(key, dst.port, dst.src_ip, None) :- -+ RouterStaticRouteEmptyNextHop(.router = router, .key = key, .dsts = dsts), -+ dsts.size() == 1, -+ Some{var dst} = dsts.nth(0). -+ - /* Return a vector of pairs (1, set[0]), ... (n, set[n - 1]). */ - function numbered_vec(set: Set<'A>) : Vec<(bit<16>, 'A)> = { - var vec = vec_with_capacity(set.size()); -diff --git a/tests/automake.mk b/tests/automake.mk -index 742e5cff2..a8ec64212 100644 ---- a/tests/automake.mk -+++ b/tests/automake.mk -@@ -34,6 +34,7 @@ TESTSUITE_AT = \ - tests/ovn-performance.at \ - tests/ovn-ofctrl-seqno.at \ - tests/ovn-ipam.at \ -+ tests/ovn-features.at \ - tests/ovn-lflow-cache.at \ - tests/ovn-ipsec.at - -@@ -207,6 +208,7 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac - - noinst_PROGRAMS += tests/ovstest - tests_ovstest_SOURCES = \ -+ include/ovn/features.h \ - tests/ovstest.c \ - tests/ovstest.h \ - tests/test-utils.c \ -@@ -218,6 +220,7 @@ tests_ovstest_SOURCES = \ - controller/lflow-cache.h \ - controller/ofctrl-seqno.c \ - controller/ofctrl-seqno.h \ -+ lib/test-ovn-features.c \ - northd/test-ipam.c \ - northd/ipam.c \ - northd/ipam.h -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 72c07b3fa..1aab49ae8 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -151,23 +151,24 @@ sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id) - check_datapath_type () { - datapath_type=$1 - chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} other_config:datapath-type | sed -e 's/"//g') #" -- test "${datapath_type}" = "${chassis_datapath_type}" -+ ovs_datapath_type=$(ovs-vsctl get Bridge br-int datapath-type) -+ test "${datapath_type}" = "${chassis_datapath_type}" && test "${datapath_type}" = "${ovs_datapath_type}" - } - --OVS_WAIT_UNTIL([check_datapath_type ""]) -+OVS_WAIT_UNTIL([check_datapath_type system]) - - ovs-vsctl set Bridge br-int datapath-type=foo - OVS_WAIT_UNTIL([check_datapath_type foo]) - - # Change "ovn-bridge-mappings" value. It should not change the "datapath-type". - ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings=foo-mapping --check_datapath_type foo -+AT_CHECK([check_datapath_type foo]) - - ovs-vsctl set Bridge br-int datapath-type=bar - OVS_WAIT_UNTIL([check_datapath_type bar]) - - ovs-vsctl set Bridge br-int datapath-type=\"\" --OVS_WAIT_UNTIL([check_datapath_type ""]) -+OVS_WAIT_UNTIL([check_datapath_type system]) - - # Set the datapath_type in external_ids:ovn-bridge-datapath-type. - ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foo -@@ -176,11 +177,9 @@ OVS_WAIT_UNTIL([check_datapath_type foo]) - # Change the br-int's datapath type to bar. - # It should be reset to foo since ovn-bridge-datapath-type is configured. - ovs-vsctl set Bridge br-int datapath-type=bar --OVS_WAIT_UNTIL([test foo = `ovs-vsctl get Bridge br-int datapath-type`]) - OVS_WAIT_UNTIL([check_datapath_type foo]) - - ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foobar --OVS_WAIT_UNTIL([test foobar = `ovs-vsctl get Bridge br-int datapath-type`]) - OVS_WAIT_UNTIL([check_datapath_type foobar]) - - expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""') -@@ -393,6 +392,37 @@ OVN_CLEANUP([hv]) - AT_CLEANUP - ]) - -+# check that nb_cfg overflow cases handled properly -+AT_SETUP([ovn-controller - overflow the nb_cfg value across the tables]) -+AT_KEYWORDS([ovn]) -+ovn_start -+ -+net_add n1 -+sim_add hv -+as hv -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovn-nbctl --wait=hv sync -+ -+# overflow the NB_Global nb_cfg value -+check ovn-nbctl set NB_Global . nb_cfg=9223372036854775806 -+ -+# nb_cfg must be set to zero if it exceed the value of LLONG_MAX -+# the command below will try incress the value of nb_cfg to be greater than LLONG_MAX and -+# expect zero as a return value -+check ovn-nbctl --wait=hv sync -+check ovn-nbctl --wait=hv sync -+ -+# nb_cfg should be set to 1 in the chassis_private/nb_global/sb_global table -+check_column 1 chassis_private nb_cfg -+check_column 1 sb_global nb_cfg -+check_column 1 nb:nb_global nb_cfg -+check_column 0 chassis nb_cfg -+ -+OVN_CLEANUP([hv]) -+AT_CLEANUP -+ - # Test unix command: debug/delay-nb-cfg-report - OVN_FOR_EACH_NORTHD([ - AT_SETUP([ovn-controller - debug/delay-nb-cfg-report]) -diff --git a/tests/ovn-features.at b/tests/ovn-features.at -new file mode 100644 -index 000000000..36bd83055 ---- /dev/null -+++ b/tests/ovn-features.at -@@ -0,0 +1,8 @@ -+# -+# Unit tests for the lib/features.c module. -+# -+AT_BANNER([OVN unit tests - features]) -+ -+AT_SETUP([ovn -- unit test -- OVS feature detection tests]) -+AT_CHECK([ovstest test-ovn-features run], [0], []) -+AT_CLEANUP -diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 1058d418a..0922e1aa0 100644 ---- a/tests/ovn-nbctl.at -+++ b/tests/ovn-nbctl.at -@@ -1442,11 +1442,16 @@ dnl --------------------------------------------------------------------- - - OVN_NBCTL_TEST([ovn_nbctl_routes], [routes], [ - AT_CHECK([ovn-nbctl lr-add lr0]) -+AT_CHECK([ovn-nbctl lrp-add lr0 lp0 f0:00:00:00:00:01 10.0.0.254/24]) - - dnl Check IPv4 routes - AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1]) - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.0/24 11.0.1.1 lp0]) - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.2]) -+AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp0]) -+AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp1], [1], [], -+ [ovn-nbctl: bad IPv4 nexthop argument: lp1 -+]) - - dnl Add overlapping route with 10.0.0.1/24 - AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1], [1], [], -@@ -1495,6 +1500,7 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip - 10.0.1.0/24 11.0.1.1 dst-ip lp0 -+ 10.0.10.0/24 dst-ip lp0 - 20.0.0.0/24 discard dst-ip - 9.16.1.0/24 11.0.0.1 src-ip - 10.0.0.0/24 11.0.0.2 src-ip -@@ -1502,11 +1508,13 @@ IPv4 Routes - 0.0.0.0/0 192.168.0.1 dst-ip - ]) - -+AT_CHECK([ovn-nbctl lrp-add lr0 lp1 f0:00:00:00:00:02 11.0.0.254/24]) - AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip lp1 - 10.0.1.0/24 11.0.1.1 dst-ip lp0 -+ 10.0.10.0/24 dst-ip lp0 - 20.0.0.0/24 discard dst-ip - 9.16.1.0/24 11.0.0.1 src-ip - 10.0.0.0/24 11.0.0.2 src-ip -@@ -1535,6 +1543,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 9.16.1.0/24]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes - 10.0.0.0/24 11.0.0.1 dst-ip lp1 -+ 10.0.10.0/24 dst-ip lp0 - 10.0.0.0/24 11.0.0.2 src-ip - 0.0.0.0/0 192.168.0.1 dst-ip - ]) -@@ -1544,6 +1553,7 @@ AT_CHECK([ovn-nbctl --policy=dst-ip lr-route-del lr0 10.0.0.0/24]) - AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 10.0.0.0/24]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -+ 10.0.10.0/24 dst-ip lp0 - 0.0.0.0/0 192.168.0.1 dst-ip - ]) - -@@ -1553,6 +1563,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 10.0.0.0/24 11.0.0.2]) - AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24]) - AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl - IPv4 Routes -+ 10.0.10.0/24 dst-ip lp0 - 0.0.0.0/0 192.168.0.1 dst-ip - ]) - -diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index ad1732da3..2b70f48f6 100644 ---- a/tests/ovn-northd.at -+++ b/tests/ovn-northd.at -@@ -3016,16 +3016,16 @@ for i in $(seq 1 5); do - check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i - done - --uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10) --ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20 --ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down --ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0 -+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.1.2 status=down min_tx=250 min_rx=250 detect_mult=10) -+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.2.2 status=down min_tx=500 min_rx=500 detect_mult=20 -+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.3.2 status=down -+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.4.2 status=down min_tx=0 detect_mult=0 - --wait_row_count bfd 1 logical_port=r0-sw1 detect_mult=10 dst_ip=192.168.10.2 \ -+wait_row_count bfd 1 logical_port=r0-sw1 detect_mult=10 dst_ip=192.168.1.2 \ - min_rx=250 min_tx=250 status=admin_down --wait_row_count bfd 1 logical_port=r0-sw2 detect_mult=20 dst_ip=192.168.20.2 \ -+wait_row_count bfd 1 logical_port=r0-sw2 detect_mult=20 dst_ip=192.168.2.2 \ - min_rx=500 min_tx=500 status=admin_down --wait_row_count bfd 1 logical_port=r0-sw3 detect_mult=5 dst_ip=192.168.30.2 \ -+wait_row_count bfd 1 logical_port=r0-sw3 detect_mult=5 dst_ip=192.168.3.2 \ - min_rx=1000 min_tx=1000 status=admin_down - - uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1) -@@ -3036,17 +3036,17 @@ check ovn-nbctl clear bfd $uuid_2 min_rx - wait_row_count bfd 1 logical_port=r0-sw2 min_rx=1000 - wait_row_count bfd 1 logical_port=r0-sw1 min_rx=1000 min_tx=1000 detect_mult=100 - --check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.10.2 -+check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.1.2 - wait_column down bfd status logical_port=r0-sw1 --AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0]) -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.1.2 | grep -q bfd],[0]) - --check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.20.2 -+check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.2.2 - wait_column down bfd status logical_port=r0-sw2 --AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.20.2 | grep -q bfd],[0]) -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.2.2 | grep -q bfd],[0]) - --check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw5 -+check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.5.2 r0-sw5 - wait_column down bfd status logical_port=r0-sw5 --AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.50.2 | grep -q bfd],[0]) -+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.5.2 | grep -q bfd],[0]) - - route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8") - check ovn-nbctl clear logical_router_static_route $route_uuid bfd -@@ -3659,3 +3659,73 @@ check ovn-nbctl --wait=sb sync - OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - AT_CLEANUP - ]) -+ -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- static routes flows]) -+AT_KEYWORDS([static-routes-flows]) -+ovn_start -+ -+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1 -+ -+check ovn-nbctl lr-add lr0 -+check ovn-nbctl ls-add public -+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 192.168.0.1/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 -+ -+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.0.10 -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+ -+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl -+]) -+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows |sort], [0], [dnl -+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.0.20 -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl -+ table=10(lr_in_ip_routing ), priority=65 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) -+]) -+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' |sort], [0], [dnl -+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) -+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) -+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) -+]) -+ -+# add ecmp route with wrong nexthop -+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.1.20 -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl -+ table=10(lr_in_ip_routing ), priority=65 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) -+]) -+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' |sort], [0], [dnl -+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) -+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) -+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;) -+]) -+ -+check ovn-nbctl lr-route-del lr0 -+wait_row_count nb:Logical_Router_Static_Route 0 -+ -+check ovn-nbctl --wait=sb lr-route-add lr0 1.0.0.0/24 192.168.0.10 -+ovn-sbctl dump-flows lr0 > lr0flows -+ -+AT_CHECK([grep -e "lr_in_ip_routing.*192.168.0.10" lr0flows |sort], [0], [dnl -+ table=10(lr_in_ip_routing ), priority=49 , match=(ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+]) -+ -+check ovn-nbctl --wait=sb lr-route-add lr0 2.0.0.0/24 lr0-public -+ -+ovn-sbctl dump-flows lr0 > lr0flows -+AT_CHECK([grep -e "lr_in_ip_routing.*2.0.0.0" lr0flows |sort], [0], [dnl -+ table=10(lr_in_ip_routing ), priority=49 , match=(ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) -+]) -+ -+AT_CLEANUP -+]) -diff --git a/tests/ovn.at b/tests/ovn.at -index aa80a7c48..5eb7f8b7b 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -3169,6 +3169,118 @@ OVN_CLEANUP([hv-1],[hv-2]) - AT_CLEANUP - ]) - -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- VLAN transparency, passthru=true, ARP responder disabled]) -+ovn_start -+ -+net_add net -+check ovs-vsctl add-br br-phys -+ovn_attach net br-phys 192.168.0.1 -+ -+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 10.0.0.$i" -+done -+ -+for i in 1 2; do -+ check 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 -+done -+ -+wait_for_ports_up -+ -+ovn-sbctl dump-flows ls > lsflows -+AT_CAPTURE_FILE([lsflows]) -+ -+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl -+ table=16(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;) -+]) -+ -+test_arp() { -+ local inport=$1 outport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6 -+ tag=8100fefe -+ local request=ffffffffffff${sha}${tag}08060001080006040001${sha}${spa}ffffffffffff${tpa} -+ ovs-appctl netdev-dummy/receive vif$inport $request -+ echo $request >> $outport.expected -+ -+ local reply=${sha}${reply_ha}${tag}08060001080006040002${reply_ha}${tpa}${sha}${spa} -+ ovs-appctl netdev-dummy/receive vif$outport $reply -+ echo $reply >> $inport.expected -+} -+ -+test_arp 1 2 f00000000001 0a000001 0a000002 f00000000002 -+test_arp 2 1 f00000000002 0a000002 0a000001 f00000000001 -+ -+for i in 1 2; do -+ OVN_CHECK_PACKETS([vif$i-tx.pcap], [$i.expected]) -+done -+ -+AT_CLEANUP -+]) -+ -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- VLAN transparency, passthru=true, ND/NA responder disabled]) -+ovn_start -+ -+net_add net -+check ovs-vsctl add-br br-phys -+ovn_attach net br-phys 192.168.0.1 -+ -+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 fe00::$i" -+done -+ -+for i in 1 2; do -+ check 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 -+done -+ -+wait_for_ports_up -+ -+ovn-sbctl dump-flows ls > lsflows -+AT_CAPTURE_FILE([lsflows]) -+ -+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl -+ table=16(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;) -+]) -+ -+test_nd_na() { -+ local inport=$1 outport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6 -+ tag=8100fefe -+ icmp_type=87 -+ local request=ffffffffffff${sha}${tag}86dd6000000000183aff${spa}ff0200000000000000000001ff${tpa: -6}${icmp_type}007ea100000000${tpa} -+ ovs-appctl netdev-dummy/receive vif$inport $request -+ echo $request >> $outport.expected -+ echo $request -+ -+ icmp_type=88 -+ local reply=${sha}${reply_ha}${tag}86dd6000000000183aff${tpa}${spa}${icmp_type}003da540000000${tpa} -+ ovs-appctl netdev-dummy/receive vif$outport $reply -+ echo $reply >> $inport.expected -+ echo $reply -+} -+ -+test_nd_na 1 2 f00000000001 fe000000000000000000000000000001 fe000000000000000000000000000002 f00000000002 -+test_nd_na 2 1 f00000000002 fe000000000000000000000000000002 fe000000000000000000000000000001 f00000000001 -+ -+for i in 1 2; do -+ OVN_CHECK_PACKETS([vif$i-tx.pcap], [$i.expected]) -+done -+ -+AT_CLEANUP -+]) -+ - OVN_FOR_EACH_NORTHD([ - AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts]) - ovn_start -@@ -7821,6 +7933,19 @@ mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \") - port_addr=$(ovn-nbctl get Logical-Switch-Port p91 dynamic_addresses | tr -d \") - AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:09"], [0], []) - -+# set mac_prefix to all-zeroes and check it is allocated in a random manner -+ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:00:00:00:00:00" -+ovn-nbctl ls-add sw14 -+ovn-nbctl --wait=sb set Logical-Switch sw14 other_config:mac_only=true -+ovn-nbctl --wait=sb lsp-add sw14 p141 -- lsp-set-addresses p141 dynamic -+ -+mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \") -+port_addr=$(ovn-nbctl get Logical-Switch-Port p141 dynamic_addresses | tr -d \") -+AT_CHECK([test "$mac_prefix" != "00:00:00:00:00:00"], [0], []) -+AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:0a"], [0], []) -+ovn-nbctl --wait=sb lsp-del sw14 p141 -+ovn-nbctl --wait=sb ls-del sw14 -+ - ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22" - ovn-nbctl ls-add sw10 - ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:ipv6_prefix="ae01::" -@@ -11260,7 +11385,7 @@ ovn-nbctl lsp-add foo ln-foo - ovn-nbctl lsp-set-addresses ln-foo unknown - ovn-nbctl lsp-set-options ln-foo network_name=public - ovn-nbctl lsp-set-type ln-foo localnet --AT_CHECK([ovn-nbctl set Logical_Switch_Port ln-foo tag=2]) -+check ovn-nbctl set Logical_Switch_Port ln-foo tag_request=2 - - # Create localnet port in alice - ovn-nbctl lsp-add alice ln-alice -@@ -12024,6 +12149,91 @@ OVN_CLEANUP([hv1]) - AT_CLEANUP - ]) - -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([localport doesn't suppress ARP directed to external port]) -+ -+ovn_start -+net_add n1 -+ -+check ovs-vsctl add-br br-phys -+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovn-nbctl ls-add ls -+ -+# create topology to allow to talk from localport through localnet to external port -+check ovn-nbctl lsp-add ls lp -+check ovn-nbctl lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" -+check ovn-nbctl lsp-set-type lp localport -+check ovs-vsctl add-port br-int lp -- set Interface lp external-ids:iface-id=lp -+ -+check ovn-nbctl --wait=sb ha-chassis-group-add hagrp -+check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp main 10 -+check ovn-nbctl lsp-add ls lext -+check ovn-nbctl lsp-set-addresses lext "00:00:00:00:00:02 10.0.0.2" -+check ovn-nbctl lsp-set-type lext external -+hagrp_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp` -+check ovn-nbctl set logical_switch_port lext ha_chassis_group=$hagrp_uuid -+ -+check ovn-nbctl lsp-add ls ln -+check ovn-nbctl lsp-set-addresses ln unknown -+check ovn-nbctl lsp-set-type ln localnet -+check ovn-nbctl lsp-set-options ln network_name=phys -+check ovn-nbctl --wait=hv sync -+ -+# also create second external port AFTER localnet to check that order is irrelevant -+check ovn-nbctl lsp-add ls lext2 -+check ovn-nbctl lsp-set-addresses lext2 "00:00:00:00:00:10 10.0.0.10" -+check ovn-nbctl lsp-set-type lext2 external -+check ovn-nbctl set logical_switch_port lext2 ha_chassis_group=$hagrp_uuid -+check ovn-nbctl --wait=hv sync -+ -+# create and immediately delete an external port to later check that flows for -+# deleted ports are not left over in flow table -+check ovn-nbctl lsp-add ls lext-deleted -+check ovn-nbctl lsp-set-addresses lext-deleted "00:00:00:00:00:03 10.0.0.3" -+check ovn-nbctl lsp-set-type lext-deleted external -+check ovn-nbctl set logical_switch_port lext-deleted ha_chassis_group=$hagrp_uuid -+check ovn-nbctl --wait=hv sync -+check ovn-nbctl lsp-del lext-deleted -+check ovn-nbctl --wait=hv sync -+ -+send_garp() { -+ local inport=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 -+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa} -+ ovs-appctl netdev-dummy/receive $inport $request -+} -+ -+spa=$(ip_to_hex 10 0 0 1) -+tpa=$(ip_to_hex 10 0 0 2) -+send_garp lp 000000000001 000000000002 $spa $tpa -+ -+spa=$(ip_to_hex 10 0 0 1) -+tpa=$(ip_to_hex 10 0 0 10) -+send_garp lp 000000000001 000000000010 $spa $tpa -+ -+spa=$(ip_to_hex 10 0 0 1) -+tpa=$(ip_to_hex 10 0 0 3) -+send_garp lp 000000000001 000000000003 $spa $tpa -+ -+dnl external traffic from localport should be sent to localnet -+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000002 | wc -l],[0],[dnl -+1 -+],[ignore]) -+ -+#dnl ...regardless of localnet / external ports creation order -+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a00000a | wc -l],[0],[dnl -+1 -+],[ignore]) -+ -+dnl traffic from localport should not be sent to deleted external port -+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000003 | wc -l],[0],[dnl -+0 -+],[ignore]) -+ -+AT_CLEANUP -+]) -+ - OVN_FOR_EACH_NORTHD([ - AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) - ovn_start -@@ -12668,7 +12878,7 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros - AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], []) - - # change localnet port tag. --AT_CHECK([ovn-nbctl set Logical_Switch_Port ln_port tag=2014]) -+check ovn-nbctl set Logical_Switch_Port ln_port tag_request=2014 - - # wait for earlier changes to take effect - OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-ofctl dump-flows br-int table=65 | \ -@@ -17172,6 +17382,16 @@ send_arp_reply() { - as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request - } - -+send_icmp_packet() { -+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8 -+ shift 8 -+ -+ local ip_ttl=ff -+ local ip_len=001c -+ local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data} -+ as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $packet -+} -+ - net_add n1 - - sim_add hv1 -@@ -17311,27 +17531,29 @@ logical_port=sw0-vir) = x]) - as hv1 - ovs-vsctl set interface hv1-vif3 external-ids:iface-id=sw0-vir - --AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ --logical_port=sw0-vir) = x], [0], []) -+wait_column "" Port_Binding chassis logical_port=sw0-vir - - # Cleanup hv1-vif3. - as hv1 - ovs-vsctl del-port hv1-vif3 - --AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ --logical_port=sw0-vir) = x], [0], []) -+wait_column "" Port_Binding chassis logical_port=sw0-vir - - check_virtual_offlows_present() { - hv=$1 - -- AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl -- table=44, priority=2000,ip,metadata=0x1 actions=resubmit(,45) -- table=44, priority=2000,ipv6,metadata=0x1 actions=resubmit(,45) -+ sw0_dp_key=$(printf "%x" $(fetch_column Datapath_Binding tunnel_key external_ids:name=sw0)) -+ lr0_dp_key=$(printf "%x" $(fetch_column Datapath_Binding tunnel_key external_ids:name=lr0)) -+ lr0_public_dp_key=$(printf "%x" $(fetch_column Port_Binding tunnel_key logical_port=lr0-public)) -+ -+ AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl -+ table=44, priority=2000,ip,metadata=0x$sw0_dp_key actions=resubmit(,45) -+ table=44, priority=2000,ipv6,metadata=0x$sw0_dp_key actions=resubmit(,45) - ]) - -- AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ -+ AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ - grep "priority=92" | grep 172.168.0.50], [0], [dnl -- table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37) -+ table=11, priority=92,arp,reg14=0x$lr0_public_dp_key,metadata=0x$lr0_dp_key,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37) - ]) - } - -@@ -17384,6 +17606,22 @@ logical_port=sw0-vir) = x]) - wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir - - check ovn-nbctl --wait=hv sync -+ -+# verify the traffic from virtual port is discarded if the port is not claimed -+AT_CHECK([grep lr_in_gw_redirect lr0-flows2 | grep "ip4.src == 10.0.0.10"], [0], [dnl -+ table=17(lr_in_gw_redirect ), priority=100 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("sw0-vir")), action=(eth.src = 10:54:00:00:00:10; reg1 = 172.168.0.50; next;) -+ table=17(lr_in_gw_redirect ), priority=80 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public"), action=(drop;) -+]) -+ -+eth_src=505400000003 -+eth_dst=00000000ff01 -+ip_src=$(ip_to_hex 10 0 0 10) -+ip_dst=$(ip_to_hex 172 168 0 101) -+send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000 -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=25, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl -+priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop -+]) -+ - # hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir. - check_virtual_offlows_not_present hv1 - -@@ -23116,7 +23354,7 @@ AT_CHECK([ - for hv in 1 2; do - grep table=15 hv${hv}flows | \ - grep "priority=100" | \ -- grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" - - grep table=22 hv${hv}flows | \ - grep "priority=200" | \ -@@ -23241,7 +23479,7 @@ AT_CHECK([ - for hv in 1 2; do - grep table=15 hv${hv}flows | \ - grep "priority=100" | \ -- grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" - - grep table=22 hv${hv}flows | \ - grep "priority=200" | \ -@@ -26688,6 +26926,50 @@ OVN_CLEANUP([hv1]) - AT_CLEANUP - ]) - -+# Tests that ACLs referencing port groups that include ports connected to -+# logical routers are correctly applied. -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- ACL with Port Group including router ports]) -+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 -+ -+check ovn-nbctl \ -+ -- lr-add lr \ -+ -- ls-add ls \ -+ -- lrp-add lr lrp_ls 00:00:00:00:00:01 42.42.42.1/24 \ -+ -- lsp-add ls ls_lr \ -+ -- lsp-set-addresses ls_lr router \ -+ -- lsp-set-type ls_lr router \ -+ -- lsp-set-options ls_lr router-port=lr_ls \ -+ -- lsp-add ls vm1 -+ -+check ovn-nbctl pg-add pg ls_lr \ -+ -- acl-add pg from-lport 1 'inport == @pg && ip4.dst == 42.42.42.42' drop -+ -+check ovs-vsctl add-port br-int vm1 \ -+ -- set interface vm1 external_ids:iface-id=vm1 -+ -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+dp_key=$(fetch_column Datapath_Binding tunnel_key external_ids:name=ls) -+rtr_port_key=$(fetch_column Port_Binding tunnel_key logical_port=ls_lr) -+ -+# Check that ovn-controller adds a flow to drop packets with dest IP -+# 42.42.42.42 coming from the router port. -+AT_CHECK([ovs-ofctl dump-flows br-int table=17 | grep "reg14=0x${rtr_port_key},metadata=0x${dp_key},nw_dst=42.42.42.42 actions=drop" -c], [0], [dnl -+1 -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+]) -+ - OVN_FOR_EACH_NORTHD([ - AT_SETUP([ovn -- Static route with discard nexthop]) - ovn_start -diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at -index c8fa6f03f..b742a2cb9 100644 ---- a/tests/system-common-macros.at -+++ b/tests/system-common-macros.at -@@ -330,3 +330,7 @@ m4_define([OVS_CHECK_IPROUTE_ENCAP], - # OVS_CHECK_CT_CLEAR() - m4_define([OVS_CHECK_CT_CLEAR], - [AT_SKIP_IF([! grep -q "Datapath supports ct_clear action" ovs-vswitchd.log])]) -+ -+# OVS_CHECK_CT_ZERO_SNAT() -+m4_define([OVS_CHECK_CT_ZERO_SNAT], -+ [AT_SKIP_IF([! grep -q "Datapath supports ct_zero_snat" ovs-vswitchd.log])])) -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 310bd3d5a..56cd26535 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -1348,7 +1348,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -3121,7 +3121,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -4577,7 +4577,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -4663,7 +4663,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -4903,7 +4903,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -5287,7 +5287,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -5296,6 +5296,196 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d - AT_CLEANUP - ]) - -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv4]) -+AT_SKIP_IF([test $HAVE_NC = no]) -+AT_KEYWORDS([ovnlb]) -+ -+CHECK_CONNTRACK() -+CHECK_CONNTRACK_NAT() -+ovn_start -+OVS_TRAFFIC_VSWITCHD_START() -+OVS_CHECK_CT_ZERO_SNAT() -+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: -+# 1 logical switch connetected to one logical router. -+# 2 VMs, one used as backend for a load balancer. -+ -+check ovn-nbctl \ -+ -- lr-add rtr \ -+ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 42.42.42.1/24 \ -+ -- ls-add ls \ -+ -- lsp-add ls ls-rtr \ -+ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \ -+ -- lsp-set-type ls-rtr router \ -+ -- lsp-set-options ls-rtr router-port=rtr-ls \ -+ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ -+ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ -+ -- lb-add lb-test 66.66.66.66:666 42.42.42.2:4242 tcp \ -+ -- ls-lb-add ls lb-test -+ -+ADD_NAMESPACES(vm1) -+ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1") -+ -+ADD_NAMESPACES(vm2) -+ADD_VETH(vm2, vm2, br-int, "42.42.42.3/24", "00:00:00:00:00:02", "42.42.42.1") -+ -+# Wait for ovn-controller to catch up. -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+# Start IPv4 TCP server on vm1. -+NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid]) -+ -+# Make sure connecting to the VIP works. -+NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2000 -z]) -+ -+# Start IPv4 TCP connection to VIP from vm2. -+NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2001 -z]) -+ -+# Check conntrack. We expect two entries: -+# - one in vm1's zone (firewall) -+# - one in vm2's zone (dnat) -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ -+grep "orig=.src=42\.42\.42\.3" | \ -+sed -e 's/port=2001/port=/g' \ -+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ -+ -e 's/state=[[0-9_A-Z]]*/state=/g' \ -+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl -+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) -+]) -+ -+# Start IPv4 TCP connection to backend IP from vm2 which would require -+# additional source port translation to avoid a tuple conflict. -+NS_CHECK_EXEC([vm2], [nc 42.42.42.2 4242 -p 2001 -z]) -+ -+# Check conntrack. We expect three entries: -+# - one in vm1's zone (firewall) - reused from the previous connection. -+# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection. -+# - one in vm2's zone (firewall + additional all-zero SNAT) -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ -+grep "orig=.src=42\.42\.42\.3" | \ -+sed -e 's/port=2001/port=/g' \ -+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ -+ -e 's/state=[[0-9_A-Z]]*/state=/g' \ -+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl -+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) -+]) -+ -+AT_CLEANUP -+]) -+ -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv6]) -+AT_SKIP_IF([test $HAVE_NC = no]) -+AT_KEYWORDS([ovnlb]) -+ -+CHECK_CONNTRACK() -+CHECK_CONNTRACK_NAT() -+ovn_start -+OVS_TRAFFIC_VSWITCHD_START() -+OVS_CHECK_CT_ZERO_SNAT() -+ADD_BR([br-int]) -+ -+# 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: -+# 1 logical switch connetected to one logical router. -+# 2 VMs, one used as backend for a load balancer. -+ -+check ovn-nbctl \ -+ -- lr-add rtr \ -+ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 4242::1/64 \ -+ -- ls-add ls \ -+ -- lsp-add ls ls-rtr \ -+ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \ -+ -- lsp-set-type ls-rtr router \ -+ -- lsp-set-options ls-rtr router-port=rtr-ls \ -+ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ -+ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ -+ -- lb-add lb-test [[6666::1]]:666 [[4242::2]]:4242 tcp \ -+ -- ls-lb-add ls lb-test -+ -+ADD_NAMESPACES(vm1) -+ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") -+OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) -+ -+ADD_NAMESPACES(vm2) -+ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") -+OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) -+ -+# Wait for ovn-controller to catch up. -+wait_for_ports_up -+check ovn-nbctl --wait=hv sync -+ -+# Start IPv6 TCP server on vm1. -+NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid]) -+ -+# Make sure connecting to the VIP works. -+NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2000 -z]) -+ -+# Start IPv6 TCP connection to VIP from vm2. -+NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2001 -z]) -+ -+# Check conntrack. We expect two entries: -+# - one in vm1's zone (firewall) -+# - one in vm2's zone (dnat) -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ -+grep "orig=.src=4242::3" | \ -+sed -e 's/port=2001/port=/g' \ -+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ -+ -e 's/state=[[0-9_A-Z]]*/state=/g' \ -+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl -+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) -+]) -+ -+# Start IPv6 TCP connection to backend IP from vm2 which would require -+# additional source port translation to avoid a tuple conflict. -+NS_CHECK_EXEC([vm2], [nc 4242::2 4242 -p 2001 -z]) -+ -+# Check conntrack. We expect three entries: -+# - one in vm1's zone (firewall) - reused from the previous connection. -+# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection. -+# - one in vm2's zone (firewall + additional all-zero SNAT) -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ -+grep "orig=.src=4242::3" | \ -+sed -e 's/port=2001/port=/g' \ -+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ -+ -e 's/state=[[0-9_A-Z]]*/state=/g' \ -+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl -+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) -+]) -+ -+AT_CLEANUP -+]) -+ - # When a lport is released on a chassis, ovn-controller was - # not clearing some of the flowss in the table 33 leading - # to packet drops if ct() is hit. -@@ -5527,7 +5717,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -5689,7 +5879,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -5738,7 +5928,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -@@ -5831,7 +6021,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -@@ -5893,7 +6083,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -@@ -6044,7 +6234,7 @@ as ovn-nb - OVS_APP_EXIT_AND_WAIT([ovsdb-server]) - - as northd --OVS_APP_EXIT_AND_WAIT([ovn-northd]) -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - - as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -@@ -6091,7 +6281,6 @@ check ovn-nbctl pg-add pg1 sw1-p1 - check ovn-nbctl acl-add pg1 from-lport 1002 "ip" allow-related - check ovn-nbctl acl-add pg1 to-lport 1002 "ip" allow-related - -- - OVN_POPULATE_ARP - ovn-nbctl --wait=hv sync - -@@ -6179,5 +6368,117 @@ OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) - as - OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d - /connection dropped.*/d"]) -+ -+AT_CLEANUP -+]) -+ -+OVN_FOR_EACH_NORTHD([ -+AT_SETUP(ovn -- DNAT LR hairpin IPv4) -+AT_KEYWORDS(hairpin) -+ -+ovn_start -+ -+OVS_TRAFFIC_VSWITCHD_START() -+ADD_BR([br-int]) -+ -+# Set external-ids in br-int needed for ovn-controller -+ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+start_daemon ovn-controller -+ -+# Logical network: -+# Two VMs -+# * VM1 with IP address 192.168.100.5 -+# * VM2 with IP address 192.168.100.6 -+# The VMs connect to logical switch ls1. -+# -+# An external router with IP address 172.18.1.2. We simulate this with a network namespace. -+# There will be no traffic going here in this test. -+# The external router connects to logical switch ls-pub -+# -+# One logical router (lr1) connects to ls1 and ls-pub. The router port connected to ls-pub is -+# a gateway port. -+# * The subnet connected to ls1 is 192.168.100.0/24. The Router IP address is 192.168.100.1 -+# * The subnet connected to ls-pub is 172.18.1.0/24. The Router IP address is 172.168.1.1 -+# lr1 has the following attributes: -+# * It has a "default" static route that sends traffic out the gateway router port. -+# * It has a DNAT rule that translates 172.18.2.10 to 192.168.100.6 (VM2) -+# -+# In this test, we want to ensure that a ping from VM1 to IP address 172.18.2.10 reaches VM2. -+ -+ovn-nbctl ls-add ls1 -+ovn-nbctl lsp-add ls1 vm1 -- lsp-set-addresses vm1 "00:00:00:00:00:05 192.168.100.5" -+ovn-nbctl lsp-add ls1 vm2 -- lsp-set-addresses vm2 "00:00:00:00:00:06 192.168.100.6" -+ -+ovn-nbctl ls-add ls-pub -+ovn-nbctl lsp-add ls-pub ext-router -- lsp-set-addresses ext-router "00:00:00:00:01:02 172.18.1.2" -+ -+ovn-nbctl lr-add lr1 -+ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.100.1/24 -+ovn-nbctl lsp-add ls1 ls1-lr1 \ -+ -- lsp-set-type ls1-lr1 router \ -+ -- lsp-set-addresses ls1-lr1 00:00:00:00:00:01 \ -+ -- lsp-set-options ls1-lr1 router-port=lr1-ls1 -+ -+ovn-nbctl lrp-add lr1 lr1-ls-pub 00:00:00:00:01:01 172.18.1.1/24 -+ovn-nbctl lrp-set-gateway-chassis lr1-ls-pub hv1 -+ovn-nbctl lsp-add ls-pub ls-pub-lr1 \ -+ -- lsp-set-type ls-pub-lr1 router \ -+ -- lsp-set-addresses ls-pub-lr1 00:00:00:00:01:01 \ -+ -- lsp-set-options ls-pub-lr1 router-port=lr1-ls-pub -+ -+ovn-nbctl lr-nat-add lr1 snat 172.18.1.1 192.168.100.0/24 -+ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.18.2.10 192.168.100.6 -+ovn-nbctl lr-route-add lr1 0.0.0.0/0 172.18.1.2 -+ -+#ls1_uuid=$(fetch_column Port_Binding datapath logical_port=vm1) -+#ovn-sbctl create MAC_Binding ip=172.18.2.10 datapath=$ls1_uuid logical_port=vm2 mac="00:00:00:00:00:06" -+ -+OVN_POPULATE_ARP -+ovn-nbctl --wait=hv sync -+ -+ADD_NAMESPACES(vm1) -+ADD_VETH(vm1, vm1, br-int, "192.168.100.5/24", "00:00:00:00:00:05", \ -+ "192.168.100.1") -+ -+ADD_NAMESPACES(vm2) -+ADD_VETH(vm2, vm2, br-int, "192.168.100.6/24", "00:00:00:00:00:06", \ -+ "192.168.100.1") -+ -+ADD_NAMESPACES(ext-router) -+ADD_VETH(ext-router, ext-router, br-int, "172.18.1.2/24", "00:00:00:00:01:02", \ -+ "172.18.1.1") -+ -+# Let's take a quick look at the logical flows -+ovn-sbctl lflow-list -+ -+# Let's check what ovn-trace says... -+ovn-trace ls1 'inport == "vm1" && eth.src == 00:00:00:00:00:05 && ip4.src == 192.168.100.5 && eth.dst == 00:00:00:00:00:01 && ip4.dst == 172.18.2.10 && ip.ttl == 32' -+ -+# A ping from vm1 should hairpin in lr1 and successfully DNAT to vm2 -+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.10 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+kill $(pidof ovn-controller) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d -+/.*terminating with signal 15.*/d"]) -+ - AT_CLEANUP - ]) -diff --git a/tests/testsuite.at b/tests/testsuite.at -index ddc3f11d6..b716a1ad9 100644 ---- a/tests/testsuite.at -+++ b/tests/testsuite.at -@@ -27,6 +27,7 @@ m4_include([tests/ovn.at]) - m4_include([tests/ovn-performance.at]) - m4_include([tests/ovn-northd.at]) - m4_include([tests/ovn-nbctl.at]) -+m4_include([tests/ovn-features.at]) - m4_include([tests/ovn-lflow-cache.at]) - m4_include([tests/ovn-ofctrl-seqno.at]) - m4_include([tests/ovn-sbctl.at]) -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index dc13fa9ca..3a0b7c3e3 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -805,6 +805,8 @@ static void - nbctl_pre_sync(struct ctl_context *base OVS_UNUSED) - { - force_wait = true; -+ /* Monitor nb_cfg to detect and handle potential overflows. */ -+ ovsdb_idl_add_column(base->idl, &nbrec_nb_global_col_nb_cfg); - } - - static void -@@ -3976,6 +3978,8 @@ nbctl_pre_lr_route_add(struct ctl_context *ctx) - ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name); - ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_static_routes); - -+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name); -+ - ovsdb_idl_add_column(ctx->idl, &nbrec_bfd_col_dst_ip); - - ovsdb_idl_add_column(ctx->idl, -@@ -3992,6 +3996,10 @@ nbctl_pre_lr_route_add(struct ctl_context *ctx) - &nbrec_logical_router_static_route_col_options); - } - -+static char * OVS_WARN_UNUSED_RESULT -+lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, -+ const struct nbrec_logical_router_port **lrp_p); -+ - static void - nbctl_lr_route_add(struct ctl_context *ctx) - { -@@ -4001,6 +4009,7 @@ nbctl_lr_route_add(struct ctl_context *ctx) - ctx->error = error; - return; - } -+ const struct nbrec_logical_router_port *out_lrp = NULL; - char *prefix = NULL, *next_hop = NULL; - - const char *policy = shash_find_data(&ctx->options, "--policy"); -@@ -4034,9 +4043,15 @@ nbctl_lr_route_add(struct ctl_context *ctx) - ? normalize_ipv6_addr_str(ctx->argv[3]) - : normalize_ipv4_addr_str(ctx->argv[3]); - if (!next_hop) { -- ctl_error(ctx, "bad %s nexthop argument: %s", -- v6_prefix ? "IPv6" : "IPv4", ctx->argv[3]); -- goto cleanup; -+ /* check if it is a output port. */ -+ error = lrp_by_name_or_uuid(ctx, ctx->argv[3], true, &out_lrp); -+ if (error) { -+ ctl_error(ctx, "bad %s nexthop argument: %s", -+ v6_prefix ? "IPv6" : "IPv4", ctx->argv[3]); -+ free(error); -+ goto cleanup; -+ } -+ next_hop = ""; - } - } - -@@ -4063,6 +4078,15 @@ nbctl_lr_route_add(struct ctl_context *ctx) - } - } - -+ if (ctx->argc == 5) { -+ /* validate output port. */ -+ error = lrp_by_name_or_uuid(ctx, ctx->argv[4], true, &out_lrp); -+ if (error) { -+ ctx->error = error; -+ goto cleanup; -+ } -+ } -+ - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; - bool ecmp_symmetric_reply = shash_find(&ctx->options, - "--ecmp-symmetric-reply") != NULL; -@@ -4081,7 +4105,7 @@ nbctl_lr_route_add(struct ctl_context *ctx) - ctl_error(ctx, "bfd dst_ip cannot be discard."); - goto cleanup; - } -- if (ctx->argc == 5) { -+ if (out_lrp) { - if (is_discard_route) { - ctl_error(ctx, "outport is not valid for discard routes."); - goto cleanup; -@@ -4104,22 +4128,22 @@ nbctl_lr_route_add(struct ctl_context *ctx) - nbrec_logical_router_static_route_verify_nexthop(route); - nbrec_logical_router_static_route_set_ip_prefix(route, prefix); - nbrec_logical_router_static_route_set_nexthop(route, next_hop); -- if (ctx->argc == 5) { -+ if (out_lrp) { - nbrec_logical_router_static_route_set_output_port( -- route, ctx->argv[4]); -+ route, out_lrp->name); - } - if (policy) { - nbrec_logical_router_static_route_set_policy(route, policy); - } - if (bfd) { - if (!nb_bt) { -- if (ctx->argc != 5) { -+ if (!out_lrp) { - ctl_error(ctx, "insert entry in the BFD table failed"); - goto cleanup; - } - nb_bt = nbrec_bfd_insert(ctx->txn); - nbrec_bfd_set_dst_ip(nb_bt, next_hop); -- nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ nbrec_bfd_set_logical_port(nb_bt, out_lrp->name); - } - nbrec_logical_router_static_route_set_bfd(route, nb_bt); - } -@@ -4142,8 +4166,9 @@ nbctl_lr_route_add(struct ctl_context *ctx) - route = nbrec_logical_router_static_route_insert(ctx->txn); - nbrec_logical_router_static_route_set_ip_prefix(route, prefix); - nbrec_logical_router_static_route_set_nexthop(route, next_hop); -- if (ctx->argc == 5) { -- nbrec_logical_router_static_route_set_output_port(route, ctx->argv[4]); -+ if (out_lrp) { -+ nbrec_logical_router_static_route_set_output_port(route, -+ out_lrp->name); - } - if (policy) { - nbrec_logical_router_static_route_set_policy(route, policy); -@@ -4159,19 +4184,21 @@ nbctl_lr_route_add(struct ctl_context *ctx) - nbrec_logical_router_update_static_routes_addvalue(lr, route); - if (bfd) { - if (!nb_bt) { -- if (ctx->argc != 5) { -+ if (!out_lrp) { - ctl_error(ctx, "insert entry in the BFD table failed"); - goto cleanup; - } - nb_bt = nbrec_bfd_insert(ctx->txn); - nbrec_bfd_set_dst_ip(nb_bt, next_hop); -- nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]); -+ nbrec_bfd_set_logical_port(nb_bt, out_lrp->name); - } - nbrec_logical_router_static_route_set_bfd(route, nb_bt); - } - - cleanup: -- free(next_hop); -+ if (next_hop && strlen(next_hop)) { -+ free(next_hop); -+ } - free(prefix); - } - -@@ -5847,12 +5874,18 @@ print_route(const struct nbrec_logical_router_static_route *route, - { - - char *prefix = normalize_prefix_str(route->ip_prefix); -- char *next_hop = !strcmp(route->nexthop, "discard") -- ? xasprintf("discard") -- : normalize_prefix_str(route->nexthop); -+ char *next_hop = ""; -+ -+ if (!strcmp(route->nexthop, "discard")) { -+ next_hop = xasprintf("discard"); -+ } else if (strlen(route->nexthop)) { -+ next_hop = normalize_prefix_str(route->nexthop); -+ } - ds_put_format(s, "%25s %25s", prefix, next_hop); - free(prefix); -- free(next_hop); -+ if (strlen(next_hop)) { -+ free(next_hop); -+ } - - if (route->policy) { - ds_put_format(s, " %s", route->policy); diff --git a/SPECS/ovn-2021.spec b/SPECS/ovn-2021.spec index 951f225..f2880f1 100644 --- a/SPECS/ovn-2021.spec +++ b/SPECS/ovn-2021.spec @@ -50,8 +50,8 @@ Name: %{pkgname} Summary: Open Virtual Network support Group: System Environment/Daemons URL: http://www.ovn.org/ -Version: 21.06.0 -Release: 29%{?commit0:.%{date}git%{shortcommit0}}%{?dist} +Version: 21.12.0 +Release: 11%{?commit0:.%{date}git%{shortcommit0}}%{?dist} Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1 @@ -62,8 +62,8 @@ License: ASL 2.0 and LGPLv2+ and SISSL # Always pull an upstream release, since this is what we rebase to. Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz -%define ovscommit e6ad4d8d9c9273f226ec9a993b64fccfb50bdf4c -%define ovsshortcommit e6ad4d8 +%define ovscommit 91e1ff5dde396fbcc8623ac0726066e970e6de15 +%define ovsshortcommit 91e1ff5 Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz %define ovsdir ovs-%{ovscommit} @@ -85,7 +85,7 @@ Source504: arm64-armv8a-linuxapp-gcc-config Source505: ppc_64-power8-linuxapp-gcc-config Source506: x86_64-native-linuxapp-gcc-config -Patch: ovn-%{version}.patch +Patch: %{pkgname}.patch # FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's # in the -optional repository and so we can't require it directly since RHV @@ -116,7 +116,7 @@ BuildRequires: unbound-devel # make check dependencies BuildRequires: procps-ng -%if 0%{?rhel} > 7 || 0%{?fedora} +%if 0%{?rhel} == 8 || 0%{?fedora} BuildRequires: python3-pyOpenSSL %endif BuildRequires: tcpdump @@ -477,6 +477,7 @@ fi %{_bindir}/ovn-sbctl %{_bindir}/ovn-trace %{_bindir}/ovn-detrace +%{_bindir}/ovn_detrace.py %{_bindir}/ovn-appctl %{_bindir}/ovn-ic-nbctl %{_bindir}/ovn-ic-sbctl @@ -527,133 +528,43 @@ fi %{_unitdir}/ovn-controller-vtep.service %changelog -* Tue Sep 14 2021 Ilya Maximets - 21.06.0-29 -- ovn-northd: Avoid verification of NB_Global.options if nothing changed. - [Gerrit: 50dc6ec9b9d1312caf2d6f9d19cf19d6bc73e3f3] - [Upstream: 27064c554fe6a3dfe4dd5800e884eec70cd3d793] - -* Tue Sep 14 2021 Ilya Maximets - 21.06.0-28 -- ovn-northd: Fix update of a mac prefix. - [Gerrit: ea2eb903d7220c01931c7e05f53dad8a84f0a715] - [Upstream: b6bf5e99b5a76c9a9df83896c2d1e40e3c5f4471] - -* Thu Sep 02 2021 Mohammad Heib - 21.06.0-27 -- ovn-nbctl: Monitor nb_cfg to detect and handle potential overflows (#1979774) - [Gerrit: 5a7342c8f1340e5739a8bceb2d3a81a07b371d1d] - [Upstream: b1a07090740ac9a29e4a2475ad07bf9c37991b43] - -* Mon Aug 30 2021 Numan Siddique - 21.06.0-26 -- Revert "Revert features detection and zero-snat patches." - [Gerrit: 666d6640be7155ca0b0bc12b0d5af626090e0375] +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-11 +- pinctrl: Avoid misaligned access to ovs_ra_msg. + [Gerrit: 290523cdfadc5cb401939cc21c1f8de66a6b79b2] + [Upstream: N/A] + +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-10 +- pinctrl: Avoid misaligned access to controller_event_opt_header. + [Gerrit: 2280d3a3f63e026657564f34793b9143323afaf6] + [Upstream: N/A] + +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-9 +- pinctrl: Ensure packet headers are properly aligned for ICMP errors. + [Gerrit: ca764d60c6cfd4e7121b15faf6ca3a026928da4e] + [Upstream: N/A] + +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-8 +- pinctrl: Ensure aligned accesses when processing DNS. + [Gerrit: 67829e142dcc71b1f8e4f01aab9e73333420a76f] [Upstream: N/A] -* Mon Aug 30 2021 Mark Michelson - 21.06.0-25 -- Revert features detection and zero-snat patches. (#1992705) - [Gerrit: 8f5c081c322a25576aea1cc7d2f5d996f5804196] +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-7 +- pinctrl: Ensure no misaligned accesses for SCTP packets. + [Gerrit: b5083fdb4dce839fe70ce5e1c059f11c917105f4] [Upstream: N/A] -* Wed Aug 25 2021 Lorenzo Bianconi - 21.06.0-24 -- northd: allow to configure routes with no nexthop - [Gerrit: c86436ca8edbb7c4cdf3b965269792c51592c385] - [Upstream: c0085228893e7bf07190fcccf50cf588b028edaa] - -* Wed Aug 25 2021 Lorenzo Bianconi - 21.06.0-23 -- nbctl: validate outport in nbctl_lr_route_add - [Gerrit: ddccb1dbaf54c78c810d4d32da6d96197cc16caf] - [Upstream: d866959ba6ea066f88558071eda9943258ee7fa7] - -* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-22 -- controller: run ipv6_pd only on a proper controller for l3gateway mode - [Gerrit: 11d6a1bac2e38a81646271c127b602533a3f9dc4] - [Upstream: b8b24398657e5bb82dcbdb5ce55a74e261e30767] - -* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-21 -- controller: ipv6_pd: properly update ipv6_ra_pd_list pb option in sb db - [Gerrit: 55ec986ed1a506e3c149c5a7d69fdd661970d777] - [Upstream: 9704cfe54dc2a0a6752139d07bb9f563e7e27aae] - -* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-20 -- controller: add ipv6_pd debug messages - [Gerrit: 969495b1f078b8e52fb2c1b1b53bb561c8f66d22] - [Upstream: c3afcb44846116fd6e311ca9a250a46dbff6b2a8] - -* Fri Aug 06 2021 Lorenzo Bianconi - 21.06.0-19 -- northd: do not configure ECMP routes with wrong next-hop - [Gerrit: c8b215179d2408ce1d1e0dd6569f72a3c7e5724d] - [Upstream: 9cd64780d89c4acd2ae0c12693be66962ada97cd] - -* Tue Jul 27 2021 Numan Siddique - 21.06.0-18 -- ovn-controller: Split logical flow and physical flow processing. (#1986484) - [Gerrit: 6e1e90064ad1f5769fdc96e3b735ee236c30b7e2] - [Upstream: ceb12c9190a124c70bc938e8e1bea17612b498be] - -* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-17 -- ovn.at: Fix "Symmetric IPv6 ECMP reply flows" test. - [Gerrit: 801f6c69c3bb45f981135ac6c197fdbd3f18118d] - [Upstream: 4e6c498068dc4fa9546d3661f78f0a42e99c74bb] - -* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-16 -- ovn-controller: Handle DNAT/no-NAT conntrack tuple collisions. (#1939676) - [Gerrit: abfd62cb228b7d311ae7cae18adfe9cfcf68affc] - [Upstream: 58683a4271e6a885f2f2aea27f3df88e69a5c388] - -* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-15 -- ovn-controller: Detect OVS datapath capabilities. - [Gerrit: ca1df0396e6e6eb016c3cad82db7c49cc05ec99a] - [Upstream: 56e2cd3a2f06b79b7d57cc8637fc0d258652aff5] - -* Mon Jul 26 2021 Lorenzo Bianconi - 21.06.0-14 -- northd: do not centralize traffic for unclaimed virtual ports - [Gerrit: 5b6826906a76779b527d72d1c49d211ce492e62e] +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-6 +- pinctrl: Ensure proper alignment when using pinctrl_compose_ipv*(). + [Gerrit: 31e933469d862915b8c83131f72728fe32ecac51] [Upstream: N/A] -* Thu Jul 15 2021 Ihar Hrachyshka - 21.06.0-13 -- Don't suppress localport traffic directed to external port (#1974062) - [Gerrit: 330e6e7400e1d5e4e6ef4fc6446eeaa945ac6a13] - [Upstream: 1148580290d0ace803f20aeaa0241dd51c100630] - -* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-12 -- northd: Fix multicast table full comparison. (#1979870) - [Gerrit: 38f44df1b8a0ed1ebb86183de29d9e5c3423abdb] - [Upstream: 969c98d7297b526c704c6fd2a7138f584f9ad577] - -* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-11 -- northd-ddlog: Fix IP family match for DNAT flows. - [Gerrit: 518ea2e15df2c77fc19afe74b68d616983638743] - [Upstream: 38467229905bdf09a3afa325eaa7a98183f44c72] - -* Thu Jul 15 2021 Ihar Hrachyshka - 21.06.0-10 -- Disable ARP/NA responders for vlan-passthru switches - [Gerrit: 56fbcfaf71d9a6df0b4cdee583c8d17ca7a82aab] - [Upstream: ea57f666f6eef1eb1d578f0e975baa14c5d23ec9] - -* Thu Jul 15 2021 Ben Pfaff - 21.06.0-9 -- tests: Fix "vlan traffic for external network with distributed..." - [Gerrit: ca26e77c4206a39ae6eab4a1d430ef04b726b640] - [Upstream: 5453cc8ca5535e3f33d1b191929e1a3c9ad30f20] - -* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-8 -- ovn-controller: Fix port group I-P when they contain non-vif ports. - [Gerrit: 3c7f29238c889b248155cbb2c866c0adbf8b46c1] - [Upstream: 1bb32e0f8146d7f4fff84af5e3d2836ebe939e04] - -* Thu Jul 15 2021 Numan Siddique - 21.06.0-7 -- system-tests: Fix the test file. - [Gerrit: 85337cec3f2e5967a14afc5a552ac17dff6c15f6] - [Upstream: 9c1978300fa12709e01df07ed8403d8ad43f61fb] - -* Thu Jul 15 2021 Mark Michelson - 21.06.0-6 -- northd: Swap src and dst eth addresses in router egress loop. - [Gerrit: 86207fcac41b639d14de05e1b0965ad9d8293218] - [Upstream: 9be470dc69daf16ac1fbbe13cc295f46862226ad] - -* Tue Jun 29 2021 Han Zhou - 21.06.0-5 -- ovn.at: Fix test "virtual ports -- ovn-northd-ddlog". - [Gerrit: d61cfca4cadca33e598ba1a23cfdbe81a72d3501] - [Upstream: 9e3404e03620f183adc4f05db13bf5a38618b757] - -* Fri Jun 18 2021 Mark Michelson - 21.06.0-4 -- Prepare for 21.06.1. - [Gerrit: 9ae4001f70b4a828018a55a36a1b228f4846b624] +* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-5 +- physical: Add remote parent ports to OFTABLE_REMOTE_OUTPUT flows. (#2036970) + [Gerrit: 627b25bd14085a78f1f9611f2a218ce639515e26] + [Upstream: e101e45f355a91e277630243e64897f91f13f8bc] + +* Wed Dec 22 2021 Mark Michelson - 21.12.0-4 +- Prepare for 21.12.1. + [Gerrit: 9c67f93b92d9864ac0e06dd3d96e8172441343c3] [Upstream: N/A]