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 <ihrachys@redhat.com>
+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 <ihrachys@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+(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 @@
+       </column>
+     </group>
+ 
++    <group title="Other options">
++      <column name="other_config" key="vlan-passthru"
++          type='{"type": "boolean"}'>
++        Determines whether VLAN tagged incoming traffic should be allowed.
++      </column>
++    </group>
++
+     <group title="Common Columns">
+       <column name="external_ids">
+         See <em>External IDs</em> 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 <numans@ovn.org>
+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 <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+Acked-by: Flavio Fernandes <flavio@flaviof.com>
+---
+ 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 <numans@ovn.org>
+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 <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ .../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.
+       </dd>
++
++      <dt><code>external_ids:ovn-match-northd-version</code></dt>
++      <dd>
++        The boolean flag indicates if <code>ovn-controller</code> needs to
++        check <code>ovn-northd</code> version. If this
++        flag is set to true and the <code>ovn-northd's</code> version (reported
++        in the Southbound database) doesn't match with the
++        <code>ovn-controller's</code> 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.
++      </dd>
+     </dl>
+ 
+     <p>
+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 <unistd.h>
+ 
+ #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: <c0bb41fb85934203616758871a690093009e1d1d.1603130608.git.lorenzo.bianconi@redhat.com>
+From: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
+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 <mmichels@redhat.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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: <e52278861714dc22b666a7cf70b2dba687473060.1604073622.git.lorenzo.bianconi@redhat.com>
+From: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
+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 <lmartins@redhat.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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.
+           </p>
+         </column>
++
++        <column name="options" key="bootfile_name_alt">
++          <p>
++          </p>
++            "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.
++        </column>
+       </group>
+ 
+       <group title="DHCP Options of type host_id">
+--- 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 <impleman@gmail.com>
+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 <impleman@gmail.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <lorenzo.bianconi@redhat.com>
+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 <flavio@flaviof.com>
+Fixes: 5af304e747 ("Support selection fields in load balancer.")
+Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <lorenzo.bianconi@redhat.com>
+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 <lorenzo.bianconi@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <hzhou@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <dceara@redhat.com>
+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 <mcambria@redhat.com>
+Reported-at: https://bugzilla.redhat.com/1890803
+Fixes: 8051499a6c1b ("ovn-detrace: Add support for other types of SB cookies.")
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+(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 <blp@ovn.org>
+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 <blp@ovn.org>
+Acked-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <dceara@redhat.com>
+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 <ralonsoh@redhat.com>
+Reported-at: https://bugzilla.redhat.com/1856898
+CC: Numan Siddique <numans@ovn.org>
+Fixes: f631376bf75d ("ovn-northd: Handle IPv4 addresses with prefixes in lport port security")
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <dceara@redhat.com>
+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 <lorenzo.bianconi@redhat.com>
+Fixes: 81e928526b8a ("ovn-controller: Inject GARPs to logical switch pipeline to update neighbors")
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+(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 <numans@ovn.org>
+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 <flavio@flaviof.com>
+Tested-by: Flavio Fernandes <flavio@flaviof.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <jpettit@ovn.org>
+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 <jpettit@ovn.org>
+Signed-off-by: Ben Pfaff <blp@ovn.org>
+Acked-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <config.h>
++
++#include "ovn-util.h"
++
++#include <ctype.h>
+ #include <unistd.h>
+ 
+ #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 <ctype.h>
+ 
+ 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 <hzhou@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <dceara@redhat.com>
+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 <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+(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 <dceara@redhat.com>
+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 <mmichels@redhat.com>
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+(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;
+       </li>
+ 
+       <li>
+-        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 <code>MC_FLOOD</code> 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
++        <code>MC_FLOOD_L2</code> multicast group which contains all non-router
++        logical ports.
+       </li>
+ 
+       <li>
+-        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 <code>MC_FLOOD_L2</code> which contains all
++        non-router logical ports.
+       </li>
+ 
+       <li>
+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(&eth_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(&eth_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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <numans@ovn.org>
+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 <mmichels@redhat.com>
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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_<ENUM> 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;
+           <p><b>Prerequisite:</b> <code>tcp</code></p>
+         </dd>
+ 
++        <dt><code>reject { <var>action</var>; </code>...<code> };</code></dt>
++        <dd>
++          <p>
++            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.
++          </p>
++
++          <p>
++            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.
++          </p>
++        </dd>
++
+         <dt><code>trigger_event;</code></dt>
+         <dd>
+           <p>
+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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <dceara@redhat.com>
+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 <mmichels@redhat.com>
+Fixes: e659bab31a91 ("Combine conjunctions with identical matches into one flow.")
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <blp@ovn.org>
+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 <blp@ovn.org>
+Acked-by: Numan Siddique <numans@ovn.org>
+
+(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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+---
+ 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.
+     </column>
+ 
++    <column name="load_balancers">
++      <p>
++        Load balancers associated with the datapath.
++      </p>
++    </column>
++
+     <group title="OVN_Northbound Relationship">
+       <p>
+         Each row in <ref table="Datapath_Binding"/> is associated with some
+@@ -4104,4 +4110,43 @@ tcp.flags = RST;
+       </column>
+     </group>
+   </table>
++
++  <table name="Load_Balancer">
++    <p>
++      Each row represents a load balancer.
++    </p>
++
++    <column name="name">
++      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.
++    </column>
++
++    <column name="vips">
++      A map of virtual IP addresses (and an optional port number with
++      <code>:</code> as a separator) associated with this load balancer and
++      their corresponding endpoint IP addresses (and optional port numbers
++      with <code>:</code> as separators) separated by commas.
++    </column>
++
++    <column name="protocol">
++      <p>
++        Valid protocols are <code>tcp</code>, <code>udp</code>, or
++        <code>sctp</code>.  This column is useful when a port number is
++        provided as part of the <code>vips</code> column.  If this column is
++        empty and a port number is provided as part of <code>vips</code>
++        column, OVN assumes the protocol to be <code>tcp</code>.
++      </p>
++    </column>
++
++    <column name="datapaths">
++      Datapaths to which this load balancer applies to.
++    </column>
++
++    <group title="Common Columns">
++      <column name="external_ids">
++        See <em>External IDs</em> at the beginning of this document.
++      </column>
++    </group>
++  </table>
+ </database>
+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 <dceara@redhat.com>
+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 <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <numans@ovn.org>
+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 <mmichels@redhat.com>
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <i.maximets@ovn.org>
+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 <manoj.sharma@nutanix.com>
+Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <dceara@redhat.com>
+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 <numans@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+
+(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 <config.h>
++
++#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 <sys/types.h>
++#include <netinet/in.h>
++#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 <dceara@redhat.com>
+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 <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <hzhou@ovn.org>
+Fixes: 85b3544aabb2 ("ovn-controller: A new action "select".")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <dceara@redhat.com>
+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 <dceara@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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_<ENUM> 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
+           </p>
+         </dd>
++
++        <dt><code><var>R</var> = chk_lb_hairpin();</code></dt>
++        <dd>
++          <p>
++            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 <var>R</var> is set to 1.
++          </p>
++        </dd>
++
++        <dt><code><var>R</var> = chk_lb_hairpin_reply();</code></dt>
++        <dd>
++          <p>
++            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 <var>R</var> is set to 1.
++          </p>
++        </dd>
++
++        <dt><code><var>R</var> = ct_snat_to_vip;</code></dt>
++        <dd>
++          <p>
++            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 <code>chk_lb_hairpin</code>
++            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 <code>next;</code> action explicitly after this
++            action to advance the packet to the next stage.
++          </p>
++        </dd>
+       </dl>
+     </column>
+ 
+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 <i.maximets@ovn.org>
+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 <ligs@dtdream.com>
+Fixes: c25094b3884d ("ovn: OVN Support QoS meter")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <dceara@redhat.com>
+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 <dalvarez@redhat.com>
+Reported-at: https://bugzilla.redhat.com/1871931
+Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+Acked-by: Mark Gray <mark.d.gray@redhat.com>
+Signed-off-by: Han Zhou <hzhou@ovn.org>
+(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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 @@
+     <h3>Ingress Table 12: Pre-Hairpin</h3>
+     <ul>
+       <li>
+-        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
+-        <code>reg0[6] = 1 </code> and executes <code>ct_snat(VIP)</code>
+-        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
++        <code>ip &amp;&amp; ct.trk&amp;&amp; ct.dnat</code> 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
++        <code>reg0[6] = chk_lb_hairpin();</code> and advances the packet to
++        the next table.
++      </li>
++
++      <li>
++        If the logical switch has load balancer(s) configured, then a
++        priorirty-90 flow is added with the match <code>ip</code> to check if
++        the packet is a reply for a hairpinned connection or not by executing
++        the action <code>reg0[6] = chk_lb_hairpin_reply();</code> and advances
++        the packet to the next table.
+       </li>
++
+       <li>
+-        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 <code>reg0[6] = 1 </code> and executes <code>ct_snat;</code>.
++        A priority-0 flow that simply moves traffic to the next table.
+       </li>
++    </ul>
++
++    <h3>Ingress Table 13: Nat-Hairpin</h3>
++    <ul>
++      <li>
++         If the logical switch has load balancer(s) configured, then a
++         priorirty-100 flow is added with the match
++         <code>ip &amp;&amp; (ct.new || ct.est) &amp;&amp; ct.trk &amp;&amp;
++         ct.dnat &amp;&amp; reg0[6] == 1</code> which hairpins the traffic by
++         NATting source IP to the load balancer VIP by executing the action
++         <code>ct_snat_to_vip</code> and advances the packet to the next table.
++      </li>
++
++      <li>
++         If the logical switch has load balancer(s) configured, then a
++         priorirty-90 flow is added with the match
++         <code>ip &amp;&amp; reg0[6] == 1</code> 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 <code>ct_snat</code> and advances the
++         packet to the next table.
++      </li>
++
+       <li>
+         A priority-0 flow that simply moves traffic to the next table.
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 13: Hairpin</h3>
++    <h3>Ingress Table 14: Hairpin</h3>
+     <ul>
+       <li>
+         A priority-1 flow that hairpins traffic matched by non-default
+@@ -748,7 +779,7 @@
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 14: ARP/ND responder</h3>
++    <h3>Ingress Table 15: ARP/ND responder</h3>
+ 
+     <p>
+       This table implements ARP/ND responder in a logical switch for known
+@@ -1038,7 +1069,7 @@ output;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 15: DHCP option processing</h3>
++    <h3>Ingress Table 16: DHCP option processing</h3>
+ 
+     <p>
+       This table adds the DHCPv4 options to a DHCPv4 packet from the
+@@ -1099,7 +1130,7 @@ next;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 16: DHCP responses</h3>
++    <h3>Ingress Table 17: DHCP responses</h3>
+ 
+     <p>
+       This table implements DHCP responder for the DHCP replies generated by
+@@ -1180,7 +1211,7 @@ output;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 17 DNS Lookup</h3>
++    <h3>Ingress Table 18 DNS Lookup</h3>
+ 
+     <p>
+       This table looks up and resolves the DNS names to the corresponding
+@@ -1209,7 +1240,7 @@ reg0[4] = dns_lookup(); next;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 18 DNS Responses</h3>
++    <h3>Ingress Table 19 DNS Responses</h3>
+ 
+     <p>
+       This table implements DNS responder for the DNS replies generated by
+@@ -1244,7 +1275,7 @@ output;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress table 19 External ports</h3>
++    <h3>Ingress table 20 External ports</h3>
+ 
+     <p>
+       Traffic from the <code>external</code> logical ports enter the ingress
+@@ -1287,7 +1318,7 @@ output;
+       </li>
+     </ul>
+ 
+-    <h3>Ingress Table 20 Destination Lookup</h3>
++    <h3>Ingress Table 21 Destination Lookup</h3>
+ 
+     <p>
+       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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <i.maximets@ovn.org>
+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 <xu.rong@zte.com.cn>
+Fixes: 252e1642fb59 ("ovn-controller: pending_ct_zones should be destroyed")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <numans@ovn.org>
+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 <dceara@redhat.com>
+Acked-by: Mark Michelson <mmichels@redhat.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <i.maximets@ovn.org>
+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 <manoj.sharma@nutanix.com>
+Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <manoj.sharma@nutanix.com>
+Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <ankur.sharma@nutanix.com>
+Fixes: 20bc58a67f39 ("External IP based NAT: Add Columns and CLI")
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Acked-by: Ankur Sharma <ankur.sharma@nutanix.com>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+
+(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 <i.maximets@ovn.org>
+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 <taoyunxiang@cmss.chinamobile.com>
+Fixes: 5820502a5507 ("ovn-nbctl.c: Fix lr-policy-del command")
+Acked-by: Dumitru Ceara <dceara@redhat.com>
+Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Numan Siddique <numans@ovn.org>
+---
+ 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 <nusiddiq@redhat.com> - 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 <nusiddiq@redhat.com> - 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 <nusiddiq@redhat.com> - 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 <nusiddiq@redhat.com> - 20.09.0-14
+- Backport Load balancer hairpin improvement patches. (#1833373)
+
+* Thu Nov 12 2020 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 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 <ihrachys@redhat.com> - 20.09.0-12
+- Backport "Allow VLAN traffic when LS:vlan-passthru=true" (#1846018)
+
+* Wed Nov 11 2020 Numan Siddique <nusiddiq@redhat.com> - 20.09.0-11
+- Backport "northd: Don't poll ovsdb before the connection is fully established" (#1896671)
+
+* Thu Nov 5 2020 Dumitru Ceara <dceara@redhat.com> - 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 <lorenzo.bianconi@redhat.com> - 20.09.0-9
+- Backport "dhcp: add iPXE support to OVN" (#1765506)
+
+* Fri Oct 30 2020 Dumitru Ceara <dceara@redhat.com> - 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 <dceara@redhat.com> - 20.09.0-7
+- Backport "ovn-northd: Handle IPv6 addresses with prefixes for port security." (#1856898)
+
+* Tue Oct 20 2020 Numan Siddique <nusiddiq@redhat.com> - 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 <lorenzo.bianconi@redhat.com> - 20.09.0-5
+- Backport "controller: IPv6 Prefix-Delegation: introduce RENEW/REBIND msg support" (#1826686)
+
+* Mon Oct 19 2020 Dumitru Ceara <dceara@redhat.com> - 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 <lorenzo.bianconi@redhat.com> - 20.09.0-3
+- Backport "northd: properly reconfigure ipam when subnet is changed" (#1865866)
+
 * Fri Oct 9 2020 Dumitru Ceara <dceara@redhat.com> - 20.09.0-2
 - Backport "ovn-northd: Add localnet ports to Multicast_Groups created by IGMP_Group." (#1886314)