diff mbox series

[ovs-dev,v6] ofproto-dpif: APIs and CLI option to add/delete static fdb entry

Message ID 20210612020958.52820-1-vdasari@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,v6] ofproto-dpif: APIs and CLI option to add/delete static fdb entry | expand

Commit Message

Vasu Dasari June 12, 2021, 2:09 a.m. UTC
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 <bridge> <port> <vlan> <Mac>
    ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05

To del:
    ovs-appctl fdb/del <bridge> <port> <vlan> <Mac>
    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 <vdasari@gmail.com>
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 <echaudro@redhat.com>
---
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
 v6:
 - Fixed a probable bug with Eelco's code review comments in
   is_mac_learning_update_needed()
---
 NEWS                         |   2 +
 lib/mac-learning.c           | 149 +++++++++++++++++++++++++++++++----
 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, 380 insertions(+), 29 deletions(-)

Comments

Ben Pfaff June 14, 2021, 3:47 p.m. UTC | #1
On Fri, Jun 11, 2021 at 10:09:58PM -0400, Vasu Dasari wrote:
> 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.

I didn't really review this patch, but I noticed that there is no
documentation for the new commands.  Please add some in
vswitchd/ovs-vswitchd.8.in, near the documentation for the existing fdb
commands.
Vasu Dasari June 15, 2021, 1:07 a.m. UTC | #2
Ok Ben, Will do that.

Thanks
-Vasu

*Vasu Dasari*


On Mon, Jun 14, 2021 at 11:48 AM Ben Pfaff <blp@ovn.org> wrote:

> On Fri, Jun 11, 2021 at 10:09:58PM -0400, Vasu Dasari wrote:
> > 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.
>
> I didn't really review this patch, but I noticed that there is no
> documentation for the new commands.  Please add some in
> vswitchd/ovs-vswitchd.8.in, near the documentation for the existing fdb
> commands.
>
Eelco Chaudron June 23, 2021, 2:41 p.m. UTC | #3
> On 12 Jun 2021, at 4:09, Vasu Dasari wrote:

See my inline comments below.

Cheers,

Eelco


> 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 <bridge> <port> <vlan> <Mac>
>     ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05
>
> To del:
>     ovs-appctl fdb/del <bridge> <port> <vlan> <Mac>
>     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 <vdasari@gmail.com>
> 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 <echaudro@redhat.com>
> ---
> 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
>  v6:
>  - Fixed a probable bug with Eelco's code review comments in
>    is_mac_learning_update_needed()
> ---
>  NEWS                         |   2 +
>  lib/mac-learning.c           | 149 +++++++++++++++++++++++++++++++----
>  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, 380 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} <bridge> <port> <vlan> <mac>'

I think this needs its own section, not being part of  "- Userspace datapath:". So something like:

    - Added ability to add and delete static mac entries using:
       'ovs-appctl fdb/{add,del} <bridge> <port> <vlan> <mac>'

>     - 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..2f51a6553 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

NIT: If you break the line before 80, I would also move the above "It" to the line below.

> + * 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

NIT: I would indent MAC_ENTRY_AGE_STATIC_ENTRY with the "If" above.

> + */
>  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 MAC_ENTRY_AGE_STATIC_ENTRY;
> +    } 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;

Do not clear this here, as it would reset the static count on clear stats, which would cause negative numbers, etc.
This is not really a statistics/total counter, but more a current number of static entries. Maybe it should be named as such, i.e. current_static?

Please add a ml->total_static = 0 to the mac_learning_create() function.

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

