diff mbox series

[ovs-dev,v6] Add a northbound interface to program MAC_Binding table

Message ID 20220324214158.137613-1-svc.eng.git-mail@nutanix.com
State Superseded
Headers show
Series [ovs-dev,v6] Add a northbound interface to program MAC_Binding table | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

svc.eng.git-mail March 24, 2022, 9:41 p.m. UTC
From: Karthik Chandrashekar <karthik.c@nutanix.com>

Add a new NB and SB table for managing Static MAC_Binding entries.
This table is currently supported for logical routers. OVN northd
is responsible for propagating the values from NB to SB. OVN controller
is responsible for installation MAC lookup flows. The priority of
the installed flows are based on override_dynamic_mac flag. This helps
control the precedence of statically programmed vs dynamically learnt
MAC Bindings.

Signed-off-by: Karthik Chandrashekar <karthik.c@nutanix.com>
---
 controller/lflow.c             | 103 ++++++++++++++++-----
 controller/lflow.h             |  10 +-
 controller/ovn-controller.c    |  38 +++++++-
 lib/automake.mk                |   2 +
 lib/static-mac-binding-index.c |  43 +++++++++
 lib/static-mac-binding-index.h |  27 ++++++
 northd/en-northd.c             |   8 ++
 northd/inc-proc-northd.c       |  14 ++-
 northd/northd.c                |  76 +++++++++++++++
 northd/northd.h                |   5 +
 ovn-nb.ovsschema               |  15 ++-
 ovn-nb.xml                     |  29 ++++++
 ovn-sb.ovsschema               |  15 ++-
 ovn-sb.xml                     |  27 ++++++
 tests/ovn-nbctl.at             |  69 ++++++++++++++
 tests/ovn-northd.at            |  25 ++++-
 tests/ovn.at                   |  90 ++++++++++++++++++
 utilities/ovn-nbctl.c          | 164 ++++++++++++++++++++++++++++++++-
 18 files changed, 720 insertions(+), 40 deletions(-)
 create mode 100644 lib/static-mac-binding-index.c
 create mode 100644 lib/static-mac-binding-index.h

Comments

0-day Robot March 24, 2022, 10:59 p.m. UTC | #1
References:  <20220324214158.137613-1-svc.eng.git-mail@nutanix.com>
 

Bleep bloop.  Greetings svc.eng.git-mail, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line lacks whitespace around operator
#1033 FILE: utilities/ovn-nbctl.c:456:
  static-mac-binding-add LOGICAL_PORT IP MAC\n\

WARNING: Line lacks whitespace around operator
#1035 FILE: utilities/ovn-nbctl.c:458:
  static-mac-binding-del LOGICAL_PORT IP\n\

WARNING: Line lacks whitespace around operator
#1037 FILE: utilities/ovn-nbctl.c:460:
  static-mac-binding-list           List all Static_MAC_Binding entries\n\

Lines checked: 1209, Warnings: 3, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Lorenzo Bianconi April 6, 2022, 10:30 a.m. UTC | #2
> 
> Add a new NB and SB table for managing Static MAC_Binding entries.
> This table is currently supported for logical routers. OVN northd
> is responsible for propagating the values from NB to SB. OVN controller
> is responsible for installation MAC lookup flows. The priority of
> the installed flows are based on override_dynamic_mac flag. This helps
> control the precedence of statically programmed vs dynamically learnt
> MAC Bindings.

Hi Karthik,

this patch does not apply on ovn master.

I am still wondering for the openflow duplication when there are a conflicts
between static mac bindings and dynamic ones. Assuming we have few static
mac bindings I guess it is ok.

@Numan: what do you think about it?

If it is ok, you can add my tested-by in v7.

Tetsted-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>

Regards,
Lorenzo

