diff --git a/controller/lflow.c b/controller/lflow.c
index 680b8cca1..ab7f3d286 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -557,6 +557,27 @@ update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs)
     return false;
 }
 
+static void
+lflow_parse_ctrl_meter(const struct sbrec_logical_flow *lflow,
+                       struct ovn_extend_table *meter_table,
+                       uint32_t *meter_id)
+{
+    ovs_assert(meter_id);
+    *meter_id = NX_CTLR_NO_METER;
+
+    if (lflow->controller_meter) {
+        *meter_id = ovn_extend_table_assign_id(meter_table,
+                                               lflow->controller_meter,
+                                               lflow->header_.uuid);
+        if (*meter_id == EXT_TABLE_ID_INVALID) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "Unable to assign id for meter: %s",
+                         lflow->controller_meter);
+            return;
+        }
+    }
+}
+
 static void
 add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
                           const struct sbrec_datapath_binding *dp,
@@ -572,6 +593,13 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
         .dp = dp,
     };
 
+    /* Parse any meter to be used if this flow should punt packets to
+     * controller.
+     */
+    uint32_t ctrl_meter_id = NX_CTLR_NO_METER;
+    lflow_parse_ctrl_meter(lflow, l_ctx_out->meter_table,
+                           &ctrl_meter_id);
+
     /* Encode OVN logical actions into OpenFlow. */
     uint64_t ofpacts_stub[1024 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
@@ -595,6 +623,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
         .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP,
         .fdb_ptable = OFTABLE_GET_FDB,
         .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
+        .ctrl_meter_id = ctrl_meter_id,
     };
     ovnacts_encode(ovnacts->data, ovnacts->size, &ep, &ofpacts);
 
@@ -621,9 +650,11 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
             }
         }
         if (!m->n) {
-            ofctrl_add_flow(l_ctx_out->flow_table, ptable, lflow->priority,
-                            lflow->header_.uuid.parts[0], &m->match, &ofpacts,
-                            &lflow->header_.uuid);
+            ofctrl_add_flow_metered(l_ctx_out->flow_table, ptable,
+                                    lflow->priority,
+                                    lflow->header_.uuid.parts[0], &m->match,
+                                    &ofpacts, &lflow->header_.uuid,
+                                    ctrl_meter_id);
         } else {
             uint64_t conj_stubs[64 / 8];
             struct ofpbuf conj;
@@ -641,7 +672,8 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
 
             ofctrl_add_or_append_flow(l_ctx_out->flow_table, ptable,
                                       lflow->priority, 0,
-                                      &m->match, &conj, &lflow->header_.uuid);
+                                      &m->match, &conj, &lflow->header_.uuid,
+                                      ctrl_meter_id);
             ofpbuf_uninit(&conj);
         }
     }
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index 053631590..d5aaca64b 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -66,6 +66,7 @@ struct ovn_flow {
     struct ofpact *ofpacts;
     size_t ofpacts_len;
     uint64_t cookie;
+    uint32_t ctrl_meter_id; /* Meter to be used for controller actions. */
 };
 
 /* A desired flow, in struct ovn_desired_flow_table, calculated by the
@@ -220,7 +221,8 @@ static struct desired_flow *desired_flow_alloc(
     uint16_t priority,
     uint64_t cookie,
     const struct match *match,
-    const struct ofpbuf *actions);
+    const struct ofpbuf *actions,
+    uint32_t meter_id);
 static struct desired_flow *desired_flow_lookup(
     struct ovn_desired_flow_table *,
     const struct ovn_flow *target);
@@ -1014,8 +1016,9 @@ link_flow_to_sb(struct ovn_desired_flow_table *flow_table,
 /* Flow table interfaces to the rest of ovn-controller. */
 
 /* Adds a flow to 'desired_flows' with the specified 'match' and 'actions' to
- * the OpenFlow table numbered 'table_id' with the given 'priority' and
- * OpenFlow 'cookie'.  The caller retains ownership of 'match' and 'actions'.
+ * the OpenFlow table numbered 'table_id' with the given 'priority', OpenFlow
+ * 'cookie' and 'meter_id'. The caller retains ownership of 'match' and
+ * 'actions'.
  *
  * The flow is also linked to the sb_uuid that generates it.
  *
@@ -1024,15 +1027,15 @@ link_flow_to_sb(struct ovn_desired_flow_table *flow_table,
  *
  * The caller should initialize its own hmap to hold the flows. */
 void