First thing mac_entry_set_port() does is "if (mac_entry_get_port(ml, e) != port)", so I think we can remove the check above and always set the 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) {

Guess this needs to be "if (mac && mac_entry_age(ml, mac) == MAC_ENTRY_AGE_STATIC_ENTRY) to make sure we only remove static entries.

> +        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
> @@ -372,13 +459,27 @@ is_mac_learning_update_needed(const struct mac_learning *ml,
>      OVS_REQ_RDLOCK(ml->rwlock)
>  {
>      struct mac_entry *mac;
> +    int age;
>
>      if (!mac_learning_may_learn(ml, src, vlan)) {
>          return false;
>      }
>
>      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;
> +    }
> +
> +    age = mac_entry_age(ml, mac);
> +    /* if mac is a static entry, then there is no need to update */
> +    if (age == MAC_ENTRY_AGE_STATIC_ENTRY) {

For debugging, we need some coverage counter when the update comes from a different port, i.e., where the MAC is seen on another port than the static one.

    if (mac_entry_get_port(ml, mac) != in_port) {
        COVERAGE_INC(mac_learning_static_none_move);
    }

> +        return false;
> +    }
> +
> +    /* If entry is still alive, just update the mac_entry so, that expires
> +     * gets updated */
> +    if (age > 0) {
>          return true;
>      }
>
> @@ -513,13 +614,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..07fb6331a 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;

I think this is not a statistic value, just the current number of static entries in the table (see other comments above).
So I would move it up, below max_entries, and make it looks something like this:

        size_t max_entries;         /* Max number of learned MACs. */
+       size_t static_entries;      /* Current number of static MAC entries. */

>      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..851088d79 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");
> +        }

As the port number is not requered for deleting, I think it should be deleted from the command, so it would become:

   ovs-appctl fdb/del <bridge> <vlan> <mac>

> +        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);

Maybe this should reflect more the value, like "Current static MAC entries in the table"?

And move it up, for example:

   Current/maximum MAC entries in the table: 17/8192
+  Current static MAC entries in the table : 17
   Total number of learned MAC entries     : 18
-  Total number of static  MAC entries     : 17
   Total number of expired MAC entries     : 0
   Total number of evicted MAC entries     : 0
   Total number of port moved MAC entries  : 0

>      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..b2c5734d2 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 and 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

I noticed you fixed the potential issue I pointed out with the flush. To make sure we would catch it, I think you should have some interleaved entries. Maybe change it to the following?

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
    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 Count number of static entries remaining
> +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
> -- 
> 2.29.2
Eelco Chaudron June 23, 2021, 2:46 p.m. UTC | #4
Vasu, I reviewed your v7, but added my comments in the v6 email :(

As only the documentation updated in v7, it should reflect the same code areas.

Cheers,

Eelco


On 23 Jun 2021, at 16:41, Eelco Chaudron wrote:

>> On 12 Jun 2021, at 4:09, Vasu Dasari wrote:
>
> See my inline comments below.
>
> Cheers,
>
> Eelco
>
>
>> 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 <bridge> <port> <vlan> <Mac>
>>     ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05
>>
>> To del:
>>     ovs-appctl fdb/del <bridge> <port> <vlan> <Mac>
>>     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 <vdasari@gmail.com>
>> 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 <echaudro@redhat.com>
>> ---
>> 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
>>  v6:
>>  - Fixed a probable bug with Eelco's code review comments in
>>    is_mac_learning_update_needed()
>> ---
>>  NEWS                         |   2 +
>>  lib/mac-learning.c           | 149 +++++++++++++++++++++++++++++++----
>>  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, 380 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} <bridge> <port> <vlan> <mac>'
>
> I think this needs its own section, not being part of  "- Userspace datapath:". So something like:
>
>     - Added ability to add and delete static mac entries using:
>        'ovs-appctl fdb/{add,del} <bridge> <port> <vlan> <mac>'
>
>>     - 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..2f51a6553 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
>
> NIT: If you break the line before 80, I would also move the above "It" to the line below.
>
>> + * 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
>
> NIT: I would indent MAC_ENTRY_AGE_STATIC_ENTRY with the "If" above.
>
>> + */
>>  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 MAC_ENTRY_AGE_STATIC_ENTRY;
>> +    } 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;
>
> Do not clear this here, as it would reset the static count on clear stats, which would cause negative numbers, etc.
> This is not really a statistics/total counter, but more a current number of static entries. Maybe it should be named as such, i.e. current_static?
>
> Please add a ml->total_static = 0 to the mac_learning_create() function.
>
>>          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) {
>
> First thing mac_entry_set_port() does is "if (mac_entry_get_port(ml, e) != port)", so I think we can remove the check above and always set the 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) {
>
> Guess this needs to be "if (mac && mac_entry_age(ml, mac) == MAC_ENTRY_AGE_STATIC_ENTRY) to make sure we only remove static entries.
>
>> +        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
>> @@ -372,13 +459,27 @@ is_mac_learning_update_needed(const struct mac_learning *ml,
>>      OVS_REQ_RDLOCK(ml->rwlock)
>>  {
>>      struct mac_entry *mac;
>> +    int age;
>>
>>      if (!mac_learning_may_learn(ml, src, vlan)) {
>>          return false;
>>      }
>>
>>      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;
>> +    }
>> +
>> +    age = mac_entry_age(ml, mac);
>> +    /* if mac is a static entry, then there is no need to update */
>> +    if (age == MAC_ENTRY_AGE_STATIC_ENTRY) {
>
> For debugging, we need some coverage counter when the update comes from a different port, i.e., where the MAC is seen on another port than the static one.
>
>     if (mac_entry_get_port(ml, mac) != in_port) {
>         COVERAGE_INC(mac_learning_static_none_move);
>     }
>
>> +        return false;
>> +    }
>> +
>> +    /* If entry is still alive, just update the mac_entry so, that expires
>> +     * gets updated */
>> +    if (age > 0) {
>>          return true;
>>      }
>>
>> @@ -513,13 +614,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..07fb6331a 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;
>
> I think this is not a statistic value, just the current number of static entries in the table (see other comments above).
> So I would move it up, below max_entries, and make it looks something like this:
>
>         size_t max_entries;         /* Max number of learned MACs. */
> +       size_t static_entries;      /* Current number of static MAC entries. */
>
>>      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..851088d79 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");
>> +        }
>
> As the port number is not requered for deleting, I think it should be deleted from the command, so it would become:
>
>    ovs-appctl fdb/del <bridge> <vlan> <mac>
>
>> +        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);
>
> Maybe this should reflect more the value, like "Current static MAC entries in the table"?
>
> And move it up, for example:
>
>    Current/maximum MAC entries in the table: 17/8192
> +  Current static MAC entries in the table : 17
>    Total number of learned MAC entries     : 18
> -  Total number of static  MAC entries     : 17
>    Total number of expired MAC entries     : 0
>    Total number of evicted MAC entries     : 0
>    Total number of port moved MAC entries  : 0
>
>>      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..b2c5734d2 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 and 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
>
> I noticed you fixed the potential issue I pointed out with the flush. To make sure we would catch it, I think you should have some interleaved entries. Maybe change it to the following?
>
> 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
>     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 Count number of static entries remaining
>> +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
>> -- 
>> 2.29.2
Vasu Dasari June 24, 2021, 10:55 a.m. UTC | #5
Hi Eelco,

