Message ID | 20220321235955.76127-1-svc.eng.git-mail@nutanix.com |
---|---|
State | Changes Requested |
Headers | show |
Series | [ovs-dev,v5] Add a northbound interface to program MAC_Binding table | expand |
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 |
References: <20220321235955.76127-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: 1207, 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
> 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. Hi Karthik, thx for working on v5. I think this is mostly fine. Just two comments inline. 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 | 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 > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
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.bianconi@redhat.com> wrote: > From: Karthik Chandrashekar <karthik.c@nutanix.com<mailto: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. Hi Karthik, thx for working on v5. I think this is mostly fine. Just two comments inline. Regards, Lorenzo > > Signed-off-by: Karthik Chandrashekar <karthik.c@nutanix.com<mailto: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 | 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 > dev@openvswitch.org<mailto:dev@openvswitch.org> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
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..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}, };