diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index a33d02681..f023a37b9 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -394,7 +394,6 @@ struct ovnact_controller_event {
     int event_type;   /* controller event type */
     struct ovnact_gen_option *options;
     size_t n_options;
-    char *meter;
 };
 
 /* OVNACT_BIND_VPORT. */
diff --git a/lib/actions.c b/lib/actions.c
index 2355a9ace..c572e88ae 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -1644,9 +1644,6 @@ format_TRIGGER_EVENT(const struct ovnact_controller_event *event,
 {
     ds_put_format(s, "trigger_event(event = \"%s\"",
                   event_to_string(event->event_type));
-    if (event->meter) {
-        ds_put_format(s, ", meter = \"%s\"", event->meter);
-    }
     for (const struct ovnact_gen_option *o = event->options;
          o < &event->options[event->n_options]; o++) {
         ds_put_cstr(s, ", ");
@@ -1821,24 +1818,11 @@ encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
 
 static void
 encode_TRIGGER_EVENT(const struct ovnact_controller_event *event,
-                     const struct ovnact_encode_params *ep OVS_UNUSED,
+                     const struct ovnact_encode_params *ep,
                      struct ofpbuf *ofpacts)
 {
-    uint32_t meter_id = NX_CTLR_NO_METER;
-    size_t oc_offset;
-
-    if (event->meter) {
-        meter_id = ovn_extend_table_assign_id(ep->meter_table, event->meter,
-                                              ep->lflow_uuid);
-        if (meter_id == EXT_TABLE_ID_INVALID) {
-            VLOG_WARN("Unable to assign id for trigger meter: %s",
-                      event->meter);
-            return;
-        }
-    }
-
-    oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false,
-                                           meter_id, ofpacts);
+    size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_EVENT, false,
+                                                  ep->ctrl_meter_id, ofpacts);
     ovs_be32 ofs = htonl(event->event_type);
     ofpbuf_put(ofpacts, &ofs, sizeof ofs);
 
@@ -2372,27 +2356,12 @@ parse_trigger_event(struct action_context *ctx,
                                      sizeof *event->options);
         }
 
-        if (lexer_match_id(ctx->lexer, "meter")) {
-            if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) {
-                return;
-            }
-            /* If multiple meters are given, use the most recent. */
-            if (ctx->lexer->token.type == LEX_T_STRING &&
-                strlen(ctx->lexer->token.s)) {
-                free(event->meter);
-                event->meter = xstrdup(ctx->lexer->token.s);
-            } else if (ctx->lexer->token.type != LEX_T_STRING) {
-                lexer_syntax_error(ctx->lexer, "expecting string");
-                return;
-            }
-            lexer_get(ctx->lexer);
-        } else {
-            struct ovnact_gen_option *o = &event->options[event->n_options++];
-            memset(o, 0, sizeof *o);
-            parse_gen_opt(ctx, o,
-                    &ctx->pp->controller_event_opts->event_opts[event_type],
-                    event_to_string(event_type));
-            }
+        struct ovnact_gen_option *o = &event->options[event->n_options++];
+        memset(o, 0, sizeof *o);
+        parse_gen_opt(ctx, o,
+                      &ctx->pp->controller_event_opts->event_opts[event_type],
+                      event_to_string(event_type));
+
         if (ctx->lexer->error) {
             return;
         }
@@ -2413,7 +2382,6 @@ static void
 ovnact_controller_event_free(struct ovnact_controller_event *event)
 {
     free_gen_options(event->options, event->n_options);
-    free(event->meter);
 }
 
 static void
diff --git a/lib/copp.c b/lib/copp.c
index e3d14938a..bbe66924b 100644
--- a/lib/copp.c
+++ b/lib/copp.c
@@ -37,6 +37,7 @@ static char *copp_proto_names[COPP_PROTO_MAX] = {
     [COPP_ND_NS_RESOLVE] = "nd-ns-resolve",
     [COPP_ND_RA_OPTS]    = "nd-ra-opts",
     [COPP_TCP_RESET]     = "tcp-reset",
+    [COPP_REJECT]        = "reject",
     [COPP_BFD]           = "bfd",
 };
 
diff --git a/lib/copp.h b/lib/copp.h
index c34e1e029..e238d963a 100644
--- a/lib/copp.h
+++ b/lib/copp.h
@@ -36,6 +36,7 @@ enum copp_proto {
     COPP_ND_RA_OPTS,
     COPP_TCP_RESET,
     COPP_BFD,
+    COPP_REJECT,
     COPP_PROTO_MAX,
     COPP_PROTO_INVALID = COPP_PROTO_MAX,
 };
diff --git a/northd/automake.mk b/northd/automake.mk
index 4fc81c17b..6da54deb8 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -30,7 +30,8 @@ ddlog_sources = \
 	northd/ovn.dl \
 	northd/ovn.rs \
 	northd/helpers.dl \
-	northd/bitwise.dl
+	northd/bitwise.dl \
+	northd/copp.dl
 ddlog_nodist_sources = \
 	northd/OVN_Northbound.dl \
 	northd/OVN_Southbound.dl
diff --git a/northd/copp.dl b/northd/copp.dl
new file mode 100644
index 000000000..ffb9fb32e
--- /dev/null
+++ b/northd/copp.dl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+function cOPP_ARP() : string { "arp" }
+function cOPP_ARP_RESOLVE() : string { "arp-resolve" }
+function cOPP_DHCPV4_OPTS() : string { "dhcpv4-opts" }
+function cOPP_DHCPV6_OPTS() : string { "dhcpv6-opts" }
+function cOPP_DNS() : string { "dns" }
+function cOPP_EVENT_ELB() : string { "event-elb" }
+function cOPP_ICMP4_ERR() : string { "icmp4-error" }
+function cOPP_ICMP6_ERR() : string { "icmp6-error" }
+function cOPP_IGMP() : string { "igmp" }
+function cOPP_ND_NA() : string { "nd-na" }
+function cOPP_ND_NS() : string { "nd-ns" }
+function cOPP_ND_NS_RESOLVE() : string { "nd-ns-resolve" }
+function cOPP_ND_RA_OPTS() : string { "nd-ra-opts" }
+function cOPP_TCP_RESET() : string { "tcp-reset" }
+function cOPP_REJECT() : string { "reject" }
+function cOPP_BFD() : string { "bfd" }
diff --git a/northd/lrouter.dl b/northd/lrouter.dl
index 85c07716b..4a24f3f61 100644
--- a/northd/lrouter.dl
+++ b/northd/lrouter.dl
@@ -431,6 +431,23 @@ LogicalRouterLBs(lr, vec_empty()) :-
     nb::Logical_Router(._uuid = lr),
     not LogicalRouterLB(lr, _).
 
+// LogicalRouterCopp maps from each LR to its collection of Copp meters,
+// dropping any Copp meter whose meter name doesn't exist.
+relation LogicalRouterCopp(lr: uuid, meters: Map<string,string>)
+LogicalRouterCopp(lr, meters) :- LogicalRouterCopp0(lr, meters).
+LogicalRouterCopp(lr, map_empty()) :-
+    nb::Logical_Router(._uuid = lr),
+    not LogicalRouterCopp0(lr, _).
+
+relation LogicalRouterCopp0(lr: uuid, meters: Map<string,string>)
+LogicalRouterCopp0(lr, meters) :-
+    nb::Logical_Router(._uuid = lr, .copp = Some{copp_uuid}),
+    nb::Copp(._uuid = copp_uuid, .meters = meters),
+    var entry = FlatMap(meters),
+    (var copp_id, var meter_name) = entry,
+    &nb::Meter(.name = meter_name),
+    var meters = (copp_id, meter_name).group_by(lr).to_map().
+
 /* Router relation collects all attributes of a logical router.
  *
  * `l3dgw_port` - optional redirect port (see `DistributedGatewayPort`)
@@ -466,6 +483,7 @@ typedef Router = Router {
     mcast_cfg:          Intern<McastRouterCfg>,
     learn_from_arp_request: bool,
     force_lb_snat: bool,
+    copp:               Map<string, string>,
 }
 
 relation Router[Intern<Router>]
@@ -492,13 +510,15 @@ Router[Router{
         .lbs        = lbs,
         .mcast_cfg  = mcast_cfg,
         .learn_from_arp_request = learn_from_arp_request,
-        .force_lb_snat = force_lb_snat}.intern()] :-
+        .force_lb_snat = force_lb_snat,
+        .copp       = copp}.intern()] :-
     lr in nb::Logical_Router(),
     lr.is_enabled(),
     LogicalRouterRedirectPort(lr._uuid, l3dgw_port),
     LogicalRouterNATs(lr._uuid, nats),
     LogicalRouterLBs(lr._uuid, lbs),
     LogicalRouterSnatIPs(lr._uuid, snat_ips),
+    LogicalRouterCopp(lr._uuid, copp),
     mcast_cfg in &McastRouterCfg(.datapath = lr._uuid),
     var learn_from_arp_request = lr.options.get_bool_def("always_learn_from_arp_request", true),
     var force_lb_snat = lb_force_snat_router_ip(lr.options).
diff --git a/northd/lswitch.dl b/northd/lswitch.dl
index 402df48ef..7e7b62a4d 100644
--- a/northd/lswitch.dl
+++ b/northd/lswitch.dl
@@ -202,6 +202,23 @@ LogicalSwitchHasNonRouterPort(ls, false) :-
     nb::Logical_Switch(._uuid = ls),
     not LogicalSwitchHasNonRouterPort0(ls).
 
+// LogicalSwitchCopp maps from each LS to its collection of Copp meters,
+// dropping any Copp meter whose meter name doesn't exist.
+relation LogicalSwitchCopp(ls: uuid, meters: Map<string,string>)
+LogicalSwitchCopp(ls, meters) :- LogicalSwitchCopp0(ls, meters).
+LogicalSwitchCopp(ls, map_empty()) :-
+    nb::Logical_Switch(._uuid = ls),
+    not LogicalSwitchCopp0(ls, _).
+
+relation LogicalSwitchCopp0(ls: uuid, meters: Map<string,string>)
+LogicalSwitchCopp0(ls, meters) :-
+    nb::Logical_Switch(._uuid = ls, .copp = Some{copp_uuid}),
+    nb::Copp(._uuid = copp_uuid, .meters = meters),
+    var entry = FlatMap(meters),
+    (var copp_id, var meter_name) = entry,
+    &nb::Meter(.name = meter_name),
+    var meters = (copp_id, meter_name).group_by(ls).to_map().
+
 /* Switch relation collects all attributes of a logical switch */
 
 typedef Switch = Switch {
@@ -223,6 +240,7 @@ typedef Switch = Switch {
     ipv6_prefix:       Option<in6_addr>,
     mcast_cfg:         Intern<McastSwitchCfg>,
     is_vlan_transparent: bool,
+    copp:              Map<string, string>,
 
     /* Does this switch have at least one port with type != "router"? */
     has_non_router_port: bool
@@ -259,6 +277,7 @@ Switch[Switch{
            .ipv6_prefix       = ipv6_prefix,
            .mcast_cfg         = mcast_cfg,
            .has_non_router_port = has_non_router_port,
+           .copp              = copp,
            .is_vlan_transparent = is_vlan_transparent
        }.intern()] :-
     nb::Logical_Switch[ls],
@@ -269,6 +288,7 @@ Switch[Switch{
     LogicalSwitchHasUnknownPorts(ls._uuid, has_unknown_ports),
     LogicalSwitchLocalnetPorts(ls._uuid, localnet_ports),
     LogicalSwitchHasNonRouterPort(ls._uuid, has_non_router_port),
+    LogicalSwitchCopp(ls._uuid, copp),
     mcast_cfg in &McastSwitchCfg(.datapath = ls._uuid),
     var subnet =
         match (ls.other_config.get("subnet")) {
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 214596610..150f01e02 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -3460,11 +3460,11 @@ ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_lb *lb,
     }
 }
 
-static
-void build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
-                          struct ovn_northd_lb_vip *lb_vip_nb,
-                          struct ds *action, char *selection_fields,
-                          bool ls_dp)
+static bool
+build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
+                     struct ovn_northd_lb_vip *lb_vip_nb,
+                     struct ds *action, char *selection_fields,
+                     bool ls_dp)
 {
     bool skip_hash_fields = false, reject = false;
 
@@ -3516,6 +3516,7 @@ void build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
         ds_chomp(action, ')');
         ds_put_format(action, "; hash_fields=\"%s\");", selection_fields);
     }
+    return reject;
 }
 
 static void
@@ -4341,9 +4342,10 @@ ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
 
 /* Adds a row with the specified contents to the Logical_Flow table. */
 #define ovn_lflow_add_with_hint__(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
-                                  ACTIONS, CTRL_METER, STAGE_HINT) \
+                                  ACTIONS, IN_OUT_PORT, CTRL_METER, \
+                                  STAGE_HINT) \
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
-                     CTRL_METER, STAGE_HINT, OVS_SOURCE_LOCATOR)
+                     IN_OUT_PORT, CTRL_METER, STAGE_HINT, OVS_SOURCE_LOCATOR)
 
 #define ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
                                 ACTIONS, STAGE_HINT) \