> 
> Signed-off-by: Karthik Chandrashekar <karthik.c@nutanix.com>
> ---
>  controller/lflow.c             | 103 ++++++++++++++++-----
>  controller/lflow.h             |  10 +-
>  controller/ovn-controller.c    |  38 +++++++-
>  lib/automake.mk                |   2 +
>  lib/static-mac-binding-index.c |  43 +++++++++
>  lib/static-mac-binding-index.h |  27 ++++++
>  northd/en-northd.c             |   8 ++
>  northd/inc-proc-northd.c       |  14 ++-
>  northd/northd.c                |  76 +++++++++++++++
>  northd/northd.h                |   5 +
>  ovn-nb.ovsschema               |  15 ++-
>  ovn-nb.xml                     |  29 ++++++
>  ovn-sb.ovsschema               |  15 ++-
>  ovn-sb.xml                     |  27 ++++++
>  tests/ovn-nbctl.at             |  69 ++++++++++++++
>  tests/ovn-northd.at            |  25 ++++-
>  tests/ovn.at                   |  90 ++++++++++++++++++
>  utilities/ovn-nbctl.c          | 164 ++++++++++++++++++++++++++++++++-
>  18 files changed, 720 insertions(+), 40 deletions(-)
>  create mode 100644 lib/static-mac-binding-index.c
>  create mode 100644 lib/static-mac-binding-index.h
> 
> diff --git a/controller/lflow.c b/controller/lflow.c
> index e169edef1..075373e7b 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -1622,40 +1622,50 @@ static void
>  consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>                         const struct hmap *local_datapaths,
>                         const struct sbrec_mac_binding *b,
> -                       struct ovn_desired_flow_table *flow_table)
> +                       const struct sbrec_static_mac_binding *smb,
> +                       struct ovn_desired_flow_table *flow_table,
> +                       uint16_t priority)
>  {
> +    if (!b && !smb) {
> +        return;
> +    }
> +
> +    char *logical_port = !b ? smb->logical_port : b->logical_port;
> +    char *ip = !b ? smb->ip : b->ip;
> +    char *mac = !b ? smb->mac : b->mac;
> +
>      const struct sbrec_port_binding *pb
> -        = lport_lookup_by_name(sbrec_port_binding_by_name, b->logical_port);
> +        = lport_lookup_by_name(sbrec_port_binding_by_name, logical_port);
>      if (!pb || !get_local_datapath(local_datapaths,
>                                     pb->datapath->tunnel_key)) {
>          return;
>      }
>  
> -    struct eth_addr mac;
> -    if (!eth_addr_from_string(b->mac, &mac)) {
> +    struct eth_addr mac_addr;
> +    if (!eth_addr_from_string(mac, &mac_addr)) {
>          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -        VLOG_WARN_RL(&rl, "bad 'mac' %s", b->mac);
> +        VLOG_WARN_RL(&rl, "bad 'mac' %s", mac);
>          return;
>      }
>  
>      struct match get_arp_match = MATCH_CATCHALL_INITIALIZER;
>      struct match lookup_arp_match = MATCH_CATCHALL_INITIALIZER;
>  
> -    if (strchr(b->ip, '.')) {
> -        ovs_be32 ip;
> -        if (!ip_parse(b->ip, &ip)) {
> +    if (strchr(ip, '.')) {
> +        ovs_be32 ip_addr;
> +        if (!ip_parse(ip, &ip_addr)) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
> +            VLOG_WARN_RL(&rl, "bad 'ip' %s", ip);
>              return;
>          }
> -        match_set_reg(&get_arp_match, 0, ntohl(ip));
> -        match_set_reg(&lookup_arp_match, 0, ntohl(ip));
> +        match_set_reg(&get_arp_match, 0, ntohl(ip_addr));
> +        match_set_reg(&lookup_arp_match, 0, ntohl(ip_addr));
>          match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_ARP));
>      } else {
>          struct in6_addr ip6;
> -        if (!ipv6_parse(b->ip, &ip6)) {
> +        if (!ipv6_parse(ip, &ip6)) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
> +            VLOG_WARN_RL(&rl, "bad 'ip' %s", ip);
>              return;
>          }
>          ovs_be128 value;
> @@ -1678,20 +1688,22 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>      uint64_t stub[1024 / 8];
>      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
>      uint8_t value = 1;
> -    put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
> +    put_load(mac_addr.ea, sizeof mac_addr.ea, MFF_ETH_DST, 0, 48, &ofpacts);
>      put_load(&value, sizeof value, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1,
>               &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100,
> -                    b->header_.uuid.parts[0], &get_arp_match,
> -                    &ofpacts, &b->header_.uuid);
> +    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, priority,
> +                    !b ? smb->header_.uuid.parts[0] : b->header_.uuid.parts[0],
> +                    &get_arp_match, &ofpacts,
> +                    !b ? &smb->header_.uuid : &b->header_.uuid);
>  
>      ofpbuf_clear(&ofpacts);
>      put_load(&value, sizeof value, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1,
>               &ofpacts);
> -    match_set_dl_src(&lookup_arp_match, mac);
> -    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, 100,
> -                    b->header_.uuid.parts[0], &lookup_arp_match,
> -                    &ofpacts, &b->header_.uuid);
> +    match_set_dl_src(&lookup_arp_match, mac_addr);
> +    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, priority,
> +                    !b ? smb->header_.uuid.parts[0] : b->header_.uuid.parts[0],
> +                    &lookup_arp_match, &ofpacts,
> +                    !b ? &smb->header_.uuid : &b->header_.uuid);
>  
>      ofpbuf_uninit(&ofpacts);
>  }
> @@ -1701,13 +1713,23 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>  static void
>  add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>                     const struct sbrec_mac_binding_table *mac_binding_table,
> +                   const struct sbrec_static_mac_binding_table *smb_table,
>                     const struct hmap *local_datapaths,
>                     struct ovn_desired_flow_table *flow_table)
>  {
> +    /* Add flows for learnt MAC bindings */
>      const struct sbrec_mac_binding *b;
>      SBREC_MAC_BINDING_TABLE_FOR_EACH (b, mac_binding_table) {
>          consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
> -                               b, flow_table);
> +                               b, NULL, flow_table, 100);
> +    }
> +
> +    /* Add flows for statically configured MAC bindings */
> +    const struct sbrec_static_mac_binding *smb;
> +    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (smb, smb_table) {
> +        consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
> +                               NULL, smb, flow_table,
> +                               smb->override_dynamic_mac ? 150 : 50);
>      }
>  }
>  
> @@ -2346,7 +2368,7 @@ add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table,
>  
>  /* Handles neighbor changes in mac_binding table. */
>  void
> -lflow_handle_changed_neighbors(
> +lflow_handle_changed_mac_bindings(
>      struct ovsdb_idl_index *sbrec_port_binding_by_name,
>      const struct sbrec_mac_binding_table *mac_binding_table,
>      const struct hmap *local_datapaths,
> @@ -2373,7 +2395,36 @@ lflow_handle_changed_neighbors(
>              VLOG_DBG("handle new mac_binding "UUID_FMT,
>                       UUID_ARGS(&mb->header_.uuid));
>              consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
> -                                   mb, flow_table);
> +                                   mb, NULL, flow_table, 100);
> +        }
> +    }
> +}
> +
> +/* Handles changes to static_mac_binding table. */
> +void
> +lflow_handle_changed_static_mac_bindings(
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +    const struct sbrec_static_mac_binding_table *smb_table,
> +    const struct hmap *local_datapaths,
> +    struct ovn_desired_flow_table *flow_table)
> +{
> +    const struct sbrec_static_mac_binding *smb;
> +    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (smb, smb_table) {
> +        if (sbrec_static_mac_binding_is_deleted(smb)) {
> +            VLOG_DBG("handle deleted static_mac_binding "UUID_FMT,
> +                     UUID_ARGS(&smb->header_.uuid));
> +            ofctrl_remove_flows(flow_table, &smb->header_.uuid);
> +        } else {
> +            if (!sbrec_static_mac_binding_is_new(smb)) {
> +                VLOG_DBG("handle updated static_mac_binding "UUID_FMT,
> +                         UUID_ARGS(&smb->header_.uuid));
> +                ofctrl_remove_flows(flow_table, &smb->header_.uuid);
> +            }
> +            VLOG_DBG("handle new static_mac_binding "UUID_FMT,
> +                     UUID_ARGS(&smb->header_.uuid));
> +            consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
> +                                   NULL, smb, flow_table,
> +                                   smb->override_dynamic_mac ? 150 : 50);
>          }
>      }
>  }
> @@ -2443,7 +2494,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
>  
>      add_logical_flows(l_ctx_in, l_ctx_out);
>      add_neighbor_flows(l_ctx_in->sbrec_port_binding_by_name,
> -                       l_ctx_in->mac_binding_table, l_ctx_in->local_datapaths,
> +                       l_ctx_in->mac_binding_table,
> +                       l_ctx_in->static_mac_binding_table,
> +                       l_ctx_in->local_datapaths,
>                         l_ctx_out->flow_table);
>      add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths,
>                           l_ctx_out->flow_table,
> diff --git a/controller/lflow.h b/controller/lflow.h
> index d61733bc2..c2944378b 100644
> --- a/controller/lflow.h
> +++ b/controller/lflow.h
> @@ -147,6 +147,7 @@ struct lflow_ctx_in {
>      const struct sbrec_fdb_table *fdb_table;
>      const struct sbrec_chassis *chassis;
>      const struct sbrec_load_balancer_table *lb_table;
> +    const struct sbrec_static_mac_binding_table *static_mac_binding_table;
>      const struct hmap *local_datapaths;
>      const struct shash *addr_sets;
>      const struct shash *port_groups;
> @@ -191,9 +192,14 @@ bool lflow_handle_addr_set_update(const char *as_name, struct addr_set_diff *,
>                                    struct lflow_ctx_out *,
>                                    bool *changed);
>  
> -void lflow_handle_changed_neighbors(
> +void lflow_handle_changed_mac_bindings(
>      struct ovsdb_idl_index *sbrec_port_binding_by_name,
> -    const struct sbrec_mac_binding_table *,
> +    const struct sbrec_mac_binding_table *mac_binding_table,
> +    const struct hmap *local_datapaths,
> +    struct ovn_desired_flow_table *);
> +void lflow_handle_changed_static_mac_bindings(
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +    const struct sbrec_static_mac_binding_table *smb_table,
>      const struct hmap *local_datapaths,
>      struct ovn_desired_flow_table *);
>  bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index ea5e9df41..2ec9aee32 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -969,7 +969,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      SB_NODE(dns, "dns") \
>      SB_NODE(load_balancer, "load_balancer") \
>      SB_NODE(fdb, "fdb") \
> -    SB_NODE(meter, "meter")
> +    SB_NODE(meter, "meter") \
> +    SB_NODE(static_mac_binding, "static_mac_binding")
>  
>  enum sb_engine_node {
>  #define SB_NODE(NAME, NAME_STR) SB_##NAME,
> @@ -2295,6 +2296,10 @@ init_lflow_ctx(struct engine_node *node,
>          (struct sbrec_fdb_table *)EN_OVSDB_GET(
>              engine_get_input("SB_fdb", node));
>  
> +    struct sbrec_static_mac_binding_table *smb_table =
> +        (struct sbrec_static_mac_binding_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_static_mac_binding", node));
> +
>      struct ovsrec_open_vswitch_table *ovs_table =
>          (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
>              engine_get_input("OVS_open_vswitch", node));
> @@ -2343,6 +2348,7 @@ init_lflow_ctx(struct engine_node *node,
>      l_ctx_in->fdb_table = fdb_table,
>      l_ctx_in->chassis = chassis;
>      l_ctx_in->lb_table = lb_table;
> +    l_ctx_in->static_mac_binding_table = smb_table;
>      l_ctx_in->local_datapaths = &rt_data->local_datapaths;
>      l_ctx_in->addr_sets = addr_sets;
>      l_ctx_in->port_groups = port_groups;
> @@ -2485,13 +2491,39 @@ lflow_output_sb_mac_binding_handler(struct engine_node *node, void *data)
>  
>      struct ed_type_lflow_output *lfo = data;
>  
> -    lflow_handle_changed_neighbors(sbrec_port_binding_by_name,
> +    lflow_handle_changed_mac_bindings(sbrec_port_binding_by_name,
>              mac_binding_table, local_datapaths, &lfo->flow_table);
>  
>      engine_set_node_state(node, EN_UPDATED);
>      return true;
>  }
>  
> +static bool
> +lflow_output_sb_static_mac_binding_handler(struct engine_node *node,
> +                                           void *data)
> +{
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_port_binding", node),
> +                "name");
> +
> +    struct sbrec_static_mac_binding_table *smb_table =
> +        (struct sbrec_static_mac_binding_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_static_mac_binding", node));
> +
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +    const struct hmap *local_datapaths = &rt_data->local_datapaths;
> +
> +    struct ed_type_lflow_output *lfo = data;
> +
> +    lflow_handle_changed_static_mac_bindings(sbrec_port_binding_by_name,
> +        smb_table, local_datapaths, &lfo->flow_table);
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
>  static bool
>  lflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
>  {
> @@ -3322,6 +3354,8 @@ main(int argc, char *argv[])
>  
>      engine_add_input(&en_lflow_output, &en_sb_mac_binding,
>                       lflow_output_sb_mac_binding_handler);
> +    engine_add_input(&en_lflow_output, &en_sb_static_mac_binding,
> +                     lflow_output_sb_static_mac_binding_handler);
>      engine_add_input(&en_lflow_output, &en_sb_logical_flow,
>                       lflow_output_sb_logical_flow_handler);
>      /* Using a noop handler since we don't really need any data from datapath
> diff --git a/lib/automake.mk b/lib/automake.mk
> index 829aedfc5..3a2da1fe4 100644
> --- a/lib/automake.mk
> +++ b/lib/automake.mk
> @@ -38,6 +38,8 @@ lib_libovn_la_SOURCES = \
>  	lib/inc-proc-eng.h \
>  	lib/lb.c \
>  	lib/lb.h \
> +	lib/static-mac-binding-index.c \
> +	lib/static-mac-binding-index.h \
>  	lib/stopwatch-names.h \
>  	lib/vif-plug-provider.h \
>  	lib/vif-plug-provider.c \
> diff --git a/lib/static-mac-binding-index.c b/lib/static-mac-binding-index.c
> new file mode 100644
> index 000000000..fecc9b74f
> --- /dev/null
> +++ b/lib/static-mac-binding-index.c
> @@ -0,0 +1,43 @@
> +/* Copyright (c) 2021
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "lib/static-mac-binding-index.h"
> +#include "lib/ovn-sb-idl.h"
> +
> +struct ovsdb_idl_index *
> +static_mac_binding_index_create(struct ovsdb_idl *idl)
> +{
> +    return ovsdb_idl_index_create2(idl,
> +                                   &sbrec_static_mac_binding_col_logical_port,
> +                                   &sbrec_static_mac_binding_col_ip);
> +}
> +
> +const struct sbrec_static_mac_binding *
> +static_mac_binding_lookup(struct ovsdb_idl_index *smb_index,
> +                          const char *logical_port, const char *ip)
> +{
> +    struct sbrec_static_mac_binding *target =
> +        sbrec_static_mac_binding_index_init_row(smb_index);
> +    sbrec_static_mac_binding_index_set_logical_port(target, logical_port);
> +    sbrec_static_mac_binding_index_set_ip(target, ip);
> +
> +    struct sbrec_static_mac_binding *smb =
> +        sbrec_static_mac_binding_index_find(smb_index, target);
> +    sbrec_static_mac_binding_index_destroy_row(target);
> +
> +    return smb;
> +}
> diff --git a/lib/static-mac-binding-index.h b/lib/static-mac-binding-index.h
> new file mode 100644
> index 000000000..3d4ff06a2
> --- /dev/null
> +++ b/lib/static-mac-binding-index.h
> @@ -0,0 +1,27 @@
> +/* Copyright (c) 2021
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_STATIC_MAC_BINDING_INDEX_H
> +#define OVN_STATIC_MAC_BINDING_INDEX_H 1
> +
> +struct ovsdb_idl;
> +
> +struct ovsdb_idl_index *static_mac_binding_index_create(struct ovsdb_idl *);
> +const struct sbrec_static_mac_binding *static_mac_binding_lookup(
> +    struct ovsdb_idl_index *smb_index,
> +    const char *logical_port,
> +    const char *ip);
> +
> +#endif /* lib/static-mac-binding-index.h */
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 79da7e1c4..4907a1ff2 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -55,6 +55,10 @@ void en_northd_run(struct engine_node *node, void *data)
>          engine_ovsdb_node_get_index(
>              engine_get_input("SB_ip_multicast", node),
>              "sbrec_ip_mcast_by_dp");
> +    input_data.sbrec_static_mac_binding_by_lport_ip =
> +        engine_ovsdb_node_get_index(
> +            engine_get_input("SB_static_mac_binding", node),
> +            "sbrec_static_mac_binding_by_lport_ip");
>  
>      input_data.nbrec_nb_global_table =
>          EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
> @@ -72,6 +76,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("NB_meter", node));
>      input_data.nbrec_acl_table =
>          EN_OVSDB_GET(engine_get_input("NB_acl", node));
> +    input_data.nbrec_static_mac_binding_table =
> +        EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>  
>      input_data.sbrec_sb_global_table =
>          EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> @@ -103,6 +109,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("SB_ip_multicast", node));
>      input_data.sbrec_chassis_private_table =
>          EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
> +    input_data.sbrec_static_mac_binding_table =
> +        EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>  
>      northd_run(&input_data, data,
>                 eng_ctx->ovnnb_idl_txn,
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index af55221e3..43093cb5a 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -20,6 +20,7 @@
>  
>  #include "chassis-index.h"
>  #include "ip-mcast-index.h"
> +#include "static-mac-binding-index.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/ovn-nb-idl.h"
>  #include "lib/ovn-sb-idl.h"
> @@ -60,7 +61,8 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      NB_NODE(gateway_chassis, "gateway_chassis") \
>      NB_NODE(ha_chassis_group, "ha_chassis_group") \
>      NB_NODE(ha_chassis, "ha_chassis") \
> -    NB_NODE(bfd, "bfd")
> +    NB_NODE(bfd, "bfd") \
> +    NB_NODE(static_mac_binding, "static_mac_binding")
>  
>      enum nb_engine_node {
>  #define NB_NODE(NAME, NAME_STR) NB_##NAME,
> @@ -109,7 +111,8 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      SB_NODE(service_monitor, "service_monitor") \
>      SB_NODE(load_balancer, "load_balancer") \
>      SB_NODE(bfd, "bfd") \
> -    SB_NODE(fdb, "fdb")
> +    SB_NODE(fdb, "fdb") \
> +    SB_NODE(static_mac_binding, "static_mac_binding")
>  
>  enum sb_engine_node {
>  #define SB_NODE(NAME, NAME_STR) SB_##NAME,
> @@ -178,6 +181,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_nb_gateway_chassis, NULL);
>      engine_add_input(&en_northd, &en_nb_ha_chassis_group, NULL);
>      engine_add_input(&en_northd, &en_nb_ha_chassis, NULL);
> +    engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
>  
>      engine_add_input(&en_northd, &en_sb_sb_global, NULL);
>      engine_add_input(&en_northd, &en_sb_chassis, NULL);
> @@ -206,6 +210,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_sb_service_monitor, NULL);
>      engine_add_input(&en_northd, &en_sb_load_balancer, NULL);
>      engine_add_input(&en_northd, &en_sb_fdb, NULL);
> +    engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL);
>      engine_add_input(&en_lflow, &en_nb_bfd, NULL);
>      engine_add_input(&en_lflow, &en_sb_bfd, NULL);
>      engine_add_input(&en_lflow, &en_sb_logical_flow, NULL);
> @@ -228,6 +233,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>                           ip_mcast_index_create(sb->idl);
>      struct ovsdb_idl_index *sbrec_chassis_by_hostname =
>          chassis_hostname_index_create(sb->idl);
> +    struct ovsdb_idl_index *sbrec_static_mac_binding_by_lport_ip
> +        = static_mac_binding_index_create(sb->idl);
>  
>      engine_init(&en_lflow, &engine_arg);
>  
> @@ -246,6 +253,9 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_ovsdb_node_add_index(&en_sb_ip_multicast,
>                                  "sbrec_ip_mcast_by_dp",
>                                  sbrec_ip_mcast_by_dp);
> +    engine_ovsdb_node_add_index(&en_sb_static_mac_binding,
> +                                "sbrec_static_mac_binding_by_lport_ip",
> +                                sbrec_static_mac_binding_by_lport_ip);
>  }
>  
>  void inc_proc_northd_run(struct ovsdb_idl_txn *ovnnb_txn,
> diff --git a/northd/northd.c b/northd/northd.c
> index a2cf8d6fc..ce161f592 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -28,6 +28,7 @@
>  #include "ovn/lex.h"
>  #include "lib/chassis-index.h"
>  #include "lib/ip-mcast-index.h"
> +#include "lib/static-mac-binding-index.h"
>  #include "lib/copp.h"
>  #include "lib/mcast-group-index.h"
>  #include "lib/ovn-l7.h"
> @@ -14988,6 +14989,80 @@ build_meter_groups(struct northd_input *input_data,
>      }
>  }
>  
> +static const struct nbrec_static_mac_binding *
> +static_mac_binding_by_port_ip(struct northd_input *input_data,
> +                       const char *logical_port, const char *ip)
> +{
> +    const struct nbrec_static_mac_binding *nb_smb = NULL;
> +
> +    NBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (
> +        nb_smb, input_data->nbrec_static_mac_binding_table) {
> +        if (!strcmp(nb_smb->logical_port, logical_port) &&
> +            !strcmp(nb_smb->ip, ip)) {
> +            break;
> +        }
> +    }
> +
> +    return nb_smb;
> +}
> +
> +static void
> +build_static_mac_binding_table(struct northd_input *input_data,
> +                               struct ovsdb_idl_txn *ovnsb_txn,
> +                               struct hmap *ports)
> +{
> +    /* Cleanup SB Static_MAC_Binding entries which do not have corresponding
> +     * NB Static_MAC_Binding entries. */
> +    const struct nbrec_static_mac_binding *nb_smb;
> +    const struct sbrec_static_mac_binding *sb_smb, *sb_smb_next;
> +    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_SAFE (sb_smb, sb_smb_next,
> +        input_data->sbrec_static_mac_binding_table) {
> +        nb_smb = static_mac_binding_by_port_ip(input_data,
> +                                               sb_smb->logical_port,
> +                                               sb_smb->ip);
> +        if (!nb_smb) {
> +            sbrec_static_mac_binding_delete(sb_smb);
> +        }
> +    }
> +
> +    /* Create/Update SB Static_MAC_Binding entries with corresponding values
> +     * from NB Static_MAC_Binding entries. */
> +    NBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (
> +        nb_smb, input_data->nbrec_static_mac_binding_table) {
> +        struct ovn_port *op = ovn_port_find(ports, nb_smb->logical_port);
> +        if (op && op->nbrp) {
> +            struct ovn_datapath *od = op->od;
> +            if (od && od->sb) {
> +                const struct sbrec_static_mac_binding *mb =
> +                    static_mac_binding_lookup(
> +                        input_data->sbrec_static_mac_binding_by_lport_ip,
> +                        nb_smb->logical_port, nb_smb->ip);
> +                if (!mb) {
> +                    /* Create new entry */
> +                    mb = sbrec_static_mac_binding_insert(ovnsb_txn);
> +                    sbrec_static_mac_binding_set_logical_port(
> +                        mb, nb_smb->logical_port);
> +                    sbrec_static_mac_binding_set_ip(mb, nb_smb->ip);
> +                    sbrec_static_mac_binding_set_mac(mb, nb_smb->mac);
> +                    sbrec_static_mac_binding_set_override_dynamic_mac(mb,
> +                        nb_smb->override_dynamic_mac);
> +                    sbrec_static_mac_binding_set_datapath(mb, od->sb);
> +                } else {
> +                    /* Update existing entry if there is a change*/
> +                    if (strcmp(mb->mac, nb_smb->mac)) {
> +                        sbrec_static_mac_binding_set_mac(mb, nb_smb->mac);
> +                    }
> +                    if (mb->override_dynamic_mac !=
> +                        nb_smb->override_dynamic_mac) {
> +                        sbrec_static_mac_binding_set_override_dynamic_mac(mb,
> +                            nb_smb->override_dynamic_mac);
> +                    }
> +                }
> +            }
> +        }
> +    }
> +}
> +
>  void
>  northd_init(struct northd_data *data)
>  {
> @@ -15141,6 +15216,7 @@ ovnnb_db_run(struct northd_input *input_data,
>      build_lrouter_groups(&data->ports, &data->lr_list);
>      build_ip_mcast(input_data, ovnsb_txn, &data->datapaths);
>      build_meter_groups(input_data, &data->meter_groups);
> +    build_static_mac_binding_table(input_data, ovnsb_txn, &data->ports);
>      stopwatch_stop(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>      stopwatch_start(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>      ovn_update_ipv6_prefix(&data->ports);
> diff --git a/northd/northd.h b/northd/northd.h
> index ebcb40de7..2d804a22e 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -28,6 +28,8 @@ struct northd_input {
>      const struct nbrec_address_set_table *nbrec_address_set_table;
>      const struct nbrec_meter_table *nbrec_meter_table;
>      const struct nbrec_acl_table *nbrec_acl_table;
> +    const struct nbrec_static_mac_binding_table
> +        *nbrec_static_mac_binding_table;
>  
>      /* Southbound table references */
>      const struct sbrec_sb_global_table *sbrec_sb_global_table;
> @@ -45,12 +47,15 @@ struct northd_input {
>      const struct sbrec_dns_table *sbrec_dns_table;
>      const struct sbrec_ip_multicast_table *sbrec_ip_multicast_table;
>      const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
> +    const struct sbrec_static_mac_binding_table
> +        *sbrec_static_mac_binding_table;
>  
>      /* Indexes */
>      struct ovsdb_idl_index *sbrec_chassis_by_name;
>      struct ovsdb_idl_index *sbrec_chassis_by_hostname;
>      struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
>      struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
> +    struct ovsdb_idl_index *sbrec_static_mac_binding_by_lport_ip;
>  };
>  
>  struct northd_data {
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 80b830629..043f71dd5 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "6.1.0",
> -    "cksum": "4010776751 31237",
> +    "version": "6.2.0",
> +    "cksum": "3076170876 31573",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -606,5 +606,14 @@
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
>              "indexes": [["logical_port", "dst_ip"]],
> -            "isRoot": true}}
> +            "isRoot": true},
> +        "Static_MAC_Binding": {
> +            "columns": {
> +                "logical_port": {"type": "string"},
> +                "ip": {"type": "string"},
> +                "mac": {"type": "string"},
> +                "override_dynamic_mac": {"type": "boolean"}},
> +            "indexes": [["logical_port", "ip"]],
> +             "isRoot": true}
>      }
> +}
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 4d7a23c52..ab7e7615c 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -4293,4 +4293,33 @@
>        </column>
>      </group>
>    </table>
> +
> +  <table name="Static_MAC_Binding">
> +    <p>
> +      Each record represents a Static_MAC_Binding entry for a logical router.
> +    </p>
> +
> +    <group title="Configuration">
> +      <p>
> +        <code>ovn-northd</code> reads configuration from these columns
> +        and propagates the value to SBDB.
> +      </p>
> +
> +      <column name="logical_port">
> +        The logical router port for the binding.
> +      </column>
> +
> +      <column name="ip">
> +        The bound IP address.
> +      </column>
> +
> +      <column name="mac">
> +        The Ethernet address to which the IP is bound.
> +      </column>
> +
> +      <column name="override_dynamic_mac">
> +        Override dynamically learnt MACs.
> +      </column>
> +    </group>
> +  </table>
>  </database>
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 122614dd5..66664c840 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.21.0",
> -    "cksum": "2362446865 26963",
> +    "version": "20.22.0",
> +    "cksum": "1686121686 27471",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -533,6 +533,17 @@
>                                        "minInteger": 1,
>                                        "maxInteger": 16777215}}}},
>              "indexes": [["mac", "dp_key"]],
> +            "isRoot": true},
> +        "Static_MAC_Binding": {
> +            "columns": {
> +                "logical_port": {"type": "string"},
> +                "ip": {"type": "string"},
> +                "mac": {"type": "string"},
> +                "override_dynamic_mac": {"type": "boolean"},
> +                "datapath": {"type": {
> +                                  "key": {"type": "uuid",
> +                                          "refTable": "Datapath_Binding"}}}},
> +            "indexes": [["logical_port", "ip"]],
>              "isRoot": true}
>      }
>  }
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index f7c41ccdc..9831b9fdb 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -4531,4 +4531,31 @@ tcp.flags = RST;
>        The key of the port binding on which this FDB was learnt.
>      </column>
>    </table>
> +
> +  <table name="Static_MAC_Binding" title="IP to MAC bindings">
> +    <p>
> +      Each record represents a Static_MAC_Binding entry for a logical router.
> +    </p>
> +
> +
> +    <column name="logical_port">
> +        The logical router port for the binding.
> +    </column>
> +
> +    <column name="ip">
> +      The bound IP address.
> +    </column>
> +
> +    <column name="mac">
> +      The Ethernet address to which the IP is bound.
> +    </column>
> +
> +    <column name="override_dynamic_mac">
> +      Override dynamically learnt MACs.
> +    </column>
> +
> +    <column name="datapath">
> +      The logical datapath to which the logical router port belongs.
> +    </column>
> +  </table>
>  </database>
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 539a121c0..4e65443c9 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -2269,6 +2269,75 @@ AT_CHECK([ovn-nbctl list forwarding_group], [0], [])
>  
>  dnl ---------------------------------------------------------------------
>  
> +OVN_NBCTL_TEST([ovn_nbctl_static_mac_binding], [lr static_mac_binding], [
> +
> +AT_CHECK([ovn-nbctl lr-add lr0])
> +AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24])
> +AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 10.0.0.10 00:00:33:44:55:66])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:88])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 foo 00:00:44:55:66:88], [1], [],
> +  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
> +])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.200 foo], [1], [],
> +  [ovn-nbctl: invalid mac address foo.
> +])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:77], [1], [],
> +  [ovn-nbctl: lr0-p0, 172.16.0.11: a Static_MAC_Binding with this logical_port and ip already exists
> +])
> +
> +AT_CHECK([ovn-nbctl --may-exist static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:77])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66])
> +AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p1 172.16.0.11 00:00:44:55:66:88])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
> +LOGICAL_PORT             IP                       MAC
> +lr0-p0                   10.0.0.10                00:00:33:44:55:66
> +lr0-p0                   172.16.0.11              00:00:44:55:66:77
> +lr0-p0                   192.168.10.10            00:00:11:22:33:44
> +lr0-p0                   192.168.10.100           00:00:22:33:44:55
> +lr0-p1                   10.0.0.10                00:00:33:44:55:66
> +lr0-p1                   172.16.0.11              00:00:44:55:66:88
> +])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 foo], [1], [],
> +  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
> +])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p1 10.0.0.100], [1], [],
> +  [ovn-nbctl: no matching Static_MAC_Binding with port lr0-p1 and ip 10.0.0.100
> +])
> +
> +AT_CHECK([ovn-nbctl --if-exists static-mac-binding-del lr0-p1 10.0.0.100])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 10.0.0.10])
> +AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 192.168.10.100])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
> +LOGICAL_PORT             IP                       MAC
> +lr0-p0                   172.16.0.11              00:00:44:55:66:77
> +lr0-p0                   192.168.10.10            00:00:11:22:33:44
> +lr0-p1                   10.0.0.10                00:00:33:44:55:66
> +lr0-p1                   172.16.0.11              00:00:44:55:66:88
> +])
> +
> +AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p1 10.0.0.10])
> +AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
> +LOGICAL_PORT             IP                       MAC
> +lr0-p0                   172.16.0.11              00:00:44:55:66:77
> +lr0-p0                   192.168.10.10            00:00:11:22:33:44
> +lr0-p1                   172.16.0.11              00:00:44:55:66:88
> +])
> +
> +])
> +
> +dnl ---------------------------------------------------------------------
> +
>  OVN_NBCTL_TEST([ovn_nbctl_negative], [basic negative tests], [
>  AT_CHECK([ovn-nbctl --id=@ls create logical_switch name=foo -- \
>            set logical_switch foo1 name=bar],
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 17d4f31b3..8665e6be9 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -6474,4 +6474,27 @@ AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort],
>  ])
>  
>  AT_CLEANUP
> -])
> +
> +AT_SETUP([LR NB Static_MAC_Binding table])
> +ovn_start
> +
> +# Create logical routers
> +ovn-nbctl lr-add lr0
> +ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24
> +ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24
> +
> +ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44
> +ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55
> +
> +wait_row_count nb:Static_MAC_Binding 2 logical_port=lr0-p0
> +wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.10 mac="00\:00\:11\:22\:33\:44"
> +wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 mac="00\:00\:22\:33\:44\:55"
> +
> +ovn-nbctl static-mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66
> +wait_row_count nb:Static_MAC_Binding 1 logical_port=lr0-p1
> +wait_row_count Static_MAC_Binding 1 logical_port=lr0-p1 ip=10.0.0.10 mac="00\:00\:33\:44\:55\:66"
> +
> +ovn-nbctl --may-exist static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:55:66
> +wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 mac="00\:00\:22\:33\:55\:66"
> +
> +AT_CLEANUP
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 166b5f72e..682a52394 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -30082,3 +30082,93 @@ AT_CHECK([as hv1 ovs-ofctl -OOpenFlow15 dump-meters br-int | grep -q rate=100],
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>  ])
> +
> +AT_SETUP([ovn -- lr static_mac_binding])
> +AT_KEYWORDS([static_mac_binding])
> +ovn_start
> +
> +# Add chassis
> +net_add n1
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +
> +# Create a logical router
> +ovn-nbctl lr-add lr0
> +ovn-nbctl lrp-add lr0 lr0-ls0 00:00:11:11:22:22 20.0.0.1/24
> +ovn-nbctl lrp-add lr0 lr0-ext-ls0 00:00:11:11:33:33 172.16.1.10/24
> +
> +# Create logical switch and connect to logical router
> +ovn-nbctl ls-add ls0
> +ovn-nbctl lsp-add ls0 ls0-lr0
> +ovn-nbctl lsp-set-type ls0-lr0 router
> +ovn-nbctl lsp-set-addresses ls0-lr0 router
> +ovn-nbctl --wait=sb lsp-set-options ls0-lr0 router-port=lr0-ls0
> +
> +# Create external gateway switch and connect to logical router
> +ovn-nbctl ls-add ext-ls0
> +ovn-nbctl lsp-add ext-ls0 ext-ls0-lr0
> +ovn-nbctl lsp-set-type ext-ls0-lr0 router
> +ovn-nbctl lsp-set-addresses ext-ls0-lr0 router
> +ovn-nbctl --wait=sb lsp-set-options ext-ls0-lr0 router-port=lr0-ext-ls0
> +
> +ovn-nbctl lsp-add ext-ls0 ln0 "" 1000
> +ovn-nbctl lsp-set-addresses ln0 unknown
> +ovn-nbctl lsp-set-type ln0 localnet
> +ovn-nbctl lsp-set-options ln0 network_name=phys
> +
> +# Add the lsp lp11 to ls0. This will map to VIF11.
> +ovn-nbctl lsp-add ls0 lp11
> +ovn-nbctl lsp-set-addresses lp11 "00:00:11:11:44:44 20.0.0.10"
> +
> +# Add a vif on HV1
> +ovs-vsctl add-port br-int vif11 -- \
> +    set Interface vif11 external-ids:iface-id=lp11 \
> +                              options:tx_pcap=hv1/vif11-tx.pcap \
> +                              options:rxq_pcap=hv1/vif11-rx.pcap \
> +                              ofport-request=11
> +OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up lp11) = xup])
> +
> +ovn-nbctl lrp-set-gateway-chassis lr0-ext-ls0 hv1
> +
> +ovn-nbctl --wait=sb sync
> +OVN_POPULATE_ARP
> +
> +ovn-nbctl --wait=hv lr-nat-add lr0 snat 172.16.1.10 20.0.0.0/24
> +ovn-nbctl --wait=hv lr-route-add lr0 0.0.0.0/0 172.16.1.1
> +
> +test_mac_binding_flows() {
> +    local priority=$1 mac=$2 count=$3
> +    OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep table=66 | grep priority=${priority} | grep actions=mod_dl_dst:${mac} | wc -l) -eq ${count}])
> +}
> +# Create SB MAC_Binding entry on external gateway port
> +lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
> +
> +ovn-sbctl create mac_binding ip=172.16.1.1 logical_port=lr0-ext-ls0 mac="00\:00\:11\:22\:33\:44" datapath=$lr0_dp_uuid
> +test_mac_binding_flows 100 00:00:11:22:33:44 1
> +
> +# Create Static_MAC_Binding entry on external gateway port. This should have
> +# higher priority than MAC_Binding entry
> +ovn-nbctl static-mac-binding-add lr0-ext-ls0 172.16.1.1 00:00:11:22:33:66
> +test_mac_binding_flows 50 00:00:11:22:33:66 1
> +
> +# Update MAC for existing Static_MAC_Binding. Existing flow should be updated.
> +ovn-nbctl --may-exist static-mac-binding-add lr0-ext-ls0 172.16.1.1 00:00:11:22:33:88
> +test_mac_binding_flows 50 00:00:11:22:33:66 0
> +test_mac_binding_flows 50 00:00:11:22:33:88 1
> +
> +# Update override_dynamic_mac for existing Static_MAC_Binding. Existing flow should be updated.
> +smb_uuid=$(fetch_column nb:static_mac_binding _uuid ip=172.16.1.1)
> +
> +ovn-nbctl set static_mac_binding $smb_uuid override_dynamic_mac=true
> +test_mac_binding_flows 50 00:00:11:22:33:88 0
> +test_mac_binding_flows 150 00:00:11:22:33:88 1
> +
> +# Delete Static_MAC_Binding. Higher priority flow should get deleted.
> +ovn-nbctl static-mac-binding-del lr0-ext-ls0 172.16.1.1
> +test_mac_binding_flows 150 00:00:11:22:33:88 0
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 7bcc2c66a..d024db2c1 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -373,7 +373,8 @@ Policy commands:\n\
>    lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\
>                              remove policies from ROUTER\n\
>    lr-policy-list ROUTER     print policies for ROUTER\n\
> -\n\
> +\n\n",program_name, program_name);
> +    printf("\
>  NAT commands:\n\
>    [--stateless]\n\
>    [--portrange]\n\
> @@ -416,8 +417,7 @@ Connection commands:\n\
>    del-connection             delete the connections\n\
>    [--inactivity-probe=MSECS]\n\
>    set-connection TARGET...   set the list of connections to TARGET...\n\
> -\n\n",program_name, program_name);
> -    printf("\
> +\n\
>  SSL commands:\n\
>    get-ssl                     print the SSL configuration\n\
>    del-ssl                     delete the SSL configuration\n\
> @@ -452,6 +452,13 @@ Control Plane Protection Policy commands:\n\
>    lr-copp-add NAME ROUTER\n\
>                              Add a NAME copp policy on ROUTER logical router.\n\
>  \n\
> +MAC_Binding commands:\n\
> +  static-mac-binding-add LOGICAL_PORT IP MAC\n\
> +                                    Add a Static_MAC_Binding entry\n\
> +  static-mac-binding-del LOGICAL_PORT IP\n\
> +                                    Delete Static_MAC_Binding entry\n\
> +  static-mac-binding-list           List all Static_MAC_Binding entries\n\
> +\n\
>  %s\
>  %s\
>  \n\
> @@ -5721,6 +5728,146 @@ nbctl_lrp_get_redirect_type(struct ctl_context *ctx)
>                    !redirect_type ? "overlay": redirect_type);
>  }
>  
> +static const struct nbrec_static_mac_binding *
> +static_mac_binding_by_port_ip(struct ctl_context *ctx,
> +                              const char *logical_port, const char *ip)
> +{
> +    const struct nbrec_static_mac_binding *nb_smb = NULL;
> +
> +    NBREC_STATIC_MAC_BINDING_FOR_EACH (nb_smb, ctx->idl) {
> +        if (!strcmp(nb_smb->logical_port, logical_port) &&
> +            !strcmp(nb_smb->ip, ip)) {
> +            break;
> +        }
> +    }
> +
> +    return nb_smb;
> +}
> +
> +static void
> +nbctl_pre_static_mac_binding(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
> +
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_logical_port);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_ip);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_mac);
> +}
> +
> +static void
> +nbctl_static_mac_binding_add(struct ctl_context *ctx)
> +{
> +    const char *logical_port = ctx->argv[1];
> +    const char *ip = ctx->argv[2];
> +    const char *mac = ctx->argv[3];
> +    char *new_ip = NULL;
> +
> +    const struct nbrec_logical_router_port *lrp;
> +    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    new_ip = normalize_addr_str(ip);
> +    if (!new_ip) {
> +        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ip);
> +        return;
> +    }
> +
> +    struct eth_addr ea;
> +    if (!eth_addr_from_string(mac, &ea)) {
> +        ctl_error(ctx, "invalid mac address %s.", mac);
> +        goto cleanup;
> +    }
> +
> +    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +    const struct nbrec_static_mac_binding *nb_smb =
> +        static_mac_binding_by_port_ip(ctx, logical_port, ip);
> +    if (nb_smb) {
> +        if (may_exist) {
> +            if (strcmp(nb_smb->mac, mac)) {
> +                nbrec_static_mac_binding_verify_mac(nb_smb);
> +                nbrec_static_mac_binding_set_mac(nb_smb, mac);
> +            }
> +        } else {
> +            ctl_error(ctx, "%s, %s: a Static_MAC_Binding with this "
> +                      "logical_port and ip already exists",
> +                      logical_port, new_ip);
> +        }
> +        goto cleanup;
> +    }
> +
> +    /* Create Static_MAC_Binding entry */
> +    nb_smb = nbrec_static_mac_binding_insert(ctx->txn);
> +    nbrec_static_mac_binding_set_logical_port(nb_smb, logical_port);
> +    nbrec_static_mac_binding_set_ip(nb_smb, new_ip);
> +    nbrec_static_mac_binding_set_mac(nb_smb, mac);
> +
> +cleanup:
> +    free(new_ip);
> +}
> +
> +static void
> +nbctl_static_mac_binding_del(struct ctl_context *ctx)
> +{
> +    bool must_exist = !shash_find(&ctx->options, "--if-exists");
> +    const char *logical_port = ctx->argv[1];
> +    const struct nbrec_logical_router_port *lrp;
> +    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    char *ip = normalize_addr_str(ctx->argv[2]);
> +    if (!ip) {
> +        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ctx->argv[2]);
> +        return;
> +    }
> +
> +    const struct nbrec_static_mac_binding *nb_smb =
> +        static_mac_binding_by_port_ip(ctx, logical_port, ip);
> +
> +    if (nb_smb) {
> +        /* Remove the matching Static_MAC_Binding. */
> +        nbrec_static_mac_binding_delete(nb_smb);
> +        goto cleanup;
> +    }
> +
> +    if (must_exist) {
> +        ctl_error(ctx, "no matching Static_MAC_Binding with port %s and ip %s",
> +                  logical_port, ip);
> +    }
> +
> +cleanup:
> +    free(ip);
> +}
> +
> +static void
> +nbctl_static_mac_binding_list(struct ctl_context *ctx)
> +{
> +    struct smap lr_mac_bindings = SMAP_INITIALIZER(&lr_mac_bindings);
> +    const struct nbrec_static_mac_binding *nb_smb = NULL;
> +    NBREC_STATIC_MAC_BINDING_FOR_EACH (nb_smb, ctx->idl) {
> +        char *key = xasprintf("%-25s%-25s", nb_smb->logical_port, nb_smb->ip);
> +        smap_add_format(&lr_mac_bindings, key, "%s", nb_smb->mac);
> +        free(key);
> +    }
> +
> +    const struct smap_node **nodes = smap_sort(&lr_mac_bindings);
> +    if (nodes) {
> +        ds_put_format(&ctx->output, "%-25s%-25s%s\n",
> +                      "LOGICAL_PORT", "IP", "MAC");
> +        for (size_t i = 0; i < smap_count(&lr_mac_bindings); i++) {
> +            const struct smap_node *node = nodes[i];
> +            ds_put_format(&ctx->output, "%-25s%s\n", node->key, node->value);
> +        }
> +    }
> +    smap_destroy(&lr_mac_bindings);
> +    free(nodes);
> +}
> +
>  static const struct nbrec_forwarding_group *
>  fwd_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
>  {
> @@ -7197,6 +7344,17 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>       pre_ha_ch_grp_set_chassis_prio, cmd_ha_ch_grp_set_chassis_prio, NULL,
>       "", RW },
>  
> +    /* Static_MAC_Binding commands */
> +    { "static-mac-binding-add", 3, 3, "LOGICAL_PORT IP MAC",
> +      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_add, NULL,
> +      "--may-exist", RW },
> +    { "static-mac-binding-del", 2, 2, "LOGICAL_PORT IP",
> +      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_del, NULL,
> +      "--if-exists", RW },
> +    { "static-mac-binding-list", 0, 0, "",
> +      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_list, NULL,
> +      "", RO },
> +
>      {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
>  };
>  
> -- 
> 2.25.1
> 
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/lflow.c b/controller/lflow.c
index e169edef1..075373e7b 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -1622,40 +1622,50 @@  static void
 consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                        const struct hmap *local_datapaths,
                        const struct sbrec_mac_binding *b,
