From patchwork Thu Jul 8 16:40:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Bianconi X-Patchwork-Id: 1502516 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=L3/b03fQ; dkim-atps=neutral Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4GLMVR2X0vz9sWc for ; Fri, 9 Jul 2021 02:40:47 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id B222842257; Thu, 8 Jul 2021 16:40:44 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id DE-Zwbh1kL6K; Thu, 8 Jul 2021 16:40:41 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 750C442237; Thu, 8 Jul 2021 16:40:40 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 54E17C001A; Thu, 8 Jul 2021 16:40:40 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 21C57C001A for ; Thu, 8 Jul 2021 16:40:39 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 28E4783C5C for ; Thu, 8 Jul 2021 16:40:33 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Authentication-Results: smtp1.osuosl.org (amavisd-new); dkim=pass (1024-bit key) header.d=redhat.com Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id kuGNNwyOhvTp for ; Thu, 8 Jul 2021 16:40:31 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp1.osuosl.org (Postfix) with ESMTPS id E2F6583C5F for ; Thu, 8 Jul 2021 16:40:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1625762430; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=7EcG30Tu5LBUIAF8cPTCPKPTf9UC/V4rERk4sKjhSqk=; b=L3/b03fQ1PSz/2je60noxSwGQ/fMG00VyNkXJD1hclML5XxJ1h325FweOpm2a53ZacYawm zbJWGsinzdYgvuMynOzI9xP+hS+zHkr0H//DZ9Hn0Mc8Z+Jl4PboHYYJXhaeO1pLBGZRo4 Gtad0h3juE8740K5z73M1HEbUCJI8Xk= Received: from mail-ej1-f70.google.com (mail-ej1-f70.google.com [209.85.218.70]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-600-D3nSKUGdN_i4506agKYBtA-1; Thu, 08 Jul 2021 12:40:25 -0400 X-MC-Unique: D3nSKUGdN_i4506agKYBtA-1 Received: by mail-ej1-f70.google.com with SMTP id t8-20020a1709063e48b0290501cd965554so480365eji.8 for ; Thu, 08 Jul 2021 09:40:24 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=7EcG30Tu5LBUIAF8cPTCPKPTf9UC/V4rERk4sKjhSqk=; b=iiXszQ3ZfTMfH4JyX7N2bJCmjj9oz299iHb3UNo10dpS7+fpMhqEQ9JPJUHw06Jl70 b/lchSkD/OqpMH1tbjJ6/nps+FUjtYZ1n/d8cPAQAy4fmNyS50eED/q6sdyoz9ign9QF dwEVAsFuZrGEkQeHRy3G8dhZnkfAXu5gdXtQ7csrprbjx/CSROT/zwoorX2LH9GTYWGk 6rsXuZCDCeY+9ggwwRDuKQ4ftf388kPPlWCUMYApyFzLZiCRioYoXUzRWW9obYCSWN63 QCJ0DQl+FLOjA1D59UZGH+Uh2NrW6jl1miD2t4pPvuDHk8LDaQOfzoir9ibRUNPAH+V9 C7zw== X-Gm-Message-State: AOAM531jGc4+mfzzSkEMg5xsG9MxZN4dPfWhoDOQoYYy42vhWHby/Xob FynDLxIQpvrQ1BWkhAbmTkjN1VkFhHgeWTecTUwqjOdf6pAVvRe43AHA6Nlb4E1i/juH64MAAYX NRY3qO6HdqOiEuymThqCz8kca1txm390du4wNS+Owz9eS99Vol3AbzZSzHC7p9Q+5s1KsFJ11uS I= X-Received: by 2002:a17:906:d1d1:: with SMTP id bs17mr31764953ejb.492.1625762423524; Thu, 08 Jul 2021 09:40:23 -0700 (PDT) X-Google-Smtp-Source: ABdhPJz0auJTPZsaLeCkaQUCtMjDnmZjwJSTQuOekb8cA6JEajTzrJ9oRrArGavMd8D2pCZmczmK6w== X-Received: by 2002:a17:906:d1d1:: with SMTP id bs17mr31764921ejb.492.1625762423101; Thu, 08 Jul 2021 09:40:23 -0700 (PDT) Received: from lore-desk.redhat.com (net-93-71-3-244.cust.vodafonedsl.it. [93.71.3.244]) by smtp.gmail.com with ESMTPSA id ee25sm1568103edb.6.2021.07.08.09.40.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 08 Jul 2021 09:40:22 -0700 (PDT) From: Lorenzo Bianconi To: dev@openvswitch.org Date: Thu, 8 Jul 2021 18:40:03 +0200 Message-Id: <65f2c074f66a6fba0745940b555dc2b40787a36c.1625762235.git.lorenzo.bianconi@redhat.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=lorenzo.bianconi@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Cc: dceara@redhat.com Subject: [ovs-dev] [PATCH v6 ovn 2/4] ovn-northd: Add support for CoPP. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Dumitru Ceara 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_Router 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). Acked-by: Mark D. Gray Co-authored-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Signed-off-by: Dumitru Ceara --- lib/automake.mk | 2 + lib/copp.c | 142 ++++++++++++++++++++++++++++++ lib/copp.h | 58 +++++++++++++ northd/ovn-northd.c | 55 ++++++++---- ovn-nb.ovsschema | 16 +++- ovn-nb.xml | 78 +++++++++++++++++ tests/ovn-controller.at | 52 +++++++++++ tests/ovn-northd.at | 96 ++++++++++++++++++++ utilities/ovn-nbctl.8.xml | 72 +++++++++++++++ utilities/ovn-nbctl.c | 178 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 733 insertions(+), 16 deletions(-) create mode 100644 lib/copp.c create mode 100644 lib/copp.h diff --git a/lib/automake.mk b/lib/automake.mk index 917b28e1e..ac0fde8a3 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..e3d14938a --- /dev/null +++ b/lib/copp.c @@ -0,0 +1,142 @@ +/* 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 +#include + +#include "openvswitch/shash.h" +#include "db-ctl-base.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 const char * +copp_proto_get_name(enum copp_proto proto) +{ + if (proto >= COPP_PROTO_MAX) { + return ""; + } + 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; +} + +void +copp_meter_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); + } +} + +const struct nbrec_copp * +copp_meter_add(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; +} + +void +copp_meter_del(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); + } +} + +char * +copp_proto_validate(const char *proto_name) +{ + for (size_t i = COPP_PROTO_FIRST; i < COPP_PROTO_MAX; i++) { + if (!strcmp(proto_name, copp_proto_get_name(i))) { + 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++) { + ds_put_format(&usage, "%s, ", copp_proto_get_name(i)); + } + ds_chomp(&usage, ' '); + ds_chomp(&usage, ','); + ds_put_cstr(&usage, "."); + + return ds_steal_cstr(&usage); +} diff --git a/lib/copp.h b/lib/copp.h new file mode 100644 index 000000000..c34e1e029 --- /dev/null +++ b/lib/copp.h @@ -0,0 +1,58 @@ +/* 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; +struct ctl_context; + +const char *copp_meter_get(enum copp_proto proto, + const struct nbrec_copp *copp, + const struct shash *meter_groups); + +void copp_meter_list(struct ctl_context *ctx, const struct nbrec_copp *copp); +const struct nbrec_copp * +copp_meter_add(struct ctl_context *ctx, const struct nbrec_copp *copp, + const char *proto_name, const char *meter); +void +copp_meter_del(const struct nbrec_copp *copp, const char *proto_name); +char * copp_proto_validate(const char *proto_name); + +#endif /* lib/copp.h */ diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 570c6a3ef..9845d634c 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" @@ -4102,6 +4103,7 @@ struct ovn_lflow { char *match; char *actions; char *stage_hint; + char *ctrl_meter; const char *where; }; @@ -4110,7 +4112,8 @@ static struct ovn_lflow *ovn_lflow_find(const struct hmap *lflows, const struct ovn_datapath *od, enum ovn_stage stage, uint16_t priority, const char *match, - const char *actions, uint32_t hash); + const char *actions, + const char *ctrl_meter, uint32_t hash); static char * ovn_lflow_hint(const struct ovsdb_idl_row *row) @@ -4124,20 +4127,21 @@ ovn_lflow_hint(const struct ovsdb_idl_row *row) static bool ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_datapath *od, enum ovn_stage stage, uint16_t priority, const char *match, - const char *actions) + const char *actions, const char *ctrl_meter) { return (a->od == od && a->stage == stage && a->priority == priority && !strcmp(a->match, match) - && !strcmp(a->actions, actions)); + && !strcmp(a->actions, actions) + && nullable_string_is_equal(a->ctrl_meter, 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; @@ -4146,6 +4150,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; } @@ -4164,7 +4169,7 @@ do_ovn_lflow_add(struct hmap *lflow_map, struct ovn_datapath *od, uint32_t hash, enum ovn_stage stage, uint16_t priority, const char *match, const char *actions, const struct ovsdb_idl_row *stage_hint, - const char *where) + const char *where, const char *ctrl_meter) { struct ovn_lflow *old_lflow; @@ -4172,7 +4177,7 @@ do_ovn_lflow_add(struct hmap *lflow_map, struct ovn_datapath *od, if (use_logical_dp_groups) { old_lflow = ovn_lflow_find(lflow_map, NULL, stage, priority, match, - actions, hash); + actions, ctrl_meter, hash); if (old_lflow) { hmapx_add(&old_lflow->od_group, od); return; @@ -4185,6 +4190,7 @@ do_ovn_lflow_add(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); hmapx_add(&lflow->od_group, od); hmap_insert_fast(lflow_map, &lflow->hmap_node, hash); @@ -4195,6 +4201,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, + 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)); @@ -4209,32 +4216,45 @@ ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od, if (use_logical_dp_groups && use_parallel_build) { lock_hash_row(&lflow_locks, hash); do_ovn_lflow_add(lflow_map, od, hash, stage, priority, match, - actions, stage_hint, where); + actions, stage_hint, where, ctrl_meter); unlock_hash_row(&lflow_locks, hash); } else { do_ovn_lflow_add(lflow_map, od, hash, stage, priority, match, - actions, stage_hint, where); + actions, stage_hint, where, ctrl_meter); } } /* 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, \ + 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, \ - 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, \ - 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) + static struct ovn_lflow * ovn_lflow_find(const struct hmap *lflows, const 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 *lflow; HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) { - if (ovn_lflow_equal(lflow, od, stage, priority, match, actions)) { + if (ovn_lflow_equal(lflow, od, stage, priority, match, actions, + ctrl_meter)) { return lflow; } } @@ -4252,6 +4272,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); } } @@ -12557,7 +12578,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. */ @@ -12602,6 +12624,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 @@ -14351,6 +14374,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 faf619a1c..a53d5e851 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", "version": "5.32.0", - "cksum": "204590300 28863", + "cksum": "2501921026 29540", "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"}}, @@ -322,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", diff --git a/ovn-nb.xml b/ovn-nb.xml index 36a77097c..1e285851d 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -354,6 +354,68 @@ + +