-ofctrl_check_and_add_flow(struct ovn_desired_flow_table *flow_table,
-                          uint8_t table_id, uint16_t priority,
-                          uint64_t cookie, const struct match *match,
-                          const struct ofpbuf *actions,
-                          const struct uuid *sb_uuid,
-                          bool log_duplicate_flow)
+ofctrl_check_and_add_flow_metred(struct ovn_desired_flow_table *flow_table,
+                                 uint8_t table_id, uint16_t priority,
+                                 uint64_t cookie, const struct match *match,
+                                 const struct ofpbuf *actions,
+                                 const struct uuid *sb_uuid,
+                                 uint32_t meter_id, bool log_duplicate_flow)
 {
     struct desired_flow *f = desired_flow_alloc(table_id, priority, cookie,
-                                                match, actions);
+                                                match, actions, meter_id);
 
     if (desired_flow_lookup_check_uuid(flow_table, &f->flow, sb_uuid)) {
         if (log_duplicate_flow) {
@@ -1060,8 +1063,19 @@ ofctrl_add_flow(struct ovn_desired_flow_table *desired_flows,
                 const struct match *match, const struct ofpbuf *actions,
                 const struct uuid *sb_uuid)
 {
-    ofctrl_check_and_add_flow(desired_flows, table_id, priority, cookie,
-                              match, actions, sb_uuid, true);
+    ofctrl_add_flow_metered(desired_flows, table_id, priority, cookie,
+                            match, actions, sb_uuid, NX_CTLR_NO_METER);
+}
+
+void
+ofctrl_add_flow_metered(struct ovn_desired_flow_table *desired_flows,
+                        uint8_t table_id, uint16_t priority, uint64_t cookie,
+                        const struct match *match,
+                        const struct ofpbuf *actions,
+                        const struct uuid *sb_uuid, uint32_t meter_id)
+{
+    ofctrl_check_and_add_flow_metred(desired_flows, table_id, priority, cookie,
+                                     match, actions, sb_uuid, meter_id, true);
 }
 
 /* Either add a new flow, or append actions on an existing flow. If the
@@ -1072,12 +1086,14 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
                           uint8_t table_id, uint16_t priority, uint64_t cookie,
                           const struct match *match,
                           const struct ofpbuf *actions,
-                          const struct uuid *sb_uuid)
+                          const struct uuid *sb_uuid,
+                          uint32_t meter_id)
 {
     struct desired_flow *existing;
     struct desired_flow *f;
 
-    f = desired_flow_alloc(table_id, priority, cookie, match, actions);
+    f = desired_flow_alloc(table_id, priority, cookie, match, actions,
+                           meter_id);
     existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow);
     if (existing) {
         /* There's already a flow with this particular match and action
@@ -1282,7 +1298,7 @@ ofctrl_flood_remove_flows(struct ovn_desired_flow_table *flow_table,
 static void
 ovn_flow_init(struct ovn_flow *f, uint8_t table_id, uint16_t priority,
               uint64_t cookie, const struct match *match,
-              const struct ofpbuf *actions)
+              const struct ofpbuf *actions, uint32_t meter_id)
 {
     f->table_id = table_id;
     f->priority = priority;
@@ -1291,11 +1307,13 @@ ovn_flow_init(struct ovn_flow *f, uint8_t table_id, uint16_t priority,
     f->ofpacts_len = actions->size;
     f->hash = ovn_flow_match_hash(f);
     f->cookie = cookie;
+    f->ctrl_meter_id = meter_id;
 }
 
 static struct desired_flow *
 desired_flow_alloc(uint8_t table_id, uint16_t priority, uint64_t cookie,
-                   const struct match *match, const struct ofpbuf *actions)
+                   const struct match *match, const struct ofpbuf *actions,
+                   uint32_t meter_id)
 {
     struct desired_flow *f = xmalloc(sizeof *f);
     ovs_list_init(&f->references);
@@ -1304,7 +1322,8 @@ desired_flow_alloc(uint8_t table_id, uint16_t priority, uint64_t cookie,
     ovs_list_init(&f->track_list_node);
     f->installed_flow = NULL;
     f->is_deleted = false;
-    ovn_flow_init(&f->flow, table_id, priority, cookie, match, actions);
+    ovn_flow_init(&f->flow, table_id, priority, cookie, match, actions,
+                  meter_id);
 
     return f;
 }
@@ -1330,6 +1349,7 @@ installed_flow_dup(struct desired_flow *src)
     dst->flow.ofpacts_len = src->flow.ofpacts_len;
     dst->flow.hash = src->flow.hash;
     dst->flow.cookie = src->flow.cookie;
+    dst->flow.ctrl_meter_id = src->flow.ctrl_meter_id;
     return dst;
 }
 
@@ -1356,6 +1376,7 @@ desired_flow_lookup__(struct ovn_desired_flow_table *flow_table,
         struct ovn_flow *f = &d->flow;
         if (f->table_id == target->table_id
             && f->priority == target->priority
+            && f->ctrl_meter_id == target->ctrl_meter_id
             && minimatch_equal(&f->match, &target->match)) {
 
             if (!match_cb || match_cb(d, arg)) {
diff --git a/controller/ofctrl.h b/controller/ofctrl.h
index ead8088c5..5947b5363 100644
--- a/controller/ofctrl.h
+++ b/controller/ofctrl.h
@@ -80,7 +80,15 @@ void ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
                                uint8_t table_id, uint16_t priority,
                                uint64_t cookie, const struct match *match,
                                const struct ofpbuf *actions,
-                               const struct uuid *sb_uuid);
+                               const struct uuid *sb_uuid,
+                               uint32_t meter_id);
+
+void ofctrl_add_flow_metered(struct ovn_desired_flow_table *desired_flows,
+                             uint8_t table_id, uint16_t priority,
+                             uint64_t cookie, const struct match *match,
+                             const struct ofpbuf *actions,
+                             const struct uuid *sb_uuid,
+                             uint32_t meter_id);
 
 /* Removes a bundles of flows from the flow table for a specific sb_uuid. The
  * flows are removed only if they are not referenced by any other sb_uuid(s).
@@ -110,11 +118,12 @@ void ovn_desired_flow_table_init(struct ovn_desired_flow_table *);
 void ovn_desired_flow_table_clear(struct ovn_desired_flow_table *);
 void ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *);
 
-void ofctrl_check_and_add_flow(struct ovn_desired_flow_table *,
-                               uint8_t table_id, uint16_t priority,
-                               uint64_t cookie, const struct match *,
-                               const struct ofpbuf *ofpacts,
-                               const struct uuid *, bool log_duplicate_flow);
+void ofctrl_check_and_add_flow_metred(struct ovn_desired_flow_table *,
+                                      uint8_t table_id, uint16_t priority,
+                                      uint64_t cookie, const struct match *,
+                                      const struct ofpbuf *ofpacts,
+                                      const struct uuid *, uint32_t meter_id,
+                                      bool log_duplicate_flow);
 
 
 bool ofctrl_is_connected(void);
diff --git a/controller/physical.c b/controller/physical.c
index 17ca5afbb..59eeca82a 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -847,7 +847,7 @@ put_local_common_flows(uint32_t dp_key,
          * If a parent port has multiple child ports, then this if condition
          * will be hit multiple times, but we want to add only one flow.
          * ofctrl_add_flow() logs a warning message for duplicate flows.
-         * So use the function 'ofctrl_check_and_add_flow' which doesn't
+         * So use the function 'ofctrl_check_and_add_flow_metred' which doesn't
          * log a warning.
          *
          * Other option is to add this flow for all the ports which are not
@@ -867,8 +867,9 @@ put_local_common_flows(uint32_t dp_key,
         put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, ofpacts_p);
         put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
         put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
-        ofctrl_check_and_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0,
-                                  &match, ofpacts_p, hc_uuid, false);
+        ofctrl_check_and_add_flow_metred(flow_table, OFTABLE_SAVE_INPORT, 100,
+                                         0, &match, ofpacts_p, hc_uuid,
+                                         NX_CTLR_NO_METER, false);
     }
 }
 
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 040213177..ab03df12c 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -795,6 +795,8 @@ struct ovnact_encode_params {
                          * 'get_fdb' to resubmit. */
     uint8_t fdb_lookup_ptable; /* OpenFlow table for
                                 * 'lookup_fdb' to resubmit. */
+    uint32_t ctrl_meter_id;     /* Meter to be used if the resulting flow
+                                   sends packets to controller. */
 };
 
 void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
diff --git a/lib/actions.c b/lib/actions.c
index b3433f49e..155b4a45a 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -105,10 +105,10 @@ encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts)
 }
 
 static void