-                       struct ovn_desired_flow_table *flow_table)
+                       const struct sbrec_static_mac_binding *smb,
+                       struct ovn_desired_flow_table *flow_table,
+                       uint16_t priority)
 {
+    if (!b && !smb) {
+        return;
+    }
+
+    char *logical_port = !b ? smb->logical_port : b->logical_port;
+    char *ip = !b ? smb->ip : b->ip;
+    char *mac = !b ? smb->mac : b->mac;
+
     const struct sbrec_port_binding *pb
-        = lport_lookup_by_name(sbrec_port_binding_by_name, b->logical_port);
+        = lport_lookup_by_name(sbrec_port_binding_by_name, logical_port);
     if (!pb || !get_local_datapath(local_datapaths,
                                    pb->datapath->tunnel_key)) {
         return;
     }
 
-    struct eth_addr mac;
-    if (!eth_addr_from_string(b->mac, &mac)) {
+    struct eth_addr mac_addr;
+    if (!eth_addr_from_string(mac, &mac_addr)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "bad 'mac' %s", b->mac);
+        VLOG_WARN_RL(&rl, "bad 'mac' %s", mac);
         return;
     }
 
     struct match get_arp_match = MATCH_CATCHALL_INITIALIZER;
     struct match lookup_arp_match = MATCH_CATCHALL_INITIALIZER;
 
-    if (strchr(b->ip, '.')) {
-        ovs_be32 ip;
-        if (!ip_parse(b->ip, &ip)) {
+    if (strchr(ip, '.')) {
+        ovs_be32 ip_addr;
+        if (!ip_parse(ip, &ip_addr)) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
+            VLOG_WARN_RL(&rl, "bad 'ip' %s", ip);
             return;
         }
-        match_set_reg(&get_arp_match, 0, ntohl(ip));
-        match_set_reg(&lookup_arp_match, 0, ntohl(ip));
+        match_set_reg(&get_arp_match, 0, ntohl(ip_addr));
+        match_set_reg(&lookup_arp_match, 0, ntohl(ip_addr));
         match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_ARP));
     } else {
         struct in6_addr ip6;
-        if (!ipv6_parse(b->ip, &ip6)) {
+        if (!ipv6_parse(ip, &ip6)) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
+            VLOG_WARN_RL(&rl, "bad 'ip' %s", ip);
             return;
         }
         ovs_be128 value;
@@ -1678,20 +1688,22 @@  consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
     uint64_t stub[1024 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
     uint8_t value = 1;
-    put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
+    put_load(mac_addr.ea, sizeof mac_addr.ea, MFF_ETH_DST, 0, 48, &ofpacts);
     put_load(&value, sizeof value, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1,
              &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100,
-                    b->header_.uuid.parts[0], &get_arp_match,
-                    &ofpacts, &b->header_.uuid);
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, priority,
+                    !b ? smb->header_.uuid.parts[0] : b->header_.uuid.parts[0],
+                    &get_arp_match, &ofpacts,
+                    !b ? &smb->header_.uuid : &b->header_.uuid);
 
     ofpbuf_clear(&ofpacts);
     put_load(&value, sizeof value, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1,
              &ofpacts);
-    match_set_dl_src(&lookup_arp_match, mac);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, 100,
-                    b->header_.uuid.parts[0], &lookup_arp_match,
-                    &ofpacts, &b->header_.uuid);
+    match_set_dl_src(&lookup_arp_match, mac_addr);
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, priority,
+                    !b ? smb->header_.uuid.parts[0] : b->header_.uuid.parts[0],
+                    &lookup_arp_match, &ofpacts,
+                    !b ? &smb->header_.uuid : &b->header_.uuid);
 
     ofpbuf_uninit(&ofpacts);
 }