@@ -4370,11 +4372,10 @@ ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
                      NULL, NULL, NULL, OVS_SOURCE_LOCATOR)
 
-#define ovn_lflow_add_ctrl(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
-                           CTRL_METER) \
+#define ovn_lflow_metered(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
+                          CTRL_METER) \
     ovn_lflow_add_with_hint__(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
-                              ACTIONS, CTRL_METER, NULL)
-
+                              ACTIONS, NULL, CTRL_METER, NULL)
 
 static struct ovn_lflow *
 ovn_lflow_find(const struct hmap *lflows, const struct ovn_datapath *od,
@@ -5330,7 +5331,6 @@ ls_has_dns_records(const struct nbrec_logical_switch *nbs)
 static bool
 build_empty_lb_event_flow(struct ovn_lb_vip *lb_vip,
                           const struct nbrec_load_balancer *lb,
-                          struct shash *meter_groups,
                           struct ds *match, struct ds *action)
 {
     bool controller_event = smap_get_bool(&lb->options, "event", false) ||
@@ -5344,11 +5344,6 @@ build_empty_lb_event_flow(struct ovn_lb_vip *lb_vip,
     ds_clear(match);
 
     bool ipv4 = IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
-    char *meter = "";
-
-    if (meter_groups && shash_find(meter_groups, "event-elb")) {
-        meter = "event-elb";
-    }
 
     ds_put_format(match, "ip%s.dst == %s && %s",
                   ipv4 ? "4": "6", lb_vip->vip_str, lb->protocol);
@@ -5363,11 +5358,11 @@ build_empty_lb_event_flow(struct ovn_lb_vip *lb_vip,
 
     ds_put_format(action,
                   "trigger_event(event = \"%s\", "
-                  "meter = \"%s\", vip = \"%s\", "
+                  "vip = \"%s\", "
                   "protocol = \"%s\", "
                   "load_balancer = \"" UUID_FMT "\");",
                   event_to_string(OVN_EVENT_EMPTY_LB_BACKENDS),
-                  meter, vip, lb->protocol,
+                  vip, lb->protocol,
                   UUID_ARGS(&lb->header_.uuid));
     if (lb_vip->vip_port) {
         free(vip);
@@ -5723,10 +5718,12 @@ build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows,
                   "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,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            stage_hint);
+    ovn_lflow_add_with_hint__(lflows, od, stage,
+                              acl->priority + OVN_ACL_PRI_OFFSET,
+                              ds_cstr(&match), ds_cstr(&actions), NULL,
+                              copp_meter_get(COPP_REJECT, od->nbs->copp,
+                                             meter_groups),
+                              stage_hint);
 
     free(next_action);
     ds_destroy(&match);
@@ -6223,7 +6220,8 @@ build_qos(struct ovn_datapath *od, struct hmap *lflows) {
 
 static void
 build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
-               struct ds *match, struct ds *action)
+               struct ds *match, struct ds *action,
+               struct shash *meter_groups)
 {
     for (size_t i = 0; i < lb->n_vips; i++) {
         struct ovn_lb_vip *lb_vip = &lb->vips[i];
@@ -6265,8 +6263,9 @@ build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
         }
 
         /* New connections in Ingress table. */
-        build_lb_vip_actions(lb_vip, lb_vip_nb, action,
-                             lb->selection_fields, true);
+        const char *meter = NULL;
+        bool reject = build_lb_vip_actions(lb_vip, lb_vip_nb, action,
+                                           lb->selection_fields, true);
 
         ds_put_format(match, "ct.new && %s.dst == %s", ip_match,
                       lb_vip->vip_str);
@@ -6276,9 +6275,16 @@ build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
             priority = 120;
         }
         for (size_t j = 0; j < lb->n_nb_ls; j++) {
-            ovn_lflow_add_with_hint(lflows, lb->nb_ls[j], S_SWITCH_IN_STATEFUL,
-                                    priority, ds_cstr(match),
-                                    ds_cstr(action), &lb->nlb->header_);
+            struct ovn_datapath *od = lb->nb_ls[j];
+
+            if (reject) {
+                meter = copp_meter_get(COPP_REJECT, od->nbs->copp,
+                                       meter_groups);
+            }
+            ovn_lflow_add_with_hint__(lflows, od, S_SWITCH_IN_STATEFUL,
+                                      priority, ds_cstr(match),
+                                      ds_cstr(action), NULL, meter,
+                                      &lb->nlb->header_);
         }
     }
 }
@@ -6883,6 +6889,7 @@ static void
 build_dhcpv4_options_flows(struct ovn_port *op,
                            struct lport_addresses *lsp_addrs,
                            struct ovn_port *inport, bool is_external,
+                           struct shash *meter_groups,
                            struct hmap *lflows)
 {
     struct ds match = DS_EMPTY_INITIALIZER;
@@ -6906,10 +6913,15 @@ build_dhcpv4_options_flows(struct ovn_port *op,
                               op->json_key);
             }
 
-            ovn_lflow_add_with_lport_and_hint(
-                lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100, ds_cstr(&match),
-                ds_cstr(&options_action), inport->key,
-                &op->nbsp->dhcpv4_options->header_);
+            ovn_lflow_add_with_hint__(lflows, op->od,
+                                      S_SWITCH_IN_DHCP_OPTIONS, 100,
+                                      ds_cstr(&match),
+                                      ds_cstr(&options_action),
+                                      inport->key,
+                                      copp_meter_get(COPP_DHCPV4_OPTS,
+                                                     op->od->nbs->copp,
+                                                     meter_groups),
+                                      &op->nbsp->dhcpv4_options->header_);
             ds_clear(&match);
             /* Allow ip4.src = OFFER_IP and
              * ip4.dst = {SERVER_IP, 255.255.255.255} for the below
@@ -6929,10 +6941,15 @@ build_dhcpv4_options_flows(struct ovn_port *op,
                               op->json_key);
             }
 
-            ovn_lflow_add_with_lport_and_hint(
-                lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100, ds_cstr(&match),
-                ds_cstr(&options_action), inport->key,
-                &op->nbsp->dhcpv4_options->header_);
+            ovn_lflow_add_with_hint__(lflows, op->od,
+                                      S_SWITCH_IN_DHCP_OPTIONS, 100,
+                                      ds_cstr(&match),
+                                      ds_cstr(&options_action),
+                                      inport->key,
+                                      copp_meter_get(COPP_DHCPV4_OPTS,
+                                                     op->od->nbs->copp,
+                                                     meter_groups),
+                                      &op->nbsp->dhcpv4_options->header_);
             ds_clear(&match);
 
             /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
@@ -6965,6 +6982,7 @@ static void
 build_dhcpv6_options_flows(struct ovn_port *op,
                            struct lport_addresses *lsp_addrs,
                            struct ovn_port *inport, bool is_external,
+                           struct shash *meter_groups,
                            struct hmap *lflows)
 {
     struct ds match = DS_EMPTY_INITIALIZER;
@@ -6987,10 +7005,15 @@ build_dhcpv6_options_flows(struct ovn_port *op,
                               op->json_key);
             }
 
-            ovn_lflow_add_with_lport_and_hint(
-                lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, 100, ds_cstr(&match),
-                ds_cstr(&options_action), inport->key,
-                &op->nbsp->dhcpv6_options->header_);
+            ovn_lflow_add_with_hint__(lflows, op->od,
+                                      S_SWITCH_IN_DHCP_OPTIONS, 100,
+                                      ds_cstr(&match),
+                                      ds_cstr(&options_action),
+                                      inport->key,
+                                      copp_meter_get(COPP_DHCPV6_OPTS,
+                                                     op->od->nbs->copp,
+                                                     meter_groups),
+                                      &op->nbsp->dhcpv6_options->header_);
 
             /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means the
              * put_dhcpv6_opts action is successful */
@@ -7181,6 +7204,7 @@ static void
 build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
                                          struct hmap *lflows,
                                          struct hmap *ports,
+                                         struct shash *meter_groups,
                                          struct ds *actions,
                                          struct ds *match)
 {
@@ -7328,11 +7352,15 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
                             op->lsp_addrs[i].ipv6_addrs[j].addr_s,
                             op->lsp_addrs[i].ipv6_addrs[j].addr_s,
                             op->lsp_addrs[i].ea_s);
-                    ovn_lflow_add_with_hint(lflows, op->od,
-                                            S_SWITCH_IN_ARP_ND_RSP, 50,
-                                            ds_cstr(match),
-                                            ds_cstr(actions),
-                                            &op->nbsp->header_);
+                    ovn_lflow_add_with_hint__(lflows, op->od,
+                                              S_SWITCH_IN_ARP_ND_RSP, 50,
+                                              ds_cstr(match),
+                                              ds_cstr(actions),
+                                              NULL,
+                                              copp_meter_get(COPP_ND_NA,
+                                                  op->od->nbs->copp,
+                                                  meter_groups),
+                                              &op->nbsp->header_);
 
                     /* Do not reply to a solicitation from the port that owns
                      * the address (otherwise DAD detection will fail). */
@@ -7453,7 +7481,8 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
  * priority 100 flows. */
 static void
 build_lswitch_dhcp_options_and_response(struct ovn_port *op,
-                                        struct hmap *lflows)
+                                        struct hmap *lflows,
+                                        struct shash *meter_groups)
 {
     if (op->nbsp) {
         if (!lsp_is_enabled(op->nbsp) || lsp_is_router(op->nbsp)) {
@@ -7482,17 +7511,19 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op,
                     build_dhcpv4_options_flows(
                         op, &op->lsp_addrs[i],
                         op->od->localnet_ports[j], is_external,
-                        lflows);
+                        meter_groups, lflows);
                     build_dhcpv6_options_flows(
                         op, &op->lsp_addrs[i],
                         op->od->localnet_ports[j], is_external,
-                        lflows);
+                        meter_groups, lflows);
                 }
             } else {
                 build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op,
-                                           is_external, lflows);
+                                           is_external, meter_groups,
+                                           lflows);
                 build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op,
-                                           is_external, lflows);
+                                           is_external, meter_groups,
+                                           lflows);
             }
         }
     }
@@ -7522,13 +7553,15 @@ build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
 */
 static void
 build_lswitch_dns_lookup_and_response(struct ovn_datapath *od,
-                                      struct hmap *lflows)
+                                      struct hmap *lflows,
+                                      struct shash *meter_groups)
 {
     if (od->nbs && ls_has_dns_records(od->nbs)) {
-
-        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
-                      "udp.dst == 53",
-                      REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;");
+        ovn_lflow_metered(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
+                          "udp.dst == 53",
+                          REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;",
+                          copp_meter_get(COPP_DNS, od->nbs->copp,
+                                         meter_groups));
         const char *dns_action = "eth.dst <-> eth.src; ip4.src <-> ip4.dst; "
                       "udp.dst = udp.src; udp.src = 53; outport = inport; "
                       "flags.loopback = 1; output;";
@@ -7565,7 +7598,8 @@ build_lswitch_external_port(struct ovn_port *op,
 static void
 build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
                                         struct hmap *lflows,
-                                        struct ds *actions)
+                                        struct ds *actions,
+                                        struct shash *meter_groups)
 {
     if (od->nbs) {
 
@@ -7586,12 +7620,16 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
             }
             ds_put_cstr(actions, "igmp;");
             /* Punt IGMP traffic to controller. */
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "ip4 && ip.proto == 2", ds_cstr(actions));
+            ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
+                              "ip4 && ip.proto == 2", ds_cstr(actions),
+                              copp_meter_get(COPP_IGMP, od->nbs->copp,
+                                             meter_groups));
 
             /* Punt MLD traffic to controller. */
