diff --git a/controller/binding.c b/controller/binding.c
index 2acbec701..524e21235 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -506,23 +506,6 @@ update_active_pb_ras_pd(const struct sbrec_port_binding *pb,
     }
 }
 
-/* Corresponds to each Port_Binding.type. */
-enum en_lport_type {
-    LP_UNKNOWN,
-    LP_VIF,
-    LP_CONTAINER,
-    LP_PATCH,
-    LP_L3GATEWAY,
-    LP_LOCALNET,
-    LP_LOCALPORT,
-    LP_L2GATEWAY,
-    LP_VTEP,
-    LP_CHASSISREDIRECT,
-    LP_VIRTUAL,
-    LP_EXTERNAL,
-    LP_REMOTE
-};
-
 /* Local bindings. binding.c module binds the logical port (represented by
  * Port_Binding rows) and sets the 'chassis' column when it sees the
  * OVS interface row (of type "" or "internal") with the
@@ -576,7 +559,7 @@ static struct local_binding *local_binding_create(
 static void local_binding_add(struct shash *local_bindings,
                               struct local_binding *);
 static struct local_binding *local_binding_find(
-    struct shash *local_bindings, const char *name);
+    const struct shash *local_bindings, const char *name);
 static void local_binding_destroy(struct local_binding *,
                                   struct shash *binding_lports);
 static void local_binding_delete(struct local_binding *,
@@ -661,7 +644,8 @@ local_binding_data_destroy(struct local_binding_data *lbinding_data)
 }
 
 const struct sbrec_port_binding *
-local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name)
+local_binding_get_primary_pb(struct shash *local_bindings,
+                             const char *pb_name)
 {
     struct local_binding *lbinding =
         local_binding_find(local_bindings, pb_name);
@@ -670,6 +654,19 @@ local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name)
     return b_lport ? b_lport->pb : NULL;
 }
 
+ofp_port_t
+local_binding_get_lport_ofport(const struct shash *local_bindings,
+                               const char *pb_name)
+{
+    struct local_binding *lbinding =
+        local_binding_find(local_bindings, pb_name);
+    struct binding_lport *b_lport =
+        local_binding_get_primary_or_localport_lport(lbinding);
+
+    return (b_lport && lbinding->iface && lbinding->iface->n_ofport) ?
+            u16_to_ofp(lbinding->iface->ofport[0]) : 0;
+}
+
 bool
 local_binding_is_up(struct shash *local_bindings, const char *pb_name)
 {
@@ -836,7 +833,7 @@ is_lport_vif(const struct sbrec_port_binding *pb)
     return !pb->type[0];
 }
 
-static enum en_lport_type
+enum en_lport_type
 get_lport_type(const struct sbrec_port_binding *pb)
 {
     if (is_lport_vif(pb)) {
@@ -2479,7 +2476,7 @@ local_binding_create(const char *name, const struct ovsrec_interface *iface)
 }
 
 static struct local_binding *
-local_binding_find(struct shash *local_bindings, const char *name)
+local_binding_find(const struct shash *local_bindings, const char *name)
 {
     return shash_find_data(local_bindings, name);
 }
diff --git a/controller/binding.h b/controller/binding.h
index b1717bd2b..f1abc4b9c 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -114,6 +114,9 @@ void local_binding_data_destroy(struct local_binding_data *);
 
 const struct sbrec_port_binding *local_binding_get_primary_pb(
     struct shash *local_bindings, const char *pb_name);
+ofp_port_t local_binding_get_lport_ofport(const struct shash *local_bindings,
+                                          const char *pb_name);
+
 bool local_binding_is_up(struct shash *local_bindings, const char *pb_name);
 bool local_binding_is_down(struct shash *local_bindings, const char *pb_name);
 void local_binding_set_up(struct shash *local_bindings, const char *pb_name,
@@ -134,4 +137,24 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *,
 void binding_tracked_dp_destroy(struct hmap *tracked_datapaths);
 
 void binding_dump_local_bindings(struct local_binding_data *, struct ds *);
+
+/* Corresponds to each Port_Binding.type. */
+enum en_lport_type {
+    LP_UNKNOWN,
+    LP_VIF,
+    LP_CONTAINER,
+    LP_PATCH,
+    LP_L3GATEWAY,
+    LP_LOCALNET,
+    LP_LOCALPORT,
+    LP_L2GATEWAY,
+    LP_VTEP,
+    LP_CHASSISREDIRECT,
+    LP_VIRTUAL,
+    LP_EXTERNAL,
+    LP_REMOTE
+};
+
+enum en_lport_type get_lport_type(const struct sbrec_port_binding *);
+
 #endif /* controller/binding.h */
diff --git a/controller/lflow.c b/controller/lflow.c
index c1d55f378..2e08261c6 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -58,6 +58,7 @@ struct lookup_port_aux {
     const struct sbrec_datapath_binding *dp;
     const struct sbrec_logical_flow *lflow;
     struct lflow_resource_ref *lfrr;
+    const struct hmap *chassis_tunnels;
 };
 
 struct condition_aux {
@@ -145,7 +146,8 @@ tunnel_ofport_cb(const void *aux_, const char *port_name, ofp_port_t *ofport)
         return false;
     }
 
-    if (!get_tunnel_ofport(pb->chassis->name, NULL, ofport)) {
+    if (!get_chassis_tunnel_ofport(aux->chassis_tunnels, pb->chassis->name,
+                                   NULL, ofport)) {
         return false;
     }
 
@@ -612,6 +614,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
         .dp = dp,
         .lflow = lflow,
         .lfrr = l_ctx_out->lfrr,
+        .chassis_tunnels = l_ctx_in->chassis_tunnels,
     };
 
     /* Parse any meter to be used if this flow should punt packets to
diff --git a/controller/lflow.h b/controller/lflow.h
index c5af197d1..6fcda5355 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -147,6 +147,7 @@ struct lflow_ctx_in {
     const struct shash *port_groups;
     const struct sset *active_tunnels;
     const struct sset *related_lport_ids;
+    const struct hmap *chassis_tunnels;
 };
 
 struct lflow_ctx_out {
diff --git a/controller/local_data.c b/controller/local_data.c
index c6dfd0be4..e5177b972 100644
--- a/controller/local_data.c
+++ b/controller/local_data.c
@@ -18,10 +18,14 @@
 /* OVS includes. */
 #include "include/openvswitch/json.h"
 #include "lib/hmapx.h"
+#include "lib/flow.h"
 #include "lib/util.h"
+#include "lib/vswitch-idl.h"
 #include "openvswitch/vlog.h"
 
 /* OVN includes. */
+#include "encaps.h"
+#include "lport.h"
 #include "lib/ovn-util.h"
 #include "lib/ovn-sb-idl.h"
 #include "local_data.h"
@@ -268,6 +272,166 @@ tracked_datapaths_destroy(struct hmap *tracked_datapaths)
     hmap_destroy(tracked_datapaths);
 }
 
