diff --git a/lib/automake.mk b/lib/automake.mk
index 0c8245c..7ba7ca0 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -9,6 +9,8 @@ lib_libovn_la_SOURCES = \
 	lib/actions.c \
 	lib/chassis-index.c \
 	lib/chassis-index.h \
+	lib/copp.c \
+	lib/copp.h \
 	lib/ovn-dirs.h \
 	lib/expr.c \
 	lib/extend-table.h \
diff --git a/lib/copp.c b/lib/copp.c
new file mode 100644
index 0000000..820cc29
--- /dev/null
+++ b/lib/copp.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2019, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "openvswitch/shash.h"
+#include "smap.h"
+#include "lib/ovn-nb-idl.h"
+#include "lib/copp.h"
+
+static char *copp_proto_names[COPP_PROTO_MAX] = {
+    [COPP_ARP]           = "arp",
+    [COPP_ARP_RESOLVE]   = "arp-resolve",
+    [COPP_DHCPV4_OPTS]   = "dhcpv4-opts",
+    [COPP_DHCPV6_OPTS]   = "dhcpv6-opts",
+    [COPP_DNS]           = "dns",
+    [COPP_EVENT_ELB]     = "event-elb",
+    [COPP_ICMP4_ERR]     = "icmp4-error",
+    [COPP_ICMP6_ERR]     = "icmp6-error",
+    [COPP_IGMP]          = "igmp",
+    [COPP_ND_NA]         = "nd-na",
+    [COPP_ND_NS]         = "nd-ns",
+    [COPP_ND_NS_RESOLVE] = "nd-ns-resolve",
+    [COPP_ND_RA_OPTS]    = "nd-ra-opts",
+    [COPP_TCP_RESET]     = "tcp-reset",
+};
+
+static bool copp_port_support[COPP_PROTO_MAX] = {
+    [COPP_DHCPV4_OPTS] = true,
+    [COPP_DHCPV6_OPTS] = true,
+    [COPP_ICMP4_ERR]   = true,
+    [COPP_ICMP6_ERR]   = true,
+    [COPP_ND_RA_OPTS]  = true,
+    [COPP_TCP_RESET]   = true,
+};
+
+/* Return true if the copp meter can be configured on a logical port. Return
+ * false if the meter is only supported on a logical switch/router.
+ */
+bool copp_port_meter_supported(enum copp_proto proto)
+{
+    if (proto >= COPP_PROTO_MAX) {
+        return false;
+    }
+
+    return copp_port_support[proto];
+}
+
+const char *
+copp_proto_get(enum copp_proto proto)
+{
+    if (proto >= COPP_PROTO_MAX) {
+        return "<Invalid control protocol ID>";
+    }
+    return copp_proto_names[proto];
+}
+
+const char *
+copp_meter_get(enum copp_proto proto, const struct nbrec_copp *copp,
+               const struct shash *meter_groups)
+{
+    if (!copp || proto >= COPP_PROTO_MAX) {
+        return NULL;
+    }
+
+    const char *meter = smap_get(&copp->meters, copp_proto_names[proto]);
+
+    if (meter && shash_find(meter_groups, meter)) {
+        return meter;
+    }
+
+    return NULL;
+}
+
+const char *
+copp_port_meter_get(enum copp_proto proto, const struct nbrec_copp *port_copp,
+                    const struct nbrec_copp *dp_copp,
+                    const struct shash *meter_groups)
+{
+    const char *meter = copp_meter_get(proto, port_copp, meter_groups);
+
+    if (!meter) {
+        return copp_meter_get(proto, dp_copp, meter_groups);
+    }
+    return meter;
+}
diff --git a/lib/copp.h b/lib/copp.h
new file mode 100644
index 0000000..e989206
--- /dev/null
+++ b/lib/copp.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2019, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_COPP_H
+#define OVN_COPP_H 1
+
+/*
+ * Control plane protection - metered actions.
+ */
+enum copp_proto {
+    COPP_PROTO_FIRST,
+    COPP_ARP = COPP_PROTO_FIRST,
+    COPP_ARP_RESOLVE,
+    COPP_DHCPV4_OPTS,
+    COPP_DHCPV6_OPTS,
+    COPP_DNS,
+    COPP_EVENT_ELB,
+    COPP_ICMP4_ERR,
+    COPP_ICMP6_ERR,
+    COPP_IGMP,
+    COPP_ND_NA,
+    COPP_ND_NS,
+    COPP_ND_NS_RESOLVE,
+    COPP_ND_RA_OPTS,
+    COPP_TCP_RESET,
+    COPP_PROTO_MAX,
+    COPP_PROTO_INVALID = COPP_PROTO_MAX,
+};
+
+struct nbrec_copp;
+
+bool copp_port_meter_supported(enum copp_proto proto);
+
+const char *copp_proto_get(enum copp_proto);
+
+const char *copp_meter_get(enum copp_proto proto,
+                           const struct nbrec_copp *copp,
+                           const struct shash *meter_groups);
+
+const char *copp_port_meter_get(enum copp_proto proto,
+                                const struct nbrec_copp *port_copp,
+                                const struct nbrec_copp *dp_copp,
+                                const struct shash *meter_groups);
+
+
+#endif /* lib/copp.h */
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index c23c270..4808299 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -29,6 +29,7 @@
 #include "openvswitch/json.h"
 #include "ovn/lex.h"
 #include "lib/chassis-index.h"