@@ -1701,13 +1713,23 @@  consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 static void
 add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                    const struct sbrec_mac_binding_table *mac_binding_table,
+                   const struct sbrec_static_mac_binding_table *smb_table,
                    const struct hmap *local_datapaths,
                    struct ovn_desired_flow_table *flow_table)
 {
+    /* Add flows for learnt MAC bindings */
     const struct sbrec_mac_binding *b;
     SBREC_MAC_BINDING_TABLE_FOR_EACH (b, mac_binding_table) {
         consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
-                               b, flow_table);
+                               b, NULL, flow_table, 100);
+    }
+
+    /* Add flows for statically configured MAC bindings */
+    const struct sbrec_static_mac_binding *smb;
+    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (smb, smb_table) {
+        consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
+                               NULL, smb, flow_table,
+                               smb->override_dynamic_mac ? 150 : 50);
     }
 }
 
@@ -2346,7 +2368,7 @@  add_lb_hairpin_flows(const struct sbrec_load_balancer_table *lb_table,
 
 /* Handles neighbor changes in mac_binding table. */
 void
-lflow_handle_changed_neighbors(
+lflow_handle_changed_mac_bindings(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     const struct sbrec_mac_binding_table *mac_binding_table,
     const struct hmap *local_datapaths,
@@ -2373,7 +2395,36 @@  lflow_handle_changed_neighbors(
             VLOG_DBG("handle new mac_binding "UUID_FMT,
                      UUID_ARGS(&mb->header_.uuid));
             consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
-                                   mb, flow_table);
+                                   mb, NULL, flow_table, 100);
+        }
+    }
+}
+
+/* Handles changes to static_mac_binding table. */
+void
+lflow_handle_changed_static_mac_bindings(
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    const struct sbrec_static_mac_binding_table *smb_table,
+    const struct hmap *local_datapaths,
+    struct ovn_desired_flow_table *flow_table)
+{
+    const struct sbrec_static_mac_binding *smb;
+    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (smb, smb_table) {
+        if (sbrec_static_mac_binding_is_deleted(smb)) {
+            VLOG_DBG("handle deleted static_mac_binding "UUID_FMT,
+                     UUID_ARGS(&smb->header_.uuid));
+            ofctrl_remove_flows(flow_table, &smb->header_.uuid);
+        } else {
+            if (!sbrec_static_mac_binding_is_new(smb)) {
+                VLOG_DBG("handle updated static_mac_binding "UUID_FMT,
+                         UUID_ARGS(&smb->header_.uuid));
+                ofctrl_remove_flows(flow_table, &smb->header_.uuid);
+            }
+            VLOG_DBG("handle new static_mac_binding "UUID_FMT,
+                     UUID_ARGS(&smb->header_.uuid));
+            consider_neighbor_flow(sbrec_port_binding_by_name, local_datapaths,
+                                   NULL, smb, flow_table,
+                                   smb->override_dynamic_mac ? 150 : 50);
         }
     }
 }
