@@ -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,40 @@ 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) {
+ /* Remove any flows that should be removed. */
+ 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);
+ }
+ }
+ SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (smb, smb_table) {
+ if (!sbrec_static_mac_binding_is_deleted(smb)) {
+ 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 +2498,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,
@@ -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 *);
@@ -968,7 +968,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
SB_NODE(dhcpv6_options, "dhcpv6_options") \
SB_NODE(dns, "dns") \
SB_NODE(load_balancer, "load_balancer") \
- 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,
@@ -2294,6 +2295,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));
@@ -2342,6 +2347,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;
@@ -2484,13 +2490,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)
{
@@ -3301,6 +3333,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
@@ -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 \
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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 */
@@ -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,
@@ -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,
@@ -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"
@@ -14961,6 +14962,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)
{
@@ -15116,6 +15191,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);
@@ -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 {
@@ -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}
}
+}
@@ -4264,4 +4264,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>
@@ -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}
}
}
@@ -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>
@@ -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],
@@ -6292,3 +6292,27 @@ AT_CHECK([grep -e 'lr_in_ip_routing ' lrflows | grep -e 'igmp' -e 'mld' | sed
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
@@ -29974,3 +29974,93 @@ OVS_WAIT_UNTIL([
OVN_CLEANUP([hv1],[hv2])
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
@@ -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\
@@ -5712,6 +5719,164 @@ 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_add(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_pre_static_mac_binding_del(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_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_pre_static_mac_binding_list(struct ctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_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_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)
{
@@ -7187,6 +7352,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_add, nbctl_static_mac_binding_add, NULL,
+ "--may-exist", RW },
+ { "static-mac-binding-del", 2, 2, "LOGICAL_PORT IP",
+ nbctl_pre_static_mac_binding_del, nbctl_static_mac_binding_del, NULL,
+ "--if-exists", RW },
+ { "static-mac-binding-list", 0, 1, "[LOGICAL_PORT]",
+ nbctl_pre_static_mac_binding_list, nbctl_static_mac_binding_list, NULL,
+ "", RO },
+
{NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
};