-encode_controller_op(enum action_opcode opcode, struct ofpbuf *ofpacts)
+encode_controller_op(enum action_opcode opcode, uint32_t meter_id,
+                     struct ofpbuf *ofpacts)
 {
-    size_t ofs = encode_start_controller_op(opcode, false, NX_CTLR_NO_METER,
-                                            ofpacts);
+    size_t ofs = encode_start_controller_op(opcode, false, meter_id, ofpacts);
     encode_finish_controller_op(ofs, ofpacts);
 }
 
@@ -1647,7 +1647,7 @@ encode_nested_actions(const struct ovnact_nest *on,
      * packet to ARP or NA and then send the packet and actions back to the
      * switch inside an OFPT_PACKET_OUT message. */
     size_t oc_offset = encode_start_controller_op(opcode, false,
-                                                  NX_CTLR_NO_METER, ofpacts);
+                                                  ep->ctrl_meter_id, ofpacts);
     ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
                                  ofpacts, OFP15_VERSION);
     encode_finish_controller_op(oc_offset, ofpacts);
@@ -1698,10 +1698,10 @@ encode_ICMP6_ERROR(const struct ovnact_nest *on,
 
 static void
 encode_IGMP(const struct ovnact_null *a OVS_UNUSED,
-            const struct ovnact_encode_params *ep OVS_UNUSED,
+            const struct ovnact_encode_params *ep,
             struct ofpbuf *ofpacts)
 {
-    encode_controller_op(ACTION_OPCODE_IGMP, ofpacts);
+    encode_controller_op(ACTION_OPCODE_IGMP, ep->ctrl_meter_id, ofpacts);
 }
 
 static void
@@ -1943,6 +1943,7 @@ format_PUT_ND(const struct ovnact_put_mac_bind *put_mac, struct ds *s)
 
 static void
 encode_put_mac(const struct ovnact_put_mac_bind *put_mac,
+               const struct ovnact_encode_params *ep,
                enum mf_field_id ip_field, enum action_opcode opcode,
                struct ofpbuf *ofpacts)
 {
@@ -1952,24 +1953,24 @@ encode_put_mac(const struct ovnact_put_mac_bind *put_mac,
         { expr_resolve_field(&put_mac->mac), MFF_ETH_SRC }
     };
     encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-    encode_controller_op(opcode, ofpacts);
+    encode_controller_op(opcode, ep->ctrl_meter_id, ofpacts);
     encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
 }
 
 static void
 encode_PUT_ARP(const struct ovnact_put_mac_bind *put_mac,
-               const struct ovnact_encode_params *ep OVS_UNUSED,
+               const struct ovnact_encode_params *ep,
                struct ofpbuf *ofpacts)
 {
-    encode_put_mac(put_mac, MFF_REG0, ACTION_OPCODE_PUT_ARP, ofpacts);
+    encode_put_mac(put_mac, ep, MFF_REG0, ACTION_OPCODE_PUT_ARP, ofpacts);
 }
 
 static void
 encode_PUT_ND(const struct ovnact_put_mac_bind *put_mac,
-              const struct ovnact_encode_params *ep OVS_UNUSED,
+              const struct ovnact_encode_params *ep,
               struct ofpbuf *ofpacts)
 {
-    encode_put_mac(put_mac, MFF_XXREG0, ACTION_OPCODE_PUT_ND, ofpacts);
+    encode_put_mac(put_mac, ep, MFF_XXREG0, ACTION_OPCODE_PUT_ND, ofpacts);
 }
 
 static void
@@ -2670,13 +2671,13 @@ encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
 
 static void
 encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
-                       const struct ovnact_encode_params *ep OVS_UNUSED,
+                       const struct ovnact_encode_params *ep,
                        struct ofpbuf *ofpacts)
 {
     struct mf_subfield dst = expr_resolve_field(&pdo->dst);
 
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_PUT_DHCP_OPTS,
-                                                  true, NX_CTLR_NO_METER,
+                                                  true, ep->ctrl_meter_id,
                                                   ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
@@ -2723,13 +2724,13 @@ encode_PUT_DHCPV4_OPTS(const struct ovnact_put_opts *pdo,
 
 static void
 encode_PUT_DHCPV6_OPTS(const struct ovnact_put_opts *pdo,
-                       const struct ovnact_encode_params *ep OVS_UNUSED,
+                       const struct ovnact_encode_params *ep,
                        struct ofpbuf *ofpacts)
 {
     struct mf_subfield dst = expr_resolve_field(&pdo->dst);
 
     size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_DHCPV6_OPTS, true, NX_CTLR_NO_METER, ofpacts);
+        ACTION_OPCODE_PUT_DHCPV6_OPTS, true, ep->ctrl_meter_id, ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -2756,10 +2757,11 @@ format_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
 
 static void
 encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED,
-                   const struct ovnact_encode_params *ep OVS_UNUSED,
+                   const struct ovnact_encode_params *ep,
                    struct ofpbuf *ofpacts)
 {
-    encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts);
+    encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ep->ctrl_meter_id,
+                         ofpacts);
 }
 
 static void