+ This table is used to define control plane protection policies, i.e., + associate entries from table to control protocol + names. +

+ + Rate limiting meter for ARP packets (request/reply) used for learning + neighbors. + + + Rate limiting meter for packets that require resolving the next-hop + (through ARP). + + + Rate limiting meter for packets that require adding DHCPv4 options. + + + Rate limiting meter for packets that require adding DHCPv6 options. + + + Rate limiting meter for DNS query packets that need to be replied to. + + + Rate limiting meter for empty load balancer events. + + + Rate limiting meter for packets that require replying with an ICMP + error. + + + Rate limiting meter for packets that require replying with an ICMPv6 + error. + + + Rate limiting meter for IGMP packets. + + + Rate limiting meter for ND neighbor advertisement packets used for + learning neighbors. + + + Rate limiting meter for ND neighbor solicitation packets used for + learning neighbors. + + + Rate limiting meter for packets that require resolving the next-hop + (through ND). + + + Rate limiting meter for packets that require adding ND router + advertisement options. + + + Rate limiting meter for packets that require replying with TCP RST + packet. + + + Rate limiting meter for BFD packets. + +
+

Each row represents one L2 logical switch. @@ -587,6 +649,14 @@ + +

+ The control plane protection policy from table + used for metering packets sent to ovn-controller from + ports of this logical switch. +