+/* Iterates the br_int ports and build the simap of patch to ofports
+ * and chassis tunnels. */
+void
+local_nonvif_data_run(const struct ovsrec_bridge *br_int,
+                      const struct sbrec_chassis *chassis_rec,
+                      struct simap *patch_ofports,
+                      struct hmap *chassis_tunnels)
+{
+    for (int i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *port_rec = br_int->ports[i];
+        if (!strcmp(port_rec->name, br_int->name)) {
+            continue;
+        }
+
+        const char *tunnel_id = smap_get(&port_rec->external_ids,
+                                         "ovn-chassis-id");
+        if (tunnel_id && encaps_tunnel_id_match(tunnel_id,
+                                                chassis_rec->name,
+                                                NULL)) {
+            continue;
+        }
+
+        const char *localnet = smap_get(&port_rec->external_ids,
+                                        "ovn-localnet-port");
+        const char *l2gateway = smap_get(&port_rec->external_ids,
+                                        "ovn-l2gateway-port");
+
+        for (int j = 0; j < port_rec->n_interfaces; j++) {
+            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
+
+            /* Get OpenFlow port number. */
+            if (!iface_rec->n_ofport) {
+                continue;
+            }
+            int64_t ofport = iface_rec->ofport[0];
+            if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) {
+                continue;
+            }
+
+            bool is_patch = !strcmp(iface_rec->type, "patch");
+            if (is_patch && localnet) {
+                simap_put(patch_ofports, localnet, ofport);
+                break;
+            } else if (is_patch && l2gateway) {
+                /* L2 gateway patch ports can be handled just like VIFs. */
+                simap_put(patch_ofports, l2gateway, ofport);
+                break;
+            } else if (tunnel_id) {
+                enum chassis_tunnel_type tunnel_type;
+                if (!strcmp(iface_rec->type, "geneve")) {
+                    tunnel_type = GENEVE;
+                } else if (!strcmp(iface_rec->type, "stt")) {
+                    tunnel_type = STT;
+                } else if (!strcmp(iface_rec->type, "vxlan")) {
+                    tunnel_type = VXLAN;
+                } else {
+                    continue;
+                }
+
+                /* We split the tunnel_id to get the chassis-id
+                 * and hash the tunnel list on the chassis-id. The
+                 * reason to use the chassis-id alone is because
+                 * there might be cases (multicast, gateway chassis)
+                 * where we need to tunnel to the chassis, but won't
+                 * have the encap-ip specifically.
+                 */
+                char *hash_id = NULL;
+                char *ip = NULL;
+
+                if (!encaps_tunnel_id_parse(tunnel_id, &hash_id, &ip)) {
+                    continue;
+                }
+                struct chassis_tunnel *tun = xmalloc(sizeof *tun);
+                hmap_insert(chassis_tunnels, &tun->hmap_node,
+                            hash_string(hash_id, 0));
+                tun->chassis_id = xstrdup(tunnel_id);
+                tun->ofport = u16_to_ofp(ofport);
+                tun->type = tunnel_type;
+
+                free(hash_id);
+                free(ip);
+                break;
+            }
+        }
+    }
+}
+
+bool
+local_nonvif_data_handle_ovs_iface_changes(
+    const struct ovsrec_interface_table *iface_table)
+{
+    const struct ovsrec_interface *iface_rec;
+    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec, iface_table) {
+        if (!strcmp(iface_rec->type, "geneve") ||
+            !strcmp(iface_rec->type, "patch") ||
+            !strcmp(iface_rec->type, "vxlan") ||
+            !strcmp(iface_rec->type, "stt")) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+get_chassis_tunnel_ofport(const struct hmap *chassis_tunnels,
+                          const char *chassis_name, char *encap_ip,
+                          ofp_port_t *ofport)
+{
+    struct chassis_tunnel *tun = NULL;
+    tun = chassis_tunnel_find(chassis_tunnels, chassis_name, encap_ip);
+    if (!tun) {
+        return false;
+    }
+
+    *ofport = tun->ofport;
+    return true;
+}
+
+
+void
+chassis_tunnels_destroy(struct hmap *chassis_tunnels)
+{
+    struct chassis_tunnel *tun;
+    HMAP_FOR_EACH_POP (tun, hmap_node, chassis_tunnels) {
+        free(tun->chassis_id);
+        free(tun);
+    }
+    hmap_destroy(chassis_tunnels);
+}
+
+
+/*
+ * This function looks up the list of tunnel ports (provided by
+ * ovn-chassis-id ports) and returns the tunnel for the given chassid-id and
+ * encap-ip. The ovn-chassis-id is formed using the chassis-id and encap-ip.
+ * The list is hashed using the chassis-id. If the encap-ip is not specified,
+ * it means we'll just return a tunnel for that chassis-id, i.e. we just check
+ * for chassis-id and if there is a match, we'll return the tunnel.
+ * If encap-ip is also provided we use both chassis-id and encap-ip to do
+ * a more specific lookup.
+ */
+struct chassis_tunnel *
+chassis_tunnel_find(const struct hmap *chassis_tunnels, const char *chassis_id,
+                    char *encap_ip)
+{
+    /*
+     * If the specific encap_ip is given, look for the chassisid_ip entry,
+     * else return the 1st found entry for the chassis.
+     */
+    struct chassis_tunnel *tun = NULL;
+    HMAP_FOR_EACH_WITH_HASH (tun, hmap_node, hash_string(chassis_id, 0),
+                             chassis_tunnels) {
+        if (encaps_tunnel_id_match(tun->chassis_id, chassis_id, encap_ip)) {
+            return tun;
+        }
+    }
+    return NULL;
+}
+
 /* static functions. */
 static struct local_datapath *
 local_datapath_add__(struct hmap *local_datapaths,
diff --git a/controller/local_data.h b/controller/local_data.h
index 8b80110fe..fb5a3c7b0 100644
--- a/controller/local_data.h
+++ b/controller/local_data.h
@@ -19,10 +19,14 @@
 /* OVS includes. */
 #include "include/openvswitch/shash.h"
 #include "lib/smap.h"
+#include "lib/simap.h"
 
 struct sbrec_datapath_binding;
 struct sbrec_port_binding;
+struct sbrec_chassis;
 struct ovsdb_idl_index;
+struct ovsrec_bridge;
+struct ovsrec_interface_table;
 
 /* A logical datapath that has some relevance to this hypervisor.  A logical
  * datapath D is relevant to hypervisor H if:
@@ -108,4 +112,39 @@ void tracked_datapath_lport_add(const struct sbrec_port_binding *,
                                 struct hmap *tracked_datapaths);
 void tracked_datapaths_destroy(struct hmap *tracked_datapaths);
 
+/* Must be a bit-field ordered from most-preferred (higher number) to
+ * least-preferred (lower number). */
+enum chassis_tunnel_type {
+    GENEVE = 1 << 2,
+    STT    = 1 << 1,
+    VXLAN  = 1 << 0
+};
+
+/* Maps from a chassis to the OpenFlow port number of the tunnel that can be
+ * used to reach that chassis. */
+struct chassis_tunnel {
+    struct hmap_node hmap_node;
+    char *chassis_id;
+    ofp_port_t ofport;
+    enum chassis_tunnel_type type;
+};
+
+void local_nonvif_data_run(const struct ovsrec_bridge *br_int,
+                           const struct sbrec_chassis *,
+                           struct simap *patch_ofports,
+                           struct hmap *chassis_tunnels);
+
+bool local_nonvif_data_handle_ovs_iface_changes(
+    const struct ovsrec_interface_table *);
+
+struct chassis_tunnel *chassis_tunnel_find(const struct hmap *chassis_tunnels,
+                                           const char *chassis_id,
+                                           char *encap_ip);
+
+bool get_chassis_tunnel_ofport(const struct hmap *chassis_tunnels,
+                               const char *chassis_name, char *encap_ip,
+                               ofp_port_t *ofport);
+
+void chassis_tunnels_destroy(struct hmap *chassis_tunnels);
+
 #endif /* controller/local_data.h */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index c299f1a2e..d69147c04 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2038,6 +2038,78 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
     free(lbs);
 }
 
+/* Engine node which is used to handle the Non VIF data like
+ *   - OVS patch ports
+ *   - Tunnel ports and the related chassis information.
+ */
+struct ed_type_non_vif_data {
+    struct simap patch_ofports; /* simap of patch ovs ports. */
+    struct hmap chassis_tunnels; /* hmap of 'struct chassis_tunnel' from the
+                                  * tunnel OVS ports. */
+};
+
+static void *
+en_non_vif_data_init(struct engine_node *node OVS_UNUSED,
+                     struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_non_vif_data *data = xzalloc(sizeof *data);
+    simap_init(&data->patch_ofports);
+    hmap_init(&data->chassis_tunnels);
+    return data;
+}
+
+static void
+en_non_vif_data_cleanup(void *data OVS_UNUSED)
+{
+    struct ed_type_non_vif_data *ed_non_vif_data = data;
+    simap_destroy(&ed_non_vif_data->patch_ofports);
+    chassis_tunnels_destroy(&ed_non_vif_data->chassis_tunnels);
+}
+
+static void
+en_non_vif_data_run(struct engine_node *node, void *data)
+{
+    struct ed_type_non_vif_data *ed_non_vif_data = data;
+    simap_destroy(&ed_non_vif_data->patch_ofports);
+    chassis_tunnels_destroy(&ed_non_vif_data->chassis_tunnels);
+    simap_init(&ed_non_vif_data->patch_ofports);
+    hmap_init(&ed_non_vif_data->chassis_tunnels);
+
+    struct ovsrec_open_vswitch_table *ovs_table =
+        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_open_vswitch", node));
+    struct ovsrec_bridge_table *bridge_table =
+        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_bridge", node));
+
+    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    ovs_assert(br_int && chassis_id);
+
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+            engine_get_input("SB_chassis", node),
+            "name");
+
+    const struct sbrec_chassis *chassis
+        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    ovs_assert(chassis);
+
+    local_nonvif_data_run(br_int, chassis, &ed_non_vif_data->patch_ofports,
+                          &ed_non_vif_data->chassis_tunnels);
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+static bool
+non_vif_data_ovs_iface_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    struct ovsrec_interface_table *iface_table =
+        (struct ovsrec_interface_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_interface", node));
+
+    return local_nonvif_data_handle_ovs_iface_changes(iface_table);
+}
+
 struct lflow_output_persistent_data {
     uint32_t conj_id_ofs;
     struct lflow_cache *lflow_cache;
@@ -2061,6 +2133,7 @@ struct ed_type_lflow_output {
 static void
 init_lflow_ctx(struct engine_node *node,
                struct ed_type_runtime_data *rt_data,
+               struct ed_type_non_vif_data *non_vif_data,
                struct ed_type_lflow_output *fo,
                struct lflow_ctx_in *l_ctx_in,
                struct lflow_ctx_out *l_ctx_out)
@@ -2167,6 +2240,7 @@ init_lflow_ctx(struct engine_node *node,
     l_ctx_in->port_groups = port_groups;
     l_ctx_in->active_tunnels = &rt_data->active_tunnels;
     l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids;
+    l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels;
 
     l_ctx_out->flow_table = &fo->flow_table;
     l_ctx_out->group_table = &fo->group_table;
@@ -2206,6 +2280,8 @@ en_lflow_output_run(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ovsrec_open_vswitch_table *ovs_table =
         (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
@@ -2254,7 +2330,7 @@ en_lflow_output_run(struct engine_node *node, void *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);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
     lflow_run(&l_ctx_in, &l_ctx_out);
 
     if (l_ctx_out.conj_id_overflow) {
@@ -2281,6 +2357,8 @@ lflow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
     struct ovsrec_open_vswitch_table *ovs_table =
         (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
             engine_get_input("OVS_open_vswitch", node));
@@ -2293,7 +2371,7 @@ lflow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
     struct ed_type_lflow_output *fo = data;
     struct lflow_ctx_in l_ctx_in;
     struct lflow_ctx_out l_ctx_out;
-    init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
 
     bool handled = lflow_handle_changed_flows(&l_ctx_in, &l_ctx_out);
 
@@ -2331,12 +2409,14 @@ lflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_lflow_output *lfo = data;
 
     struct lflow_ctx_in l_ctx_in;
     struct lflow_ctx_out l_ctx_out;
-    init_lflow_ctx(node, rt_data, lfo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, lfo, &l_ctx_in, &l_ctx_out);
     if (!lflow_handle_changed_mc_groups(&l_ctx_in, &l_ctx_out)) {
         return false;
     }
@@ -2350,12 +2430,14 @@ lflow_output_sb_port_binding_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_lflow_output *lfo = data;
 
     struct lflow_ctx_in l_ctx_in;
     struct lflow_ctx_out l_ctx_out;
-    init_lflow_ctx(node, rt_data, lfo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, lfo, &l_ctx_in, &l_ctx_out);
     if (!lflow_handle_changed_port_bindings(&l_ctx_in, &l_ctx_out)) {
         return false;
     }
@@ -2370,6 +2452,8 @@ _lflow_output_resource_ref_handler(struct engine_node *node, void *data,
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_addr_sets *as_data =
         engine_get_input_data("addr_sets", node);
@@ -2401,7 +2485,7 @@ _lflow_output_resource_ref_handler(struct engine_node *node, void *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);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
 
     bool changed;
     const char *ref_name;
@@ -2488,6 +2572,8 @@ lflow_output_runtime_data_handler(struct engine_node *node,
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     /* There is no tracked data. Fall back to full recompute of
      * flow_output. */
@@ -2507,7 +2593,7 @@ lflow_output_runtime_data_handler(struct engine_node *node,
     struct lflow_ctx_out l_ctx_out;
     struct ed_type_lflow_output *fo = data;
     struct hmap *lbs = NULL;
-    init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
 
     struct tracked_datapath *tdp;
     HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
@@ -2547,11 +2633,13 @@ lflow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_lflow_output *fo = data;
     struct lflow_ctx_in l_ctx_in;
     struct lflow_ctx_out l_ctx_out;
-    init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
 
     bool handled = lflow_handle_changed_lbs(&l_ctx_in, &l_ctx_out);
 
@@ -2564,11 +2652,13 @@ lflow_output_sb_fdb_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_lflow_output *fo = data;
     struct lflow_ctx_in l_ctx_in;
     struct lflow_ctx_out l_ctx_out;
-    init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
+    init_lflow_ctx(node, rt_data, non_vif_data, fo, &l_ctx_in, &l_ctx_out);
 
     bool handled = lflow_handle_changed_fdbs(&l_ctx_in, &l_ctx_out);
 
@@ -2583,6 +2673,7 @@ struct ed_type_pflow_output {
 
 static void init_physical_ctx(struct engine_node *node,
                               struct ed_type_runtime_data *rt_data,
+                              struct ed_type_non_vif_data *non_vif_data,
                               struct physical_ctx *p_ctx)
 {
     struct ovsdb_idl_index *sbrec_port_binding_by_name =
@@ -2624,10 +2715,6 @@ static void init_physical_ctx(struct engine_node *node,
 
     ovs_assert(br_int && chassis);
 
-    struct ovsrec_interface_table *iface_table =
-        (struct ovsrec_interface_table *)EN_OVSDB_GET(
-            engine_get_input("OVS_interface", node));
-
     struct ed_type_ct_zones *ct_zones_data =
         engine_get_input_data("ct_zones", node);
     struct simap *ct_zones = &ct_zones_data->current;
@@ -2637,7 +2724,6 @@ static void init_physical_ctx(struct engine_node *node,
     p_ctx->mc_group_table = multicast_group_table;
     p_ctx->br_int = br_int;
     p_ctx->chassis_table = chassis_table;
-    p_ctx->iface_table = iface_table;
     p_ctx->chassis = chassis;
     p_ctx->active_tunnels = &rt_data->active_tunnels;
     p_ctx->local_datapaths = &rt_data->local_datapaths;
@@ -2645,6 +2731,8 @@ static void init_physical_ctx(struct engine_node *node,
     p_ctx->ct_zones = ct_zones;
     p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
     p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
+    p_ctx->patch_ofports = &non_vif_data->patch_ofports;
+    p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
 }
 
 static void *
@@ -2677,9 +2765,11 @@ en_pflow_output_run(struct engine_node *node, void *data)
 
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct physical_ctx p_ctx;
-    init_physical_ctx(node, rt_data, &p_ctx);
+    init_physical_ctx(node, rt_data, non_vif_data, &p_ctx);
     physical_run(&p_ctx, pflow_table);
 
     engine_set_node_state(node, EN_UPDATED);
@@ -2691,17 +2781,23 @@ pflow_output_sb_port_binding_handler(struct engine_node *node,
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_pflow_output *pfo = data;
 
     struct physical_ctx p_ctx;
-    init_physical_ctx(node, rt_data, &p_ctx);
+    init_physical_ctx(node, rt_data, non_vif_data, &p_ctx);
 
     /* We handle port-binding changes for physical flow processing
      * only. flow_output runtime data handler takes care of processing
      * logical flows for any port binding changes.
      */
-    physical_handle_port_binding_changes(&p_ctx, &pfo->flow_table);
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, p_ctx.port_binding_table) {
+        bool removed = sbrec_port_binding_is_deleted(pb);
+        physical_handle_flows_for_lport(pb, removed, &p_ctx, &pfo->flow_table);
+    }
 
     engine_set_node_state(node, EN_UPDATED);
     return true;
@@ -2712,11 +2808,13 @@ pflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
 
     struct ed_type_pflow_output *pfo = data;
 
     struct physical_ctx p_ctx;
-    init_physical_ctx(node, rt_data, &p_ctx);
+    init_physical_ctx(node, rt_data, non_vif_data, &p_ctx);
 
     physical_handle_mc_group_changes(&p_ctx, &pfo->flow_table);
 
@@ -2725,19 +2823,49 @@ pflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
 }
 
 static bool
-pflow_output_ovs_iface_handler(struct engine_node *node OVS_UNUSED,
-                               void *data OVS_UNUSED)
+pflow_output_runtime_data_handler(struct engine_node *node, void *data)
 {
     struct ed_type_runtime_data *rt_data =
         engine_get_input_data("runtime_data", node);
+    struct ed_type_non_vif_data *non_vif_data =
+        engine_get_input_data("non_vif_data", node);
+
+    /* There is no tracked data. Fall back to full recompute of
+     * pflow_output. */
+    if (!rt_data->tracked) {
+        return false;
+    }
+
+    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    if (hmap_is_empty(tracked_dp_bindings)) {
+        return true;
+    }
 
     struct ed_type_pflow_output *pfo = data;
 
     struct physical_ctx p_ctx;
-    init_physical_ctx(node, rt_data, &p_ctx);
+    init_physical_ctx(node, rt_data, non_vif_data, &p_ctx);
+
+    struct tracked_datapath *tdp;
+    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
+        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
+            /* Fall back to full recompute when a local datapath
+             * is added or deleted. */
+            return false;
+        }
+
+        struct shash_node *shash_node;
+        SHASH_FOR_EACH (shash_node, &tdp->lports) {
+            struct tracked_lport *lport = shash_node->data;
+            bool removed =
+                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
+            physical_handle_flows_for_lport(lport->pb, removed, &p_ctx,
+                                            &pfo->flow_table);
+        }
+    }
 
     engine_set_node_state(node, EN_UPDATED);
-    return physical_handle_ovs_iface_changes(&p_ctx, &pfo->flow_table);
+    return true;
 }
 
 static void *
@@ -2997,6 +3125,7 @@ main(int argc, char *argv[])
     /* Define inc-proc-engine nodes. */
     ENGINE_NODE_CUSTOM_DATA(ct_zones, "ct_zones");
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(runtime_data, "runtime_data");
+    ENGINE_NODE(non_vif_data, "non_vif_data");
     ENGINE_NODE(mff_ovn_geneve, "mff_ovn_geneve");
     ENGINE_NODE(ofctrl_is_connected, "ofctrl_is_connected");
     ENGINE_NODE(pflow_output, "physical_flow_output");
@@ -3024,11 +3153,17 @@ main(int argc, char *argv[])
     engine_add_input(&en_port_groups, &en_runtime_data,
                      port_groups_runtime_data_handler);
 
+    engine_add_input(&en_non_vif_data, &en_ovs_open_vswitch, NULL);
+    engine_add_input(&en_non_vif_data, &en_ovs_bridge, NULL);
+    engine_add_input(&en_non_vif_data, &en_sb_chassis, NULL);
+    engine_add_input(&en_non_vif_data, &en_ovs_interface,
+                     non_vif_data_ovs_iface_handler);
+
     /* Note: The order of inputs is important, all OVS interface changes must
      * be handled before any ct_zone changes.
      */
-    engine_add_input(&en_pflow_output, &en_ovs_interface,
-                     pflow_output_ovs_iface_handler);
+    engine_add_input(&en_pflow_output, &en_non_vif_data,
+                     NULL);
     engine_add_input(&en_pflow_output, &en_ct_zones, NULL);
     engine_add_input(&en_pflow_output, &en_sb_chassis,
                      pflow_lflow_output_sb_chassis_handler);
@@ -3039,7 +3174,7 @@ main(int argc, char *argv[])
                      pflow_output_sb_multicast_group_handler);
 
     engine_add_input(&en_pflow_output, &en_runtime_data,
-                     NULL);
+                     pflow_output_runtime_data_handler);
     engine_add_input(&en_pflow_output, &en_sb_encap, NULL);
     engine_add_input(&en_pflow_output, &en_mff_ovn_geneve, NULL);
     engine_add_input(&en_pflow_output, &en_ovs_open_vswitch, NULL);
@@ -3051,6 +3186,8 @@ main(int argc, char *argv[])
                      lflow_output_port_groups_handler);
     engine_add_input(&en_lflow_output, &en_runtime_data,
                      lflow_output_runtime_data_handler);
+    engine_add_input(&en_lflow_output, &en_non_vif_data,
+                     NULL);
 
     engine_add_input(&en_lflow_output, &en_sb_multicast_group,
                      lflow_output_sb_multicast_group_handler);
diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h
index 578588305..78a53312f 100644
--- a/controller/ovn-controller.h
+++ b/controller/ovn-controller.h
@@ -45,14 +45,6 @@ const struct ovsrec_bridge *get_bridge(const struct ovsrec_bridge_table *,
 
 struct sbrec_encap *preferred_encap(const struct sbrec_chassis *);
 
-/* Must be a bit-field ordered from most-preferred (higher number) to
- * least-preferred (lower number). */
-enum chassis_tunnel_type {
-    GENEVE = 1 << 2,
-    STT    = 1 << 1,
-    VXLAN  = 1 << 0
-};
-
 uint32_t get_tunnel_type(const char *name);
 
 struct pb_ld_binding {
diff --git a/controller/physical.c b/controller/physical.c
index 188606927..2b8189246 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -86,46 +86,6 @@ physical_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids);
 }
 
-static struct simap localvif_to_ofport =
-    SIMAP_INITIALIZER(&localvif_to_ofport);
-static struct hmap tunnels = HMAP_INITIALIZER(&tunnels);
-
-/* Maps from a chassis to the OpenFlow port number of the tunnel that can be
- * used to reach that chassis. */
-struct chassis_tunnel {
-    struct hmap_node hmap_node;
-    char *chassis_id;
-    ofp_port_t ofport;
-    enum chassis_tunnel_type type;
-};
-
-/*
- * This function looks up the list of tunnel ports (provided by
- * ovn-chassis-id ports) and returns the tunnel for the given chassid-id and
- * encap-ip. The ovn-chassis-id is formed using the chassis-id and encap-ip.
- * The list is hashed using the chassis-id. If the encap-ip is not specified,
- * it means we'll just return a tunnel for that chassis-id, i.e. we just check
- * for chassis-id and if there is a match, we'll return the tunnel.
- * If encap-ip is also provided we use both chassis-id and encap-ip to do
- * a more specific lookup.
- */
-static struct chassis_tunnel *
-chassis_tunnel_find(const char *chassis_id, char *encap_ip)
-{
-    /*
-     * If the specific encap_ip is given, look for the chassisid_ip entry,
-     * else return the 1st found entry for the chassis.
-     */
-    struct chassis_tunnel *tun = NULL;
-    HMAP_FOR_EACH_WITH_HASH (tun, hmap_node, hash_string(chassis_id, 0),
-                             &tunnels) {
-        if (encaps_tunnel_id_match(tun->chassis_id, chassis_id, encap_ip)) {
-            return tun;
-        }
-    }
-    return NULL;
-}
-
 static void
 put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
          struct ofpbuf *ofpacts)
@@ -166,17 +126,18 @@ put_resubmit(uint8_t table_id, struct ofpbuf *ofpacts)
  * from the associated encap.
  */
 static struct chassis_tunnel *
-get_port_binding_tun(const struct sbrec_port_binding *binding)
+get_port_binding_tun(const struct sbrec_port_binding *binding,
+                     const struct hmap *chassis_tunnels)
 {
     struct sbrec_encap *encap = binding->encap;
     struct sbrec_chassis *chassis = binding->chassis;
     struct chassis_tunnel *tun = NULL;
 
     if (encap) {
-        tun = chassis_tunnel_find(chassis->name, encap->ip);
+        tun = chassis_tunnel_find(chassis_tunnels, chassis->name, encap->ip);
     }
     if (!tun) {
-        tun = chassis_tunnel_find(chassis->name, NULL);
+        tun = chassis_tunnel_find(chassis_tunnels, chassis->name, NULL);
     }
     return tun;
 }
@@ -325,12 +286,13 @@ put_remote_port_redirect_overlay(const struct
                                  uint32_t port_key,
                                  struct match *match,
                                  struct ofpbuf *ofpacts_p,
+                                 const struct hmap *chassis_tunnels,
                                  struct ovn_desired_flow_table *flow_table)
 {
     if (!is_ha_remote) {
         /* Setup encapsulation */
         const struct chassis_tunnel *rem_tun =
-            get_port_binding_tun(binding);
+            get_port_binding_tun(binding, chassis_tunnels);
         if (!rem_tun) {
             return;
         }
@@ -348,10 +310,10 @@ put_remote_port_redirect_overlay(const struct
                 continue;
             }
             if (!tun) {
-                tun = chassis_tunnel_find(ch->name, NULL);
+                tun = chassis_tunnel_find(chassis_tunnels, ch->name, NULL);
             } else {
                 struct chassis_tunnel *chassis_tunnel =
-                                       chassis_tunnel_find(ch->name, NULL);
+                    chassis_tunnel_find(chassis_tunnels, ch->name, NULL);
                 if (chassis_tunnel &&
                     tun->type != chassis_tunnel->type) {
                     static struct vlog_rate_limit rl =
@@ -385,7 +347,7 @@ put_remote_port_redirect_overlay(const struct
             if (!ch) {
                 continue;
             }
-            tun = chassis_tunnel_find(ch->name, NULL);
+            tun = chassis_tunnel_find(chassis_tunnels, ch->name, NULL);
             if (!tun) {
                 continue;
             }
@@ -926,6 +888,9 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                       const struct simap *ct_zones,
                       const struct sset *active_tunnels,
                       const struct hmap *local_datapaths,
+                      const struct shash *local_bindings,
+                      const struct simap *patch_ofports,
+                      const struct hmap *chassis_tunnels,
                       const struct sbrec_port_binding *binding,
                       const struct sbrec_chassis *chassis,
                       struct ovn_desired_flow_table *flow_table,
@@ -1082,17 +1047,25 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         if (!binding->tag) {
             goto out;
         }
-        ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
-                                      binding->parent_port));
+        ofport = local_binding_get_lport_ofport(local_bindings,
+                                                binding->parent_port);
         if (ofport) {
             tag = *binding->tag;
             nested_container = true;
             parent_port = lport_lookup_by_name(
                 sbrec_port_binding_by_name, binding->parent_port);
         }
-    } else {
-        ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
+    } else if (!strcmp(binding->type, "localnet")
+             || !strcmp(binding->type, "l2gateway")) {
+
+        ofport = u16_to_ofp(simap_get(patch_ofports,
                                       binding->logical_port));
+        if (ofport && binding->tag) {
+            tag = *binding->tag;
+        }
+    } else {
+        ofport = local_binding_get_lport_ofport(local_bindings,
+                                                binding->logical_port);
         const char *requested_chassis = smap_get(&binding->options,
                                                  "requested-chassis");
         if (ofport && requested_chassis && requested_chassis[0] &&
@@ -1103,12 +1076,6 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
              */
             ofport = 0;
         }
-
-        if ((!strcmp(binding->type, "localnet")
-            || !strcmp(binding->type, "l2gateway"))
-            && ofport && binding->tag) {
-            tag = *binding->tag;
-        }
     }
 
     bool is_ha_remote = false;
@@ -1119,7 +1086,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         /* It is remote port, may be reached by tunnel or localnet port */
         is_remote = true;
         if (localnet_port) {
-            ofport = u16_to_ofp(simap_get(&localvif_to_ofport,
+            ofport = u16_to_ofp(simap_get(patch_ofports,
                                           localnet_port->logical_port));
             if (!ofport) {
                 goto out;
@@ -1130,7 +1097,8 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                 if (!binding->chassis) {
                     goto out;
                 }
-                tun = chassis_tunnel_find(binding->chassis->name, NULL);
+                tun = chassis_tunnel_find(chassis_tunnels,
+                                          binding->chassis->name, NULL);
                 if (!tun) {
                     goto out;
                 }
@@ -1384,7 +1352,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
             put_remote_port_redirect_overlay(binding, is_ha_remote,
                                              ha_ch_ordered, mff_ovn_geneve,
                                              tun, port_key, &match, ofpacts_p,
-                                             flow_table);
+                                             chassis_tunnels, flow_table);
         }
     }
 out:
@@ -1397,8 +1365,11 @@ static void
 consider_mc_group(enum mf_field_id mff_ovn_geneve,
                   const struct simap *ct_zones,
                   const struct hmap *local_datapaths,
+                  struct shash *local_bindings,
+                  struct simap *patch_ofports,
                   const struct sbrec_chassis *chassis,
                   const struct sbrec_multicast_group *mc,
+                  const struct hmap *chassis_tunnels,
                   struct ovn_desired_flow_table *flow_table)
 {
     uint32_t dp_key = mc->datapath->tunnel_key;
@@ -1445,19 +1416,21 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
             put_load(zone_id, MFF_LOG_CT_ZONE, 0, 32, &ofpacts);
         }
 
+        const char *lport_name = (port->parent_port && *port->parent_port) ?
+                                  port->parent_port : port->logical_port;
+
         if (!strcmp(port->type, "patch")) {
             put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32,
                      &remote_ofpacts);
             put_resubmit(OFTABLE_CHECK_LOOPBACK, &remote_ofpacts);
-        } else if (simap_contains(&localvif_to_ofport,
-                           (port->parent_port && *port->parent_port)
-                           ? port->parent_port : port->logical_port)
+        } else if (local_binding_get_primary_pb(local_bindings, lport_name)
+                   || simap_contains(patch_ofports, port->logical_port)
                    || (!strcmp(port->type, "l3gateway")
                        && port->chassis == chassis)) {
             put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
             put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts);
-        } else if (port->chassis && !get_localnet_port(local_datapaths,
-                                         mc->datapath->tunnel_key)) {
+        } else if (port->chassis && !get_localnet_port(
+                local_datapaths, mc->datapath->tunnel_key)) {
             /* Add remote chassis only when localnet port not exist,
              * otherwise multicast will reach remote ports through localnet
              * port. */
@@ -1498,7 +1471,7 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
         const struct chassis_tunnel *prev = NULL;
         SSET_FOR_EACH (chassis_name, &remote_chassis) {
             const struct chassis_tunnel *tun
-                = chassis_tunnel_find(chassis_name, NULL);
+                = chassis_tunnel_find(chassis_tunnels, chassis_name, NULL);
             if (!tun) {
                 continue;
             }
@@ -1525,41 +1498,50 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
     sset_destroy(&remote_chassis);
 }
 
-/* Replaces 'old' by 'new' (destroying 'new').  Returns true if 'old' and 'new'
- * contained different data, false if they were the same. */
-static bool
-update_ofports(struct simap *old, struct simap *new)
-{
-    bool changed = !simap_equal(old, new);
-    simap_swap(old, new);
-    simap_destroy(new);
-    return changed;
-}
-
 void
-physical_handle_port_binding_changes(struct physical_ctx *p_ctx,
-                                     struct ovn_desired_flow_table *flow_table)
+physical_handle_flows_for_lport(const struct sbrec_port_binding *pb,
+                                bool removed, struct physical_ctx *p_ctx,
+                                struct ovn_desired_flow_table *flow_table)
 {
-    const struct sbrec_port_binding *binding;
-    struct ofpbuf ofpacts;
-    ofpbuf_init(&ofpacts, 0);
-    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding,
-                                               p_ctx->port_binding_table) {
-        if (sbrec_port_binding_is_deleted(binding)) {
-            ofctrl_remove_flows(flow_table, &binding->header_.uuid);
-        } else {
-            if (!sbrec_port_binding_is_new(binding)) {
-                ofctrl_remove_flows(flow_table, &binding->header_.uuid);
-            }
+    ofctrl_remove_flows(flow_table, &pb->header_.uuid);
+
+    if (!strcmp(pb->type, "external")) {
+        /* External lports have a dependency on the localnet port.
+         * We need to remove the flows of the localnet port as well
+         * and re-consider adding the flows for it.
+         */
+        struct local_datapath *ldp =
+            get_local_datapath(p_ctx->local_datapaths,
+                               pb->datapath->tunnel_key);
+        if (ldp && ldp->localnet_port) {
+            struct ofpbuf ofpacts;
+            ofctrl_remove_flows(flow_table, &ldp->localnet_port->header_.uuid);
+            ofpbuf_init(&ofpacts, 0);
             consider_port_binding(p_ctx->sbrec_port_binding_by_name,
                                   p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
                                   p_ctx->active_tunnels,
                                   p_ctx->local_datapaths,
-                                  binding, p_ctx->chassis,
+                                  p_ctx->local_bindings,
+                                  p_ctx->patch_ofports,
+                                  p_ctx->chassis_tunnels,
+                                  ldp->localnet_port, p_ctx->chassis,
                                   flow_table, &ofpacts);
+            ofpbuf_uninit(&ofpacts);
         }
     }
-    ofpbuf_uninit(&ofpacts);
+
+    if (!removed) {
+        struct ofpbuf ofpacts;
+        ofpbuf_init(&ofpacts, 0);
+        consider_port_binding(p_ctx->sbrec_port_binding_by_name,
+                              p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
+                              p_ctx->active_tunnels, p_ctx->local_datapaths,
+                              p_ctx->local_bindings,
+                              p_ctx->patch_ofports,
+                              p_ctx->chassis_tunnels, pb,
+                              p_ctx->chassis, flow_table, &ofpacts);
+        ofpbuf_uninit(&ofpacts);
+    }
 }
 
 void
@@ -1575,8 +1557,11 @@ physical_handle_mc_group_changes(struct physical_ctx *p_ctx,
                 ofctrl_remove_flows(flow_table, &mc->header_.uuid);
             }
             consider_mc_group(p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
-                              p_ctx->local_datapaths,
-                              p_ctx->chassis, mc, flow_table);
+                              p_ctx->local_datapaths, p_ctx->local_bindings,
+                              p_ctx->patch_ofports,
+                              p_ctx->chassis, mc,
+                              p_ctx->chassis_tunnels,
+                              flow_table);
         }
     }
 }
@@ -1592,135 +1577,6 @@ physical_run(struct physical_ctx *p_ctx,
         uuid_generate(hc_uuid);
     }
 
-    /* This bool tracks physical mapping changes. */
-    bool physical_map_changed = false;
-
-    struct simap new_localvif_to_ofport =
-        SIMAP_INITIALIZER(&new_localvif_to_ofport);
-    struct simap new_tunnel_to_ofport =
-        SIMAP_INITIALIZER(&new_tunnel_to_ofport);
-    for (int i = 0; i < p_ctx->br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = p_ctx->br_int->ports[i];
-        if (!strcmp(port_rec->name, p_ctx->br_int->name)) {
-            continue;
-        }
-
-        const char *tunnel_id = smap_get(&port_rec->external_ids,
-                                         "ovn-chassis-id");
-        if (tunnel_id && encaps_tunnel_id_match(tunnel_id,
-                                                p_ctx->chassis->name,
-                                                NULL)) {
-            continue;
-        }
-
-        const char *localnet = smap_get(&port_rec->external_ids,
-                                        "ovn-localnet-port");
-        const char *l2gateway = smap_get(&port_rec->external_ids,
-                                        "ovn-l2gateway-port");
-
-        for (int j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
-
-            /* Get OpenFlow port number. */
-            if (!iface_rec->n_ofport) {
-                continue;
-            }
-            int64_t ofport = iface_rec->ofport[0];
-            if (ofport < 1 || ofport > ofp_to_u16(OFPP_MAX)) {
-                continue;
-            }
-
-            /* Record as patch to local net, logical patch port, chassis, or
-             * local logical port. */
-            bool is_patch = !strcmp(iface_rec->type, "patch");
-            if (is_patch && localnet) {
-                /* localnet patch ports can be handled just like VIFs. */
-                simap_put(&new_localvif_to_ofport, localnet, ofport);
-                break;
-            } else if (is_patch && l2gateway) {
-                /* L2 gateway patch ports can be handled just like VIFs. */
-                simap_put(&new_localvif_to_ofport, l2gateway, ofport);
-                break;
-            } else if (tunnel_id) {
-                enum chassis_tunnel_type tunnel_type;
-                if (!strcmp(iface_rec->type, "geneve")) {
-                    tunnel_type = GENEVE;
-                    if (!p_ctx->mff_ovn_geneve) {
-                        continue;
-                    }
-                } else if (!strcmp(iface_rec->type, "stt")) {
-                    tunnel_type = STT;
-                } else if (!strcmp(iface_rec->type, "vxlan")) {
-                    tunnel_type = VXLAN;
-                } else {
-                    continue;
-                }
-
-                simap_put(&new_tunnel_to_ofport, tunnel_id, ofport);
-                /*
-                 * We split the tunnel_id to get the chassis-id
-                 * and hash the tunnel list on the chassis-id. The
-                 * reason to use the chassis-id alone is because
-                 * there might be cases (multicast, gateway chassis)
-                 * where we need to tunnel to the chassis, but won't
-                 * have the encap-ip specifically.
-                 */
-                char *hash_id = NULL;
-                char *ip = NULL;
-
-                if (!encaps_tunnel_id_parse(tunnel_id, &hash_id, &ip)) {
-                    continue;
-                }
-                struct chassis_tunnel *tun = chassis_tunnel_find(hash_id, ip);
-                if (tun) {
-                    /* If the tunnel's ofport has changed, update. */
-                    if (tun->ofport != u16_to_ofp(ofport) ||
-                        tun->type != tunnel_type) {
-                        tun->ofport = u16_to_ofp(ofport);
-                        tun->type = tunnel_type;
-                        physical_map_changed = true;
-                    }
-                } else {
-                    tun = xmalloc(sizeof *tun);
-                    hmap_insert(&tunnels, &tun->hmap_node,
-                                hash_string(hash_id, 0));
-                    tun->chassis_id = xstrdup(tunnel_id);
-                    tun->ofport = u16_to_ofp(ofport);
-                    tun->type = tunnel_type;
-                    physical_map_changed = true;
-                }
-                free(hash_id);
-                free(ip);
-                break;
-            } else {
-                const char *iface_id = smap_get(&iface_rec->external_ids,
-                                                "iface-id");
-                if (iface_id) {
-                    simap_put(&new_localvif_to_ofport, iface_id, ofport);
-                }
-            }
-        }
-    }
-
-    /* Remove tunnels that are no longer here. */
-    struct chassis_tunnel *tun, *tun_next;
-    HMAP_FOR_EACH_SAFE (tun, tun_next, hmap_node, &tunnels) {
-        if (!simap_find(&new_tunnel_to_ofport, tun->chassis_id)) {
-            hmap_remove(&tunnels, &tun->hmap_node);
-            physical_map_changed = true;
-            free(tun->chassis_id);
-            free(tun);
-        }
-    }
-
-    /* Capture changed or removed openflow ports. */
-    physical_map_changed |= update_ofports(&localvif_to_ofport,
-                                           &new_localvif_to_ofport);
-    if (physical_map_changed) {
-        /* Reprocess logical flow table immediately. */
-        poll_immediate_wake();
-    }
-
     struct ofpbuf ofpacts;
     ofpbuf_init(&ofpacts, 0);
 
@@ -1734,16 +1590,20 @@ physical_run(struct physical_ctx *p_ctx,
         consider_port_binding(p_ctx->sbrec_port_binding_by_name,
                               p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
                               p_ctx->active_tunnels, p_ctx->local_datapaths,
-                              binding, p_ctx->chassis,
-                              flow_table, &ofpacts);
+                              p_ctx->local_bindings,
+                              p_ctx->patch_ofports,
+                              p_ctx->chassis_tunnels, binding,
+                              p_ctx->chassis, flow_table, &ofpacts);
     }
 
     /* Handle output to multicast groups, in tables 32 and 33. */
     const struct sbrec_multicast_group *mc;
     SBREC_MULTICAST_GROUP_TABLE_FOR_EACH (mc, p_ctx->mc_group_table) {
         consider_mc_group(p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
-                          p_ctx->local_datapaths, p_ctx->chassis,
-                          mc, flow_table);
+                          p_ctx->local_datapaths, p_ctx->local_bindings,
+                          p_ctx->patch_ofports, p_ctx->chassis,
+                          mc, p_ctx->chassis_tunnels,
+                          flow_table);
     }
 
     /* Table 0, priority 100.
@@ -1758,7 +1618,8 @@ physical_run(struct physical_ctx *p_ctx,
      * We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and MFF_LOG_OUTPORT from the
      * tunnel key data where possible, then resubmit to table 33 to handle
      * packets to the local hypervisor. */
-    HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
+    struct chassis_tunnel *tun;
+    HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) {
         struct match match = MATCH_CATCHALL_INITIALIZER;
         match_set_in_port(&match, tun->ofport);
 
@@ -1789,7 +1650,7 @@ physical_run(struct physical_ctx *p_ctx,
     }
 
     /* Handle ramp switch encapsulations. */
-    HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
+    HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) {
         if (tun->type != VXLAN) {
             continue;
         }
@@ -1926,89 +1787,4 @@ physical_run(struct physical_ctx *p_ctx,
                     &ofpacts, hc_uuid);
 
     ofpbuf_uninit(&ofpacts);
-
-    simap_destroy(&new_tunnel_to_ofport);
-}
-
-bool
-physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx,
-                                  struct ovn_desired_flow_table *flow_table)
-{
-    const struct ovsrec_interface *iface_rec;
-    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec, p_ctx->iface_table) {
-        if (!strcmp(iface_rec->type, "geneve") ||
-            !strcmp(iface_rec->type, "patch") ||
-            !strcmp(iface_rec->type, "vxlan") ||
-            !strcmp(iface_rec->type, "stt")) {
-            return false;
-        }
-    }
-
-    struct ofpbuf ofpacts;
-    ofpbuf_init(&ofpacts, 0);
-
-    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec, p_ctx->iface_table) {
-        const char *iface_id = smap_get(&iface_rec->external_ids, "iface-id");
-        if (!iface_id) {
-            continue;
-        }
-
-        const struct sbrec_port_binding *lb_pb =
-            local_binding_get_primary_pb(p_ctx->local_bindings, iface_id);
-        if (!lb_pb) {
-            /* For regular VIFs (e.g. lsp) the upcoming port-binding update
-             * will remove lfows related to the unclaimed ovs port.
-             * Localport is a special case and it needs to be managed here
-             * since the port is not binded and otherwise the related lfows
-             * will not be cleared removing the ovs port.
-             */
-            lb_pb = lport_lookup_by_name(p_ctx->sbrec_port_binding_by_name,
-                                         iface_id);
-            if (!lb_pb || strcmp(lb_pb->type, "localport")) {
-                continue;
-            }
-        }
-
-        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
-        if (ovsrec_interface_is_deleted(iface_rec)) {
-            ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid);
-            simap_find_and_delete(&localvif_to_ofport, iface_id);
-        } else {
-            if (!ovsrec_interface_is_new(iface_rec)) {
-                ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid);
-            }
-
-            simap_put(&localvif_to_ofport, iface_id, ofport);
-            consider_port_binding(p_ctx->sbrec_port_binding_by_name,
-                                  p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
-                                  p_ctx->active_tunnels,
-                                  p_ctx->local_datapaths,
-                                  lb_pb, p_ctx->chassis,
-                                  flow_table, &ofpacts);
-        }
-    }
-
-    ofpbuf_uninit(&ofpacts);
-    return true;
-}
-
-bool
-get_tunnel_ofport(const char *chassis_name, char *encap_ip, ofp_port_t *ofport)
-{
-    struct chassis_tunnel *tun = NULL;
-    tun = chassis_tunnel_find(chassis_name, encap_ip);
-    if (!tun) {
-        return false;
-    }
-
-    *ofport = tun->ofport;
-    return true;
-}
-
-void
-physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *flow_table)
-{
-    if (hc_uuid) {
-        ofctrl_remove_flows(flow_table, hc_uuid);
-    }
 }