-            ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "mldv1 || mldv2", ds_cstr(actions));
+            ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
+                              "mldv1 || mldv2", ds_cstr(actions),
+                              copp_meter_get(COPP_IGMP, od->nbs->copp,
+                                             meter_groups));
 
             /* Flood all IP multicast traffic destined to 224.0.0.X to all
              * ports - RFC 4541, section 2.1.2, item 2.
@@ -8983,7 +9021,8 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
                                struct ovn_northd_lb *lb,
                                struct ovn_northd_lb_vip *vips_nb,
                                struct hmap *lflows,
-                               struct ds *match, struct ds *action)
+                               struct ds *match, struct ds *action,
+                               struct shash *meter_groups)
 {
     char *skip_snat_new_action = NULL;
     char *skip_snat_est_action = NULL;
@@ -8993,8 +9032,8 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     ds_clear(match);
     ds_clear(action);
 
-    build_lb_vip_actions(lb_vip, vips_nb, action,
-                         lb->selection_fields, false);
+    bool reject = build_lb_vip_actions(lb_vip, vips_nb, action,
+                                       lb->selection_fields, false);
 
     /* Higher priority rules are added for load-balancing in DNAT
      * table.  For every match (on a VIP[:port]), we add two flows.
@@ -9075,6 +9114,11 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         char *new_match_p = new_match;
         char *est_match_p = est_match;
         char *est_actions = NULL;
+        const char *meter = NULL;
+
+        if (reject) {
+            meter = copp_meter_get(COPP_REJECT, od->nbr->copp, meter_groups);
+        }
 
         if (sset_contains(&od->external_ips, lb_vip->vip_str)) {
             /* The load balancer vip is also present in the NAT entries.
@@ -9110,18 +9154,18 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         }
 
         if (snat_type == SKIP_SNAT) {
-            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, prio,
-                                    new_match_p, skip_snat_new_action,
-                                    &lb->nlb->header_);
+            ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_DNAT, prio,
+                                      new_match_p, skip_snat_new_action, NULL,
+                                      meter, &lb->nlb->header_);
             ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, prio,
                                     est_match_p, skip_snat_est_action,
                                     &lb->nlb->header_);
         } else if (snat_type == FORCE_SNAT) {
             char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
                                           ds_cstr(action));
-            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, prio,
-                                    new_match_p, new_actions,
-                                    &lb->nlb->header_);
+            ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_DNAT, prio,
+                                      new_match_p, new_actions, NULL,
+                                      meter, &lb->nlb->header_);
             free(new_actions);
 
             est_actions = xasprintf("flags.force_snat_for_lb = 1; "
@@ -9130,9 +9174,9 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
                                     est_match_p, est_actions,
                                     &lb->nlb->header_);
         } else {
-            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, prio,
-                                    new_match_p, ds_cstr(action),
-                                    &lb->nlb->header_);
+            ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_DNAT, prio,
+                                      new_match_p, ds_cstr(action), NULL,
+                                      meter, &lb->nlb->header_);
             ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, prio,
                                     est_match_p, "next;",
                                     &lb->nlb->header_);
@@ -9194,14 +9238,19 @@ build_lswitch_flows_for_lb(struct ovn_northd_lb *lb, struct hmap *lflows,
         struct ovn_lb_vip *lb_vip = &lb->vips[i];
 
         /* pre-stateful lb */
-        if (!build_empty_lb_event_flow(lb_vip, lb->nlb, meter_groups,
-                                       match, action)) {
+        if (!build_empty_lb_event_flow(lb_vip, lb->nlb, match, action)) {
             continue;
         }
         for (size_t j = 0; j < lb->n_nb_ls; j++) {
-            ovn_lflow_add_with_hint(lflows, lb->nb_ls[j],
-                                    S_SWITCH_IN_PRE_LB, 130, ds_cstr(match),
-                                    ds_cstr(action), &lb->nlb->header_);
+            struct ovn_datapath *od = lb->nb_ls[j];
+            ovn_lflow_add_with_hint__(lflows, od,
+                                      S_SWITCH_IN_PRE_LB, 130, ds_cstr(match),
+                                      ds_cstr(action),
+                                      NULL,
+                                      copp_meter_get(COPP_EVENT_ELB,
+                                                     od->nbs->copp,
+                                                     meter_groups),
+                                      &lb->nlb->header_);
         }
         /* Ignore L4 port information in the key because fragmented packets
          * may not have L4 information.  The pre-stateful table will send
@@ -9215,7 +9264,7 @@ build_lswitch_flows_for_lb(struct ovn_northd_lb *lb, struct hmap *lflows,
      * a higher priority rule for load balancing below also commits the
      * connection, so it is okay if we do not hit the above match on
      * REGBIT_CONNTRACK_COMMIT. */
-    build_lb_rules(lflows, lb, match, action);
+    build_lb_rules(lflows, lb, match, action, meter_groups);
 }
 
 /* If there are any load balancing rules, we should send the packet to
@@ -9281,16 +9330,21 @@ build_lrouter_flows_for_lb(struct ovn_northd_lb *lb, struct hmap *lflows,
         struct ovn_lb_vip *lb_vip = &lb->vips[i];
 
         build_lrouter_nat_flows_for_lb(lb_vip, lb, &lb->vips_nb[i],
-                                       lflows, match, action);
+                                       lflows, match, action,
+                                       meter_groups);
 
-        if (!build_empty_lb_event_flow(lb_vip, lb->nlb, meter_groups,
-                                       match, action)) {
+        if (!build_empty_lb_event_flow(lb_vip, lb->nlb, match, action)) {
             continue;
         }
         for (size_t j = 0; j < lb->n_nb_lr; j++) {
-            ovn_lflow_add_with_hint(lflows, lb->nb_lr[j], S_ROUTER_IN_DNAT,
-                                    130, ds_cstr(match), ds_cstr(action),
-                                    &lb->nlb->header_);
+            struct ovn_datapath *od = lb->nb_lr[j];
+            ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_DNAT,
+                                      130, ds_cstr(match), ds_cstr(action),
+                                      NULL,
+                                      copp_meter_get(COPP_EVENT_ELB,
+                                                     od->nbr->copp,
+                                                     meter_groups),
+                                      &lb->nlb->header_);
         }
     }
 
@@ -9532,7 +9586,7 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
                       const char *sn_ip_address, const char *eth_addr,
                       struct ds *extra_match, bool drop, uint16_t priority,
                       const struct ovsdb_idl_row *hint,
-                      struct hmap *lflows)
+                      struct hmap *lflows, struct shash *meter_groups)
 {
     struct ds match = DS_EMPTY_INITIALIZER;
     struct ds actions = DS_EMPTY_INITIALIZER;
@@ -9554,6 +9608,8 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
 
     if (drop) {
         ds_put_format(&actions, "drop;");
+        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, priority,
+                                ds_cstr(&match), ds_cstr(&actions), hint);
     } else {
         ds_put_format(&actions,
                       "%s { "
@@ -9570,11 +9626,13 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
                       ip_address,
                       ip_address,
                       eth_addr);
+        ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, priority,
+                                  ds_cstr(&match), ds_cstr(&actions), NULL,
+                                  copp_meter_get(COPP_ND_NA, od->nbr->copp,
+                                                 meter_groups),
+                                  hint);
     }
 
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, priority,
-                            ds_cstr(&match), ds_cstr(&actions), hint);
-
     ds_destroy(&match);
     ds_destroy(&actions);
 }
@@ -9582,7 +9640,8 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
 static void
 build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od,
                               struct ovn_nat *nat_entry,
-                              struct hmap *lflows)
+                              struct hmap *lflows,
+                              struct shash *meter_groups)
 {
     struct lport_addresses *ext_addrs = &nat_entry->ext_addrs;
     const struct nbrec_nat *nat = nat_entry->nb;
@@ -9592,7 +9651,7 @@ build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od,
                               ext_addrs->ipv6_addrs[0].addr_s,
                               ext_addrs->ipv6_addrs[0].sn_addr_s,
                               REG_INPORT_ETH_ADDR, NULL, false, 90,
-                              &nat->header_, lflows);
+                              &nat->header_, lflows, meter_groups);
     } else {
         build_lrouter_arp_flow(od, NULL,
                                ext_addrs->ipv4_addrs[0].addr_s,
@@ -9604,7 +9663,8 @@ build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od,
 static void
 build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
                                    struct ovn_nat *nat_entry,
-                                   struct hmap *lflows)
+                                   struct hmap *lflows,
+                                   struct shash *meter_groups)
 {
     struct lport_addresses *ext_addrs = &nat_entry->ext_addrs;
     const struct nbrec_nat *nat = nat_entry->nb;
@@ -9647,12 +9707,12 @@ build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
                               ext_addrs->ipv6_addrs[0].addr_s,
                               ext_addrs->ipv6_addrs[0].sn_addr_s,
                               mac_s, &match, false, 92,
-                              &nat->header_, lflows);
+                              &nat->header_, lflows, meter_groups);
         build_lrouter_nd_flow(op->od, op, "nd_na",
                               ext_addrs->ipv6_addrs[0].addr_s,
                               ext_addrs->ipv6_addrs[0].sn_addr_s,
                               mac_s, NULL, true, 91,
-                              &nat->header_, lflows);
+                              &nat->header_, lflows, meter_groups);
     } else {
         build_lrouter_arp_flow(op->od, op,
                                ext_addrs->ipv4_addrs[0].addr_s,
@@ -9825,7 +9885,8 @@ build_lrouter_force_snat_flows_op(struct ovn_port *op,
 }
 
 static void
-build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op)
+build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op,
+                        struct shash *meter_groups)
 {
     if (!op->has_bfd) {
         return;
@@ -9844,9 +9905,11 @@ build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op)
         ds_clear(&match);
         ds_put_format(&match, "ip4.dst == %s && udp.dst == 3784",
                       ds_cstr(&ip_list));
-        ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-                                ds_cstr(&match), "handle_bfd_msg(); ",
-                                &op->nbrp->header_);
+        ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
+                                  ds_cstr(&match), "handle_bfd_msg(); ", NULL,
+                                  copp_meter_get(COPP_BFD, op->od->nbr->copp,
+                                                 meter_groups),
+                                  &op->nbrp->header_);
     }
     if (op->lrp_networks.n_ipv6_addrs) {
         ds_clear(&ip_list);
@@ -9861,9 +9924,11 @@ build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op)
         ds_clear(&match);
         ds_put_format(&match, "ip6.dst == %s && udp.dst == 3784",
                       ds_cstr(&ip_list));
-        ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-                                ds_cstr(&match), "handle_bfd_msg(); ",
-                                &op->nbrp->header_);
+        ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
+                                  ds_cstr(&match), "handle_bfd_msg(); ", NULL,
+                                  copp_meter_get(COPP_BFD, op->od->nbr->copp,
+                                                 meter_groups),
+                                  &op->nbrp->header_);
     }
 
     ds_destroy(&ip_list);
@@ -9943,7 +10008,8 @@ build_adm_ctrl_flows_for_lrouter_port(
 static void
 build_neigh_learning_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows,
-        struct ds *match, struct ds *actions)
+        struct ds *match, struct ds *actions,
+        struct shash *meter_groups)
 {
     if (od->nbr) {
 
@@ -10023,14 +10089,20 @@ build_neigh_learning_flows_for_lrouter(
         ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
                       ds_cstr(match), "next;");
 
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-                      "arp", "put_arp(inport, arp.spa, arp.sha); next;");
+        ovn_lflow_metered(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
+                          "arp", "put_arp(inport, arp.spa, arp.sha); next;",
+                          copp_meter_get(COPP_ARP, od->nbr->copp,
+                                         meter_groups));
 
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-                      "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
+        ovn_lflow_metered(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
+                          "nd_na", "put_nd(inport, nd.target, nd.tll); next;",
+                          copp_meter_get(COPP_ND_NA, od->nbr->copp,
+                                         meter_groups));
 
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-                      "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
+        ovn_lflow_metered(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
+                          "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;",
+                          copp_meter_get(COPP_ND_NS, od->nbr->copp,
+                                         meter_groups));
     }
 
 }
@@ -10105,7 +10177,8 @@ build_neigh_learning_flows_for_lrouter_port(
 static void
 build_ND_RA_flows_for_lrouter_port(
         struct ovn_port *op, struct hmap *lflows,
-        struct ds *match, struct ds *actions)
+        struct ds *match, struct ds *actions,
+        struct shash *meter_groups)
 {
     if (!op->nbrp || op->nbrp->peer || !op->peer) {
         return;
@@ -10198,9 +10271,12 @@ build_ND_RA_flows_for_lrouter_port(
 
     if (add_rs_response_flow) {
         ds_put_cstr(actions, "); next;");
-        ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
-                                50, ds_cstr(match), ds_cstr(actions),
-                                &op->nbrp->header_);
+        ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
+                                  50, ds_cstr(match), ds_cstr(actions), NULL,
+                                  copp_meter_get(COPP_ND_RA_OPTS,
+                                                 op->od->nbr->copp,
+                                                 meter_groups),
+                                  &op->nbrp->header_);
         ds_clear(actions);
         ds_clear(match);
         ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && "
@@ -10853,7 +10929,8 @@ static void
 build_check_pkt_len_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows,
         struct hmap *ports,
-        struct ds *match, struct ds *actions)
+        struct ds *match, struct ds *actions,
+        struct shash *meter_groups)
 {
     if (od->nbr) {
 
@@ -10915,10 +10992,15 @@ build_check_pkt_len_flows_for_lrouter(
                         rp->lrp_networks.ipv4_addrs[0].addr_s,
                         gw_mtu,
                         ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-                    ovn_lflow_add_with_hint(lflows, od,
-                                            S_ROUTER_IN_LARGER_PKTS, 50,
-                                            ds_cstr(match), ds_cstr(actions),
-                                            &rp->nbrp->header_);
+                    ovn_lflow_add_with_hint__(lflows, od,
+                                              S_ROUTER_IN_LARGER_PKTS, 50,
+                                              ds_cstr(match), ds_cstr(actions),
+                                              NULL,
+                                              copp_meter_get(
+                                                    COPP_ICMP4_ERR,
+                                                    rp->od->nbr->copp,
+                                                    meter_groups),
+                                              &rp->nbrp->header_);
                 }
 
                 if (rp->lrp_networks.ipv6_addrs) {
@@ -10944,10 +11026,15 @@ build_check_pkt_len_flows_for_lrouter(
                         rp->lrp_networks.ipv6_addrs[0].addr_s,
                         gw_mtu,
                         ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-                    ovn_lflow_add_with_hint(lflows, od,
-                                            S_ROUTER_IN_LARGER_PKTS, 50,
-                                            ds_cstr(match), ds_cstr(actions),
-                                            &rp->nbrp->header_);
+                    ovn_lflow_add_with_hint__(lflows, od,
+                                              S_ROUTER_IN_LARGER_PKTS, 50,
+                                              ds_cstr(match), ds_cstr(actions),
+                                              NULL,
+                                              copp_meter_get(
+                                                    COPP_ICMP6_ERR,
+                                                    rp->od->nbr->copp,
+                                                    meter_groups),
+                                              &rp->nbrp->header_);
                 }
             }
         }
@@ -11002,7 +11089,8 @@ build_gateway_redirect_flows_for_lrouter(
 static void
 build_arp_request_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows,
-        struct ds *match, struct ds *actions)
+        struct ds *match, struct ds *actions,
+        struct shash *meter_groups)
 {
     if (od->nbr) {
         for (int i = 0; i < od->nbr->n_static_routes; i++) {
@@ -11039,26 +11127,33 @@ build_arp_request_flows_for_lrouter(
                           "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
                           route->nexthop);
 
-            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
-                                    ds_cstr(match), ds_cstr(actions),
-                                    &route->header_);
-        }
-
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-                      "eth.dst == 00:00:00:00:00:00 && ip4",
-                      "arp { "
-                      "eth.dst = ff:ff:ff:ff:ff:ff; "
-                      "arp.spa = " REG_SRC_IPV4 "; "
-                      "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
-                      "arp.op = 1; " /* ARP request */
-                      "output; "
-                      "};");
-        ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-                      "eth.dst == 00:00:00:00:00:00 && ip6",
-                      "nd_ns { "
-                      "nd.target = " REG_NEXT_HOP_IPV6 "; "
-                      "output; "
-                      "};");
+            ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
+                                      ds_cstr(match), ds_cstr(actions), NULL,
+                                      copp_meter_get(COPP_ND_NS_RESOLVE,
+                                                     od->nbr->copp,
+                                                     meter_groups),
+                                      &route->header_);
+        }
+
+        ovn_lflow_metered(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
+                          "eth.dst == 00:00:00:00:00:00 && ip4",
+                          "arp { "
+                          "eth.dst = ff:ff:ff:ff:ff:ff; "
+                          "arp.spa = " REG_SRC_IPV4 "; "
+                          "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
+                          "arp.op = 1; " /* ARP request */
+                          "output; "
+                          "};",
+                          copp_meter_get(COPP_ARP_RESOLVE, od->nbr->copp,
+                                         meter_groups));
+        ovn_lflow_metered(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
+                          "eth.dst == 00:00:00:00:00:00 && ip6",
+                          "nd_ns { "
+                          "nd.target = " REG_NEXT_HOP_IPV6 "; "
+                          "output; "
+                          "};",
+                          copp_meter_get(COPP_ND_NS_RESOLVE, od->nbr->copp,
+                                         meter_groups));
         ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
     }
 }