+ + @@ -1982,6 +2052,14 @@ + +

+ The control plane protection policy from table + used for metering packets sent to ovn-controller from + logical ports of this router. +

+
+

Additional options for the logical router. diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at index 42b10097e..ca210ba61 100644 --- a/tests/ovn-controller.at +++ b/tests/ovn-controller.at @@ -601,3 +601,55 @@ cat hv1/ovn-controller.log OVN_CLEANUP([hv1]) AT_CLEANUP + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-controller - ovn action metering]) +AT_KEYWORDS([action-metering]) + +dnl This test is not suported by ovn-northd-ddlog yet. +AT_SKIP_IF([test NORTHD_TYPE = ovn-northd-ddlog && test "$RUN_ANYWAY" != yes]) + +ovn_start + +net_add n1 +sim_add hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 + +check ovn-nbctl lr-add lr1 \ + -- lrp-add lr1 rp-ls1 00:00:01:01:02:03 192.168.1.254/24 +check ovn-nbctl ls-add ls1 \ + -- lsp-add ls1 lsp1 \ + -- lsp-set-addresses lsp1 "00:00:00:00:00:01 192.168.1.1" \ + -- lsp-add ls1 ls1-rp \ + -- set Logical_Switch_Port ls1-rp type=router options:router-port=rp-ls1 \ + -- lsp-set-addresses ls1-rp router + +as hv1 +check ovs-vsctl \ + -- add-port br-int vif1 \ + -- set Interface vif1 external_ids:iface-id=lsp1 + +check ovn-nbctl --event lb-add lb1 192.168.1.100:80 "" +check ovn-nbctl ls-lb-add ls1 lb1 + +# controller-event metering +check ovn-nbctl meter-add event-elb drop 100 pktps 10 +check ovn-nbctl ls-copp-add ls1 event-elb event-elb + +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep controller | grep userdata=00.00.00.0f | grep -q meter_id=1]) + +# reject metering +check ovn-nbctl meter-add acl-meter drop 1 pktps 0 +check ovn-nbctl ls-copp-add ls1 reject acl-meter +check ovn-nbctl acl-add ls1 from-lport 1002 'inport == "lsp1" && ip && udp' reject +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep controller | grep userdata=00.00.00.16 | grep -q meter_id=2]) + +# arp metering +check ovn-nbctl meter-add arp-meter drop 200 pktps 0 +check ovn-nbctl lr-copp-add lr1 arp-resolve arp-meter +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep controller | grep userdata=00.00.00.00 | grep -q meter_id=3]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 02640d163..1c5402e19 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -3058,6 +3058,102 @@ wait_row_count bfd 3 AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn -- check CoPP config]) +AT_KEYWORDS([northd-CoPP]) + +dnl This test is not suported by ovn-northd-ddlog yet. +AT_SKIP_IF([test NORTHD_TYPE = ovn-northd-ddlog && test "$RUN_ANYWAY" != yes]) + +ovn_start + +check ovn-nbctl --wait=sb lr-add r0 +check ovn-nbctl --wait=sb lrp-add r0 r0-sw1 00:00:00:00:00:01 192.168.1.1/24 +check ovn-nbctl --wait=sb ls-add sw1 +check ovn-nbctl --wait=sb lsp-add sw1 sw1-r0 +check ovn-nbctl --wait=sb lsp-set-type sw1-r0 router +check ovn-nbctl --wait=sb lsp-set-options sw1-r0 router-port=r0-sw1 +check ovn-nbctl --wait=sb lsp-set-addresses sw1-r0 00:00:00:00:00:01 + +check ovn-nbctl --event lb-add lb0 192.168.1.100:80 "" +check ovn-nbctl ls-lb-add sw1 lb0 +check ovn-nbctl --wait=hv meter-add meter0 drop 100 pktps 10 +check ovn-nbctl --wait=hv ls-copp-add sw1 event-elb meter0 +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +event-elb: meter0 +]) + +AT_CHECK([ovn-sbctl list logical_flow | grep trigger_event -A 2 | grep -q meter0]) + +check ovn-nbctl --wait=hv meter-add meter1 drop 200 pktps 10 +check ovn-nbctl --wait=hv lr-copp-add r0 arp meter1 +AT_CHECK([ovn-nbctl lr-copp-list r0], [0], [dnl +arp: meter1 +]) + +AT_CHECK([ovn-sbctl list logical_flow | grep arp -A 2 | grep -q meter1]) + +check ovn-nbctl --wait=hv lr-copp-del r0 arp +AT_CHECK([ovn-nbctl lr-copp-list r0], [0], [dnl +]) + +AT_CHECK([ovn-sbctl list logical_flow | grep arp -A 2 | grep -q meter1],[1]) + +check ovn-nbctl --wait=hv ls-copp-del sw1 event-elb +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +]) + +AT_CHECK([ovn-sbctl list logical_flow | grep trigger_event -A 2 | grep -q meter0],[1]) + +# let's try to add an usupported protocol "dhcp" +AT_CHECK([ovn-nbctl --wait=hv ls-copp-add sw1 dhcp meter1],[1],[],[dnl +ovn-nbctl: Invalid control protocol. Allowed values: arp, arp-resolve, dhcpv4-opts, dhcpv6-opts, dns, event-elb, icmp4-error, icmp6-error, igmp, nd-na, nd-ns, nd-ns-resolve, nd-ra-opts, tcp-reset, bfd, reject. +]) +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +]) + +#Let's try to add a valid protocol to an unknown datapath +AT_CHECK([ovn-nbctl --wait=hv ls-copp-add sw10 arp meter1],[1],[],[dnl +ovn-nbctl: sw10: switch name not found +]) +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +]) + +check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw1 +check ovn-nbctl --wait=hv lr-copp-add r0 bfd meter0 +AT_CHECK([ovn-nbctl lr-copp-list r0], [0], [dnl +bfd: meter0 +]) +AT_CHECK([ovn-sbctl list logical_flow | grep bfd -A 2 | grep -q meter0]) + +check ovn-nbctl --wait=hv set Logical_Switch sw1 \ + other_config:mcast_querier="false" \ + other_config:mcast_snoop="true" +check ovn-nbctl --wait=hv ls-copp-add sw1 igmp meter1 +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +igmp: meter1 +]) +AT_CHECK([ovn-sbctl list logical_flow | grep igmp -A 2 | grep -q meter1]) + +# let's add igmp meter1 twice +AT_CHECK([ovn-nbctl --wait=hv ls-copp-add sw1 igmp meter1]) +AT_CHECK([ovn-nbctl ls-copp-list sw1], [0], [dnl +igmp: meter1 +]) + +# let's delete a wrong meter +AT_CHECK([ovn-nbctl --wait=hv lr-copp-del r0 event-elb]) +AT_CHECK([ovn-nbctl lr-copp-list r0], [0], [dnl +bfd: meter0 +]) + +check ovn-nbctl lr-copp-del r0 +AT_CHECK([ovn-nbctl lr-copp-list r0], [0], [dnl +]) + +AT_CLEANUP +]) + AT_SETUP([check LSP attached to multiple LS]) ovn_start diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml index c3ff87e74..101849911 100644 --- a/utilities/ovn-nbctl.8.xml +++ b/utilities/ovn-nbctl.8.xml @@ -1446,6 +1446,78 @@ +