+#include "lib/copp.h"
 #include "lib/ip-mcast-index.h"
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-l7.h"
@@ -3351,6 +3352,7 @@ struct ovn_lflow {
     char *match;
     char *actions;
     char *stage_hint;
+    char *ctrl_meter;
     const char *where;
 };
 
@@ -3371,14 +3373,15 @@ ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_lflow *b)
             && a->stage == b->stage
             && a->priority == b->priority
             && !strcmp(a->match, b->match)
-            && !strcmp(a->actions, b->actions));
+            && !strcmp(a->actions, b->actions)
+            && nullable_string_is_equal(a->ctrl_meter, b->ctrl_meter));
 }
 
 static void
 ovn_lflow_init(struct ovn_lflow *lflow, struct ovn_datapath *od,
                enum ovn_stage stage, uint16_t priority,
-               char *match, char *actions, char *stage_hint,
-               const char *where)
+               char *match, char *actions, char *ctrl_meter,
+               char *stage_hint, const char *where)
 {
     lflow->od = od;
     lflow->stage = stage;
@@ -3386,6 +3389,7 @@ ovn_lflow_init(struct ovn_lflow *lflow, struct ovn_datapath *od,
     lflow->match = match;
     lflow->actions = actions;
     lflow->stage_hint = stage_hint;
+    lflow->ctrl_meter = ctrl_meter;
     lflow->where = where;
 }
 
@@ -3394,36 +3398,44 @@ static void
 ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
                  enum ovn_stage stage, uint16_t priority,
                  const char *match, const char *actions,
-                 const char *stage_hint, const char *where)
+                 const char *ctrl_meter, const char *stage_hint,
+                 const char *where)
 {
     ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od));
 
     struct ovn_lflow *lflow = xmalloc(sizeof *lflow);
     ovn_lflow_init(lflow, od, stage, priority,
                    xstrdup(match), xstrdup(actions),
+                   nullable_xstrdup(ctrl_meter),
                    nullable_xstrdup(stage_hint), where);
     hmap_insert(lflow_map, &lflow->hmap_node, ovn_lflow_hash(lflow));
 }
 
 /* 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, STAGE_HINT) \
+                                ACTIONS, CTRL_METER, STAGE_HINT) \
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
-                     STAGE_HINT, OVS_SOURCE_LOCATOR)
+                     CTRL_METER, STAGE_HINT, OVS_SOURCE_LOCATOR)
 
 #define ovn_lflow_add(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \
     ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
-                            ACTIONS, NULL)
+                            ACTIONS, NULL, NULL)
+
+#define ovn_lflow_add_ctrl(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, \
+                           CTRL_METER) \
+    ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
+                            ACTIONS, CTRL_METER, NULL)
 
 static struct ovn_lflow *
 ovn_lflow_find(struct hmap *lflows, struct ovn_datapath *od,
                enum ovn_stage stage, uint16_t priority,
-               const char *match, const char *actions, uint32_t hash)
+               const char *match, const char *actions, const char *ctrl_meter,
+               uint32_t hash)
 {
     struct ovn_lflow target;
     ovn_lflow_init(&target, od, stage, priority,
                    CONST_CAST(char *, match), CONST_CAST(char *, actions),
-                   NULL, NULL);
+                   CONST_CAST(char *, ctrl_meter), NULL, NULL);
 
     struct ovn_lflow *lflow;
     HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) {
@@ -3442,6 +3454,7 @@ ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow)
         free(lflow->match);
         free(lflow->actions);
         free(lflow->stage_hint);
+        free(lflow->ctrl_meter);
         free(lflow);
     }
 }
@@ -4528,7 +4541,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
             ovn_lflow_add_with_hint(lflows, od, stage,
                                     acl->priority + OVN_ACL_PRI_OFFSET,
                                     acl->match, ds_cstr(&actions),
-                                    stage_hint);
+                                    NULL, stage_hint);
             ds_destroy(&actions);
         } else {
             struct ds match = DS_EMPTY_INITIALIZER;
@@ -4557,7 +4570,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
                                     acl->priority + OVN_ACL_PRI_OFFSET,
                                     ds_cstr(&match),
                                     ds_cstr(&actions),
-                                    stage_hint);
+                                    NULL, stage_hint);
 
             /* Match on traffic in the request direction for an established
              * connection tracking entry that has not been marked for
@@ -4577,7 +4590,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
             ovn_lflow_add_with_hint(lflows, od, stage,
                                     acl->priority + OVN_ACL_PRI_OFFSET,
                                     ds_cstr(&match), ds_cstr(&actions),
-                                    stage_hint);
+                                    NULL, stage_hint);
 
             ds_destroy(&match);
             ds_destroy(&actions);
@@ -8843,7 +8856,8 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
             = !strcmp(sbflow->pipeline, "ingress") ? P_IN : P_OUT;
         struct ovn_lflow *lflow = ovn_lflow_find(
             &lflows, od, ovn_stage_build(dp_type, pipeline, sbflow->table_id),
-            sbflow->priority, sbflow->match, sbflow->actions, sbflow->hash);
+            sbflow->priority, sbflow->match, sbflow->actions,
+            sbflow->controller_meter, sbflow->hash);
         if (lflow) {
             ovn_lflow_destroy(&lflows, lflow);
         } else {
@@ -8862,6 +8876,7 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
         sbrec_logical_flow_set_priority(sbflow, lflow->priority);
         sbrec_logical_flow_set_match(sbflow, lflow->match);
         sbrec_logical_flow_set_actions(sbflow, lflow->actions);
+        sbrec_logical_flow_set_controller_meter(sbflow, lflow->ctrl_meter);
 
         /* Trim the source locator lflow->where, which looks something like
          * "ovn/northd/ovn-northd.c:1234", down to just the part following the
@@ -10272,6 +10287,8 @@ main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_priority);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_match);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_logical_flow_col_actions);
+    add_column_noalert(ovnsb_idl_loop.idl,
+                       &sbrec_logical_flow_col_controller_meter);
 
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_multicast_group);
     add_column_noalert(ovnsb_idl_loop.idl,
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 084305b..f4bf000 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.17.0",
-    "cksum": "1128988054 23237",
+    "version": "5.18.0",
+    "cksum": "600367587 24306",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -26,6 +26,14 @@
                 "ipsec": {"type": "boolean"}},
             "maxRows": 1,
             "isRoot": true},
+        "Copp": {
+            "columns": {
+                "meters": {
+                    "type": {"key": "string",
+                             "value": "string",
+                             "min": 0,
+                             "max": "unlimited"}}},
+            "isRoot": true},
         "Logical_Switch": {
             "columns": {
                 "name": {"type": "string"},
@@ -54,6 +62,9 @@
                                          "refType": "weak"},
                                   "min": 0,
                                   "max": "unlimited"}},
+                "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
+                                          "refType": "weak"},
+                                  "min": 0, "max": 1}},
                 "other_config": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
@@ -108,6 +119,9 @@
                                      "refType": "strong"},
                              "min": 0,
                              "max": 1}},
+                "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
+                                          "refType": "weak"},
+                                  "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
@@ -265,6 +279,9 @@
                                                   "refType": "weak"},
                                            "min": 0,
                                            "max": "unlimited"}},
+                "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
+                                          "refType": "weak"},
+                                  "min": 0, "max": 1}},
                 "options": {
                      "type": {"key": "string",
                               "value": "string",
@@ -303,6 +320,9 @@
                 "ipv6_ra_configs": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
+                "copp": {"type": {"key": {"type": "uuid", "refTable": "Copp",
+                                          "refType": "weak"},
+                                  "min": 0, "max": 1}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index d8f3237..6cac51a 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -147,6 +147,65 @@
     </group>
   </table>
 
+  <table name="Copp" title="Control plane protection">
+    <p>
+      This table is used to define control plane protection policies, i.e.,
+      associate entries from table <ref table="Meter"/> to control protocol
+      names.
+    </p>
+    <column name="meters" key="arp">
+      Rate limiting meter for ARP packets (request/reply) used for learning
+      neighbors.
+    </column>
+    <column name="meters" key="arp-resolve">
+      Rate limiting meter for packets that require resolving the next-hop
+      (through ARP).
+    </column>
+    <column name="meters" key="dhcpv4-opts">
+      Rate limiting meter for packets that require adding DHCPv4 options.
+    </column>
+    <column name="meters" key="dhcpv6-opts">
+      Rate limiting meter for packets that require adding DHCPv6 options.
+    </column>
+    <column name="meters" key="dns">
+      Rate limiting meter for DNS query packets that need to be replied to.
+    </column>
+    <column name="meters" key="event-elb">
+      Rate limiting meter for empty load balancer events.
+    </column>
+    <column name="meters" key="icmp4-error">
+      Rate limiting meter for packets that require replying with an ICMP
+      error.
+    </column>
+    <column name="meters" key="icmp6-error">
+      Rate limiting meter for packets that require replying with an ICMPv6
+      error.
+    </column>
+    <column name="meters" key="igmp">
+      Rate limiting meter for IGMP packets.
+    </column>
+    <column name="meters" key="nd-na">
+      Rate limiting meter for ND neighbor advertisement packets used for
+      learning neighbors.
+    </column>
+    <column name="meters" key="nd-ns">
+      Rate limiting meter for ND neighbor solicitation packets used for
+      learning neighbors.
+    </column>
+    <column name="meters" key="nd-ns-resolve">
+      Rate limiting meter for packets that require resolving the next-hop
+      (through ND).
+    </column>
+    <column name="meters" key="nd-ra-opts">
+      Rate limiting meter for packets that require adding ND router
+      advertisement options.
+    </column>
+    <column name="meters" key="tcp-reset">
+      Rate limiting meter for packets that require replying with TCP RST
+      packet.
+    </column>
+  </table>
+
   <table name="Logical_Switch" title="L2 logical switch">
     <p>
       Each row represents one L2 logical switch.
@@ -347,6 +406,14 @@
       </column>
     </group>
 
+    <column name="copp">
+      <p>
+        The control plane protection policy from table <ref table="Copp"/>
+        used for metering packets sent to <code>ovn-controller</code> from
+        ports of this logical switch.
+      </p>
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
@@ -1136,6 +1203,14 @@
       </column>
     </group>
 
+    <column name="copp">
+      <p>
+        The control plane protection policy from table <ref table="Copp"/>
+        used for metering packets sent to <code>ovn-controller</code> from
+        this logical port.
+      </p>
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         <p>
@@ -1563,6 +1638,14 @@
       </column>
     </group>
 
+    <column name="copp">
+      <p>
+        The control plane protection policy from table <ref table="Copp"/>
+        used for metering packets sent to <code>ovn-controller</code> from
+        logical ports of this router.
+      </p>
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
@@ -2087,6 +2170,14 @@
       </column>
     </group>
 
+    <column name="copp">
+      <p>
+        The control plane protection policy from table <ref table="Copp"/>
+        used for metering packets sent to <code>ovn-controller</code> from
+        this logical port.
+      </p>
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 88ebd13..b60a3f8 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -959,6 +959,100 @@
       </dd>
     </dl>
 
+    <h1> Control Plane Protection Policy commands</h1>
+
+    <dl>
+      <dt><code>ls-copp-add</code> <var>switch</var> <var>proto</var>
+      <var>meter</var></dt>
+      <dd>
+        Adds the control <code>proto</code> to <code>meter</code> mapping
+        to the <code>switch</code> control plane protection policy. If no
+        policy exists yet, it creates one. If a mapping already existed for
+        <code>proto</code>, this will overwrite it.
+      </dd>
+
+      <dt><code>ls-copp-del</code> <var>switch</var> [<var>proto</var>]</dt>
+      <dd>
+        Removes the control <code>proto</code> mapping from the
+        <code>switch</code> control plane protection policy. If
+        <code>proto</code> is not specified, the whole control plane
+        protection policy is destroyed.
+      </dd>
+
+      <dt><code>ls-copp-list</code> <var>switch</var></dt>
+      <dd>
+        Display the current control plane protection policy for
+        <code>switch</code>.
+      </dd>
+
+      <dt><code>lsp-copp-add</code> <var>proto</var> <var>proto</var>
+      <var>meter</var></dt>
+      <dd>
+        Adds the control <code>proto</code> to <code>meter</code> mapping
+        to the <code>port</code> control plane protection policy. If no
+        policy exists yet, it creates one. If a mapping already existed for
+        <code>proto</code>, this will overwrite it.
+      </dd>
+
+      <dt><code>lsp-copp-del</code> <var>port</var> [<var>proto</var>]</dt>
+      <dd>
+        Removes the control <code>proto</code> mapping from the
+        <code>port</code> control plane protection policy. If
+        <code>proto</code> is not specified, the whole control plane
+        protection policy is destroyed.
+      </dd>
+      <dt><code>lsp-copp-list</code> <var>port</var></dt>
+      <dd>
+        Display the current control plane protection policy for
+        <code>port</code>.
+      </dd>
+
+      <dt><code>lr-copp-add</code> <var>router</var> <var>proto</var>
+      <var>meter</var></dt>
+      <dd>
+        Adds the control <code>proto</code> to <code>meter</code> mapping
+        to the <code>router</code> control plane protection policy. If no
+        policy exists yet, it creates one. If a mapping already existed for
+        <code>proto</code>, this will overwrite it.
+      </dd>
+
+      <dt><code>lr-copp-del</code> <var>router</var> [<var>proto</var>]</dt>
+      <dd>
+        Removes the control <code>proto</code> mapping from the
+        <code>router</code> control plane protection policy. If
+        <code>proto</code> is not specified, the whole control plane
+        protection policy is destroyed.
+      </dd>
+
+      <dt><code>lr-copp-list</code> <var>router</var></dt>
+      <dd>
+        Display the current control plane protection policy for
+        <code>router</code>.
+      </dd>
+
+      <dt><code>lrp-copp-add</code> <var>proto</var> <var>proto</var>
+      <var>meter</var></dt>
+      <dd>
+        Adds the control <code>proto</code> to <code>meter</code> mapping
+        to the <code>port</code> control plane protection policy. If no
+        policy exists yet, it creates one. If a mapping already existed for
+        <code>proto</code>, this will overwrite it.
+      </dd>
+
+      <dt><code>lrp-copp-del</code> <var>port</var> [<var>proto</var>]</dt>
+      <dd>
+        Removes the control <code>proto</code> mapping from the
+        <code>port</code> control plane protection policy. If
+        <code>proto</code> is not specified, the whole control plane
+        protection policy is destroyed.
+      </dd>
+      <dt><code>lrp-copp-list</code> <var>port</var></dt>
+      <dd>
+        Display the current control plane protection policy for
+        <code>port</code>.
+      </dd>
+    </dl>
+
     <h1>Database Commands</h1>
     <p>These commands query and modify the contents of <code>ovsdb</code> tables.
     They are a slight abstraction of the <code>ovsdb</code> interface and
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 8188948..a5801c2 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -27,6 +27,7 @@
 #include "jsonrpc.h"
 #include "openvswitch/json.h"
 #include "lib/acl-log.h"
+#include "lib/copp.h"
 #include "lib/ovn-nb-idl.h"
 #include "lib/ovn-util.h"
 #include "packets.h"
@@ -753,6 +754,48 @@ chassis with optional PRIORITY to the HA chassis group GRP\n\
   ha-chassis-group-del-chassis GRP CHASSIS Deletes the HA chassis\
 CHASSIS from the HA chassis group GRP\n\
 \n\
+Control Plane Protection Policy commands:\n\
+  ls-copp-add SWITCH PROTO METER\n\
+                            Add a copp policy for PROTO packets on SWITCH\n\
+                            based on an existing METER.\n\
+  ls-copp-del SWITCH [PROTO]\n\
+                            Delete the copp policy for PROTO packets on\n\
+                            SWITCH. If PROTO is not specified, delete all\n\
+                            copp policies on SWITCH.\n\
+  ls-copp-list SWITCH\n\
+                            List all copp policies defined for control\n\
+                            protocols on SWITCH.\n\
+  lsp-copp-add PORT PROTO METER\n\
+                            Add a copp policy for PROTO packets on switch\n\
+                            PORT based on an existing METER.\n\
+  lsp-copp-del PORT [PROTO]\n\
+                            Delete the copp policy for PROTO packets on\n\
+                            switch PORT. If PROTO is not specified, delete\n\
+                            all copp policies on switch PORT.\n\
+  lsp-copp-list PORT\n\
+                            List all copp policies defined for control\n\
+                            protocols on switch PORT.\n\
+  lr-copp-add ROUTER PROTO METER\n\
+                            Add a copp policy for PROTO packets on ROUTER\n\
+                            based on an existing METER.\n\
+  lr-copp-del ROUTER [PROTO]\n\
+                            Delete the copp policy for PROTO packets on\n\
+                            ROUTER. If PROTO is not specified, delete all\n\
+                            copp policies on ROUTER.\n\
+  lr-copp-list ROUTER\n\
+                            List all copp policies defined for control\n\
+                            protocols on ROUTER.\n\
+  lrp-copp-add PORT PROTO METER\n\
+                            Add a copp policy for PROTO packets on router\n\
+                            PORT based on an existing METER.\n\
+  lrp-copp-del PORT [PROTO]\n\
+                            Delete the copp policy for PROTO packets on\n\
+                            router PORT. If PROTO is not specified, delete\n\
+                            all copp policies on router PORT.\n\
+  lrp-copp-list PORT\n\
+                            List all copp policies defined for control\n\
+                            protocols on router PORT.\n\
+\n\
 %s\
 %s\
 \n\
@@ -4853,6 +4896,353 @@ nbctl_lr_route_list(struct ctl_context *ctx)
     free(ipv6_routes);
 }
 
+static char *
+copp_proto_validate(const char *proto_name, bool per_port)
+{
+    for (size_t i = COPP_PROTO_FIRST; i < COPP_PROTO_MAX; i++) {
+        if (!strcmp(proto_name, copp_proto_get(i))) {
+            if (per_port && !copp_port_meter_supported(i)) {
+                break;
+            }
+            return NULL;
+        }
+    }
+
+    struct ds usage = DS_EMPTY_INITIALIZER;
+
+    ds_put_cstr(&usage, "Invalid control protocol. Allowed values: ");
+    for (size_t i = COPP_PROTO_FIRST; i < COPP_PROTO_MAX; i++) {
+        if (per_port && !copp_port_meter_supported(i)) {
+            continue;
+        }
+        ds_put_format(&usage, "%s, ", copp_proto_get(i));
+    }
+    ds_chomp(&usage, ' ');
+    ds_chomp(&usage, ',');
+    ds_put_cstr(&usage, ".");
+
+    char *usage_str = xstrdup(ds_cstr(&usage));
+    ds_destroy(&usage);
+    return usage_str;
+}
+
+static const struct nbrec_copp *
+copp_add_meter(struct ctl_context *ctx, const struct nbrec_copp *copp,
+               const char *proto_name, const char *meter)
+{
+    if (!copp) {
+        copp = nbrec_copp_insert(ctx->txn);
+    }
+
+    struct smap meters;
+    smap_init(&meters);
+    smap_clone(&meters, &copp->meters);
+    smap_replace(&meters, proto_name, meter);
+    nbrec_copp_set_meters(copp, &meters);
+    smap_destroy(&meters);
+
+    return copp;
+}
+
+static void
+copp_del_meter(const struct nbrec_copp *copp, const char *proto_name)
+{
+    if (!copp) {
+        return;
+    }
+
+    if (proto_name) {
+        if (smap_get(&copp->meters, proto_name)) {
+            struct smap meters;
+            smap_init(&meters);
+            smap_clone(&meters, &copp->meters);
+            smap_remove(&meters, proto_name);
+            nbrec_copp_set_meters(copp, &meters);
+            smap_destroy(&meters);
+        }
+    } else {
+        nbrec_copp_delete(copp);
+    }
+}
+
+static void
+copp_list(struct ctl_context *ctx, const struct nbrec_copp *copp)
+{
+    if (!copp) {
+        return;
+    }
+
+    struct smap_node *node;
+
+    SMAP_FOR_EACH (node, &copp->meters) {
+        ds_put_format(&ctx->output, "%s: %s\n", node->key, node->value);
+    }
+}
+
+static void
+nbctl_ls_copp_add(struct ctl_context *ctx)
+{
+    const char *ls_name = ctx->argv[1];
+    const char *proto_name = ctx->argv[2];
+    const char *meter = ctx->argv[3];
+
+    char *error = copp_proto_validate(proto_name, false);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_logical_switch *ls = NULL;
+    error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_copp *copp =
+        copp_add_meter(ctx, ls->copp, proto_name, meter);
+    nbrec_logical_switch_set_copp(ls, copp);
+}
+
+static void
+nbctl_ls_copp_del(struct ctl_context *ctx)
+{
+    const char *ls_name = ctx->argv[1];
+    const char *proto_name = NULL;
+    char *error;
+
+    if (ctx->argc == 3) {
+        proto_name = ctx->argv[2];
+        error = copp_proto_validate(proto_name, false);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+    }
+
+    const struct nbrec_logical_switch *ls = NULL;
+    error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_del_meter(ls->copp, proto_name);
+}
+
+static void
+nbctl_ls_copp_list(struct ctl_context *ctx)
+{
+    const char *ls_name = ctx->argv[1];
+
+    const struct nbrec_logical_switch *ls = NULL;
+    char *error = ls_by_name_or_uuid(ctx, ls_name, true, &ls);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_list(ctx, ls->copp);
+}
+
+static void
+nbctl_lsp_copp_add(struct ctl_context *ctx)
+{
+    const char *lsp_name = ctx->argv[1];
+    const char *proto_name = ctx->argv[2];
+    const char *meter = ctx->argv[3];
+
+    char *error = copp_proto_validate(proto_name, true);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    error = lsp_by_name_or_uuid(ctx, lsp_name, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_copp *copp =
+        copp_add_meter(ctx, lsp->copp, proto_name, meter);
+    nbrec_logical_switch_port_set_copp(lsp, copp);
+}
+
+static void
+nbctl_lsp_copp_del(struct ctl_context *ctx)
+{
+    const char *lsp_name = ctx->argv[1];
+    const char *proto_name = NULL;
+    char *error;
+
+    if (ctx->argc == 3) {
+        proto_name = ctx->argv[2];
+        error = copp_proto_validate(proto_name, true);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+    }
+
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    error = lsp_by_name_or_uuid(ctx, lsp_name, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_del_meter(lsp->copp, proto_name);
+}
+
+static void
+nbctl_lsp_copp_list(struct ctl_context *ctx)
+{
+    const char *lsp_name = ctx->argv[1];
+
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    char *error = lsp_by_name_or_uuid(ctx, lsp_name, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_list(ctx, lsp->copp);
+}
+
+static void
+nbctl_lr_copp_add(struct ctl_context *ctx)
+{
+    const char *lr_name = ctx->argv[1];
+    const char *proto_name = ctx->argv[2];
+    const char *meter = ctx->argv[3];
+
+    char *error = copp_proto_validate(proto_name, false);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_logical_router *lr = NULL;
+    error = lr_by_name_or_uuid(ctx, lr_name, true, &lr);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_copp *copp =
+        copp_add_meter(ctx, lr->copp, proto_name, meter);
+    nbrec_logical_router_set_copp(lr, copp);
+}
+
+static void
+nbctl_lr_copp_del(struct ctl_context *ctx)
+{
+    const char *lr_name = ctx->argv[1];
+    const char *proto_name = NULL;
+    char *error;
+
+    if (ctx->argc == 3) {
+        proto_name = ctx->argv[2];
+        error = copp_proto_validate(proto_name, false);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+    }
+
+    const struct nbrec_logical_router *lr = NULL;
+    error = lr_by_name_or_uuid(ctx, lr_name, true, &lr);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_del_meter(lr->copp, proto_name);
+}
+
+static void
+nbctl_lr_copp_list(struct ctl_context *ctx)
+{
+    const char *lr_name = ctx->argv[1];
+
+    const struct nbrec_logical_router *lr = NULL;
+    char *error = lr_by_name_or_uuid(ctx, lr_name, true, &lr);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_list(ctx, lr->copp);
+}
+
+static void
+nbctl_lrp_copp_add(struct ctl_context *ctx)
+{
+    const char *lrp_name = ctx->argv[1];
+    const char *proto_name = ctx->argv[2];
+    const char *meter = ctx->argv[3];
+
+    char *error = copp_proto_validate(proto_name, true);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_logical_router_port *lrp = NULL;
+    error = lrp_by_name_or_uuid(ctx, lrp_name, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    const struct nbrec_copp *copp =
+        copp_add_meter(ctx, lrp->copp, proto_name, meter);
+    nbrec_logical_router_port_set_copp(lrp, copp);
+}
+
+static void
+nbctl_lrp_copp_del(struct ctl_context *ctx)
+{
+    const char *lrp_name = ctx->argv[1];
+    const char *proto_name = NULL;
+    char *error;
+
+    if (ctx->argc == 3) {
+        proto_name = ctx->argv[2];
+        error = copp_proto_validate(proto_name, true);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+    }
+
+    const struct nbrec_logical_router_port *lrp = NULL;
+    error = lrp_by_name_or_uuid(ctx, lrp_name, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_del_meter(lrp->copp, proto_name);
+}
+
+static void
+nbctl_lrp_copp_list(struct ctl_context *ctx)
+{
+    const char *lrp_name = ctx->argv[1];
+
+    const struct nbrec_logical_router_port *lrp = NULL;
+    char *error = lrp_by_name_or_uuid(ctx, lrp_name, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    copp_list(ctx, lrp->copp);
+}
+
 static void
 verify_connections(struct ctl_context *ctx)
 {
@@ -5780,6 +6170,28 @@ static const struct ctl_command_syntax nbctl_commands[] = {
     {"dhcp-options-get-options", 1, 1, "DHCP_OPT_UUID", NULL,
      nbctl_dhcp_options_get_options, NULL, "", RO },
 
+    /* Control plane protection commands */
+    {"ls-copp-add", 3, 3, "SWITCH PROTO METER", NULL, nbctl_ls_copp_add, NULL,
+       "", RW},
+    {"ls-copp-del", 1, 2, "SWITCH [PROTO]", NULL, nbctl_ls_copp_del, NULL,
+       "", RW},
+    {"ls-copp-list", 1, 1, "SWITCH", NULL, nbctl_ls_copp_list, NULL, "", RO},
+    {"lsp-copp-add", 3, 3, "PORT PROTO METER", NULL, nbctl_lsp_copp_add, NULL,
+       "", RW},
+    {"lsp-copp-del", 1, 2, "PORT [PROTO]", NULL, nbctl_lsp_copp_del, NULL,
+       "", RW},
+    {"lsp-copp-list", 1, 1, "PORT", NULL, nbctl_lsp_copp_list, NULL, "", RO},
+    {"lr-copp-add", 3, 3, "ROUTER PROTO METER", NULL, nbctl_lr_copp_add, NULL,
+       "", RW},
+    {"lr-copp-del", 1, 2, "ROUTER [PROTO]", NULL, nbctl_lr_copp_del, NULL,
+       "", RW},
+    {"lr-copp-list", 1, 1, "ROUTER", NULL, nbctl_lr_copp_list, NULL, "", RO},
+    {"lrp-copp-add", 3, 3, "PORT PROTO METER", NULL, nbctl_lrp_copp_add, NULL,
+       "", RW},
+    {"lrp-copp-del", 1, 2, "PORT [PROTO]", NULL, nbctl_lrp_copp_del, NULL,
+       "", RW},
+    {"lrp-copp-list", 1, 1, "PORT", NULL, nbctl_lrp_copp_list, NULL, "", RO},
+
     /* Connection commands. */
     {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "", RO},
     {"del-connection", 0, 0, "", pre_connection, cmd_del_connection, NULL, "", RW},