@@ -2443,7 +2494,9 @@  lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
 
     add_logical_flows(l_ctx_in, l_ctx_out);
     add_neighbor_flows(l_ctx_in->sbrec_port_binding_by_name,
-                       l_ctx_in->mac_binding_table, l_ctx_in->local_datapaths,
+                       l_ctx_in->mac_binding_table,
+                       l_ctx_in->static_mac_binding_table,
+                       l_ctx_in->local_datapaths,
                        l_ctx_out->flow_table);
     add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths,
                          l_ctx_out->flow_table,
diff --git a/controller/lflow.h b/controller/lflow.h
index d61733bc2..c2944378b 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -147,6 +147,7 @@  struct lflow_ctx_in {
     const struct sbrec_fdb_table *fdb_table;
     const struct sbrec_chassis *chassis;
     const struct sbrec_load_balancer_table *lb_table;
+    const struct sbrec_static_mac_binding_table *static_mac_binding_table;
     const struct hmap *local_datapaths;
     const struct shash *addr_sets;
     const struct shash *port_groups;
@@ -191,9 +192,14 @@  bool lflow_handle_addr_set_update(const char *as_name, struct addr_set_diff *,
                                   struct lflow_ctx_out *,
                                   bool *changed);
 
-void lflow_handle_changed_neighbors(
+void lflow_handle_changed_mac_bindings(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct sbrec_mac_binding_table *,
+    const struct sbrec_mac_binding_table *mac_binding_table,
+    const struct hmap *local_datapaths,
+    struct ovn_desired_flow_table *);
+void lflow_handle_changed_static_mac_bindings(
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    const struct sbrec_static_mac_binding_table *smb_table,
     const struct hmap *local_datapaths,
     struct ovn_desired_flow_table *);
 bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index ea5e9df41..2ec9aee32 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -969,7 +969,8 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     SB_NODE(dns, "dns") \
     SB_NODE(load_balancer, "load_balancer") \
     SB_NODE(fdb, "fdb") \
-    SB_NODE(meter, "meter")
+    SB_NODE(meter, "meter") \
+    SB_NODE(static_mac_binding, "static_mac_binding")
 
 enum sb_engine_node {
 #define SB_NODE(NAME, NAME_STR) SB_##NAME,
@@ -2295,6 +2296,10 @@  init_lflow_ctx(struct engine_node *node,
         (struct sbrec_fdb_table *)EN_OVSDB_GET(
             engine_get_input("SB_fdb", node));
 
+    struct sbrec_static_mac_binding_table *smb_table =
+        (struct sbrec_static_mac_binding_table *)EN_OVSDB_GET(
+            engine_get_input("SB_static_mac_binding", node));
+
     struct ovsrec_open_vswitch_table *ovs_table =
         (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
             engine_get_input("OVS_open_vswitch", node));
@@ -2343,6 +2348,7 @@  init_lflow_ctx(struct engine_node *node,
     l_ctx_in->fdb_table = fdb_table,
     l_ctx_in->chassis = chassis;
     l_ctx_in->lb_table = lb_table;
+    l_ctx_in->static_mac_binding_table = smb_table;
     l_ctx_in->local_datapaths = &rt_data->local_datapaths;
     l_ctx_in->addr_sets = addr_sets;
     l_ctx_in->port_groups = port_groups;
@@ -2485,13 +2491,39 @@  lflow_output_sb_mac_binding_handler(struct engine_node *node, void *data)
 
     struct ed_type_lflow_output *lfo = data;
 
-    lflow_handle_changed_neighbors(sbrec_port_binding_by_name,
+    lflow_handle_changed_mac_bindings(sbrec_port_binding_by_name,
             mac_binding_table, local_datapaths, &lfo->flow_table);
 
     engine_set_node_state(node, EN_UPDATED);
     return true;
 }
 
+static bool
+lflow_output_sb_static_mac_binding_handler(struct engine_node *node,
+                                           void *data)
+{
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "name");
+
+    struct sbrec_static_mac_binding_table *smb_table =
+        (struct sbrec_static_mac_binding_table *)EN_OVSDB_GET(
+            engine_get_input("SB_static_mac_binding", node));
+
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+    const struct hmap *local_datapaths = &rt_data->local_datapaths;
+
+    struct ed_type_lflow_output *lfo = data;
+
+    lflow_handle_changed_static_mac_bindings(sbrec_port_binding_by_name,
+        smb_table, local_datapaths, &lfo->flow_table);
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
 static bool
 lflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
 {
@@ -3322,6 +3354,8 @@  main(int argc, char *argv[])
 
     engine_add_input(&en_lflow_output, &en_sb_mac_binding,
                      lflow_output_sb_mac_binding_handler);
+    engine_add_input(&en_lflow_output, &en_sb_static_mac_binding,
+                     lflow_output_sb_static_mac_binding_handler);
     engine_add_input(&en_lflow_output, &en_sb_logical_flow,
                      lflow_output_sb_logical_flow_handler);
     /* Using a noop handler since we don't really need any data from datapath
diff --git a/lib/automake.mk b/lib/automake.mk
index 829aedfc5..3a2da1fe4 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -38,6 +38,8 @@  lib_libovn_la_SOURCES = \
 	lib/inc-proc-eng.h \
 	lib/lb.c \
 	lib/lb.h \
+	lib/static-mac-binding-index.c \
+	lib/static-mac-binding-index.h \
 	lib/stopwatch-names.h \
 	lib/vif-plug-provider.h \
 	lib/vif-plug-provider.c \
diff --git a/lib/static-mac-binding-index.c b/lib/static-mac-binding-index.c
new file mode 100644
index 000000000..fecc9b74f
--- /dev/null
+++ b/lib/static-mac-binding-index.c
@@ -0,0 +1,43 @@ 
+/* Copyright (c) 2021
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lib/static-mac-binding-index.h"
+#include "lib/ovn-sb-idl.h"
+
+struct ovsdb_idl_index *
+static_mac_binding_index_create(struct ovsdb_idl *idl)
+{
+    return ovsdb_idl_index_create2(idl,
+                                   &sbrec_static_mac_binding_col_logical_port,
+                                   &sbrec_static_mac_binding_col_ip);
+}
+
+const struct sbrec_static_mac_binding *
+static_mac_binding_lookup(struct ovsdb_idl_index *smb_index,
+                          const char *logical_port, const char *ip)
+{
+    struct sbrec_static_mac_binding *target =
+        sbrec_static_mac_binding_index_init_row(smb_index);
+    sbrec_static_mac_binding_index_set_logical_port(target, logical_port);
+    sbrec_static_mac_binding_index_set_ip(target, ip);
+
+    struct sbrec_static_mac_binding *smb =
+        sbrec_static_mac_binding_index_find(smb_index, target);
+    sbrec_static_mac_binding_index_destroy_row(target);
+
+    return smb;
+}
diff --git a/lib/static-mac-binding-index.h b/lib/static-mac-binding-index.h
new file mode 100644
index 000000000..3d4ff06a2
--- /dev/null
+++ b/lib/static-mac-binding-index.h
@@ -0,0 +1,27 @@ 
+/* Copyright (c) 2021
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_STATIC_MAC_BINDING_INDEX_H
+#define OVN_STATIC_MAC_BINDING_INDEX_H 1
+
+struct ovsdb_idl;
+
+struct ovsdb_idl_index *static_mac_binding_index_create(struct ovsdb_idl *);
+const struct sbrec_static_mac_binding *static_mac_binding_lookup(
+    struct ovsdb_idl_index *smb_index,
+    const char *logical_port,
+    const char *ip);
+
+#endif /* lib/static-mac-binding-index.h */
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 79da7e1c4..4907a1ff2 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -55,6 +55,10 @@  void en_northd_run(struct engine_node *node, void *data)
         engine_ovsdb_node_get_index(
             engine_get_input("SB_ip_multicast", node),
             "sbrec_ip_mcast_by_dp");
+    input_data.sbrec_static_mac_binding_by_lport_ip =
+        engine_ovsdb_node_get_index(
+            engine_get_input("SB_static_mac_binding", node),
+            "sbrec_static_mac_binding_by_lport_ip");
 
     input_data.nbrec_nb_global_table =
         EN_OVSDB_GET(engine_get_input("NB_nb_global", node));
@@ -72,6 +76,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("NB_meter", node));
     input_data.nbrec_acl_table =
         EN_OVSDB_GET(engine_get_input("NB_acl", node));
+    input_data.nbrec_static_mac_binding_table =
+        EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
 
     input_data.sbrec_sb_global_table =
         EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
@@ -103,6 +109,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("SB_ip_multicast", node));
     input_data.sbrec_chassis_private_table =
         EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
+    input_data.sbrec_static_mac_binding_table =
+        EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
 
     northd_run(&input_data, data,
                eng_ctx->ovnnb_idl_txn,
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index af55221e3..43093cb5a 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -20,6 +20,7 @@ 
 
 #include "chassis-index.h"
 #include "ip-mcast-index.h"
+#include "static-mac-binding-index.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/ovn-nb-idl.h"
 #include "lib/ovn-sb-idl.h"
@@ -60,7 +61,8 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     NB_NODE(gateway_chassis, "gateway_chassis") \
     NB_NODE(ha_chassis_group, "ha_chassis_group") \
     NB_NODE(ha_chassis, "ha_chassis") \
-    NB_NODE(bfd, "bfd")
+    NB_NODE(bfd, "bfd") \
+    NB_NODE(static_mac_binding, "static_mac_binding")
 
     enum nb_engine_node {
 #define NB_NODE(NAME, NAME_STR) NB_##NAME,
@@ -109,7 +111,8 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     SB_NODE(service_monitor, "service_monitor") \
     SB_NODE(load_balancer, "load_balancer") \
     SB_NODE(bfd, "bfd") \
-    SB_NODE(fdb, "fdb")
+    SB_NODE(fdb, "fdb") \
+    SB_NODE(static_mac_binding, "static_mac_binding")
 
 enum sb_engine_node {
 #define SB_NODE(NAME, NAME_STR) SB_##NAME,
@@ -178,6 +181,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_nb_gateway_chassis, NULL);
     engine_add_input(&en_northd, &en_nb_ha_chassis_group, NULL);
     engine_add_input(&en_northd, &en_nb_ha_chassis, NULL);
+    engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
 
     engine_add_input(&en_northd, &en_sb_sb_global, NULL);
     engine_add_input(&en_northd, &en_sb_chassis, NULL);
@@ -206,6 +210,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_sb_service_monitor, NULL);
     engine_add_input(&en_northd, &en_sb_load_balancer, NULL);
     engine_add_input(&en_northd, &en_sb_fdb, NULL);
+    engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL);
     engine_add_input(&en_lflow, &en_nb_bfd, NULL);
     engine_add_input(&en_lflow, &en_sb_bfd, NULL);
     engine_add_input(&en_lflow, &en_sb_logical_flow, NULL);
@@ -228,6 +233,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                          ip_mcast_index_create(sb->idl);
     struct ovsdb_idl_index *sbrec_chassis_by_hostname =
         chassis_hostname_index_create(sb->idl);
+    struct ovsdb_idl_index *sbrec_static_mac_binding_by_lport_ip
+        = static_mac_binding_index_create(sb->idl);
 
     engine_init(&en_lflow, &engine_arg);
 
@@ -246,6 +253,9 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_ovsdb_node_add_index(&en_sb_ip_multicast,
                                 "sbrec_ip_mcast_by_dp",
                                 sbrec_ip_mcast_by_dp);
+    engine_ovsdb_node_add_index(&en_sb_static_mac_binding,
+                                "sbrec_static_mac_binding_by_lport_ip",
+                                sbrec_static_mac_binding_by_lport_ip);
 }
 
 void inc_proc_northd_run(struct ovsdb_idl_txn *ovnnb_txn,
diff --git a/northd/northd.c b/northd/northd.c
index a2cf8d6fc..ce161f592 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -28,6 +28,7 @@ 
 #include "ovn/lex.h"
 #include "lib/chassis-index.h"
 #include "lib/ip-mcast-index.h"
+#include "lib/static-mac-binding-index.h"
 #include "lib/copp.h"
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-l7.h"
@@ -14988,6 +14989,80 @@  build_meter_groups(struct northd_input *input_data,
     }
 }
 
+static const struct nbrec_static_mac_binding *
+static_mac_binding_by_port_ip(struct northd_input *input_data,
+                       const char *logical_port, const char *ip)
+{
+    const struct nbrec_static_mac_binding *nb_smb = NULL;
+
+    NBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (
+        nb_smb, input_data->nbrec_static_mac_binding_table) {
+        if (!strcmp(nb_smb->logical_port, logical_port) &&
+            !strcmp(nb_smb->ip, ip)) {
+            break;
+        }
+    }
+
+    return nb_smb;
+}
+
+static void
+build_static_mac_binding_table(struct northd_input *input_data,
+                               struct ovsdb_idl_txn *ovnsb_txn,
+                               struct hmap *ports)
+{
+    /* Cleanup SB Static_MAC_Binding entries which do not have corresponding
+     * NB Static_MAC_Binding entries. */
+    const struct nbrec_static_mac_binding *nb_smb;
+    const struct sbrec_static_mac_binding *sb_smb, *sb_smb_next;
+    SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_SAFE (sb_smb, sb_smb_next,
+        input_data->sbrec_static_mac_binding_table) {
+        nb_smb = static_mac_binding_by_port_ip(input_data,
+                                               sb_smb->logical_port,
+                                               sb_smb->ip);
+        if (!nb_smb) {
+            sbrec_static_mac_binding_delete(sb_smb);
+        }
+    }
+
+    /* Create/Update SB Static_MAC_Binding entries with corresponding values
+     * from NB Static_MAC_Binding entries. */
+    NBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH (
+        nb_smb, input_data->nbrec_static_mac_binding_table) {
+        struct ovn_port *op = ovn_port_find(ports, nb_smb->logical_port);
+        if (op && op->nbrp) {
+            struct ovn_datapath *od = op->od;
+            if (od && od->sb) {
+                const struct sbrec_static_mac_binding *mb =
+                    static_mac_binding_lookup(
+                        input_data->sbrec_static_mac_binding_by_lport_ip,
+                        nb_smb->logical_port, nb_smb->ip);
+                if (!mb) {
+                    /* Create new entry */
+                    mb = sbrec_static_mac_binding_insert(ovnsb_txn);
+                    sbrec_static_mac_binding_set_logical_port(
+                        mb, nb_smb->logical_port);
+                    sbrec_static_mac_binding_set_ip(mb, nb_smb->ip);
+                    sbrec_static_mac_binding_set_mac(mb, nb_smb->mac);
+                    sbrec_static_mac_binding_set_override_dynamic_mac(mb,
+                        nb_smb->override_dynamic_mac);
+                    sbrec_static_mac_binding_set_datapath(mb, od->sb);
+                } else {
+                    /* Update existing entry if there is a change*/
+                    if (strcmp(mb->mac, nb_smb->mac)) {
+                        sbrec_static_mac_binding_set_mac(mb, nb_smb->mac);
+                    }
+                    if (mb->override_dynamic_mac !=
+                        nb_smb->override_dynamic_mac) {
+                        sbrec_static_mac_binding_set_override_dynamic_mac(mb,
+                            nb_smb->override_dynamic_mac);
+                    }
+                }
+            }
+        }
+    }
+}
+
 void
 northd_init(struct northd_data *data)
 {
@@ -15141,6 +15216,7 @@  ovnnb_db_run(struct northd_input *input_data,
     build_lrouter_groups(&data->ports, &data->lr_list);
     build_ip_mcast(input_data, ovnsb_txn, &data->datapaths);
     build_meter_groups(input_data, &data->meter_groups);
+    build_static_mac_binding_table(input_data, ovnsb_txn, &data->ports);
     stopwatch_stop(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
     stopwatch_start(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
     ovn_update_ipv6_prefix(&data->ports);
diff --git a/northd/northd.h b/northd/northd.h
index ebcb40de7..2d804a22e 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -28,6 +28,8 @@  struct northd_input {
     const struct nbrec_address_set_table *nbrec_address_set_table;
     const struct nbrec_meter_table *nbrec_meter_table;
     const struct nbrec_acl_table *nbrec_acl_table;
+    const struct nbrec_static_mac_binding_table
+        *nbrec_static_mac_binding_table;
 
     /* Southbound table references */
     const struct sbrec_sb_global_table *sbrec_sb_global_table;
@@ -45,12 +47,15 @@  struct northd_input {
     const struct sbrec_dns_table *sbrec_dns_table;
     const struct sbrec_ip_multicast_table *sbrec_ip_multicast_table;
     const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
+    const struct sbrec_static_mac_binding_table
+        *sbrec_static_mac_binding_table;
 
     /* Indexes */
     struct ovsdb_idl_index *sbrec_chassis_by_name;
     struct ovsdb_idl_index *sbrec_chassis_by_hostname;
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
     struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
+    struct ovsdb_idl_index *sbrec_static_mac_binding_by_lport_ip;
 };
 
 struct northd_data {
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 80b830629..043f71dd5 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "6.1.0",
-    "cksum": "4010776751 31237",
+    "version": "6.2.0",
+    "cksum": "3076170876 31573",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -606,5 +606,14 @@ 
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "indexes": [["logical_port", "dst_ip"]],
-            "isRoot": true}}
+            "isRoot": true},
+        "Static_MAC_Binding": {
+            "columns": {
+                "logical_port": {"type": "string"},
+                "ip": {"type": "string"},
+                "mac": {"type": "string"},
+                "override_dynamic_mac": {"type": "boolean"}},
+            "indexes": [["logical_port", "ip"]],
+             "isRoot": true}
     }
+}
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 4d7a23c52..ab7e7615c 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4293,4 +4293,33 @@ 
       </column>
     </group>
   </table>
+
+  <table name="Static_MAC_Binding">
+    <p>
+      Each record represents a Static_MAC_Binding entry for a logical router.
+    </p>
+
+    <group title="Configuration">
+      <p>
+        <code>ovn-northd</code> reads configuration from these columns
+        and propagates the value to SBDB.
+      </p>
+
+      <column name="logical_port">
+        The logical router port for the binding.
+      </column>
+
+      <column name="ip">
+        The bound IP address.
+      </column>
+
+      <column name="mac">
+        The Ethernet address to which the IP is bound.
+      </column>
+
+      <column name="override_dynamic_mac">
+        Override dynamically learnt MACs.
+      </column>
+    </group>
+  </table>
 </database>
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 122614dd5..66664c840 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.21.0",
-    "cksum": "2362446865 26963",
+    "version": "20.22.0",
+    "cksum": "1686121686 27471",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -533,6 +533,17 @@ 
                                       "minInteger": 1,
                                       "maxInteger": 16777215}}}},
             "indexes": [["mac", "dp_key"]],
