Hi,

I’ve updated the patch and sent out v6. There was a memory leak in ovn-nbctl.c 
which is fixed now. Hopefully the build should pass now.

Thank you,
Karthik C

On 3/24/22, 9:27 AM, "Lorenzo Bianconi" <lorenzo.bianc...@redhat.com> wrote:
> From: Karthik Chandrashekar 
> <karthi...@nutanix.com<mailto:karthi...@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.

Hi Karthik,

thx for working on v5. I think this is mostly fine.
Just two comments inline.

Regards,
Lorenzo

>
> Signed-off-by: Karthik Chandrashekar 
> <karthi...@nutanix.com<mailto:karthi...@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          | 162 ++++++++++++++++++++++++++++++++-
>  18 files changed, 718 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);

here we will end up with 2 ~ identical flows (with different priorities) if
static mac binding and dynamic one collides. I think it will not a big deal 
since this would be a rare event.

@Numan: what do you think?

>          }
>      }
>  }
> @@ -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], [])
>

1992: ovn-nbctl - lr static_mac_binding - direct failed. Can you please double
check?

>  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..37cf4da35 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,144 @@ 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);
> +        }
> +    }
> +}
> +
>  static const struct nbrec_forwarding_group *
>  fwd_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
>  {
> @@ -7197,6 +7342,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
> d...@openvswitch.org<mailto:d...@openvswitch.org>
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to