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 new file mode 100644 index 0000000..7ac6694 --- /dev/null +++ b/SOURCES/0001-Allow-VLAN-traffic-when-LS-vlan-passthru-true.patch @@ -0,0 +1,190 @@ +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 new file mode 100644 index 0000000..0fd0391 --- /dev/null +++ b/SOURCES/0001-Fix-OVN-update-issue-when-ovn-controller-is-updated-.patch @@ -0,0 +1,297 @@ +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 new file mode 100644 index 0000000..a0d1d96 --- /dev/null +++ b/SOURCES/0001-Provide-the-option-to-pin-ovn-controller-and-ovn-nor.patch @@ -0,0 +1,488 @@ +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-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch b/SOURCES/0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch new file mode 100644 index 0000000..ef4a978 --- /dev/null +++ b/SOURCES/0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch @@ -0,0 +1,343 @@ +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-dhcp-add-iPXE-support-to-OVN.patch b/SOURCES/0001-dhcp-add-iPXE-support-to-OVN.patch new file mode 100644 index 0000000..5e08118 --- /dev/null +++ b/SOURCES/0001-dhcp-add-iPXE-support-to-OVN.patch @@ -0,0 +1,498 @@ +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 new file mode 100644 index 0000000..972daab --- /dev/null +++ b/SOURCES/0001-northd-Don-t-poll-ovsdb-before-the-connection-is-ful.patch @@ -0,0 +1,50 @@ +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-lb_action-when-there-are-no-active-backen.patch b/SOURCES/0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch new file mode 100644 index 0000000..266df95 --- /dev/null +++ b/SOURCES/0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch @@ -0,0 +1,77 @@ +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-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 new file mode 100644 index 0000000..6ac69e0 --- /dev/null +++ b/SOURCES/0001-northd-Use-enum-ovn_stage-for-the-table-value-in-the.patch @@ -0,0 +1,389 @@ +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-properly-reconfigure-ipam-when-subnet-is-chan.patch b/SOURCES/0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch new file mode 100644 index 0000000..5705999 --- /dev/null +++ b/SOURCES/0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch @@ -0,0 +1,55 @@ +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.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 new file mode 100644 index 0000000..78547ec --- /dev/null +++ b/SOURCES/0001-ofctrl.c-Fix-duplicated-flow-handling-in-I-P-while-m.patch @@ -0,0 +1,238 @@ +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-detrace-Only-decode-br-int-OVS-interfaces.patch b/SOURCES/0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch new file mode 100644 index 0000000..7e6b0fb --- /dev/null +++ b/SOURCES/0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch @@ -0,0 +1,98 @@ +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-ovn-sbctl-Add-convenient-names-for-more-ta.patch b/SOURCES/0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch new file mode 100644 index 0000000..9641180 --- /dev/null +++ b/SOURCES/0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch @@ -0,0 +1,93 @@ +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-Handle-IPv6-addresses-with-prefixes-for-p.patch b/SOURCES/0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch new file mode 100644 index 0000000..f2d3d26 --- /dev/null +++ b/SOURCES/0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch @@ -0,0 +1,212 @@ +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-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch b/SOURCES/0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch new file mode 100644 index 0000000..bc2f79b --- /dev/null +++ b/SOURCES/0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch @@ -0,0 +1,215 @@ +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-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch b/SOURCES/0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch new file mode 100644 index 0000000..4d0da67 --- /dev/null +++ b/SOURCES/0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch @@ -0,0 +1,219 @@ +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-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 new file mode 100644 index 0000000..36077ec --- /dev/null +++ b/SOURCES/0002-northd-Move-functions-from-ovn-northd.c-into-ovn-uti.patch @@ -0,0 +1,275 @@ +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 new file mode 100644 index 0000000..e7cbec0 --- /dev/null +++ b/SOURCES/0002-ofctrl.c-Avoid-repeatedly-linking-an-installed-flow-.patch @@ -0,0 +1,92 @@ +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 new file mode 100644 index 0000000..a56ab5a --- /dev/null +++ b/SOURCES/0002-ovn-detrace-Improve-DB-connection-error-messages.patch @@ -0,0 +1,36 @@ +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-northd-Limit-self-originated-ARP-ND-broadcast-do.patch b/SOURCES/0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch new file mode 100644 index 0000000..0a79e7c --- /dev/null +++ b/SOURCES/0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch @@ -0,0 +1,498 @@ +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-trace-Don-t-assert-for-next-stage-ingress.patch b/SOURCES/0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch new file mode 100644 index 0000000..035f8f5 --- /dev/null +++ b/SOURCES/0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch @@ -0,0 +1,50 @@ +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/0003-actions-Add-a-new-OVN-action-reject.patch b/SOURCES/0003-actions-Add-a-new-OVN-action-reject.patch new file mode 100644 index 0000000..4bcb5aa --- /dev/null +++ b/SOURCES/0003-actions-Add-a-new-OVN-action-reject.patch @@ -0,0 +1,462 @@ +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-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch b/SOURCES/0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch new file mode 100644 index 0000000..ef0d190 --- /dev/null +++ b/SOURCES/0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch @@ -0,0 +1,43 @@ +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 new file mode 100644 index 0000000..7cdc0a9 --- /dev/null +++ b/SOURCES/0003-ofctrl.c-Only-merge-actions-for-conjunctive-flows.patch @@ -0,0 +1,218 @@ +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-tests-Introduce-new-testing-helpers.patch b/SOURCES/0003-tests-Introduce-new-testing-helpers.patch new file mode 100644 index 0000000..b316d91 --- /dev/null +++ b/SOURCES/0003-tests-Introduce-new-testing-helpers.patch @@ -0,0 +1,2233 @@ +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 new file mode 100644 index 0000000..8799f5e --- /dev/null +++ b/SOURCES/0004-Add-new-table-Load_Balancer-in-Southbound-database.patch @@ -0,0 +1,428 @@ +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-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 new file mode 100644 index 0000000..bbdf637 --- /dev/null +++ b/SOURCES/0004-ofctrl.c-Do-not-change-flow-ordering-when-merging-op.patch @@ -0,0 +1,79 @@ +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-Optimize-logical-flow-generation-for-reje.patch b/SOURCES/0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch new file mode 100644 index 0000000..a88d060 --- /dev/null +++ b/SOURCES/0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch @@ -0,0 +1,444 @@ +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-test-ovn-Fix-expression-leak.patch b/SOURCES/0004-test-ovn-Fix-expression-leak.patch new file mode 100644 index 0000000..b51722e --- /dev/null +++ b/SOURCES/0004-test-ovn-Fix-expression-leak.patch @@ -0,0 +1,31 @@ +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/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch b/SOURCES/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch new file mode 100644 index 0000000..ff8de74 --- /dev/null +++ b/SOURCES/0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch @@ -0,0 +1,59 @@ +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-Refactor-load-balancer-vip-parsing.patch b/SOURCES/0005-northd-Refactor-load-balancer-vip-parsing.patch new file mode 100644 index 0000000..067279a --- /dev/null +++ b/SOURCES/0005-northd-Refactor-load-balancer-vip-parsing.patch @@ -0,0 +1,1179 @@ +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 new file mode 100644 index 0000000..6fa4546 --- /dev/null +++ b/SOURCES/0005-ofctrl.c-Simplify-active-desired-flow-selection.patch @@ -0,0 +1,217 @@ +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-trace-Handle-IPv6-packets-for-tcp_reset-action.patch b/SOURCES/0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch new file mode 100644 index 0000000..6c36a93 --- /dev/null +++ b/SOURCES/0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch @@ -0,0 +1,98 @@ +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 new file mode 100644 index 0000000..f5e38b5 --- /dev/null +++ b/SOURCES/0006-actions-Fix-leak-of-select-group-members.patch @@ -0,0 +1,47 @@ +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 new file mode 100644 index 0000000..311bad7 --- /dev/null +++ b/SOURCES/0006-controller-Add-load-balancer-hairpin-OF-flows.patch @@ -0,0 +1,902 @@ +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 new file mode 100644 index 0000000..dd98c85 --- /dev/null +++ b/SOURCES/0006-ofctrl.c-Always-log-the-most-recent-flow-changes.patch @@ -0,0 +1,42 @@ +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/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 new file mode 100644 index 0000000..840d3da --- /dev/null +++ b/SOURCES/0007-actions-Add-new-actions-chk_lb_hairpin-chk_lb_hairpi.patch @@ -0,0 +1,474 @@ +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 new file mode 100644 index 0000000..619ad4d --- /dev/null +++ b/SOURCES/0007-ofctrl-Fix-leak-of-meter-mod-bands.patch @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..1e57565 --- /dev/null +++ b/SOURCES/0007-ofctrl.c-Add-a-predictable-resolution-for-conflictin.patch @@ -0,0 +1,418 @@ +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/0008-northd-Make-use-of-new-hairpin-actions.patch b/SOURCES/0008-northd-Make-use-of-new-hairpin-actions.patch new file mode 100644 index 0000000..0e9babd --- /dev/null +++ b/SOURCES/0008-northd-Make-use-of-new-hairpin-actions.patch @@ -0,0 +1,609 @@ +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-pinctrl-Fix-leak-of-DNS-cache-records.patch b/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch new file mode 100644 index 0000000..7f23de7 --- /dev/null +++ b/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch @@ -0,0 +1,72 @@ +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 new file mode 100644 index 0000000..79bd340 --- /dev/null +++ b/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..57112ed --- /dev/null +++ b/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch @@ -0,0 +1,46 @@ +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/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch b/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch new file mode 100644 index 0000000..f33d9a2 --- /dev/null +++ b/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch @@ -0,0 +1,30 @@ +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-sbctl-Add-Load-Balancer-support-for-vflows-option.patch b/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch new file mode 100644 index 0000000..ff7ae66 --- /dev/null +++ b/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch @@ -0,0 +1,96 @@ +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 new file mode 100644 index 0000000..99655a3 --- /dev/null +++ b/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch @@ -0,0 +1,50 @@ +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/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 new file mode 100644 index 0000000..f87080e --- /dev/null +++ b/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch @@ -0,0 +1,40 @@ +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/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch b/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch new file mode 100644 index 0000000..17d12df --- /dev/null +++ b/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch @@ -0,0 +1,36 @@ +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-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 new file mode 100644 index 0000000..009ab96 --- /dev/null +++ b/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch @@ -0,0 +1,33 @@ +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-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 new file mode 100644 index 0000000..bf9294a --- /dev/null +++ b/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch @@ -0,0 +1,31 @@ +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-nbctl-Fix-leak-of-array-of-new-policies.patch b/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch new file mode 100644 index 0000000..a54515a --- /dev/null +++ b/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch @@ -0,0 +1,29 @@ +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/SPECS/ovn2.13.spec b/SPECS/ovn2.13.spec index 27cf835..97b0686 100644 --- a/SPECS/ovn2.13.spec +++ b/SPECS/ovn2.13.spec @@ -60,7 +60,7 @@ Summary: Open Virtual Network support Group: System Environment/Daemons URL: http://www.openvswitch.org/ Version: %{upstreamver}.0 -Release: 2%{?commit0:.%{date}git%{shortcommit0}}%{?dist} +Release: 17%{?commit0:.%{date}git%{shortcommit0}}%{?dist} Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1 @@ -74,7 +74,7 @@ Source: https://github.com/ovn-org/ovn/archive/%{commit0}.tar.gz#/ovn-%{shortcom # Upstream version is called 20.03, not 2.13. Once we switch to using the # same versioning scheme for RH, we can reference %{version} here. # XXX Are OVN releases listed on openvswitch.org? -Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz +Source: https://www.openvswitch.org/releases/ovn-%{version}.tar.gz %endif @@ -115,6 +115,84 @@ Patch001: 0001-ovn-nbctl-add-may-exist-if-exists-options-for-policy.patch # Bug 1886314 Patch010: 0001-ovn-northd-Add-localnet-ports-to-Multicast_Groups-cr.patch +# Bug 1865866 +Patch020: 0001-northd-properly-reconfigure-ipam-when-subnet-is-chan.patch + +# Bug 1871931 +Patch030: 0001-ofctrl.c-Fix-duplicated-flow-handling-in-I-P-while-m.patch +Patch031: 0002-ofctrl.c-Avoid-repeatedly-linking-an-installed-flow-.patch +Patch032: 0003-ofctrl.c-Only-merge-actions-for-conjunctive-flows.patch +Patch033: 0004-ofctrl.c-Do-not-change-flow-ordering-when-merging-op.patch +Patch034: 0005-ofctrl.c-Simplify-active-desired-flow-selection.patch +Patch035: 0006-ofctrl.c-Always-log-the-most-recent-flow-changes.patch +Patch036: 0007-ofctrl.c-Add-a-predictable-resolution-for-conflictin.patch + +# Bug 1826686 +Patch040: 0001-controller-IPv6-Prefix-Delegation-introduce-RENEW-RE.patch + +# Bug 1876990 +Patch050: 0001-northd-Use-enum-ovn_stage-for-the-table-value-in-the.patch +Patch051: 0002-ovn-trace-Don-t-assert-for-next-stage-ingress.patch +Patch052: 0003-actions-Add-a-new-OVN-action-reject.patch +Patch053: 0004-ovn-northd-Optimize-logical-flow-generation-for-reje.patch +Patch054: 0005-ovn-trace-Handle-IPv6-packets-for-tcp_reset-action.patch + +# Bug 1856898 +Patch060: 0001-ovn-northd-Handle-IPv6-addresses-with-prefixes-for-p.patch + +# Bug 1890803 +Patch070: 0001-ovn-detrace-Only-decode-br-int-OVS-interfaces.patch +Patch071: 0002-ovn-detrace-Improve-DB-connection-error-messages.patch + +# Bug 1765506 +Patch080: 0001-dhcp-add-iPXE-support-to-OVN.patch + +# Bug 1894478 +Patch090: 0001-pinctrl-Directly-update-MAC_Bindings-created-by-self.patch +Patch091: 0002-ovn-northd-Limit-self-originated-ARP-ND-broadcast-do.patch + +# Bug 1896671 +Patch100: 0001-northd-Don-t-poll-ovsdb-before-the-connection-is-ful.patch + +# Bug 1846018 +Patch110: 0001-Allow-VLAN-traffic-when-LS-vlan-passthru-true.patch + +# Bug 1888445 +Patch120: 0001-northd-Fix-lb_action-when-there-are-no-active-backen.patch + +# Bug 1833373 +Patch130: 0001-ovn-nbctl-ovn-sbctl-Add-convenient-names-for-more-ta.patch +Patch131: 0002-northd-Move-functions-from-ovn-northd.c-into-ovn-uti.patch +Patch132: 0003-tests-Introduce-new-testing-helpers.patch +Patch133: 0004-Add-new-table-Load_Balancer-in-Southbound-database.patch +Patch134: 0005-northd-Refactor-load-balancer-vip-parsing.patch +Patch135: 0006-controller-Add-load-balancer-hairpin-OF-flows.patch +Patch136: 0007-actions-Add-new-actions-chk_lb_hairpin-chk_lb_hairpi.patch +Patch137: 0008-northd-Make-use-of-new-hairpin-actions.patch +Patch138: 0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch +Patch139: 0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch + +# Bug 1899936 and memory leak fixes. +Patch140: 0001-Provide-the-option-to-pin-ovn-controller-and-ovn-nor.patch +Patch141: 0002-controller-Allow-pinctrl-thread-to-handle-packet-ins.patch +Patch142: 0003-northd-Fix-leaks-of-strings-while-formatting-ecmp-fl.patch +Patch143: 0004-test-ovn-Fix-expression-leak.patch +Patch144: 0005-actions-Fix-leak-of-child-ports-in-fwd-group.patch +Patch145: 0006-actions-Fix-leak-of-select-group-members.patch +Patch146: 0007-ofctrl-Fix-leak-of-meter-mod-bands.patch +Patch147: 0008-pinctrl-Fix-leak-of-DNS-cache-records.patch +Patch148: 0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch +Patch149: 0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch +Patch150: 0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch +Patch151: 0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch +Patch152: 0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch +Patch153: 0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch +Patch154: 0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch +Patch155: 0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch + +# Bug 1900484 +Patch160: 0001-Fix-OVN-update-issue-when-ovn-controller-is-updated-.patch + # OpenvSwitch backports (800-) if required. Patch800: 0001-Revert-ovsdb-idl-Avoid-sending-redundant-conditional.patch Patch801: 0002-ovsdb-idl-Try-committing-the-pending-txn-in-ovsdb_id.patch @@ -567,6 +645,64 @@ fi %{_unitdir}/ovn-controller-vtep.service %changelog +* Mon Nov 23 2020 Numan Siddique - 20.09.0-17 +- Backport "Fix OVN update issue when ovn-controller is updated first from 20.06 to 20.09. (#1900484) + +* Mon Nov 23 2020 Numan Siddique - 20.09.0-16 +- Backport "controller: Allow pinctrl thread to handle packet-ins when version mismatch with northd." (#1899936) +- Backport memory leak fix patches. + +* Sat Nov 21 2020 Numan Siddique - 20.09.0-15 +- Backport "Provide the option to pin ovn-controller and ovn-northd to a specific version." (#1899936) + +* Fri Nov 20 2020 Numan Siddique - 20.09.0-14 +- Backport Load balancer hairpin improvement patches. (#1833373) + +* Thu Nov 12 2020 Lorenzo Bianconi - 20.09.0-13 +- Backport "northd: Fix lb_action when there are no active backends for lb health_check" (#1888445) + +* Wed Nov 11 2020 Ihar Hrachyshka - 20.09.0-12 +- Backport "Allow VLAN traffic when LS:vlan-passthru=true" (#1846018) + +* Wed Nov 11 2020 Numan Siddique - 20.09.0-11 +- Backport "northd: Don't poll ovsdb before the connection is fully established" (#1896671) + +* Thu Nov 5 2020 Dumitru Ceara - 20.09.0-10 +- Backport "pinctrl: Directly update MAC_Bindings created by self originated GARPs." (#1894478) +- Backport "ovn-northd: Limit self originated ARP/ND broadcast domain." (#1894478) + +* Fri Oct 30 2020 Lorenzo Bianconi - 20.09.0-9 +- Backport "dhcp: add iPXE support to OVN" (#1765506) + +* Fri Oct 30 2020 Dumitru Ceara - 20.09.0-8 +- Backport "ovn-detrace: Only decode br-int OVS interfaces." (#1890803) +- Backport "ovn-detrace: Improve DB connection error messages." (#1890803) + +* Fri Oct 23 2020 Dumitru Ceara - 20.09.0-7 +- Backport "ovn-northd: Handle IPv6 addresses with prefixes for port security." (#1856898) + +* Tue Oct 20 2020 Numan Siddique - 20.09.0-6 +- Backport "northd: Use 'enum ovn_stage' for the table value in the 'next' OVN action." (#1876990) +- Backport "ovn-trace: Don't assert for next(stage=ingress,..) (#1876990) +- Backport "actions: Add a new OVN action - reject {}." (#1876990) +- Backport "ovn-northd: Optimize logical flow generation for reject ACLs." (#1876990) +- Backport "ovn-trace: Handle IPv6 packets for tcp_reset action." (#1876990) + +* Mon Oct 19 2020 Lorenzo Bianconi - 20.09.0-5 +- Backport "controller: IPv6 Prefix-Delegation: introduce RENEW/REBIND msg support" (#1826686) + +* Mon Oct 19 2020 Dumitru Ceara - 20.09.0-4 +- Backport "ofctrl.c: Fix duplicated flow handling in I-P while" (#1871931) +- Backport "ofctrl.c: Avoid repeatedly linking an installed flow and" (#1871931) +- Backport "ofctrl.c: Only merge actions for conjunctive flows." (#1871931) +- Backport "ofctrl.c: Do not change flow ordering when merging" (#1871931) +- Backport "ofctrl.c: Simplify active desired flow selection." (#1871931) +- Backport "ofctrl.c: Always log the most recent flow changes." (#1871931) +- Backport "ofctrl.c: Add a predictable resolution for conflicting" (#1871931) + +* Fri Oct 9 2020 Lorenzo Bianconi - 20.09.0-3 +- Backport "northd: properly reconfigure ipam when subnet is changed" (#1865866) + * Fri Oct 9 2020 Dumitru Ceara - 20.09.0-2 - Backport "ovn-northd: Add localnet ports to Multicast_Groups created by IGMP_Group." (#1886314)