Control Plane Protection Policy commands

+

+ These commands manage meters configured in table + linking them to logical datapaths through copp column in + or tables. + Protocol packets for which CoPP is enforced when sending packets to + ovn-controller (if configured): +

    +
  • ARP
  • +
  • ND_NS
  • +
  • ND_NA
  • +
  • ND_RA
  • +
  • ND
  • +
  • DNS
  • +
  • IGMP
  • +
  • packets that require ARP resolution before forwarding
  • +
  • packets that require ND_NS before forwarding
  • +
  • packets that need to be replied to with ICMP Errors
  • +
  • packets that need to be replied to with TCP RST
  • +
  • packets that need to be replied to with DHCP_OPTS
  • +
  • BFD
  • +
+

+ +
+
ls-copp-add switch proto + meter
+
+ Adds the control proto to meter mapping + to the switch control plane protection policy. If no + policy exists yet, it creates one. If a mapping already existed for + proto, this will overwrite it. +
+ +
ls-copp-del switch [proto]
+
+ Removes the control proto mapping from the + switch control plane protection policy. If + proto is not specified, the whole control plane + protection policy is destroyed. +
+ +
ls-copp-list switch
+
+ Display the current control plane protection policy for + switch. +
+ +
lr-copp-add router proto + meter
+
+ Adds the control proto to meter mapping + to the router control plane protection policy. If no + policy exists yet, it creates one. If a mapping already existed for + proto, this will overwrite it. +
+ +
lr-copp-del router [proto]
+
+ Removes the control proto mapping from the + router control plane protection policy. If + proto is not specified, the whole control plane + protection policy is destroyed. +
+ +
lr-copp-list router
+
+ Display the current control plane protection policy for + router. +
+
+