@@ -2770,10 +2772,11 @@ format_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
 
 static void
 encode_BFD_MSG(const struct ovnact_null *a OVS_UNUSED,
-               const struct ovnact_encode_params *ep OVS_UNUSED,
+               const struct ovnact_encode_params *ep,
                struct ofpbuf *ofpacts)
 {
-    encode_controller_op(ACTION_OPCODE_BFD_MSG, ofpacts);
+    encode_controller_op(ACTION_OPCODE_BFD_MSG, ep->ctrl_meter_id,
+                         ofpacts);
 }
 
 static void
@@ -2868,13 +2871,13 @@ format_DNS_LOOKUP(const struct ovnact_result *dl, struct ds *s)
 
 static void
 encode_DNS_LOOKUP(const struct ovnact_result *dl,
-                  const struct ovnact_encode_params *ep OVS_UNUSED,
+                  const struct ovnact_encode_params *ep,
                   struct ofpbuf *ofpacts)
 {
     struct mf_subfield dst = expr_resolve_field(&dl->dst);
 
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DNS_LOOKUP,
-                                                  true, NX_CTLR_NO_METER,
+                                                  true, ep->ctrl_meter_id,
                                                   ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
@@ -3052,13 +3055,13 @@ encode_put_nd_ra_option(const struct ovnact_gen_option *o,
 
 static void
 encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
-                      const struct ovnact_encode_params *ep OVS_UNUSED,
+                      const struct ovnact_encode_params *ep,
                       struct ofpbuf *ofpacts)
 {
     struct mf_subfield dst = expr_resolve_field(&po->dst);
 
     size_t oc_offset = encode_start_controller_op(
-        ACTION_OPCODE_PUT_ND_RA_OPTS, true, NX_CTLR_NO_METER, ofpacts);
+        ACTION_OPCODE_PUT_ND_RA_OPTS, true, ep->ctrl_meter_id, ofpacts);
     nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
     ovs_be32 ofs = htonl(dst.ofs);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
@@ -3341,7 +3344,7 @@ format_OVNFIELD_LOAD(const struct ovnact_load *load , struct ds *s)
 
 static void
 encode_OVNFIELD_LOAD(const struct ovnact_load *load,
-            const struct ovnact_encode_params *ep OVS_UNUSED,
+            const struct ovnact_encode_params *ep,
             struct ofpbuf *ofpacts)
 {
     const struct ovn_field *f = ovn_field_from_name(load->dst.symbol->name);
@@ -3349,7 +3352,7 @@ encode_OVNFIELD_LOAD(const struct ovnact_load *load,
     case OVN_ICMP4_FRAG_MTU: {
         size_t oc_offset = encode_start_controller_op(
             ACTION_OPCODE_PUT_ICMP4_FRAG_MTU, true,
-            NX_CTLR_NO_METER, ofpacts);
+            ep->ctrl_meter_id, ofpacts);
         ofpbuf_put(ofpacts, &load->imm.value.be16_int, sizeof(ovs_be16));
         encode_finish_controller_op(oc_offset, ofpacts);
         break;
@@ -3357,7 +3360,7 @@ encode_OVNFIELD_LOAD(const struct ovnact_load *load,
     case OVN_ICMP6_FRAG_MTU: {
         size_t oc_offset = encode_start_controller_op(
             ACTION_OPCODE_PUT_ICMP6_FRAG_MTU, true,
-            NX_CTLR_NO_METER, ofpacts);
+            ep->ctrl_meter_id, ofpacts);
         ofpbuf_put(ofpacts, &load->imm.value.be32_int, sizeof(ovs_be32));
         encode_finish_controller_op(oc_offset, ofpacts);
         break;
@@ -3461,7 +3464,7 @@ encode_BIND_VPORT(const struct ovnact_bind_vport *vp,
     };
     encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
     size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_BIND_VPORT,
-                                                  false, NX_CTLR_NO_METER,
+                                                  false, ep->ctrl_meter_id,
                                                   ofpacts);
     ovs_be32 vp_key = htonl(vport_key);
     ofpbuf_put(ofpacts, &vp_key, sizeof(ovs_be32));
@@ -3499,14 +3502,15 @@ format_HANDLE_SVC_CHECK(const struct ovnact_handle_svc_check *svc_chk,
 
 static void
 encode_HANDLE_SVC_CHECK(const struct ovnact_handle_svc_check *svc_chk,
-                        const struct ovnact_encode_params *ep OVS_UNUSED,
+                        const struct ovnact_encode_params *ep,
                         struct ofpbuf *ofpacts)
 {
     const struct arg args[] = {
         { expr_resolve_field(&svc_chk->port), MFF_LOG_INPORT },
     };
     encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-    encode_controller_op(ACTION_OPCODE_HANDLE_SVC_CHECK, ofpacts);
+    encode_controller_op(ACTION_OPCODE_HANDLE_SVC_CHECK, ep->ctrl_meter_id,
+                         ofpacts);
     encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
 }
 
@@ -3755,7 +3759,7 @@ format_PUT_FDB(const struct ovnact_put_fdb *put_fdb, struct ds *s)
 
 static void
 encode_PUT_FDB(const struct ovnact_put_fdb *put_fdb,
-               const struct ovnact_encode_params *ep OVS_UNUSED,
+               const struct ovnact_encode_params *ep,
                struct ofpbuf *ofpacts)
 {
     const struct arg args[] = {
@@ -3763,7 +3767,7 @@ encode_PUT_FDB(const struct ovnact_put_fdb *put_fdb,
         { expr_resolve_field(&put_fdb->mac), MFF_ETH_SRC }
     };
     encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-    encode_controller_op(ACTION_OPCODE_PUT_FDB, ofpacts);
+    encode_controller_op(ACTION_OPCODE_PUT_FDB, ep->ctrl_meter_id, ofpacts);
     encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
 }
 
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 3afa80a3b..8844621e9 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -1673,6 +1673,7 @@ for (f in AggregatedFlow()) {
             .pipeline         = pipeline,
             .table_id         = f.stage.table_id,
             .priority         = f.priority,
+            .controller_meter = None,
             .__match          = f.__match,
             .actions          = f.actions,
             .external_ids     = external_ids)
@@ -1685,6 +1686,7 @@ for (f in AggregatedFlow()) {
                 .pipeline         = pipeline,
                 .table_id         = f.stage.table_id,
                 .priority         = f.priority,
+                .controller_meter = None,
                 .__match          = f.__match,
                 .actions          = f.actions,
                 .external_ids     = external_ids);
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 205a30a37..609699475 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "20.17.0",
-    "cksum": "669123379 26536",
+    "version": "20.18.0",
+    "cksum": "3870294693 26668",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -109,6 +109,8 @@
                                               "maxInteger": 65535}}},
                 "match": {"type": "string"},
                 "actions": {"type": "string"},
+                "controller_meter": {"type": {"key": {"type": "string"},
+                                     "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index b29866e88..3ffdda8c7 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2441,6 +2441,12 @@ tcp.flags = RST;
       </dl>
     </column>
 
+    <column name="controller_meter">
+      The name of the meter in table <ref table="Meter"/> to be used for
+      all packets that the logical flow might send to
+      <code>ovn-controller</code>.
+    </column>
+
     <column name="external_ids" key="stage-name">
       Human-readable name for this flow's stage in the pipeline.
     </column>
