[ovs-dev] [RFC ovn 2/5] ovn-northd: Add support for CoPP.

Lorenzo Bianconi lorenzo.bianconi at redhat.com
Sat Apr 24 10:39:32 UTC 2021


From: Dumitru Ceara <dceara at redhat.com>

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).

Co-authored-by: Lorenzo Bianconi <lorenzo.bianconi at redhat.com>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi at redhat.com>
Signed-off-by: Dumitru Ceara <dceara at redhat.com>
---
 lib/automake.mk           |   2 +
 lib/copp.c                | 101 ++++++++++
 lib/copp.h                |  59 ++++++
 northd/ovn-northd.c       |  44 +++-
 ovn-nb.ovsschema          |  24 ++-
 ovn-nb.xml                |  94 +++++++++
 utilities/ovn-nbctl.8.xml |  94 +++++++++
 utilities/ovn-nbctl.c     | 412 ++++++++++++++++++++++++++++++++++++++
 8 files changed, 817 insertions(+), 13 deletions(-)
 create mode 100644 lib/copp.c
 create mode 100644 lib/copp.h

diff --git a/lib/automake.mk b/lib/automake.mk
index 781be2109..20e296fff 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 000000000..d5a5584d3
--- /dev/null
+++ b/lib/copp.c
@@ -0,0 +1,101 @@
+/* Copyright (c) 2021, 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",
+    [COPP_BFD]           = "bfd",
+};
+
+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,
+    [COPP_BFD]         = 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 000000000..e3f679d73
--- /dev/null
+++ b/lib/copp.h
@@ -0,0 +1,59 @@
+/* Copyright (c) 2021, 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_BFD,
+    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 94fae5648..2e9c7de22 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -32,6 +32,7 @@
 #include "ovn/lex.h"
 #include "lib/chassis-index.h"
 #include "lib/ip-mcast-index.h"
+#include "lib/copp.h"
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-l7.h"
 #include "lib/ovn-nb-idl.h"
@@ -4018,6 +4019,7 @@ struct ovn_lflow {
     char *match;
     char *actions;
     char *stage_hint;
+    char *ctrl_meter;
     const char *where;
 };
 
@@ -4051,14 +4053,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)
 {
     hmapx_init(&lflow->od_group);
     lflow->od = od;
@@ -4067,6 +4070,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;
 }
 
@@ -4107,6 +4111,7 @@ 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, bool shared,
+                 const char *ctrl_meter,
                  const struct ovsdb_idl_row *stage_hint, const char *where)
 {
     ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od));
@@ -4120,6 +4125,7 @@ ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
      * one datapath in a group, so it could be hashed correctly. */
     ovn_lflow_init(lflow, NULL, stage, priority,
                    xstrdup(match), xstrdup(actions),
+                   nullable_xstrdup(ctrl_meter),
                    ovn_lflow_hint(stage_hint), where);
 
     hash = ovn_lflow_hash(lflow);
@@ -4134,14 +4140,24 @@ 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) \
+    ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, true, \
+                     CTRL_METER, STAGE_HINT, OVS_SOURCE_LOCATOR)
+
 #define ovn_lflow_add_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
                                 ACTIONS, STAGE_HINT) \
-    ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, true, \
-                     STAGE_HINT, OVS_SOURCE_LOCATOR)
+    ovn_lflow_add_with_hint__(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
+                              ACTIONS, NULL, STAGE_HINT)
 
 #define ovn_lflow_add(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, true, \
-                     NULL, OVS_SOURCE_LOCATOR)
+                     NULL, NULL, OVS_SOURCE_LOCATOR)
+
+#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)
 
 /* Adds a row with the specified contents to the Logical_Flow table.
  * Combining of this logical flow with already existing ones, e.g., by using
@@ -4156,21 +4172,22 @@ ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
 #define ovn_lflow_add_unique_with_hint(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, \
                                        ACTIONS, STAGE_HINT) \
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, false, \
-                     STAGE_HINT, OVS_SOURCE_LOCATOR)
+                     NULL, STAGE_HINT, OVS_SOURCE_LOCATOR)
 
 #define ovn_lflow_add_unique(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS) \
     ovn_lflow_add_at(LFLOW_MAP, OD, STAGE, PRIORITY, MATCH, ACTIONS, false, \
-                     NULL, OVS_SOURCE_LOCATOR)
+                     NULL, NULL, OVS_SOURCE_LOCATOR)
 
 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);
 
     return ovn_lflow_find_by_lflow(lflows, &target, hash);
 }
@@ -4186,6 +4203,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);
     }
 }
@@ -12333,7 +12351,8 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
         lflow = ovn_lflow_find(
             &lflows, logical_datapath_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) {
             /* This is a valid lflow.  Checking if the datapath group needs
              * updates. */
@@ -12378,6 +12397,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
@@ -14082,6 +14102,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_logical_dp_group);
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 29019809c..431be7a50 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.31.0",
-    "cksum": "2352750632 28701",
+    "version": "5.32.0",
+    "cksum": "859932567 29770",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -30,6 +30,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"},
@@ -58,6 +66,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"}},
@@ -117,6 +128,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"}}},
@@ -319,6 +333,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",
@@ -360,6 +377,9 @@
                 "ipv6_prefix": {"type": {"key": "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 feb38b5d3..8a1586a19 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -339,6 +339,68 @@
 
   </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>
+    <column name="meters" key="bfd">
+      Rate limiting meter for BFD packets.
+    </column>
+  </table>
+
   <table name="Logical_Switch" title="L2 logical switch">
     <p>
       Each row represents one L2 logical switch.
@@ -572,6 +634,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="Other options">
       <column name="other_config" key="vlan-passthru"
           type='{"type": "boolean"}'>
@@ -1390,6 +1460,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>
@@ -1929,6 +2007,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="Options">
       <p>
         Additional options for the logical router.
@@ -2541,6 +2627,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="Options">
       <p>
         Additional options for the logical router port.
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 03d47dba5..f586223e7 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -1131,6 +1131,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 042c21002..e10416af5 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 "memory.h"
@@ -835,6 +836,48 @@ chassis with optional PRIORITY to the HA chassis group GRP\n\
   ha-chassis-group-remove-chassis GRP CHASSIS Removes 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\
@@ -5711,6 +5754,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)
 {
@@ -6673,6 +7063,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},
-- 
2.30.2




More information about the dev mailing list