@@ -11189,7 +11284,8 @@ build_dhcpv6_reply_flows_for_lrouter_port(
 static void
 build_ipv6_input_flows_for_lrouter_port(
         struct ovn_port *op, struct hmap *lflows,
-        struct ds *match, struct ds *actions)
+        struct ds *match, struct ds *actions,
+        struct shash *meter_groups)
 {
     if (op->nbrp && (!op->derived)) {
         /* No ingress packets are accepted on a chassisredirect
@@ -11232,7 +11328,7 @@ build_ipv6_input_flows_for_lrouter_port(
                                   op->lrp_networks.ipv6_addrs[i].addr_s,
                                   op->lrp_networks.ipv6_addrs[i].sn_addr_s,
                                   REG_INPORT_ETH_ADDR, match, false, 90,
-                                  &op->nbrp->header_, lflows);
+                                  &op->nbrp->header_, lflows, meter_groups);
         }
 
         /* UDP/TCP/SCTP port unreachable */
@@ -11246,9 +11342,13 @@ build_ipv6_input_flows_for_lrouter_port(
                                      "eth.dst <-> eth.src; "
                                      "ip6.dst <-> ip6.src; "
                                      "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_TCP_RESET,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11258,9 +11358,13 @@ build_ipv6_input_flows_for_lrouter_port(
                          "eth.dst <-> eth.src; "
                          "ip6.dst <-> ip6.src; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_TCP_RESET,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11273,9 +11377,13 @@ build_ipv6_input_flows_for_lrouter_port(
                          "icmp6.type = 1; "
                          "icmp6.code = 4; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_ICMP6_ERR,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11288,9 +11396,13 @@ build_ipv6_input_flows_for_lrouter_port(
                          "icmp6.type = 1; "
                          "icmp6.code = 3; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        70, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          70, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_ICMP6_ERR,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
             }
         }
 
@@ -11321,9 +11433,12 @@ build_ipv6_input_flows_for_lrouter_port(
                           "icmp6.code = 0; /* TTL exceeded in transit */ "
                           "next; };",
                           op->lrp_networks.ipv6_addrs[i].addr_s);
-            ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-                                    ds_cstr(match), ds_cstr(actions),
-                                    &op->nbrp->header_);
+            ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
+                                      ds_cstr(match), ds_cstr(actions), NULL,
+                                      copp_meter_get(COPP_ICMP6_ERR,
+                                                     op->od->nbr->copp,
+                                                     meter_groups),
+                                      &op->nbrp->header_);
         }
     }
 
@@ -11331,7 +11446,8 @@ build_ipv6_input_flows_for_lrouter_port(
 
 static void
 build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-                                  struct hmap *lflows)
+                                  struct hmap *lflows,
+                                  struct shash *meter_groups)
 {
     if (od->nbr) {
 
@@ -11357,7 +11473,7 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
             if (!strcmp(nat_entry->nb->type, "snat")) {
                 continue;
             }
-            build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
+            build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows, meter_groups);
         }
 
         /* Now handle SNAT entries too, one per unique SNAT IP. */
@@ -11372,7 +11488,7 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
             struct ovn_nat *nat_entry =
                 CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
                              struct ovn_nat, ext_addr_list_node);
-            build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
+            build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows, meter_groups);
         }
     }
 }
@@ -11381,7 +11497,8 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
 static void
 build_lrouter_ipv4_ip_input(struct ovn_port *op,
                             struct hmap *lflows,
-                            struct ds *match, struct ds *actions)
+                            struct ds *match, struct ds *actions,
+                            struct shash *meter_groups)
 {
     /* No ingress packets are accepted on a chassisredirect
      * port, so no need to program flows for that port. */
@@ -11419,7 +11536,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
         }
 
         /* BFD msg handling */
-        build_lrouter_bfd_flows(lflows, op);
+        build_lrouter_bfd_flows(lflows, op, meter_groups);
 
         /* ICMP time exceeded */
         for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
@@ -11439,9 +11556,12 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                           "ip.ttl = 255; "
                           "next; };",
                           op->lrp_networks.ipv4_addrs[i].addr_s);
-            ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-                                    ds_cstr(match), ds_cstr(actions),
-                                    &op->nbrp->header_);
+            ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
+                                      ds_cstr(match), ds_cstr(actions), NULL,
+                                      copp_meter_get(COPP_ICMP4_ERR,
+                                                     op->od->nbr->copp,
+                                                     meter_groups),
+                                      &op->nbrp->header_);
         }
 
         /* ARP reply.  These flows reply to ARP requests for the router's own
@@ -11518,7 +11638,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
 
             build_lrouter_nd_flow(op->od, op, "nd_na",
                                   ip_address, NULL, REG_INPORT_ETH_ADDR,
-                                  match, false, 90, NULL, lflows);
+                                  match, false, 90, NULL,
+                                  lflows, meter_groups);
         }
 
         if (!op->od->is_gw_router && !op->od->l3dgw_port) {
@@ -11535,9 +11656,13 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                                      "icmp4.type = 3; "
                                      "icmp4.code = 3; "
                                      "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_ICMP4_ERR,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11547,9 +11672,13 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                          "eth.dst <-> eth.src; "
                          "ip4.dst <-> ip4.src; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_TCP_RESET,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11559,9 +11688,13 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                          "eth.dst <-> eth.src; "
                          "ip4.dst <-> ip4.src; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        80, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          80, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_TCP_RESET,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
 
                 ds_clear(match);
                 ds_put_format(match,
@@ -11574,9 +11707,13 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                          "icmp4.type = 3; "
                          "icmp4.code = 2; "
                          "next; };";
-                ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-                                        70, ds_cstr(match), action,
-                                        &op->nbrp->header_);
+                ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+                                          70, ds_cstr(match), action, NULL,
+                                          copp_meter_get(
+                                              COPP_ICMP4_ERR,
+                                              op->od->nbr->copp,
+                                              meter_groups),
+                                          &op->nbrp->header_);
             }
         }
 
@@ -11620,7 +11757,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
             if (!strcmp(nat_entry->nb->type, "snat")) {
                 continue;
             }
-            build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
+            build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows,
+                                               meter_groups);
         }
 
         /* Now handle SNAT entries too, one per unique SNAT IP. */
@@ -11635,7 +11773,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
             struct ovn_nat *nat_entry =
                 CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
                              struct ovn_nat, ext_addr_list_node);
-            build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
+            build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows,
+                                               meter_groups);
         }
     }
 }
@@ -12318,15 +12457,16 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
     build_lswitch_input_port_sec_od(od, lsi->lflows);
     build_lswitch_learn_fdb_od(od, lsi->lflows);
     build_lswitch_arp_nd_responder_default(od, lsi->lflows);
-    build_lswitch_dns_lookup_and_response(od, lsi->lflows);
+    build_lswitch_dns_lookup_and_response(od, lsi->lflows, lsi->meter_groups);
     build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows);
-    build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions);
+    build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions,
+                                            lsi->meter_groups);
     build_lswitch_output_port_sec_od(od, lsi->lflows);
 
     /* Build Logical Router Flows. */
     build_adm_ctrl_flows_for_lrouter(od, lsi->lflows);
     build_neigh_learning_flows_for_lrouter(od, lsi->lflows, &lsi->match,
-                                           &lsi->actions);
+                                           &lsi->actions, lsi->meter_groups);
     build_ND_RA_flows_for_lrouter(od, lsi->lflows);
     build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports,
                                          lsi->bfd_connections);
@@ -12335,13 +12475,14 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
     build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports);
     build_arp_resolve_flows_for_lrouter(od, lsi->lflows);
     build_check_pkt_len_flows_for_lrouter(od, lsi->lflows, lsi->ports,
-                                          &lsi->match, &lsi->actions);
+                                          &lsi->match, &lsi->actions,
+                                          lsi->meter_groups);
     build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,
                                              &lsi->actions);
     build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
-                                        &lsi->actions);
+                                        &lsi->actions, lsi->meter_groups);
     build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
-    build_lrouter_arp_nd_for_datapath(od, lsi->lflows);
+    build_lrouter_arp_nd_for_datapath(od, lsi->lflows, lsi->meter_groups);
     build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports, &lsi->match,
                                     &lsi->actions);
 }
@@ -12361,9 +12502,11 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
                                               &lsi->match);
     build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows,
                                              lsi->ports,
+                                             lsi->meter_groups,
                                              &lsi->actions,
                                              &lsi->match);
-    build_lswitch_dhcp_options_and_response(op, lsi->lflows);
+    build_lswitch_dhcp_options_and_response(op, lsi->lflows,
+                                            lsi->meter_groups);
     build_lswitch_external_port(op, lsi->lflows);
     build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups,
                                     &lsi->actions, &lsi->match);
@@ -12377,16 +12520,17 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
                                                 &lsi->actions);
     build_ip_routing_flows_for_lrouter_port(op, lsi->ports, lsi->lflows);
     build_ND_RA_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
-                                       &lsi->actions);
+                                       &lsi->actions, lsi->meter_groups);
     build_arp_resolve_flows_for_lrouter_port(op, lsi->lflows, lsi->ports,
                                              &lsi->match, &lsi->actions);
     build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
                                                  &lsi->actions);
     build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
     build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
-                                            &lsi->match, &lsi->actions);
+                                            &lsi->match, &lsi->actions,
+                                            lsi->meter_groups);
     build_lrouter_ipv4_ip_input(op, lsi->lflows,
-                                &lsi->match, &lsi->actions);
+                                &lsi->match, &lsi->actions, lsi->meter_groups);
     build_lrouter_force_snat_flows_op(op, lsi->lflows, &lsi->match,
                                       &lsi->actions);
 }
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 25a1e1fdb..7bfaae992 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -14,6 +14,7 @@
 
 import OVN_Northbound as nb
 import OVN_Southbound as sb
