From patchwork Thu Jun 10 11:04:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vasu Dasari X-Patchwork-Id: 1490386 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::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=LnI3P6zv; dkim-atps=neutral Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (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 4G11MD6B9kz9sRN for ; Thu, 10 Jun 2021 21:04:24 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 5B850403EA; Thu, 10 Jun 2021 11:04:21 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id y4ty11f3dNyL; Thu, 10 Jun 2021 11:04:19 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp2.osuosl.org (Postfix) with ESMTPS id 899B540223; Thu, 10 Jun 2021 11:04:18 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 4DB23C000E; Thu, 10 Jun 2021 11:04:18 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 06A88C000B for ; Thu, 10 Jun 2021 11:04:17 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id DBB2F607D6 for ; Thu, 10 Jun 2021 11:04:16 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Authentication-Results: smtp3.osuosl.org (amavisd-new); dkim=pass (2048-bit key) header.d=gmail.com Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id jKNZHtdkVmhT for ; Thu, 10 Jun 2021 11:04:15 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.8.0 Received: from mail-qk1-x731.google.com (mail-qk1-x731.google.com [IPv6:2607:f8b0:4864:20::731]) by smtp3.osuosl.org (Postfix) with ESMTPS id EEBE360754 for ; Thu, 10 Jun 2021 11:04:14 +0000 (UTC) Received: by mail-qk1-x731.google.com with SMTP id j184so26839755qkd.6 for ; Thu, 10 Jun 2021 04:04:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=atn1CIm9cZQ9xUlGjGYc/5J06DhD/EtQrc/g8MwaQPE=; b=LnI3P6zvMzbnUep96LK0ZMzWAwimfcctRsbCgOOXxOvh7WIGeUsgXzcmD6OCmQ14+U 2SnnaAVcMTrmAQG1ENJ5vg3TUh9VE/quLMeKXS8RDv2tjoSlDbNVvfZnzvxM8Ijzh4Nd mOAriJy4OHy/jlwsqSvV+TO7t8vjnEuEz2LWekpLOSuyYSgp+Lf64NZr0tH5dBOI4/U9 GJAB8i2PGRJZzRY9AvrUL+arUckv0dYEts1cGtE1bjdJJlzy5//PLAu9zuVt8rCbXGqQ XPe2csJKzetsUJiPaB87Wdeiyzum12NPddlpxOQihn2hYFQgXQfBEkysB/kMPeb34Vdz 7PSQ== 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:mime-version :content-transfer-encoding; bh=atn1CIm9cZQ9xUlGjGYc/5J06DhD/EtQrc/g8MwaQPE=; b=SSy9UrE2/1SymamLESA7UoFEEwck/LzImWTQDO3eVySSwBCHoNiJqi5alC0zr88t54 eAVbrytAwaHLIVXdBsmZBCHMgr+003Nk4IA6Hj8giwQ8QFiUZDXgrddJJ7Ny2kkh+mn6 ozbdb7l+eOiq4xgb+7wiE5jgnB+PVty6dFaKD2yGeKsRaVZqsokHVvA3xDzygr7ANa2P GkxeWZFLMIxfow0Xpd+22DgEOXj6hioPlM8t/7awuRYXdQruJxMEotNZT2yxeuLQ9gJp qFIl2o1oJppIvQrlqTm11WsWGewcg8jZ2HD9hhRigguz+mR4ltu6Q72wK+2L2BVDTq0r 3Gsg== X-Gm-Message-State: AOAM531CCopCLihMvlf5R412naBiyrAWNBz0IUAJb/c/CPd/XPu2u5UU EwdOu24rZq+tYORJUh+XHo9Jg+Xqn15KmP77 X-Google-Smtp-Source: ABdhPJyYRDpI07KUUJoBXBJLas/oQXcvYo3VDqm0FPGRlhvfKxnA0ZdSH96JeKIf5YAYDAYs2+/WnA== X-Received: by 2002:a37:7307:: with SMTP id o7mr4012382qkc.352.1623323052750; Thu, 10 Jun 2021 04:04:12 -0700 (PDT) Received: from localhost.localdomain ([73.61.18.144]) by smtp.gmail.com with ESMTPSA id n194sm1906122qka.66.2021.06.10.04.04.11 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 10 Jun 2021 04:04:12 -0700 (PDT) From: Vasu Dasari To: dev@openvswitch.org Date: Thu, 10 Jun 2021 07:04:01 -0400 Message-Id: <20210610110401.26451-1-vdasari@gmail.com> X-Mailer: git-send-email 2.29.2 MIME-Version: 1.0 Subject: [ovs-dev] [PATCH v5] ofproto-dpif: APIs and CLI option to add/delete static fdb entry 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" Currently there is an option to add/flush/show ARP/ND neighbor. This covers L3 side. For L2 side, there is only fdb show command. This patch gives an option to add/del an fdb entry via ovs-appctl. CLI command looks like: To add: ovs-appctl fdb/add ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05 To del: ovs-appctl fdb/del ovs-appctl fdb/del br0 p1 0 50:54:00:00:00:05 Added two new APIs to provide convenient interface to add and delete static-macs. bool xlate_add_static_mac_entry(const struct ofproto_dpif *, ofp_port_t in_port, struct eth_addr dl_src, int vlan); bool xlate_delete_static_mac_entry(const struct ofproto_dpif *, struct eth_addr dl_src, int vlan); 1. Static entry should not age. To indicate that entry being programmed is a static entry, 'expires' field in 'struct mac_entry' will be set to a MAC_ENTRY_AGE_STATIC_ENTRY. A check for this value is made while deleting mac entry as part of regular aging process. 2. Another change to of mac-update logic, when a packet with same dl_src as that of a static-mac entry arrives on any port, the logic will not modify the expires field. 3. While flushing fdb entries, made sure static ones are not evicted. 4. Updated "ovs-appctl fdb/stats-show br0" to display numberof static entries in switch Added following tests: ofproto-dpif - static-mac add/del/flush ofproto-dpif - static-mac mac moves Signed-off-by: Vasu Dasari Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2019-June/048894.html Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1597752 Tested-by: Eelco Chaudron --- v1: - Fixed 0-day robot warnings v2: - Fix valgrind error in the modified code in mac_learning_insert() where a read is is performed on e->expires which is not initialized v3: - Addressed code review comments - Added more documentation - Fixed mac_entry_age() and is_mac_learning_update_needed() to have common understanding of return values when mac_entry is a static one. - Added NEWS item v4: - Addressed code review comments - Static entries will not be purged when fdb/flush is performed. - Static entries will not be overwritten when packet with same dl_src arrives on any port of switch - Provided bit more detail while doing fdb/add, to indicate if static-mac is overriding already present entry - Separated test cases for a bit more clarity v5: - Addressed code review comments - Added new total_static counter to count number of static entries. - Removed mac_entry_set_idle_time() - Added mac_learning_add_static_entry() and mac_learning_del_static_entry() - Modified APIs xlate_add_static_mac_entry() and xlate_delete_static_mac_entry() return 0 on success else a failure code --- NEWS | 2 + lib/mac-learning.c | 141 ++++++++++++++++++++++++++++++----- lib/mac-learning.h | 17 +++++ ofproto/ofproto-dpif-xlate.c | 48 ++++++++++-- ofproto/ofproto-dpif-xlate.h | 5 ++ ofproto/ofproto-dpif.c | 95 ++++++++++++++++++++++- tests/ofproto-dpif.at | 93 +++++++++++++++++++++++ 7 files changed, 372 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index ebba17b22..501b26cee 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ Post-v2.15.0 - Userspace datapath: * Auto load balancing of PMDs now partially supports cross-NUMA polling cases, e.g if all PMD threads are running on the same NUMA node. + * Added ability to add and delete static mac entries using: + 'ovs-appctl fdb/{add,del} ' - ovs-ctl: * New option '--no-record-hostname' to disable hostname configuration in ovsdb on startup. diff --git a/lib/mac-learning.c b/lib/mac-learning.c index 3d5293d3b..66cb3eefb 100644 --- a/lib/mac-learning.c +++ b/lib/mac-learning.c @@ -35,12 +35,23 @@ COVERAGE_DEFINE(mac_learning_expired); COVERAGE_DEFINE(mac_learning_evicted); COVERAGE_DEFINE(mac_learning_moved); -/* Returns the number of seconds since 'e' (within 'ml') was last learned. */ +/* + * This function will return age of mac entry in the fdb. It + * will return either one of the following: + * 1. Number of seconds since 'e' (within 'ml') was last learned. + * 2. If the mac entry is a static entry, it returns + * MAC_ENTRY_AGE_STATIC_ENTRY + */ int mac_entry_age(const struct mac_learning *ml, const struct mac_entry *e) { - time_t remaining = e->expires - time_now(); - return ml->idle_time - remaining; + /* For static fdb entries, expires would be MAC_ENTRY_AGE_STATIC_ENTRY */ + if (MAC_ENTRY_AGE_STATIC_ENTRY == e->expires) { + return e->expires; + } else { + time_t remaining = e->expires - time_now(); + return ml->idle_time - remaining; + } } static uint32_t @@ -187,6 +198,7 @@ mac_learning_clear_statistics(struct mac_learning *ml) { if (ml != NULL) { ml->total_learned = 0; + ml->total_static = 0; ml->total_expired = 0; ml->total_evicted = 0; ml->total_moved = 0; @@ -309,16 +321,19 @@ mac_learning_may_learn(const struct mac_learning *ml, } /* Searches 'ml' for and returns a MAC learning entry for 'src_mac' in 'vlan', - * inserting a new entry if necessary. The caller must have already verified, - * by calling mac_learning_may_learn(), that 'src_mac' and 'vlan' are - * learnable. + * inserting a new entry if necessary. If entry being added is a + * 1. cache entry: caller must have already verified, by calling + * mac_learning_may_learn(), that 'src_mac' and 'vlan' are learnable. + * 2. static entry: new mac static fdb entry will be created or if one + * exists already, converts that entry to a static fdb type. * * If the returned MAC entry is new (that is, if it has a NULL client-provided * port, as returned by mac_entry_get_port()), then the caller must initialize * the new entry's port to a nonnull value with mac_entry_set_port(). */ -struct mac_entry * -mac_learning_insert(struct mac_learning *ml, - const struct eth_addr src_mac, uint16_t vlan) +static struct mac_entry * +mac_learning_insert__(struct mac_learning *ml, const struct eth_addr src_mac, + uint16_t vlan, bool is_static) + OVS_REQ_WRLOCK(ml->rwlock) { struct mac_entry *e; @@ -336,8 +351,11 @@ mac_learning_insert(struct mac_learning *ml, e->vlan = vlan; e->grat_arp_lock = TIME_MIN; e->mlport = NULL; - COVERAGE_INC(mac_learning_learned); - ml->total_learned++; + e->expires = 0; + if (!is_static) { + COVERAGE_INC(mac_learning_learned); + ml->total_learned++; + } } else { ovs_list_remove(&e->lru_node); } @@ -348,11 +366,80 @@ mac_learning_insert(struct mac_learning *ml, ovs_list_remove(&e->port_lru_node); ovs_list_push_back(&e->mlport->port_lrus, &e->port_lru_node); } - e->expires = time_now() + ml->idle_time; + + /* Update 'expires' for mac entry */ + if (is_static) { + /* Increment total_static only if entry is a new one or entry is + * is converted from cache to static type */ + if (e->expires != MAC_ENTRY_AGE_STATIC_ENTRY) { + ml->total_static++; + } + e->expires = MAC_ENTRY_AGE_STATIC_ENTRY; + } else { + e->expires = time_now() + ml->idle_time; + } return e; } +/* Adds a new dynamic mac entry to fdb */ +struct mac_entry * +mac_learning_insert(struct mac_learning *ml, + const struct eth_addr src_mac, uint16_t vlan) +{ + return mac_learning_insert__(ml, src_mac, vlan, false); +} + +/* Adds a new static mac entry to fdb. It returns pointer to mac_entry + * pointing to the fdb entry + * + * Returns 'true' if mac entry is inserted, 'false' otherwise + */ +bool +mac_learning_add_static_entry(struct mac_learning *ml, + const struct eth_addr src_mac, uint16_t vlan, + void *in_port) + OVS_EXCLUDED(ml->rwlock) +{ + struct mac_entry *mac = NULL; + bool inserted = false; + + ovs_rwlock_wrlock(&ml->rwlock); + mac = mac_learning_insert__(ml, src_mac, vlan, true); + if (mac) { + if (mac_entry_get_port(ml, mac) != in_port) { + mac_entry_set_port(ml, mac, in_port); + } + inserted = true; + } + ovs_rwlock_unlock(&ml->rwlock); + + return inserted; +} + +/* Delete a static mac entry from fdb if it exists. + * + * Returns 'true' if mac entry is found, 'false' otherwise + */ +bool +mac_learning_del_static_entry(struct mac_learning *ml, + const struct eth_addr dl_src, uint16_t vlan) +{ + struct mac_entry *mac = NULL; + bool deleted = false; + + ovs_rwlock_wrlock(&ml->rwlock); + mac = mac_learning_lookup(ml, dl_src, vlan); + if (mac) { + mac_learning_expire(ml, mac); + ml->total_static--; + deleted = true; + } + ovs_rwlock_unlock(&ml->rwlock); + + return deleted; +} + /* Checks whether a MAC learning update is necessary for MAC learning table * 'ml' given that a packet matching 'src' was received on 'in_port' in 'vlan', * and given that the packet was gratuitous ARP if 'is_gratuitous_arp' is @@ -378,10 +465,16 @@ is_mac_learning_update_needed(const struct mac_learning *ml, } mac = mac_learning_lookup(ml, src, vlan); - if (!mac || mac_entry_age(ml, mac)) { + /* If mac entry is missing it needs to be added to fdb */ + if (!mac) { return true; } + /* if mac is a static entry, then there is no need to update */ + if (mac_entry_age(ml, mac) == MAC_ENTRY_AGE_STATIC_ENTRY) { + return false; + } + if (is_gratuitous_arp) { /* We don't want to learn from gratuitous ARP packets that are * reflected back over bond members so we lock the learning table. For @@ -513,13 +606,27 @@ mac_learning_expire(struct mac_learning *ml, struct mac_entry *e) free(e); } -/* Expires all the mac-learning entries in 'ml'. */ +/* Expires all the dynamic mac-learning entries in 'ml'. */ void mac_learning_flush(struct mac_learning *ml) { - struct mac_entry *e; - while (get_lru(ml, &e)){ - mac_learning_expire(ml, e); + struct mac_entry *e, *first_static_mac = NULL; + + while (get_lru(ml, &e) && (e != first_static_mac)) { + /* static-mac should not be evicted */ + if (MAC_ENTRY_AGE_STATIC_ENTRY == e->expires) { + /* Make note of first static-mac encountered, so that this while + * loop will break on visting this mac again via get_lru() */ + if (!first_static_mac) { + first_static_mac = e; + } + + /* Remove from lru head and append it to tail */ + ovs_list_remove(&e->lru_node); + ovs_list_push_back(&ml->lrus, &e->lru_node); + } else { + mac_learning_expire(ml, e); + } } hmap_shrink(&ml->table); } diff --git a/lib/mac-learning.h b/lib/mac-learning.h index 0ddab06cb..f03243607 100644 --- a/lib/mac-learning.h +++ b/lib/mac-learning.h @@ -57,6 +57,11 @@ * list starting from the LRU end, deleting each entry that has been idle too * long. * + * Fourth, a mac entry can be configured statically via API or appctl commands. + * Static entries are programmed to have an age of MAC_ENTRY_AGE_STATIC_ENTRY. + * Age of static entries will not be updated by a receiving packet as part of + * regular packet processing. + * * Finally, the number of MAC learning table entries has a configurable maximum * size to prevent memory exhaustion. When a new entry must be inserted but * the table is already full, the implementation uses an eviction strategy @@ -94,6 +99,9 @@ struct mac_learning; /* Time, in seconds, before expiring a mac_entry due to inactivity. */ #define MAC_ENTRY_DEFAULT_IDLE_TIME 300 +/* Age value to represent a static entry */ +#define MAC_ENTRY_AGE_STATIC_ENTRY INT_MAX + /* Time, in seconds, to lock an entry updated by a gratuitous ARP to avoid * relearning based on a reflection from a bond member. */ #define MAC_GRAT_ARP_LOCK_TIME 5 @@ -162,6 +170,7 @@ struct mac_learning { /* Statistics */ uint64_t total_learned; + uint64_t total_static; uint64_t total_expired; uint64_t total_evicted; uint64_t total_moved; @@ -218,6 +227,14 @@ bool mac_learning_update(struct mac_learning *ml, struct eth_addr src, int vlan, bool is_gratuitous_arp, bool is_bond, void *in_port) OVS_EXCLUDED(ml->rwlock); +bool mac_learning_add_static_entry(struct mac_learning *ml, + const struct eth_addr src, + uint16_t vlan, void *in_port) + OVS_EXCLUDED(ml->rwlock); +bool mac_learning_del_static_entry(struct mac_learning *ml, + const struct eth_addr src, + uint16_t vlan) + OVS_EXCLUDED(ml->rwlock); /* Lookup. */ struct mac_entry *mac_learning_lookup(const struct mac_learning *ml, diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 7108c8a30..b74255fc7 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -7989,26 +7989,58 @@ xlate_send_packet(const struct ofport_dpif *ofport, bool oam, ofpacts.data, ofpacts.size, packet); } -void -xlate_mac_learning_update(const struct ofproto_dpif *ofproto, - ofp_port_t in_port, struct eth_addr dl_src, - int vlan, bool is_grat_arp) +/* Get xbundle for a ofp_port in a ofproto datapath */ +static struct xbundle* +ofp_port_to_xbundle(const struct ofproto_dpif *ofproto, ofp_port_t ofp_port) { struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp); struct xbridge *xbridge; - struct xbundle *xbundle; xbridge = xbridge_lookup(xcfg, ofproto); if (!xbridge) { - return; + return NULL; } - xbundle = lookup_input_bundle__(xbridge, in_port, NULL); + return lookup_input_bundle__(xbridge, ofp_port, NULL); +} + +void +xlate_mac_learning_update(const struct ofproto_dpif *ofproto, + ofp_port_t in_port, struct eth_addr dl_src, + int vlan, bool is_grat_arp) +{ + struct xbundle *xbundle = NULL; + + xbundle = ofp_port_to_xbundle(ofproto, in_port); if (!xbundle) { return; } - update_learning_table__(xbridge, xbundle, dl_src, vlan, is_grat_arp); + update_learning_table__(xbundle->xbridge, + xbundle, dl_src, vlan, is_grat_arp); +} + +bool +xlate_add_static_mac_entry(const struct ofproto_dpif *ofproto, + ofp_port_t in_port, + struct eth_addr dl_src, int vlan) +{ + struct xbundle *xbundle = ofp_port_to_xbundle(ofproto, in_port); + + /* Return here if xbundle */ + if (!xbundle || (xbundle == &ofpp_none_bundle)) { + return false; + } + + return mac_learning_add_static_entry(ofproto->ml, dl_src, vlan, + xbundle->ofbundle); +} + +bool +xlate_delete_static_mac_entry(const struct ofproto_dpif *ofproto, + struct eth_addr dl_src, int vlan) +{ + return mac_learning_del_static_entry(ofproto->ml, dl_src, vlan); } void diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h index 3426a27b2..92c930087 100644 --- a/ofproto/ofproto-dpif-xlate.h +++ b/ofproto/ofproto-dpif-xlate.h @@ -225,6 +225,11 @@ int xlate_send_packet(const struct ofport_dpif *, bool oam, struct dp_packet *); void xlate_mac_learning_update(const struct ofproto_dpif *ofproto, ofp_port_t in_port, struct eth_addr dl_src, int vlan, bool is_grat_arp); +bool xlate_add_static_mac_entry(const struct ofproto_dpif *, + ofp_port_t in_port, + struct eth_addr dl_src, int vlan); +bool xlate_delete_static_mac_entry(const struct ofproto_dpif *, + struct eth_addr dl_src, int vlan); void xlate_set_support(const struct ofproto_dpif *, const struct dpif_backer_support *); diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 47db9bb57..75843fc0e 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -5854,18 +5854,98 @@ ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc OVS_UNUSED, LIST_FOR_EACH (e, lru_node, &ofproto->ml->lrus) { struct ofbundle *bundle = mac_entry_get_port(ofproto->ml, e); char name[OFP_MAX_PORT_NAME_LEN]; + int age = mac_entry_age(ofproto->ml, e); ofputil_port_to_string(ofbundle_get_a_port(bundle)->up.ofp_port, - NULL, name, sizeof name); - ds_put_format(&ds, "%5s %4d "ETH_ADDR_FMT" %3d\n", - name, e->vlan, ETH_ADDR_ARGS(e->mac), - mac_entry_age(ofproto->ml, e)); + NULL, name, sizeof name); + ds_put_format(&ds, "%5s %4d "ETH_ADDR_FMT" ", + name, e->vlan, ETH_ADDR_ARGS(e->mac)); + if (MAC_ENTRY_AGE_STATIC_ENTRY == age) { + ds_put_format(&ds, "static\n"); + } else { + ds_put_format(&ds, "%3d\n", age); + } } ovs_rwlock_unlock(&ofproto->ml->rwlock); unixctl_command_reply(conn, ds_cstr(&ds)); ds_destroy(&ds); } +static void +ofproto_unixctl_fdb_update(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[], void *aux OVS_UNUSED) +{ + const char *br_name = argv[1]; + const char *port_name = argv[2]; + struct eth_addr mac; + uint16_t vlan = atoi(argv[3]); + const char *op = (const char *) aux; + const struct ofproto_dpif *ofproto; + ofp_port_t in_port = OFPP_NONE; + struct ofproto_port ofproto_port; + struct ds ds = DS_EMPTY_INITIALIZER; + + ofproto = ofproto_dpif_lookup_by_name(br_name); + if (!ofproto) { + unixctl_command_reply_error(conn, "no such bridge"); + return; + } + + if (!eth_addr_from_string(argv[4], &mac)) { + unixctl_command_reply_error(conn, "bad MAC address"); + return; + } + + if (ofproto_port_query_by_name(&ofproto->up, port_name, &ofproto_port)) { + unixctl_command_reply_error(conn, + "software error, odp port is present but no ofp port"); + return; + } + in_port = ofproto_port.ofp_port; + ofproto_port_destroy(&ofproto_port); + + if (!strcmp(op, "add")) { + /* Give a bit more information if the entry being added is overriding + * an existing entry */ + const struct mac_entry *mac_entry; + const struct ofbundle *bundle = NULL; + int age; + + ovs_rwlock_rdlock(&ofproto->ml->rwlock); + mac_entry = mac_learning_lookup(ofproto->ml, mac, vlan); + if (mac_entry) { + bundle = mac_entry ? + mac_entry_get_port(ofproto->ml, mac_entry) : NULL; + age = mac_entry->expires; + } + ovs_rwlock_unlock(&ofproto->ml->rwlock); + + if (bundle && ((strcmp(bundle->name, port_name)) || + (age != MAC_ENTRY_AGE_STATIC_ENTRY))) { + char old_port_name[OFP_MAX_PORT_NAME_LEN]; + + ofputil_port_to_string(ofbundle_get_a_port(bundle)->up.ofp_port, + NULL, old_port_name, sizeof old_port_name); + ds_put_format(&ds, "Overriding already existing %s entry on %s\n", + (age == MAC_ENTRY_AGE_STATIC_ENTRY) ? "static" : "dynamic", + old_port_name); + } + + if (!xlate_add_static_mac_entry(ofproto, in_port, mac, vlan)) { + ds_put_format(&ds, "could not add static mac entry\n"); + } + unixctl_command_reply(conn, ds_cstr(&ds)); + } else if (!strcmp(op, "del")) { + if (!xlate_delete_static_mac_entry(ofproto, mac, vlan)) { + ds_put_format(&ds, "could not find static mac entry\n"); + } + unixctl_command_reply(conn, ds_cstr(&ds)); + } else { + unixctl_command_reply_error(conn, "software error, unknown operation"); + } + ds_destroy(&ds); +} + static void ofproto_unixctl_fdb_stats_clear(struct unixctl_conn *conn, int argc, const char *argv[], void *aux OVS_UNUSED) @@ -5914,6 +5994,9 @@ ofproto_unixctl_fdb_stats_show(struct unixctl_conn *conn, int argc OVS_UNUSED, ds_put_format(&ds, " Total number of learned MAC entries : %"PRIu64"\n", ofproto->ml->total_learned); + ds_put_format(&ds, + " Total number of static MAC entries : %"PRIu64"\n", + ofproto->ml->total_static); ds_put_format(&ds, " Total number of expired MAC entries : %"PRIu64"\n", ofproto->ml->total_expired); @@ -6417,6 +6500,10 @@ ofproto_unixctl_init(void) } registered = true; + unixctl_command_register("fdb/add", "[bridge port vlan mac]", 4, 4, + ofproto_unixctl_fdb_update, "add"); + unixctl_command_register("fdb/del", "[bridge port vlan mac]", 4, 4, + ofproto_unixctl_fdb_update, "del"); unixctl_command_register("fdb/flush", "[bridge]", 0, 1, ofproto_unixctl_fdb_flush, NULL); unixctl_command_register("fdb/show", "bridge", 1, 1, diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 31064ed95..6fdbef265 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -6753,6 +6753,99 @@ PORTNAME portName=p2 ])]) +AT_SETUP([ofproto-dpif - static-mac add/del/flush]) +OVS_VSWITCHD_START([set bridge br0 fail-mode=standalone]) +add_of_ports br0 1 2 + +dnl Generate some dynamic fdb entries on some ports +OFPROTO_TRACE([ovs-dummy], [in_port(1),eth(src=50:54:00:00:00:01)], [-generate], [100,2]) +OFPROTO_TRACE([ovs-dummy], [in_port(2),eth(src=50:54:00:00:00:02)], [-generate], [100,1]) + +dnl Add some static mac entries +AT_CHECK([ovs-appctl fdb/add br0 p1 0 50:54:00:00:01:01]) +AT_CHECK([ovs-appctl fdb/add br0 p2 0 50:54:00:00:02:02]) + +dnl Check initial fdb +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ *[[0-9]]\{1,\}$//' | grep -v port | sort], [0], [dnl + 1 0 50:54:00:00:00:01 + 1 0 50:54:00:00:01:01 static + 2 0 50:54:00:00:00:02 + 2 0 50:54:00:00:02:02 static +]) + +dnl Remove static mac entry +AT_CHECK([ovs-appctl fdb/del br0 p1 0 50:54:00:00:01:01]) + +dnl Check that entry is removed +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | grep "50:54:00:00:01:01"], [1], [dnl +]) + +# Add some more cache ande static entries, to test out flush operation +for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do + ovs-appctl ofproto/trace ovs-dummy "in_port(1),eth(src=50:54:00:00:0$i:ff)" -generate +done + +for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do + ovs-appctl fdb/add br0 p2 0 50:54:00:0$i:00:ff +done + +dnl Flush mac entries, only dynamic ones should be evicted. +AT_CHECK([ovs-appctl fdb/flush br0], [0], [dnl +table successfully flushed +]) + +dnl Check that entry is removed +AT_CHECK_UNQUOTED([ovs-appctl fdb/stats-show br0 | grep static], [0], [dnl + Total number of static MAC entries : 17 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([ofproto-dpif - static-mac mac moves]) +OVS_VSWITCHD_START([set bridge br0 fail-mode=standalone]) +add_of_ports br0 1 2 + +dnl Generate a dynamic entry +OFPROTO_TRACE([ovs-dummy], [in_port(1),eth(src=50:54:00:00:00:00)], [-generate], [100,2]) + +dnl Convert dynamically learnt dl_src to a static-mac +AT_CHECK([ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:00], [0], [dnl +Overriding already existing dynamic entry on 1 +]) + +dnl Check fdb +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ *[[0-9]]\{1,\}$//' | grep -v port | sort], [0], [dnl + 1 0 50:54:00:00:00:00 static +]) + +dnl Move the static mac to different port +AT_CHECK([ovs-appctl fdb/add br0 p2 0 50:54:00:00:00:00], [0], [dnl +Overriding already existing static entry on 1 +]) + +dnl Check fdb +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ *[[0-9]]\{1,\}$//' | grep -v port | sort], [0], [dnl + 2 0 50:54:00:00:00:00 static +]) + +dnl static-mac should not be converted to a dynamic one when a packet with same dl_src +dnl arrives on any port of the switch +dnl Packet arriving on p1 +OFPROTO_TRACE([ovs-dummy], [in_port(1),eth(src=50:54:00:00:00:00)], [-generate], [100,2]) +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ *[[0-9]]\{1,\}$//' | grep -v port | sort], [0], [dnl + 2 0 50:54:00:00:00:00 static +]) + +dnl Packet arriving on p2 +OFPROTO_TRACE([ovs-dummy], [in_port(2),eth(src=50:54:00:00:00:00)], [-generate], [100,1]) +AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ *[[0-9]]\{1,\}$//' | grep -v port | sort], [0], [dnl + 2 0 50:54:00:00:00:00 static +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + AT_SETUP([ofproto-dpif - basic truncate action]) OVS_VSWITCHD_START add_of_ports br0 1 2 3 4 5