+            "isRoot": true},
+        "Static_MAC_Binding": {
+            "columns": {
+                "logical_port": {"type": "string"},
+                "ip": {"type": "string"},
+                "mac": {"type": "string"},
+                "override_dynamic_mac": {"type": "boolean"},
+                "datapath": {"type": {
+                                  "key": {"type": "uuid",
+                                          "refTable": "Datapath_Binding"}}}},
+            "indexes": [["logical_port", "ip"]],
             "isRoot": true}
     }
 }
diff --git a/ovn-sb.xml b/ovn-sb.xml
index f7c41ccdc..9831b9fdb 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -4531,4 +4531,31 @@  tcp.flags = RST;
       The key of the port binding on which this FDB was learnt.
     </column>
   </table>
+
+  <table name="Static_MAC_Binding" title="IP to MAC bindings">
+    <p>
+      Each record represents a Static_MAC_Binding entry for a logical router.
+    </p>
+
+
+    <column name="logical_port">
+        The logical router port for the binding.
+    </column>
+
+    <column name="ip">
+      The bound IP address.
+    </column>
+
+    <column name="mac">
+      The Ethernet address to which the IP is bound.
+    </column>
+
+    <column name="override_dynamic_mac">
+      Override dynamically learnt MACs.
+    </column>
+
+    <column name="datapath">
+      The logical datapath to which the logical router port belongs.
+    </column>
+  </table>
 </database>
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 539a121c0..4e65443c9 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -2269,6 +2269,75 @@  AT_CHECK([ovn-nbctl list forwarding_group], [0], [])
 
 dnl ---------------------------------------------------------------------
 