diff --git a/controller/physical.h b/controller/physical.h
index feab41df4..c4540ad7f 100644
--- a/controller/physical.h
+++ b/controller/physical.h
@@ -34,6 +34,7 @@ struct simap;
 struct sbrec_multicast_group_table;
 struct sbrec_port_binding_table;
 struct sset;
+struct local_nonvif_data;
 
 /* OVN Geneve option information.
  *
@@ -49,25 +50,23 @@ struct physical_ctx {
     const struct ovsrec_bridge *br_int;
     const struct sbrec_chassis_table *chassis_table;
     const struct sbrec_chassis *chassis;
-    const struct ovsrec_interface_table *iface_table;
     const struct sset *active_tunnels;
     struct hmap *local_datapaths;
     struct sset *local_lports;
     const struct simap *ct_zones;
     enum mf_field_id mff_ovn_geneve;
     struct shash *local_bindings;
+    struct simap *patch_ofports;
+    struct hmap *chassis_tunnels;
 };
 
 void physical_register_ovs_idl(struct ovsdb_idl *);
 void physical_run(struct physical_ctx *,
                   struct ovn_desired_flow_table *);
-void physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *);
-void physical_handle_port_binding_changes(struct physical_ctx *,
-                                          struct ovn_desired_flow_table *);
 void physical_handle_mc_group_changes(struct physical_ctx *,
                                       struct ovn_desired_flow_table *);
-bool physical_handle_ovs_iface_changes(struct physical_ctx *,
-                                       struct ovn_desired_flow_table *);
-bool get_tunnel_ofport(const char *chassis_name, char *encap_ip,
-                       ofp_port_t *ofport);
+void physical_handle_flows_for_lport(const struct sbrec_port_binding *,
+                                     bool removed,
+                                     struct physical_ctx *,
+                                     struct ovn_desired_flow_table *);
 #endif /* controller/physical.h */