Thank you for a thorough review. I have Ack'd all comments and created a
patch at Patch v8
<https://mail.openvswitch.org/pipermail/ovs-dev/2021-June/384363.html>.

 My comments inline: (Mostly all Acks)

Thanks
-Vasu

*Vasu Dasari*


On Wed, Jun 23, 2021 at 10:41 AM Eelco Chaudron <echaudro@redhat.com> wrote:

> > On 12 Jun 2021, at 4:09, Vasu Dasari wrote:
>
> See my inline comments below.
>
> Cheers,
>
> Eelco
>
>
> > 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 <bridge> <port> <vlan> <Mac>
> >     ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05
> >
> > To del:
> >     ovs-appctl fdb/del <bridge> <port> <vlan> <Mac>
> >     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 <vdasari@gmail.com>
> > 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 <echaudro@redhat.com>
> > ---
> > 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
> >  v6:
> >  - Fixed a probable bug with Eelco's code review comments in
> >    is_mac_learning_update_needed()
> > ---
> >  NEWS                         |   2 +
> >  lib/mac-learning.c           | 149 +++++++++++++++++++++++++++++++----
> >  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, 380 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} <bridge> <port> <vlan> <mac>'
>
> I think this needs its own section, not being part of  "- Userspace
> datapath:". So something like:
>
>     - Added ability to add and delete static mac entries using:
>        'ovs-appctl fdb/{add,del} <bridge> <port> <vlan> <mac>'
>
> Ack

> >     - 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..2f51a6553 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
>
> NIT: If you break the line before 80, I would also move the above "It" to
> the line below.
>
Ack

>
> > + * 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
>
> NIT: I would indent MAC_ENTRY_AGE_STATIC_ENTRY with the "If" above.
>
Ack

>
> > + */
> >  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 MAC_ENTRY_AGE_STATIC_ENTRY;
> > +    } 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;
>
> Do not clear this here, as it would reset the static count on clear stats,
> which would cause negative numbers, etc.
> This is not really a statistics/total counter, but more a current number
> of static entries. Maybe it should be named as such, i.e. current_static?
>
> Please add a ml->total_static = 0 to the mac_learning_create() function.
>
Ack