+OVN_NBCTL_TEST([ovn_nbctl_static_mac_binding], [lr static_mac_binding], [
+
+AT_CHECK([ovn-nbctl lr-add lr0])
+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24])
+
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 10.0.0.10 00:00:33:44:55:66])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:88])
+
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 foo 00:00:44:55:66:88], [1], [],
+  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
+])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.200 foo], [1], [],
+  [ovn-nbctl: invalid mac address foo.
+])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:77], [1], [],
+  [ovn-nbctl: lr0-p0, 172.16.0.11: a Static_MAC_Binding with this logical_port and ip already exists
+])
+
+AT_CHECK([ovn-nbctl --may-exist static-mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:77])
+
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66])
+AT_CHECK([ovn-nbctl static-mac-binding-add lr0-p1 172.16.0.11 00:00:44:55:66:88])
+
+AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   10.0.0.10                00:00:33:44:55:66
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p0                   192.168.10.100           00:00:22:33:44:55
+lr0-p1                   10.0.0.10                00:00:33:44:55:66
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 foo], [1], [],
+  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
+])
+
+AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p1 10.0.0.100], [1], [],
+  [ovn-nbctl: no matching Static_MAC_Binding with port lr0-p1 and ip 10.0.0.100
+])
+
+AT_CHECK([ovn-nbctl --if-exists static-mac-binding-del lr0-p1 10.0.0.100])
+
+AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 10.0.0.10])
+AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p0 192.168.10.100])
+
+AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p1                   10.0.0.10                00:00:33:44:55:66
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+AT_CHECK([ovn-nbctl static-mac-binding-del lr0-p1 10.0.0.10])
+AT_CHECK([ovn-nbctl static-mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+])
+
+dnl ---------------------------------------------------------------------
+
 OVN_NBCTL_TEST([ovn_nbctl_negative], [basic negative tests], [
 AT_CHECK([ovn-nbctl --id=@ls create logical_switch name=foo -- \
           set logical_switch foo1 name=bar],
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 17d4f31b3..8665e6be9 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -6474,4 +6474,27 @@  AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort],
 ])
 
 AT_CLEANUP
