[ovs-dev,RFC,v2,ovn,2/5] ovn-northd: Add support for CoPP.
diff mbox series

Message ID 20191105125322.4760.83141.stgit@dceara.remote.csb
State New
Headers show
Series
  • Add CoPP (Control Plane Protection).
Related show

Commit Message

Dumitru Ceara Nov. 5, 2019, 12:53 p.m. UTC
Add new 'Copp' (Control plane protection) table to OVN Northbound DB:
- this stores mappings between control plane protocol names and meters
  that should be used to rate limit controller-destined traffic for
  those protocols.

Add new 'copp' columns to the following OVN Northbound DB tables:
- Logical_Switch
- Logical_Switch_Port
- Logical_Router
- Logical_Router_Port

This allows defining control plane policies with different
granularities. For example a user can decide to enforce a general
policy for the logical switch but at the same time configure a
different policy on some of the ports of the logical switch.
Control plane protocol policies applied to a logical port take
precedence over the ones defined at logical switch level. For
logical routers and logical router ports we take the same approach.

For now, no control plane protection policy is installed for any of
the existing flows that punt packets to ovn-controller. This will be
added in follow-up patches.

Add CLI commands in 'ovn-nbctl' to allow the user to manage Control
Plane Protection Policies at different levels (logical switch,
logical router, logical port).

Signed-off-by: Dumitru Ceara <dceara@redhat.com>
---
 lib/automake.mk           |    2 
 lib/copp.c                |   99 +++++++++++
 lib/copp.h                |   58 ++++++
 northd/ovn-northd.c       |   43 +++--
 ovn-nb.ovsschema          |   24 ++-
 ovn-nb.xml                |   91 ++++++++++
 utilities/ovn-nbctl.8.xml |   94 ++++++++++
 utilities/ovn-nbctl.c     |  412 +++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 808 insertions(+), 15 deletions(-)
 create mode 100644 lib/copp.c
 create mode 100644 lib/copp.h

Patch
diff mbox series

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},