Synchronization Commands

diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index dc13fa9ca..7d2ee0bea 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" @@ -425,6 +426,28 @@ 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\ + 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\ +\n\ %s\ %s\ \n\ @@ -6000,6 +6023,147 @@ nbctl_lr_route_list(struct ctl_context *ctx) free(ipv6_routes); } +static void +nbctl_pre_copp(struct ctl_context *ctx) +{ + nbctl_pre_context(ctx); + ovsdb_idl_add_column(ctx->idl, &nbrec_copp_col_meters); + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_col_copp); + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_copp); +} + +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); + 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_meter_add(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); + 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_meter_del(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_meter_list(ctx, ls->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); + 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_meter_add(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); + 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_meter_del(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_meter_list(ctx, lr->copp); +} + static void verify_connections(struct ctl_context *ctx) { @@ -6756,6 +6920,20 @@ static const struct ctl_command_syntax nbctl_commands[] = { nbctl_pre_dhcp_options_options, nbctl_dhcp_options_get_options, NULL, "", RO }, + /* Control plane protection commands */ + {"ls-copp-add", 3, 3, "SWITCH PROTO METER", nbctl_pre_copp, + nbctl_ls_copp_add, NULL, "", RW}, + {"ls-copp-del", 1, 2, "SWITCH [PROTO]", nbctl_pre_copp, + nbctl_ls_copp_del, NULL, "", RW}, + {"ls-copp-list", 1, 1, "SWITCH", nbctl_pre_copp, nbctl_ls_copp_list, + NULL, "", RO}, + {"lr-copp-add", 3, 3, "ROUTER PROTO METER", nbctl_pre_copp, + nbctl_lr_copp_add, NULL, "", RW}, + {"lr-copp-del", 1, 2, "ROUTER [PROTO]", nbctl_pre_copp, + nbctl_lr_copp_del, NULL, "", RW}, + {"lr-copp-list", 1, 1, "ROUTER", nbctl_pre_copp, nbctl_lr_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},