+import copp
 import ovsdb
 import allocate
 import ovn
@@ -1626,6 +1627,23 @@ function mFF_N_LOG_REGS()          : bit<32> = 10
  *      enables or disables this feature.
  */
 
+// A Flow including a 'controller_meter' column for metering.
+//
+// Most flows have an empty 'controller_meter' so we provide a Flow relation
+// that provides it as empty without specifying it explicitly.
+relation MeteredFlow(
+    logical_datapath: uuid,
+    stage:            Stage,
+    priority:         integer,
+    __match:          string,
+    actions:          string,
+    tags:             Map<string,string>,
+    controller_meter: Option<string>,
+    external_ids:     Map<string,string>
+)
+MeteredFlow(logical_datapath, stage, priority, __match, actions, tags, None, external_ids) :-
+    TaggedFlow(logical_datapath, stage, priority, __match, actions, tags, external_ids).
+
 relation Flow(
     logical_datapath: uuid,
     stage:            Stage,
@@ -1680,6 +1698,7 @@ relation AggregatedFlow (
     __match:           string,
     actions:           string,
     tags:              Map<string,string>,
+    controller_meter:  Option<string>,
     external_ids:      Map<string,string>
 )
 AggregatedFlow(.logical_datapaths = g.to_set(),
@@ -1688,19 +1707,21 @@ AggregatedFlow(.logical_datapaths = g.to_set(),
                .__match = __match,
                .actions = actions,
                .tags = tags,
+               .controller_meter = controller_meter,
                .external_ids = external_ids) :-
     UseLogicalDatapathGroups[true],
-    TaggedFlow(logical_datapath, stage, priority, __match, actions, tags, external_ids),
-    var g = logical_datapath.group_by((stage, priority, __match, actions, tags, external_ids)).
+    MeteredFlow(logical_datapath, stage, priority, __match, actions, tags, controller_meter, external_ids),
+    var g = logical_datapath.group_by((stage, priority, __match, actions, tags, controller_meter, external_ids)).
 AggregatedFlow(.logical_datapaths = set_singleton(logical_datapath),
                .stage = stage,
                .priority = priority,
                .__match = __match,
                .actions = actions,
                .tags = tags,
+               .controller_meter = controller_meter,
                .external_ids = external_ids) :-
     UseLogicalDatapathGroups[false],
-    TaggedFlow(logical_datapath, stage, priority, __match, actions, tags, external_ids).
+    MeteredFlow(logical_datapath, stage, priority, __match, actions, tags, controller_meter, external_ids).
 
 for (f in AggregatedFlow()) {
     var pipeline = if (f.stage.pipeline == Ingress) "ingress" else "egress" in
@@ -1708,13 +1729,13 @@ for (f in AggregatedFlow()) {
     if (f.logical_datapaths.size() == 1) {
         Some{var dp} = f.logical_datapaths.nth(0) in
         sb::Out_Logical_Flow(
-            ._uuid = hash128((dp, f.stage, f.priority, f.__match, f.actions, f.external_ids)),
+            ._uuid = hash128((dp, f.stage, f.priority, f.__match, f.actions, f.controller_meter, f.external_ids)),
             .logical_datapath = Some{dp},
             .logical_dp_group = None,
             .pipeline         = pipeline,
             .table_id         = f.stage.table_id,
             .priority         = f.priority,
-            .controller_meter = None,
+            .controller_meter = f.controller_meter,
             .__match          = f.__match,
             .actions          = f.actions,
             .tags             = f.tags,
@@ -1722,13 +1743,13 @@ for (f in AggregatedFlow()) {
     } else {
         var group_uuid = hash128(f.logical_datapaths) in {
             sb::Out_Logical_Flow(
-                ._uuid = hash128((group_uuid, f.stage, f.priority, f.__match, f.actions, f.external_ids)),
+                ._uuid = hash128((group_uuid, f.stage, f.priority, f.__match, f.actions, f.controller_meter, f.external_ids)),
                 .logical_datapath = None,
                 .logical_dp_group = Some{group_uuid},
                 .pipeline         = pipeline,
                 .table_id         = f.stage.table_id,
                 .priority         = f.priority,
-                .controller_meter = None,
+                .controller_meter = f.controller_meter,
                 .__match          = f.__match,
                 .actions          = f.actions,
                 .tags             = f.tags,
@@ -2076,18 +2097,8 @@ if (lsp.__type == "router" or lsp.__type == "localnet") {
         lsp.name)
 }
 
-relation HasEventElbMeter(has_meter: bool)
-
-HasEventElbMeter(true) :-
-    &nb::Meter(.name = "event-elb").
-
-HasEventElbMeter(false) :-
-    Unit(),
-    not &nb::Meter(.name = "event-elb").
-
 /* Empty LoadBalancer Controller event */
-function build_empty_lb_event_flow(key: string, lb: Intern<nb::Load_Balancer>,
-                                   meter: bool): Option<(string, string)> {
+function build_empty_lb_event_flow(key: string, lb: Intern<nb::Load_Balancer>): Option<(string, string)> {
     (var ip, var port) = match (ip_address_and_port_from_lb_key(key)) {
         Some{(ip, port)} -> (ip, port),
         _ -> return None
@@ -2097,10 +2108,6 @@ function build_empty_lb_event_flow(key: string, lb: Intern<nb::Load_Balancer>,
         Some{"tcp"} -> "tcp",
         _ -> "udp"
     };
-    var meter = match (meter) {
-        true -> "event-elb",
-        _ -> ""
-    };
     var vip = match (port) {
         0 -> "${ip}",
         _ -> "${ip.to_bracketed_string()}:${port}"
@@ -2114,7 +2121,6 @@ function build_empty_lb_event_flow(key: string, lb: Intern<nb::Load_Balancer>,
 
     var action = "trigger_event("
                  "event = \"empty_lb_backends\", "
-                 "meter = \"${meter}\", "
                  "vip = \"${vip}\", "
                  "protocol = \"${protocol}\", "
                  "load_balancer = \"${uuid2str(lb._uuid)}\");";
@@ -2139,20 +2145,20 @@ LoadBalancerEmptyEvents(lb) :-
     var local_events = local_options.get_bool_def("event", false),
     global_events or local_events.
 
-Flow(.logical_datapath = sw._uuid,
-     .stage            = s_SWITCH_IN_PRE_LB(),
-     .priority         = 130,
-     .__match          = __match,
-     .actions          = __action,
-     .external_ids     = stage_hint(lb._uuid)) :-
+MeteredFlow(.logical_datapath = sw._uuid,
+            .stage            = s_SWITCH_IN_PRE_LB(),
+            .priority         = 130,
+            .__match          = __match,
+            .actions          = __action,
+            .tags             = map_empty(),
+            .controller_meter = sw.copp.get(cOPP_EVENT_ELB()),
+            .external_ids     = stage_hint(lb._uuid)) :-
     SwitchLBVIP(.sw_uuid = sw_uuid, .lb = lb, .vip = vip, .backends = backends),
     LoadBalancerEmptyEvents(lb),
     not lb.options.get_bool_def("reject", false),
     sw in &Switch(._uuid = sw_uuid),
     backends == "",
-    HasEventElbMeter(has_elb_meter),
-    Some {(var __match, var __action)} = build_empty_lb_event_flow(
-        vip, lb, has_elb_meter).
+    Some {(var __match, var __action)} = build_empty_lb_event_flow(vip, lb).
 
 /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
  * packet to conntrack for defragmentation.
@@ -2343,6 +2349,7 @@ relation Reject(
     stage: Stage,
     acl: Intern<nb::ACL>,
     fair_meter: bool,
+    controller_meter: Option<string>,
     extra_match: string,
     extra_actions: string)
 
@@ -2354,7 +2361,8 @@ function next_to_stage(stage: Stage): string {
     };
     "next(pipeline=${pipeline},table=${stage.table_id})"
 }
-for (Reject(lsuuid, pipeline, stage, acl, fair_meter, extra_match_, extra_actions_)) {
+for (Reject(lsuuid, pipeline, stage, acl, fair_meter, controller_meter,
+            extra_match_, extra_actions_)) {
     var extra_match = match (extra_match_) {
         "" -> "",
         s -> "(${s}) && "
@@ -2373,12 +2381,14 @@ for (Reject(lsuuid, pipeline, stage, acl, fair_meter, extra_match_, extra_action
                   "reject { "
                   "/* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ "
                   "outport <-> inport; ${next_to_stage(next_stage)}; };" in
-    Flow(.logical_datapath = lsuuid,
-         .stage            = stage,
-         .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
-         .__match          = __match,
-         .actions          = actions,
-         .external_ids     = stage_hint(acl._uuid))
+    MeteredFlow(.logical_datapath = lsuuid,
+                .stage            = stage,
+                .priority         = acl.priority + oVN_ACL_PRI_OFFSET(),
+                .__match          = __match,
+                .actions          = actions,
+                .tags             = map_empty(),
+                .controller_meter = controller_meter,
+                .external_ids     = stage_hint(acl._uuid))
 }
 
 /* build_acls */
@@ -2719,12 +2729,13 @@ for (&SwitchACL(.sw = sw, .acl = acl, .has_fair_meter = fair_meter)) {
          * use for this datapath.  In that case, the actions differ
          * depending on whether the connection was previously committed
          * to the connection tracker with ct_commit. */
+        var controller_meter = sw.copp.get(cOPP_REJECT()) in
         if (has_stateful) {
             /* If the packet is not tracked or not part of an established
              * connection, then we can simply reject/drop it. */
             var __match = "${rEGBIT_ACL_HINT_DROP()} == 1" in
             if (acl.action == "reject") {
-                Reject(sw._uuid, pipeline, stage, acl, fair_meter, __match, "")
+                Reject(sw._uuid, pipeline, stage, acl, fair_meter, controller_meter, __match, "")
             } else {
                 Flow(.logical_datapath = sw._uuid,
                      .stage            = stage,
@@ -2747,7 +2758,7 @@ for (&SwitchACL(.sw = sw, .acl = acl, .has_fair_meter = fair_meter)) {
             var __match = "${rEGBIT_ACL_HINT_BLOCK()} == 1" in
             var actions = "ct_commit { ct_label.blocked = 1; }; " in
             if (acl.action == "reject") {
-                Reject(sw._uuid, pipeline, stage, acl, fair_meter, __match, actions)
+                Reject(sw._uuid, pipeline, stage, acl, fair_meter, controller_meter, __match, actions)
             } else {
                 Flow(.logical_datapath = sw._uuid,
                      .stage            = stage,
@@ -2761,7 +2772,7 @@ for (&SwitchACL(.sw = sw, .acl = acl, .has_fair_meter = fair_meter)) {
              * so a "reject/drop" ACL is simply the "reject/drop"
              * logical flow action in all cases. */
             if (acl.action == "reject") {
-                Reject(sw._uuid, pipeline, stage, acl, fair_meter, "", "")
+                Reject(sw._uuid, pipeline, stage, acl, fair_meter, controller_meter, "", "")
             } else {
                 Flow(.logical_datapath = sw._uuid,
                      .stage            = stage,
@@ -3007,7 +3018,7 @@ function ct_lb(backends: string,
 }
 function build_lb_vip_actions(lbvip: Intern<LBVIPWithStatus>,
                               stage: Stage,
-                              actions0: string): string {
+                              actions0: string): (string, bool) {
     var up_backends = set_empty();
     for (pair in lbvip.backends) {
         (var backend, var up) = pair;
@@ -3022,27 +3033,29 @@ function build_lb_vip_actions(lbvip: Intern<LBVIPWithStatus>,
 
     if (up_backends.is_empty()) {
         if (lbvip.lb.options.get_bool_def("reject", false)) {
-            return "reg0 = 0; reject { outport <-> inport; ${next_to_stage(stage)};};"
+            return ("reg0 = 0; reject { outport <-> inport; ${next_to_stage(stage)};};", true)
         } else if (lbvip.health_check.is_some()) {
-            return "drop;"
+            return ("drop;", false)
         } // else fall through
     };
 
     var actions = ct_lb(up_backends.to_vec().join(","), lbvip.lb.selection_fields,
                         lbvip.lb.protocol);
-    actions0 ++ actions
+    (actions0 ++ actions, false)
 }
-Flow(.logical_datapath = sw._uuid,
-     .stage            = s_SWITCH_IN_STATEFUL(),
-     .priority         = priority,
-     .__match          = __match,
-     .actions          = actions,
-     .external_ids     = stage_hint(lb._uuid)) :-
+MeteredFlow(.logical_datapath = sw._uuid,
+            .stage            = s_SWITCH_IN_STATEFUL(),
+            .priority         = priority,
+            .__match          = __match,
+            .actions          = actions,
+            .tags             = map_empty(),
+            .controller_meter = meter,
+            .external_ids     = stage_hint(lb._uuid)) :-
     sw in &Switch(),
     LBVIPWithStatus[lbvip@&LBVIPWithStatus{.lb = lb}],
     sw.load_balancer.contains(lb._uuid),
     var priority = if (lbvip.vip_port != 0) { 120 } else { 110 },
-    var actions = {
+    (var actions, var reject) = {
         /* Store the original destination IP to be used when generating
          * hairpin flows.
          */
@@ -3062,6 +3075,11 @@ Flow(.logical_datapath = sw._uuid,
 
         build_lb_vip_actions(lbvip, s_SWITCH_OUT_QOS_MARK(), actions0 ++ actions1)
     },
+    var meter = if (reject) {
+        sw.copp.get(cOPP_REJECT())
+    } else {
+        None
+    },
     var __match = "ct.new && " ++ get_match_for_lb_key(lbvip.vip_addr, lbvip.vip_port, lb.protocol, false, false).
 
 /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
@@ -3515,12 +3533,14 @@ for (SwitchPortIPv6Address(.port = &SwitchPort{.lsp = lsp, .json_name = json_nam
                   "output; "
                   "};" in
     {
-        Flow(.logical_datapath = sw._uuid,
-             .stage            = s_SWITCH_IN_ARP_ND_RSP(),
-             .priority         = 50,
-             .__match          = __match,
-             .actions          = actions,
-             .external_ids     = stage_hint(lsp._uuid));
+        MeteredFlow(.logical_datapath = sw._uuid,
+                    .stage            = s_SWITCH_IN_ARP_ND_RSP(),
+                    .priority         = 50,
+                    .__match          = __match,
+                    .actions          = actions,
+                    .tags             = map_empty(),
+                    .controller_meter = sw.copp.get(cOPP_ND_NA()),
+                    .external_ids     = stage_hint(lsp._uuid));
 
         /* Do not reply to a solicitation from the port that owns the
          * address (otherwise DAD detection will fail). */
@@ -3774,12 +3794,14 @@ for (lsp in &SwitchPort
                                 "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
                                 "udp.src == 68 && udp.dst == 67" ++ sfx
                             in
-                            Flow(.logical_datapath = lsuuid,
-                                 .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
-                                 .priority         = 100,
-                                 .__match          = __match,
-                                 .actions          = options_action,
-                                 .external_ids     = stage_hint(lsp.lsp._uuid));
+                            MeteredFlow(.logical_datapath = lsuuid,
+                                        .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
+                                        .priority         = 100,
+                                        .__match          = __match,
+                                        .actions          = options_action,
+                                        .tags             = map_empty(),
+                                        .controller_meter = lsp.sw.copp.get(cOPP_DHCPV4_OPTS()),
+                                        .external_ids     = stage_hint(lsp.lsp._uuid));
 
                             /* Allow ip4.src = OFFER_IP and
                              * ip4.dst = {SERVER_IP, 255.255.255.255} for the below
@@ -3791,12 +3813,14 @@ for (lsp in &SwitchPort
                              */
                             var __match = pfx ++ "eth.src == ${ea} && "
                                 "${ipv4_addr_match} && udp.src == 68 && udp.dst == 67" ++ sfx in
-                            Flow(.logical_datapath = lsuuid,
-                                 .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
-                                 .priority         = 100,
-                                 .__match          = __match,
-                                 .actions          = options_action,
-                                 .external_ids     = stage_hint(lsp.lsp._uuid));
+                            MeteredFlow(.logical_datapath = lsuuid,
+                                        .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
+                                        .priority         = 100,
+                                        .__match          = __match,
+                                        .actions          = options_action,
+                                        .tags             = map_empty(),
+                                        .controller_meter = lsp.sw.copp.get(cOPP_DHCPV4_OPTS()),
+                                        .external_ids     = stage_hint(lsp.lsp._uuid));
 
                             /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
                              * put_dhcp_opts action  is successful. */
@@ -3830,12 +3854,14 @@ for (lsp in &SwitchPort
                                 " && ip6.dst == ff02::1:2 && udp.src == 546 &&"
                                 " udp.dst == 547" ++ sfx in
                             {
-                                Flow(.logical_datapath = lsuuid,
-                                     .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
-                                     .priority         = 100,
-                                     .__match          = __match,
-                                     .actions          = options_action,
-                                     .external_ids     = stage_hint(lsp.lsp._uuid));
+                                MeteredFlow(.logical_datapath = lsuuid,
+                                            .stage            = s_SWITCH_IN_DHCP_OPTIONS(),
+                                            .priority         = 100,
+                                            .__match          = __match,
+                                            .actions          = options_action,
+                                            .tags             = map_empty(),
+                                            .controller_meter = lsp.sw.copp.get(cOPP_DHCPV6_OPTS()),
+                                            .external_ids     = stage_hint(lsp.lsp._uuid));
 
                                 /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means the
                                  * put_dhcpv6_opts action is successful */
@@ -3946,6 +3972,7 @@ Flow(.logical_datapath = sw._uuid,
 
 for (sw in &Switch(._uuid = ls_uuid, .mcast_cfg = mcast_cfg)
         if (mcast_cfg.enabled)) {
+    var controller_meter = sw.copp.get(cOPP_IGMP()) in
     for (SwitchMcastFloodRelayPorts(sw, relay_ports)) {
         for (SwitchMcastFloodReportPorts(sw, flood_report_ports)) {
             for (SwitchMcastFloodPorts(sw, flood_ports)) {
@@ -3964,20 +3991,24 @@ for (sw in &Switch(._uuid = ls_uuid, .mcast_cfg = mcast_cfg)
                     }
                 } in {
                     /* Punt IGMP traffic to controller. */
-                    Flow(.logical_datapath = ls_uuid,
-                         .stage            = s_SWITCH_IN_L2_LKUP(),
-                         .priority         = 100,
-                         .__match          = "ip4 && ip.proto == 2",
-                         .actions          = "${igmp_act}",
-                         .external_ids     = map_empty());
+                    MeteredFlow(.logical_datapath = ls_uuid,
+                                .stage            = s_SWITCH_IN_L2_LKUP(),
+                                .priority         = 100,
+                                .__match          = "ip4 && ip.proto == 2",
+                                .actions          = "${igmp_act}",
+                                .tags             = map_empty(),
+                                .controller_meter = controller_meter,
+                                .external_ids     = map_empty());
 
                     /* Punt MLD traffic to controller. */
-                    Flow(.logical_datapath = ls_uuid,
-                         .stage            = s_SWITCH_IN_L2_LKUP(),
-                         .priority         = 100,
-                         .__match          = "mldv1 || mldv2",
-                         .actions          = "${igmp_act}",
-                         .external_ids     = map_empty());
+                    MeteredFlow(.logical_datapath = ls_uuid,
+                                .stage            = s_SWITCH_IN_L2_LKUP(),
+                                .priority         = 100,
+                                .__match          = "mldv1 || mldv2",
+                                .actions          = "${igmp_act}",
+                                .tags             = map_empty(),
+                                .controller_meter = controller_meter,
+                                .external_ids     = map_empty());
 
                     /* Flood all IP multicast traffic destined to 224.0.0.X to
                      * all ports - RFC 4541, section 2.1.2, item 2.
@@ -4759,7 +4790,9 @@ for (&RouterPort(.lrp = lrp,
  * */
 
 /* Flows for LOOKUP_NEIGHBOR. */
-for (&Router(._uuid = lr_uuid, .learn_from_arp_request = learn_from_arp_request))
+for (&Router(._uuid = lr_uuid,
+             .learn_from_arp_request = learn_from_arp_request,
+             .copp = copp))
 var rLNR = rEGBIT_LOOKUP_NEIGHBOR_RESULT() in
 var rLNIR = rEGBIT_LOOKUP_NEIGHBOR_IP_RESULT() in
 {
@@ -4811,30 +4844,30 @@ var rLNIR = rEGBIT_LOOKUP_NEIGHBOR_IP_RESULT() in
              { if (learn_from_arp_request) "" else " || ${rLNIR} == 0" },
          .actions          = "next;",
          .external_ids     = map_empty());
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
-         .priority         = 90,
-         .__match          = "arp",
-         .actions          = "put_arp(inport, arp.spa, arp.sha); next;",
-         .external_ids     = map_empty());
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
-         .priority         = 90,
-         .__match          = "arp",
-         .actions          = "put_arp(inport, arp.spa, arp.sha); next;",
-         .external_ids     = map_empty());
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
-         .priority         = 90,
-         .__match          = "nd_na",
-         .actions          = "put_nd(inport, nd.target, nd.tll); next;",
-         .external_ids     = map_empty());
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
-         .priority         = 90,
-         .__match          = "nd_ns",
-         .actions          = "put_nd(inport, ip6.src, nd.sll); next;",
-         .external_ids     = map_empty())
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
+                .priority         = 90,
+                .__match          = "arp",
+                .actions          = "put_arp(inport, arp.spa, arp.sha); next;",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ARP()),
+                .external_ids     = map_empty());
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
+                .priority         = 90,
+                .__match          = "nd_na",
+                .actions          = "put_nd(inport, nd.target, nd.tll); next;",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ND_NA()),
+                .external_ids     = map_empty());
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_LEARN_NEIGHBOR(),
+                .priority         = 90,
+                .__match          = "nd_ns",
+                .actions          = "put_nd(inport, ip6.src, nd.sll); next;",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ND_NS()),
+                .external_ids     = map_empty())
 }
 
 /* Check if we need to learn mac-binding from ARP requests. */
@@ -5226,12 +5259,14 @@ relation LogicalRouterNdFlow(
     drop: bool,
     priority: integer,
     external_ids: Map<string,string>)
-Flow(.logical_datapath = lr._uuid,
-     .stage = s_ROUTER_IN_IP_INPUT(),
-     .priority = priority,
-     .__match = __match,
-     .actions = actions,
-     .external_ids = external_ids) :-
+MeteredFlow(.logical_datapath = lr._uuid,
+            .stage = s_ROUTER_IN_IP_INPUT(),
+            .priority = priority,
+            .__match = __match,
+            .actions = actions,
+            .tags             = map_empty(),
+            .controller_meter = controller_meter,
+            .external_ids = external_ids) :-
     LogicalRouterNdFlow(.lr = lr, .lrp = lrp, .action = action, .ip = ip,
                         .sn_ip = sn_ip, .mac = mac, .extra_match = extra_match,
                         .drop = drop, .priority = priority,
@@ -5249,18 +5284,19 @@ Flow(.logical_datapath = lr._uuid,
         clauses.append(extra_match.to_vec());
         clauses.join(" && ")
     },
-    var actions = if (drop) {
-        "drop;"
+    (var actions, var controller_meter) = if (drop) {
+        ("drop;", None)
     } else {
-        "${action} { "
-          "eth.src = ${mac}; "
-          "ip6.src = ${ip}; "
-          "nd.target = ${ip}; "
-          "nd.tll = ${mac}; "
-          "outport = inport; "
-          "flags.loopback = 1; "
-          "output; "
-        "};"
+        ("${action} { "
+           "eth.src = ${mac}; "
+           "ip6.src = ${ip}; "
+           "nd.target = ${ip}; "
+           "nd.tll = ${mac}; "
+           "outport = inport; "
+           "flags.loopback = 1; "
+           "output; "
+         "};",
+         lr.copp.get(cOPP_ND_NA()))
     }.
 
 /* ICMP time exceeded */
@@ -5382,60 +5418,69 @@ for (RouterPortNetworksIPv4Addr(
         .port = &RouterPort{
             .router = &Router{._uuid = lr_uuid,
                               .l3dgw_port = None,
-                              .is_gateway = false},
+                              .is_gateway = false,
+                              .copp = copp},
             .lrp = lrp},
          .addr = addr))
 {
     /* UDP/TCP/SCTP port unreachable. */
     var __match = "ip4 && ip4.dst == ${addr.addr} && !ip.later_frag && udp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "icmp4 {"
-                             "eth.dst <-> eth.src; "
-                             "ip4.dst <-> ip4.src; "
-                             "ip.ttl = 255; "
-                             "icmp4.type = 3; "
-                             "icmp4.code = 3; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "icmp4 {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip4.dst <-> ip4.src; "
+                                    "ip.ttl = 255; "
+                                    "icmp4.type = 3; "
+                                    "icmp4.code = 3; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ICMP4_ERR()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip4 && ip4.dst == ${addr.addr} && !ip.later_frag && tcp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "tcp_reset {"
-                             "eth.dst <-> eth.src; "
-                             "ip4.dst <-> ip4.src; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "tcp_reset {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip4.dst <-> ip4.src; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_TCP_RESET()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip4 && ip4.dst == ${addr.addr} && !ip.later_frag && sctp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "sctp_abort {"
-                             "eth.dst <-> eth.src; "
-                             "ip4.dst <-> ip4.src; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "sctp_abort {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip4.dst <-> ip4.src; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_TCP_RESET()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip4 && ip4.dst == ${addr.addr} && !ip.later_frag" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 70,
-         .__match          = __match,
-         .actions          = "icmp4 {"
-                             "eth.dst <-> eth.src; "
-                             "ip4.dst <-> ip4.src; "
-                             "ip.ttl = 255; "
-                             "icmp4.type = 3; "
-                             "icmp4.code = 2; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid))
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 70,
+                .__match          = __match,
+                .actions          = "icmp4 {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip4.dst <-> ip4.src; "
+                                    "ip.ttl = 255; "
+                                    "icmp4.type = 3; "
+                                    "icmp4.code = 2; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ICMP4_ERR()),
+                .external_ids     = stage_hint(lrp._uuid))
 }
 
 /* DHCPv6 reply handling */
@@ -5509,60 +5554,69 @@ for (RouterPortNetworksIPv6Addr(.port = &RouterPort{.lrp = lrp,
 for (RouterPortNetworksIPv6Addr(
         .port = &RouterPort{.router = &Router{._uuid = lr_uuid,
                                               .l3dgw_port = None,
-                                              .is_gateway = false},
+                                              .is_gateway = false,
+                                              .copp = copp},
                             .lrp = lrp,
                             .json_name = json_name},
         .addr = addr))
 {
     var __match = "ip6 && ip6.dst == ${addr.addr} && !ip.later_frag && tcp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "tcp_reset {"
-                             "eth.dst <-> eth.src; "
-                             "ip6.dst <-> ip6.src; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "tcp_reset {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip6.dst <-> ip6.src; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_TCP_RESET()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip6 && ip6.dst == ${addr.addr} && !ip.later_frag && sctp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "sctp_abort {"
-                             "eth.dst <-> eth.src; "
-                             "ip6.dst <-> ip6.src; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "sctp_abort {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip6.dst <-> ip6.src; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_TCP_RESET()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip6 && ip6.dst == ${addr.addr} && !ip.later_frag && udp" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 80,
-         .__match          = __match,
-         .actions          = "icmp6 {"
-                             "eth.dst <-> eth.src; "
-                             "ip6.dst <-> ip6.src; "
-                             "ip.ttl = 255; "
-                             "icmp6.type = 1; "
-                             "icmp6.code = 4; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid));
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 80,
+                .__match          = __match,
+                .actions          = "icmp6 {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip6.dst <-> ip6.src; "
+                                    "ip.ttl = 255; "
+                                    "icmp6.type = 1; "
+                                    "icmp6.code = 4; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ICMP6_ERR()),
+                .external_ids     = stage_hint(lrp._uuid));
 
     var __match = "ip6 && ip6.dst == ${addr.addr} && !ip.later_frag" in
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 70,
-         .__match          = __match,
-         .actions          = "icmp6 {"
-                             "eth.dst <-> eth.src; "
-                             "ip6.dst <-> ip6.src; "
-                             "ip.ttl = 255; "
-                             "icmp6.type = 1; "
-                             "icmp6.code = 3; "
-                             "next; };",
-         .external_ids     = stage_hint(lrp._uuid))
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 70,
+                .__match          = __match,
+                .actions          = "icmp6 {"
+                                    "eth.dst <-> eth.src; "
+                                    "ip6.dst <-> ip6.src; "
+                                    "ip.ttl = 255; "
+                                    "icmp6.type = 1; "
+                                    "icmp6.code = 3; "
+                                    "next; };",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ICMP6_ERR()),
+                .external_ids     = stage_hint(lrp._uuid))
 }
 
 /* ICMPv6 time exceeded */
@@ -5584,12 +5638,14 @@ for (RouterPortNetworksIPv6Addr(.port = &RouterPort{.router = router,
                   "icmp6.type = 3; /* Time exceeded */ "
                   "icmp6.code = 0; /* TTL exceeded in transit */ "
                   "next; };" in
-    Flow(.logical_datapath = router._uuid,
-         .stage            = s_ROUTER_IN_IP_INPUT(),
-         .priority         = 40,
-         .__match          = __match,
-         .actions          = actions,
-         .external_ids     = stage_hint(lrp._uuid))
+    MeteredFlow(.logical_datapath = router._uuid,
+                .stage            = s_ROUTER_IN_IP_INPUT(),
+                .priority         = 40,
+                .__match          = __match,
+                .actions          = actions,
+                .tags             = map_empty(),
+                .controller_meter = router.copp.get(cOPP_ICMP6_ERR()),
+                .external_ids     = stage_hint(lrp._uuid))
 }
 
 /* NAT, Defrag and load balancing. */
@@ -6247,16 +6303,16 @@ for (RouterLBVIP(
 {
     if (backends == "" and not lb.options.get_bool_def("reject", false)) {
         for (LoadBalancerEmptyEvents(lb)) {
-            for (HasEventElbMeter(has_elb_meter)) {
-                Some {(var __match, var __action)} =
-                    build_empty_lb_event_flow(vip, lb, has_elb_meter) in
-                Flow(.logical_datapath = lr_uuid,
-                     .stage            = s_ROUTER_IN_DNAT(),
-                     .priority         = 130,
-                     .__match          = __match,
-                     .actions          = __action,
-                     .external_ids     = stage_hint(lb._uuid))
-            }
+            Some {(var __match, var __action)} =
+                build_empty_lb_event_flow(vip, lb) in
+            MeteredFlow(.logical_datapath = lr_uuid,
+                        .stage            = s_ROUTER_IN_DNAT(),
+                        .priority         = 130,
+                        .__match          = __match,
+                        .actions          = __action,
+                        .tags             = map_empty(),
+                        .controller_meter = r.copp.get(cOPP_EVENT_ELB()),
+                        .external_ids     = stage_hint(lb._uuid))
         }
     };
 
@@ -6412,12 +6468,14 @@ for (RouterLBVIP(
  * via add_router_lb_flow().  One flow is for specific matching
  * on ct.new with an action of "ct_lb($targets);".  The other
  * flow is for ct.est with an action of "ct_dnat;". */
-Flow(.logical_datapath = r._uuid,
-     .stage            = s_ROUTER_IN_DNAT(),
-     .priority         = priority,
-     .__match          = __match,
-     .actions          = actions,
-     .external_ids     = stage_hint(lb._uuid)) :-
+MeteredFlow(.logical_datapath = r._uuid,
+            .stage            = s_ROUTER_IN_DNAT(),
+            .priority         = priority,
+            .__match          = __match,
+            .actions          = actions,
+            .tags             = map_empty(),
+            .controller_meter = meter,
+            .external_ids     = stage_hint(lb._uuid)) :-
     r in &Router(),
     r.l3dgw_port.is_some() or r.is_gateway,
     LBVIPWithStatus[lbvip@&LBVIPWithStatus{.lb = lb}],
@@ -6435,7 +6493,12 @@ Flow(.logical_datapath = r._uuid,
         ForceSNAT -> "flags.force_snat_for_lb = 1; ",
         _ -> ""
     },
-    var actions = build_lb_vip_actions(lbvip, s_ROUTER_OUT_SNAT(), force_snat).
+    (var actions, var reject) = build_lb_vip_actions(lbvip, s_ROUTER_OUT_SNAT(), force_snat),
+    var meter = if (reject) {
+        r.copp.get(cOPP_REJECT())
+    } else {
+        None
+    }.
 
 
 /* Defaults based on MaxRtrInterval and MinRtrInterval from RFC 4861 section
@@ -6576,12 +6639,14 @@ for (&RouterPort[port@RouterPort{.lrp = lrp@&nb::Logical_Router_Port{.peer = Non
                     Some{prf} -> ", router_preference = \"${prf}\""
                 } in
             var actions = actions0 ++ router_preference ++ prefix ++ "); next;" in
-            Flow(.logical_datapath = router._uuid,
-                 .stage            = s_ROUTER_IN_ND_RA_OPTIONS(),
-                 .priority         = 50,
-                 .__match          = __match,
-                 .actions          = actions,
-                 .external_ids     = stage_hint(lrp._uuid));
+            MeteredFlow(.logical_datapath = router._uuid,
+                        .stage            = s_ROUTER_IN_ND_RA_OPTIONS(),
+                        .priority         = 50,
+                        .__match          = __match,
+                        .actions          = actions,
+                        .tags             = map_empty(),
+                        .controller_meter = router.copp.get(cOPP_ND_RA_OPTS()),
+                        .external_ids     = stage_hint(lrp._uuid));
 
             var __match = "inport == ${json_name} && ip6.dst == ff02::2 && "
                           "nd_ra && ${rEGBIT_ND_RA_OPTS_RESULT()}" in
@@ -7478,24 +7543,26 @@ Flow(.logical_datapath = lr_uuid,
     var gw_mtu = l3dgw_port.options.get_int_def("gateway_mtu", 0),
     gw_mtu > 0,
     var mtu = gw_mtu + vLAN_ETH_HEADER_LEN().
-Flow(.logical_datapath = lr_uuid,
-     .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
-                         "ip4 && ${rEGBIT_PKT_LARGER()}",
-     .actions          = "icmp4_error {"
-                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
-                         "eth.dst = ${rp.networks.ea}; "
-                         "ip4.dst = ip4.src; "
-                         "ip4.src = ${first_ipv4.addr}; "
-                         "ip.ttl = 255; "
-                         "icmp4.type = 3; /* Destination Unreachable. */ "
-                         "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-                         /* Set icmp4.frag_mtu to gw_mtu */
-                         "icmp4.frag_mtu = ${gw_mtu}; "
-                         "next(pipeline=ingress, table=0); "
-                         "};",
-     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+MeteredFlow(.logical_datapath = lr_uuid,
+            .stage            = s_ROUTER_IN_LARGER_PKTS(),
+            .priority         = 50,
+            .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
+                                "ip4 && ${rEGBIT_PKT_LARGER()}",
+            .actions          = "icmp4_error {"
+                                "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                                "eth.dst = ${rp.networks.ea}; "
+                                "ip4.dst = ip4.src; "
+                                "ip4.src = ${first_ipv4.addr}; "
+                                "ip.ttl = 255; "
+                                "icmp4.type = 3; /* Destination Unreachable. */ "
+                                "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
+                                /* Set icmp4.frag_mtu to gw_mtu */
+                                "icmp4.frag_mtu = ${gw_mtu}; "
+                                "next(pipeline=ingress, table=0); "
+                                "};",
+            .tags             = map_empty(),
+            .controller_meter = r.copp.get(cOPP_ICMP4_ERR()),
+            .external_ids     = stage_hint(rp.lrp._uuid)) :-
     r in &Router(._uuid = lr_uuid),
     Some{var l3dgw_port} = r.l3dgw_port,
     var l3dgw_port_json_name = json_string_escape(l3dgw_port.name),
@@ -7505,24 +7572,26 @@ Flow(.logical_datapath = lr_uuid,
     rp in &RouterPort(.router = r),
     rp.lrp != l3dgw_port,
     Some{var first_ipv4} = rp.networks.ipv4_addrs.nth(0).
-Flow(.logical_datapath = lr_uuid,
-     .stage            = s_ROUTER_IN_LARGER_PKTS(),
-     .priority         = 50,
-     .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
-                         "ip6 && ${rEGBIT_PKT_LARGER()}",
-     .actions          = "icmp6_error {"
-                         "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
-                         "eth.dst = ${rp.networks.ea}; "
-                         "ip6.dst = ip6.src; "
-                         "ip6.src = ${first_ipv6.addr}; "
-                         "ip.ttl = 255; "
-                         "icmp6.type = 2; /* Packet Too Big. */ "
-                         "icmp6.code = 0; "
-                         /* Set icmp6.frag_mtu to gw_mtu */
-                         "icmp6.frag_mtu = ${gw_mtu}; "
-                         "next(pipeline=ingress, table=0); "
-                         "};",
-     .external_ids     = stage_hint(rp.lrp._uuid)) :-
+MeteredFlow(.logical_datapath = lr_uuid,
+            .stage            = s_ROUTER_IN_LARGER_PKTS(),
+            .priority         = 50,
+            .__match          = "inport == ${rp.json_name} && outport == ${l3dgw_port_json_name} && "
+                                "ip6 && ${rEGBIT_PKT_LARGER()}",
+            .actions          = "icmp6_error {"
+                                "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
+                                "eth.dst = ${rp.networks.ea}; "
+                                "ip6.dst = ip6.src; "
+                                "ip6.src = ${first_ipv6.addr}; "
+                                "ip.ttl = 255; "
+                                "icmp6.type = 2; /* Packet Too Big. */ "
+                                "icmp6.code = 0; "
+                                /* Set icmp6.frag_mtu to gw_mtu */
+                                "icmp6.frag_mtu = ${gw_mtu}; "
+                                "next(pipeline=ingress, table=0); "
+                                "};",
+            .tags             = map_empty(),
+            .controller_meter = r.copp.get(cOPP_ICMP6_ERR()),
+            .external_ids     = stage_hint(rp.lrp._uuid)) :-
     r in &Router(._uuid = lr_uuid),
     Some{var l3dgw_port} = r.l3dgw_port,
     var l3dgw_port_json_name = json_string_escape(l3dgw_port.name),
@@ -7570,12 +7639,14 @@ for (&Router(._uuid = lr_uuid,
  * In the common case where the Ethernet destination has been resolved,
  * this table outputs the packet (priority 0).  Otherwise, it composes
  * and sends an ARP/IPv6 NA request (priority 100). */
-Flow(.logical_datapath = router._uuid,
-     .stage            = s_ROUTER_IN_ARP_REQUEST(),
-     .priority         = 200,
-     .__match          = __match,
-     .actions          = actions,
-     .external_ids     = map_empty()) :-
+MeteredFlow(.logical_datapath = router._uuid,
+            .stage            = s_ROUTER_IN_ARP_REQUEST(),
+            .priority         = 200,
+            .__match          = __match,
+            .actions          = actions,
+            .tags             = map_empty(),
+            .controller_meter = router.copp.get(cOPP_ND_NS_RESOLVE()),
+            .external_ids     = map_empty()) :-
     rsr in RouterStaticRoute(.router = router),
     var dst = FlatMap(rsr.dsts),
     IPv6{var gw_ip6} = dst.nexthop,
@@ -7591,30 +7662,34 @@ Flow(.logical_datapath = router._uuid,
                   "output; "
                   "};".
 
-for (&Router(._uuid = lr_uuid))
+for (&Router(._uuid = lr_uuid, .copp = copp))
 {
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_ARP_REQUEST(),
-         .priority         = 100,
-         .__match          = "eth.dst == 00:00:00:00:00:00 && ip4",
-         .actions          = "arp { "
-                             "eth.dst = ff:ff:ff:ff:ff:ff; "
-                             "arp.spa = ${rEG_SRC()}; "
-                             "arp.tpa = ${rEG_NEXT_HOP()}; "
-                             "arp.op = 1; " /* ARP request */
-                             "output; "
-                             "};",
-         .external_ids     = map_empty());
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_ARP_REQUEST(),
+                .priority         = 100,
+                .__match          = "eth.dst == 00:00:00:00:00:00 && ip4",
+                .actions          = "arp { "
+                                    "eth.dst = ff:ff:ff:ff:ff:ff; "
+                                    "arp.spa = ${rEG_SRC()}; "
+                                    "arp.tpa = ${rEG_NEXT_HOP()}; "
+                                    "arp.op = 1; " /* ARP request */
+                                    "output; "
+                                    "};",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ARP_RESOLVE()),
+                .external_ids     = map_empty());
 
-    Flow(.logical_datapath = lr_uuid,
-         .stage            = s_ROUTER_IN_ARP_REQUEST(),
-         .priority         = 100,
-         .__match          = "eth.dst == 00:00:00:00:00:00 && ip6",
-         .actions          = "nd_ns { "
-                             "nd.target = xx${rEG_NEXT_HOP()}; "
-                             "output; "
-                             "};",
-         .external_ids     = map_empty());
+    MeteredFlow(.logical_datapath = lr_uuid,
+                .stage            = s_ROUTER_IN_ARP_REQUEST(),
+                .priority         = 100,
+                .__match          = "eth.dst == 00:00:00:00:00:00 && ip6",
+                .actions          = "nd_ns { "
+                                    "nd.target = xx${rEG_NEXT_HOP()}; "
+                                    "output; "
+                                    "};",
+                .tags             = map_empty(),
+                .controller_meter = copp.get(cOPP_ND_NS_RESOLVE()),
+                .external_ids     = map_empty());
 
     Flow(.logical_datapath = lr_uuid,
          .stage            = s_ROUTER_IN_ARP_REQUEST(),
@@ -8285,8 +8360,12 @@ nb::Out_BFD(bfd_uuid, Some{status}) :-
  * Logical router BFD flows
  */
 
-function lrouter_bfd_flows(lr_uuid: uuid, lrp_uuid: uuid, ipX: string, networks: string)
-    : (Flow, Flow)
+function lrouter_bfd_flows(lr_uuid: uuid,
+                           lrp_uuid: uuid,
+                           ipX: string,
+                           networks: string,
+                           controller_meter: Option<string>)
+    : (Flow, MeteredFlow)
 {
     (Flow{.logical_datapath = lr_uuid,
           .stage            = s_ROUTER_IN_IP_INPUT(),
@@ -8294,27 +8373,33 @@ function lrouter_bfd_flows(lr_uuid: uuid, lrp_uuid: uuid, ipX: string, networks:
           .__match          = "${ipX}.src == ${networks} && udp.dst == 3784",
           .actions          = "next; ",
           .external_ids     = stage_hint(lrp_uuid)},
-     Flow{.logical_datapath = lr_uuid,
-          .stage            = s_ROUTER_IN_IP_INPUT(),
-          .priority         = 110,
-          .__match          = "${ipX}.dst == ${networks} && udp.dst == 3784",
-          .actions          = "handle_bfd_msg(); ",
-          .external_ids     = stage_hint(lrp_uuid)})
+     MeteredFlow{.logical_datapath = lr_uuid,
+                 .stage            = s_ROUTER_IN_IP_INPUT(),
+                 .priority         = 110,
+                 .__match          = "${ipX}.dst == ${networks} && udp.dst == 3784",
+                 .actions          = "handle_bfd_msg(); ",
+                 .tags             = map_empty(),
+                 .controller_meter = controller_meter,
+                 .external_ids     = stage_hint(lrp_uuid)})
 }
 for (&RouterPort(.router = router, .networks = networks, .lrp = lrp, .has_bfd = true)) {
-    if (not networks.ipv4_addrs.is_empty()) {
-        (var a, var b) = lrouter_bfd_flows(router._uuid, lrp._uuid, "ip4",
-                                           format_v4_networks(networks, false)) in {
-            Flow[a];
-            Flow[b]
-        }
-    };
+    var controller_meter = router.copp.get(cOPP_BFD()) in {
+        if (not networks.ipv4_addrs.is_empty()) {
+            (var a, var b) = lrouter_bfd_flows(router._uuid, lrp._uuid, "ip4",
+                                               format_v4_networks(networks, false),
+                                               controller_meter) in {
+                Flow[a];
+                MeteredFlow[b]
+            }
+        };
 
-    if (not networks.ipv6_addrs.is_empty()) {
-        (var a, var b) = lrouter_bfd_flows(router._uuid, lrp._uuid, "ip6",
-                                           format_v6_networks(networks)) in {
-            Flow[a];
-            Flow[b]
+        if (not networks.ipv6_addrs.is_empty()) {
+            (var a, var b) = lrouter_bfd_flows(router._uuid, lrp._uuid, "ip6",
+                                               format_v6_networks(networks),
+                                               controller_meter) in {
+                Flow[a];
+                MeteredFlow[b]
+            }
         }
     }
 }
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 941e7bc7d..c1176e81f 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -414,6 +414,9 @@
     <column name="meters" key="bfd">
       Rate limiting meter for BFD packets.
     </column>
+    <column name="meters" key="reject">
+      Rate limiting meter for packets that trigger a reject action
+    </column>
   </table>
 
   <table name="Logical_Switch" title="L2 logical switch">
diff --git a/tests/atlocal.in b/tests/atlocal.in
index b5bc0818b..310fd46a5 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -169,6 +169,9 @@ find_command tcpdump
 # Set HAVE_LFTP
 find_command lftp
 
+# Set HAVE_SCAPY
+find_command scapy
+
 CURL_OPT="-g -v --max-time 1 --retry 2 --retry-delay 1 --connect-timeout 1"
 
 # Determine whether "diff" supports "normal" diffs.  (busybox diff does not.)
diff --git a/tests/ovn.at b/tests/ovn.at
index eaf344be9..c8407bc6e 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1668,10 +1668,6 @@ reject { };
 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)
 
-trigger_event(event = "empty_lb_backends", meter="event-elb" vip = "10.0.0.1:80", protocol = "tcp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
-    formats as trigger_event(event = "empty_lb_backends", meter = "event-elb", 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,meter_id=5)
-
 # Testing invalid vip results in extra error messages from socket-util.c
 trigger_event(event = "empty_lb_backends", vip = "10.0.0.1:80", protocol = "aarp", load_balancer = "12345678-abcd-9876-fedc-11119f8e7d6c");
     Load balancer protocol 'aarp' is not 'tcp', 'udp', or 'sctp'
@@ -17969,6 +17965,7 @@ AT_CLEANUP
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([controller event])
 AT_KEYWORDS([ovn_controller_event])
+
 ovn_start
 
 # Create hypervisors hv[12].
@@ -18032,6 +18029,7 @@ ovn-nbctl ls-lb-add sw0 lb2
 uuid_lb2=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb2)
 
 ovn-nbctl --wait=hv meter-add event-elb drop 100 pktps 10
+ovn-nbctl --wait=hv ls-copp-add sw0 event-elb event-elb
 
 OVN_POPULATE_ARP
 wait_for_ports_up
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index fc377bbd1..4288d80e5 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -6612,3 +6612,138 @@ NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.12 | FORMAT_PING], \
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn -- CoPP])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+AT_KEYWORDS([ovn-copp])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add R1
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 1000::a/64 \
+    -- lrp-set-gateway-chassis rp-public hv1
+
+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+    type=router options:router-port=rp-sw0 \
+    -- lsp-set-addresses sw0-rp router
+
+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
+    type=router options:router-port=rp-public \
+    -- lsp-set-addresses public-rp router
+
+ADD_NAMESPACES(sw01)
+ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+         "192.168.1.1")
+check ovn-nbctl lsp-add sw0 sw01 \
+    -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
+
+ADD_NAMESPACES(server)
+NS_CHECK_EXEC([server], [ip link set dev lo up])
+ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \
+         "172.16.1.1")
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
+check ovn-nbctl lsp-add public public1 \
+        -- lsp-set-addresses public1 unknown \
+        -- lsp-set-type public1 localnet \
+        -- lsp-set-options public1 network_name=phynet
+
+NS_EXEC([sw01], [tcpdump -n -i sw01 icmp -Q in > reject.pcap &])
+check ovn-nbctl meter-add acl-meter drop 1 pktps 0
+check ovn-nbctl --wait=hv ls-copp-add sw0 reject acl-meter
+check ovn-nbctl acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject
+
+AT_CHECK([ovn-nbctl ls-copp-list sw0], [0], [dnl
+reject: acl-meter
+])
+
+ip netns exec sw01 scapy -H <<-EOF
+p = IP(src="192.168.1.2", dst="192.168.1.1")/ UDP(dport = 12345) / Raw(b"X"*64)
+send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
+EOF
+
+sleep 2
+kill $(pidof tcpdump)
+
+# 1pps + 1 burst size
+OVS_WAIT_UNTIL([
+    n_reject=$(grep unreachable reject.pcap | wc -l)
+    test "${n_reject}" = "2"
+])
+
+rm -f reject.pcap
+NS_EXEC([sw01], [tcpdump -n -i sw01 icmp -Q in > reject.pcap &])
+check ovn-nbctl --wait=hv ls-copp-del sw0 reject
+
+ip netns exec sw01 scapy -H <<-EOF
+p = IP(src="192.168.1.2", dst="192.168.1.1")/ UDP(dport = 12345) / Raw(b"X"*64)
+send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
+EOF
+
+sleep 2
+kill $(pidof tcpdump)
+
+OVS_WAIT_UNTIL([
+    n_reject=$(grep unreachable reject.pcap | wc -l)
+    test "${n_reject}" = "20"
+])
+
+NS_EXEC([server], [tcpdump -n -i s1 arp[[24:4]]=0xac100164 > arp.pcap &])
+check ovn-nbctl meter-add arp-meter drop 1 pktps 0
+check ovn-nbctl --wait=hv lr-copp-add R1 arp-resolve arp-meter
+AT_CHECK([ovn-nbctl lr-copp-list R1], [0], [dnl
+arp-resolve: arp-meter
+])
+
+ip netns exec sw01 scapy -H <<-EOF
+p = IP(src="192.168.1.2", dst="172.16.1.100")/ TCP(dport = 80, flags="S") / Raw(b"X"*64)
+send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
+EOF
+
+sleep 2
+kill $(pidof tcpdump)
+
+# 1pps + 1 burst size
+OVS_WAIT_UNTIL([
+    n_arp=$(grep ARP arp.pcap | wc -l)
+    test "${n_arp}" = "2"
+])
+
+kill $(pidof ovn-controller)
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 101849911..987797860 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -1466,6 +1466,9 @@
           <li>packets that need to be replied to with ICMP Errors</li>
           <li>packets that need to be replied to with TCP RST</li>
           <li>packets that need to be replied to with DHCP_OPTS</li>
+          <li>packets that trigger a reject action</li>
+          <li>packets that trigger a SCTP abort action</li>
+          <li>controller_events</li>
           <li>BFD</li>
       </ul>
     </p>