>
> >          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) {
>
> First thing mac_entry_set_port() does is "if (mac_entry_get_port(ml, e) !=
> port)", so I think we can remove the check above and always set the 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) {
>
> Guess this needs to be "if (mac && mac_entry_age(ml, mac) ==
> MAC_ENTRY_AGE_STATIC_ENTRY) to make sure we only remove static entries.
>
> > +        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
> > @@ -372,13 +459,27 @@ is_mac_learning_update_needed(const struct
> mac_learning *ml,
> >      OVS_REQ_RDLOCK(ml->rwlock)
> >  {
> >      struct mac_entry *mac;
> > +    int age;
> >
> >      if (!mac_learning_may_learn(ml, src, vlan)) {
> >          return false;
> >      }
> >
> >      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;
> > +    }
> > +
> > +    age = mac_entry_age(ml, mac);
> > +    /* if mac is a static entry, then there is no need to update */
> > +    if (age == MAC_ENTRY_AGE_STATIC_ENTRY) {
>
> For debugging, we need some coverage counter when the update comes from a
> different port, i.e., where the MAC is seen on another port than the static
> one.
>
>     if (mac_entry_get_port(ml, mac) != in_port) {
>         COVERAGE_INC(mac_learning_static_none_move);
>     }
>
> Ack

> > +        return false;
> > +    }
> > +
> > +    /* If entry is still alive, just update the mac_entry so, that
> expires
> > +     * gets updated */
> > +    if (age > 0) {
> >          return true;
> >      }
> >
> > @@ -513,13 +614,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..07fb6331a 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;
>
> I think this is not a statistic value, just the current number of static
> entries in the table (see other comments above).
> So I would move it up, below max_entries, and make it looks something like
> this:
>
> Ack

>         size_t max_entries;         /* Max number of learned MACs. */
> +       size_t static_entries;      /* Current number of static MAC
> entries. */
>
> >      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..851088d79 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");
> > +        }
>
> As the port number is not requered for deleting, I think it should be
> deleted from the command, so it would become:
>
>    ovs-appctl fdb/del <bridge> <vlan> <mac>
>
> Ack

> > +        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);
>
> Maybe this should reflect more the value, like "Current static MAC entries
> in the table"?
>
> And move it up, for example:
>
>    Current/maximum MAC entries in the table: 17/8192
> +  Current static MAC entries in the table : 17
>    Total number of learned MAC entries     : 18
> -  Total number of static  MAC entries     : 17
>    Total number of expired MAC entries     : 0
>    Total number of evicted MAC entries     : 0
>    Total number of port moved MAC entries  : 0
>
> >      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..b2c5734d2 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 and 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
>
> I noticed you fixed the potential issue I pointed out with the flush. To
> make sure we would catch it, I think you should have some interleaved
> entries. Maybe change it to the following?
>
> Yes, I did fix the issue you identified. My earlier test was not catching
the bug. Once I improved the test, I caught the bug you identified. Will
make following change as well.

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
>     ovs-appctl fdb/add br0 p2 0 50:54:00:0$i:00:ff
> done
>
> Ack

> > +
> > +dnl Flush mac entries, only dynamic ones should be evicted.
> > +AT_CHECK([ovs-appctl fdb/flush br0], [0], [dnl
> > +table successfully flushed
> > +])
> > +
> > +dnl Count number of static entries remaining
> > +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
> > --
> > 2.29.2
>
>
diff mbox series

Patch

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} <bridge> <port> <vlan> <mac>'
    - 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..2f51a6553 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 MAC_ENTRY_AGE_STATIC_ENTRY;
+    } 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
@@ -372,13 +459,27 @@  is_mac_learning_update_needed(const struct mac_learning *ml,
     OVS_REQ_RDLOCK(ml->rwlock)
 {
     struct mac_entry *mac;
+    int age;
 
     if (!mac_learning_may_learn(ml, src, vlan)) {
         return false;
     }
 
     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;
+    }
+
+    age = mac_entry_age(ml, mac);
+    /* if mac is a static entry, then there is no need to update */
+    if (age == MAC_ENTRY_AGE_STATIC_ENTRY) {
+        return false;
+    }
+
+    /* If entry is still alive, just update the mac_entry so, that expires
+     * gets updated */
+    if (age > 0) {
         return true;
     }
 
@@ -513,13 +614,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..07fb6331a 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..851088d79 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..b2c5734d2 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 and 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 Count number of static entries remaining
+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