-])
+
+AT_SETUP([LR NB Static_MAC_Binding table])
+ovn_start
+
+# Create logical routers
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24
+ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24
+
+ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44
+ovn-nbctl static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55
+
+wait_row_count nb:Static_MAC_Binding 2 logical_port=lr0-p0
+wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.10 mac="00\:00\:11\:22\:33\:44"
+wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 mac="00\:00\:22\:33\:44\:55"
+
+ovn-nbctl static-mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66
+wait_row_count nb:Static_MAC_Binding 1 logical_port=lr0-p1
+wait_row_count Static_MAC_Binding 1 logical_port=lr0-p1 ip=10.0.0.10 mac="00\:00\:33\:44\:55\:66"
+
+ovn-nbctl --may-exist static-mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:55:66
+wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 mac="00\:00\:22\:33\:55\:66"
+
+AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index 166b5f72e..682a52394 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -30082,3 +30082,93 @@  AT_CHECK([as hv1 ovs-ofctl -OOpenFlow15 dump-meters br-int | grep -q rate=100],
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+AT_SETUP([ovn -- lr static_mac_binding])
+AT_KEYWORDS([static_mac_binding])
+ovn_start
+
+# Add chassis
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+# Create a logical router
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-ls0 00:00:11:11:22:22 20.0.0.1/24
+ovn-nbctl lrp-add lr0 lr0-ext-ls0 00:00:11:11:33:33 172.16.1.10/24
+
+# Create logical switch and connect to logical router
+ovn-nbctl ls-add ls0
+ovn-nbctl lsp-add ls0 ls0-lr0
+ovn-nbctl lsp-set-type ls0-lr0 router
+ovn-nbctl lsp-set-addresses ls0-lr0 router
+ovn-nbctl --wait=sb lsp-set-options ls0-lr0 router-port=lr0-ls0
+
+# Create external gateway switch and connect to logical router
+ovn-nbctl ls-add ext-ls0
+ovn-nbctl lsp-add ext-ls0 ext-ls0-lr0
+ovn-nbctl lsp-set-type ext-ls0-lr0 router
+ovn-nbctl lsp-set-addresses ext-ls0-lr0 router
+ovn-nbctl --wait=sb lsp-set-options ext-ls0-lr0 router-port=lr0-ext-ls0
+
+ovn-nbctl lsp-add ext-ls0 ln0 "" 1000
+ovn-nbctl lsp-set-addresses ln0 unknown
+ovn-nbctl lsp-set-type ln0 localnet
+ovn-nbctl lsp-set-options ln0 network_name=phys
+
+# Add the lsp lp11 to ls0. This will map to VIF11.
+ovn-nbctl lsp-add ls0 lp11
+ovn-nbctl lsp-set-addresses lp11 "00:00:11:11:44:44 20.0.0.10"
+
+# Add a vif on HV1
+ovs-vsctl add-port br-int vif11 -- \
+    set Interface vif11 external-ids:iface-id=lp11 \
+                              options:tx_pcap=hv1/vif11-tx.pcap \
+                              options:rxq_pcap=hv1/vif11-rx.pcap \
+                              ofport-request=11
+OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up lp11) = xup])
+
+ovn-nbctl lrp-set-gateway-chassis lr0-ext-ls0 hv1
+
+ovn-nbctl --wait=sb sync
+OVN_POPULATE_ARP
+
+ovn-nbctl --wait=hv lr-nat-add lr0 snat 172.16.1.10 20.0.0.0/24
+ovn-nbctl --wait=hv lr-route-add lr0 0.0.0.0/0 172.16.1.1
+
+test_mac_binding_flows() {
+    local priority=$1 mac=$2 count=$3
+    OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep table=66 | grep priority=${priority} | grep actions=mod_dl_dst:${mac} | wc -l) -eq ${count}])
+}
+# Create SB MAC_Binding entry on external gateway port
+lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
+
+ovn-sbctl create mac_binding ip=172.16.1.1 logical_port=lr0-ext-ls0 mac="00\:00\:11\:22\:33\:44" datapath=$lr0_dp_uuid
+test_mac_binding_flows 100 00:00:11:22:33:44 1
+
+# Create Static_MAC_Binding entry on external gateway port. This should have
+# higher priority than MAC_Binding entry
+ovn-nbctl static-mac-binding-add lr0-ext-ls0 172.16.1.1 00:00:11:22:33:66
+test_mac_binding_flows 50 00:00:11:22:33:66 1
+
+# Update MAC for existing Static_MAC_Binding. Existing flow should be updated.
+ovn-nbctl --may-exist static-mac-binding-add lr0-ext-ls0 172.16.1.1 00:00:11:22:33:88
+test_mac_binding_flows 50 00:00:11:22:33:66 0
+test_mac_binding_flows 50 00:00:11:22:33:88 1
+
+# Update override_dynamic_mac for existing Static_MAC_Binding. Existing flow should be updated.
+smb_uuid=$(fetch_column nb:static_mac_binding _uuid ip=172.16.1.1)
+
+ovn-nbctl set static_mac_binding $smb_uuid override_dynamic_mac=true
+test_mac_binding_flows 50 00:00:11:22:33:88 0
+test_mac_binding_flows 150 00:00:11:22:33:88 1
+
+# Delete Static_MAC_Binding. Higher priority flow should get deleted.
+ovn-nbctl static-mac-binding-del lr0-ext-ls0 172.16.1.1
+test_mac_binding_flows 150 00:00:11:22:33:88 0
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 7bcc2c66a..d024db2c1 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -373,7 +373,8 @@  Policy commands:\n\
   lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\
                             remove policies from ROUTER\n\
   lr-policy-list ROUTER     print policies for ROUTER\n\
-\n\
+\n\n",program_name, program_name);
+    printf("\
 NAT commands:\n\
   [--stateless]\n\
   [--portrange]\n\
@@ -416,8 +417,7 @@  Connection commands:\n\
   del-connection             delete the connections\n\
   [--inactivity-probe=MSECS]\n\
   set-connection TARGET...   set the list of connections to TARGET...\n\
-\n\n",program_name, program_name);
-    printf("\
+\n\
 SSL commands:\n\
   get-ssl                     print the SSL configuration\n\
   del-ssl                     delete the SSL configuration\n\
@@ -452,6 +452,13 @@  Control Plane Protection Policy commands:\n\
   lr-copp-add NAME ROUTER\n\
                             Add a NAME copp policy on ROUTER logical router.\n\
 \n\
+MAC_Binding commands:\n\
+  static-mac-binding-add LOGICAL_PORT IP MAC\n\
+                                    Add a Static_MAC_Binding entry\n\
+  static-mac-binding-del LOGICAL_PORT IP\n\
+                                    Delete Static_MAC_Binding entry\n\
+  static-mac-binding-list           List all Static_MAC_Binding entries\n\
+\n\
 %s\
 %s\
 \n\
@@ -5721,6 +5728,146 @@  nbctl_lrp_get_redirect_type(struct ctl_context *ctx)
                   !redirect_type ? "overlay": redirect_type);
 }
 
+static const struct nbrec_static_mac_binding *
+static_mac_binding_by_port_ip(struct ctl_context *ctx,
+                              const char *logical_port, const char *ip)
+{
+    const struct nbrec_static_mac_binding *nb_smb = NULL;
+
+    NBREC_STATIC_MAC_BINDING_FOR_EACH (nb_smb, ctx->idl) {
+        if (!strcmp(nb_smb->logical_port, logical_port) &&
+            !strcmp(nb_smb->ip, ip)) {
+            break;
+        }
+    }
+
+    return nb_smb;
+}
+
+static void
+nbctl_pre_static_mac_binding(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_ip);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_static_mac_binding_col_mac);
+}
+
+static void
+nbctl_static_mac_binding_add(struct ctl_context *ctx)
+{
+    const char *logical_port = ctx->argv[1];
+    const char *ip = ctx->argv[2];
+    const char *mac = ctx->argv[3];
+    char *new_ip = NULL;
+
+    const struct nbrec_logical_router_port *lrp;
+    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    new_ip = normalize_addr_str(ip);
+    if (!new_ip) {
+        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ip);
+        return;
+    }
+
+    struct eth_addr ea;
+    if (!eth_addr_from_string(mac, &ea)) {
+        ctl_error(ctx, "invalid mac address %s.", mac);
+        goto cleanup;
+    }
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const struct nbrec_static_mac_binding *nb_smb =
+        static_mac_binding_by_port_ip(ctx, logical_port, ip);
+    if (nb_smb) {
+        if (may_exist) {
+            if (strcmp(nb_smb->mac, mac)) {
+                nbrec_static_mac_binding_verify_mac(nb_smb);
+                nbrec_static_mac_binding_set_mac(nb_smb, mac);
+            }
+        } else {
+            ctl_error(ctx, "%s, %s: a Static_MAC_Binding with this "
+                      "logical_port and ip already exists",
+                      logical_port, new_ip);
+        }
+        goto cleanup;
+    }
+
+    /* Create Static_MAC_Binding entry */
+    nb_smb = nbrec_static_mac_binding_insert(ctx->txn);
+    nbrec_static_mac_binding_set_logical_port(nb_smb, logical_port);
+    nbrec_static_mac_binding_set_ip(nb_smb, new_ip);
+    nbrec_static_mac_binding_set_mac(nb_smb, mac);
+
+cleanup:
+    free(new_ip);
+}
+
+static void
+nbctl_static_mac_binding_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *logical_port = ctx->argv[1];
+    const struct nbrec_logical_router_port *lrp;
+    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    char *ip = normalize_addr_str(ctx->argv[2]);
+    if (!ip) {
+        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ctx->argv[2]);
+        return;
+    }
+
+    const struct nbrec_static_mac_binding *nb_smb =
+        static_mac_binding_by_port_ip(ctx, logical_port, ip);
+
+    if (nb_smb) {
+        /* Remove the matching Static_MAC_Binding. */
+        nbrec_static_mac_binding_delete(nb_smb);
+        goto cleanup;
+    }
+
+    if (must_exist) {
+        ctl_error(ctx, "no matching Static_MAC_Binding with port %s and ip %s",
+                  logical_port, ip);
+    }
+
+cleanup:
+    free(ip);
+}
+
+static void
+nbctl_static_mac_binding_list(struct ctl_context *ctx)
+{
+    struct smap lr_mac_bindings = SMAP_INITIALIZER(&lr_mac_bindings);
+    const struct nbrec_static_mac_binding *nb_smb = NULL;
+    NBREC_STATIC_MAC_BINDING_FOR_EACH (nb_smb, ctx->idl) {
+        char *key = xasprintf("%-25s%-25s", nb_smb->logical_port, nb_smb->ip);
+        smap_add_format(&lr_mac_bindings, key, "%s", nb_smb->mac);
+        free(key);
+    }
+
+    const struct smap_node **nodes = smap_sort(&lr_mac_bindings);
+    if (nodes) {
+        ds_put_format(&ctx->output, "%-25s%-25s%s\n",
+                      "LOGICAL_PORT", "IP", "MAC");
+        for (size_t i = 0; i < smap_count(&lr_mac_bindings); i++) {
+            const struct smap_node *node = nodes[i];
+            ds_put_format(&ctx->output, "%-25s%s\n", node->key, node->value);
+        }
+    }
+    smap_destroy(&lr_mac_bindings);
+    free(nodes);
+}
+
 static const struct nbrec_forwarding_group *
 fwd_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
 {
@@ -7197,6 +7344,17 @@  static const struct ctl_command_syntax nbctl_commands[] = {
      pre_ha_ch_grp_set_chassis_prio, cmd_ha_ch_grp_set_chassis_prio, NULL,
      "", RW },
 
+    /* Static_MAC_Binding commands */
+    { "static-mac-binding-add", 3, 3, "LOGICAL_PORT IP MAC",
+      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_add, NULL,
+      "--may-exist", RW },
+    { "static-mac-binding-del", 2, 2, "LOGICAL_PORT IP",
+      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_del, NULL,
+      "--if-exists", RW },
+    { "static-mac-binding-list", 0, 0, "",
+      nbctl_pre_static_mac_binding, nbctl_static_mac_binding_list, NULL,
+      "", RO },
+
     {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
 };