@@ -59,7 +59,9 @@ controller_ovn_controller_SOURCES = \
controller/route-exchange.h \
controller/route-table-notify.h \
controller/route.h \
- controller/route.c
+ controller/route.c \
+ controller/garp_rarp.h \
+ controller/garp_rarp.c
if HAVE_NETLINK
controller_ovn_controller_SOURCES += \
new file mode 100644
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) 2025, STACKIT GmbH & Co. KG
+ *
+ * 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 <net/if.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "controller/encaps.h"
+#include "controller/local_data.h"
+#include "controller/lport.h"
+#include "hash.h"
+#include "mac-binding-index.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lex.h"
+#include "packets.h"
+#include "smap.h"
+#include "sset.h"
+#include "openvswitch/shash.h"
+#include "garp_rarp.h"
+#include "ovn-sb-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(garp_rarp);
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+#define GARP_RARP_DEF_MAX_TIMEOUT 16000
+
+static bool garp_rarp_data_has_changed = false;
+static struct garp_rarp_data garp_rarp_data;
+
+/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
+static void
+get_localnet_vifs_l3gwports(
+ struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+ const struct sbrec_chassis *chassis,
+ const struct hmap *local_datapaths,
+ struct sset *localnet_vifs,
+ struct sset *local_l3gw_ports)
+{
+ struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
+ sbrec_port_binding_by_datapath);
+
+ const struct local_datapath *ld;
+ HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+ const struct sbrec_port_binding *pb;
+
+ if (!ld->localnet_port) {
+ continue;
+ }
+
+ sbrec_port_binding_index_set_datapath(target, ld->datapath);
+ SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
+ sbrec_port_binding_by_datapath) {
+ /* Get l3gw ports. Consider port bindings with type "l3gateway"
+ * that connect to gateway routers (if local), and consider port
+ * bindings of type "patch" since they might connect to
+ * distributed gateway ports with NAT addresses. */
+ if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
+ || !strcmp(pb->type, "patch")) {
+ sset_add(local_l3gw_ports, pb->logical_port);
+ }
+
+ /* Get all vifs that are directly connected to a localnet port. */
+ if (!strcmp(pb->type, "") && pb->chassis == chassis) {
+ sset_add(localnet_vifs, pb->logical_port);
+ }
+ }
+ }
+ sbrec_port_binding_index_destroy_row(target);
+}
+
+
+/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
+ * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
+ * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
+ * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
+ * fields of 'laddrs'. The logical port name is stored in 'lport'.
+ *
+ * Returns true if at least 'MAC' is found in 'address', false otherwise.
+ *
+ * The caller must call destroy_lport_addresses() and free(*lport). */
+static bool
+extract_addresses_with_port(const char *addresses,
+ struct lport_addresses *laddrs,
+ char **lport)
+{
+ int ofs;
+ if (!extract_addresses(addresses, laddrs, &ofs)) {
+ return false;
+ } else if (!addresses[ofs]) {
+ return true;
+ }
+
+ struct lexer lexer;
+ lexer_init(&lexer, addresses + ofs);
+ lexer_get(&lexer);
+
+ if (lexer.error || lexer.token.type != LEX_T_ID
+ || !lexer_match_id(&lexer, "is_chassis_resident")) {
+ VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
+ lexer_destroy(&lexer);
+ return true;
+ }
+
+ if (!lexer_match(&lexer, LEX_T_LPAREN)) {
+ VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
+ "'is_chassis_resident' in address '%s'", addresses);
+ lexer_destroy(&lexer);
+ return false;
+ }
+
+ if (lexer.token.type != LEX_T_STRING) {
+ VLOG_INFO_RL(&rl,
+ "Syntax error: expecting quoted string after "
+ "'is_chassis_resident' in address '%s'", addresses);
+ lexer_destroy(&lexer);
+ return false;
+ }
+
+ *lport = xstrdup(lexer.token.s);
+
+ lexer_get(&lexer);
+ if (!lexer_match(&lexer, LEX_T_RPAREN)) {
+ VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in "
+ "'is_chassis_resident()' in address '%s'",
+ addresses);
+ lexer_destroy(&lexer);
+ return false;
+ }
+
+ lexer_destroy(&lexer);
+ return true;
+}
+
+static void
+consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ const char *nat_address,
+ const struct sbrec_port_binding *pb,
+ struct sset *nat_address_keys,
+ const struct sbrec_chassis *chassis,
+ struct shash *nat_addresses,
+ struct sset *non_local_lports,
+ struct sset *local_lports)
+{
+ struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
+ char *lport = NULL;
+ const struct sbrec_port_binding *cr_pb = NULL;
+ bool rc = extract_addresses_with_port(nat_address, laddrs, &lport);
+ if (lport) {
+ cr_pb = lport_lookup_by_name(sbrec_port_binding_by_name, lport);
+ }
+ if (!rc
+ || (!lport && !strcmp(pb->type, "patch"))) {
+ destroy_lport_addresses(laddrs);
+ free(laddrs);
+ free(lport);
+ return;
+ }
+ if (lport) {
+ if (!cr_pb || (cr_pb->chassis != chassis)) {
+ sset_add(non_local_lports, lport);
+ destroy_lport_addresses(laddrs);
+ free(laddrs);
+ free(lport);
+ return;
+ } else {
+ sset_add(local_lports, lport);
+ }
+ }
+ free(lport);
+
+ for (size_t i = 0; i < laddrs->n_ipv4_addrs; i++) {
+ char *name = xasprintf("%s-%s", pb->logical_port,
+ laddrs->ipv4_addrs[i].addr_s);
+ sset_add(nat_address_keys, name);
+ free(name);
+ }
+ if (laddrs->n_ipv4_addrs == 0) {
+ char *name = xasprintf("%s-noip", pb->logical_port);
+ sset_add(nat_address_keys, name);
+ free(name);
+ }
+ shash_add(nat_addresses, pb->logical_port, laddrs);
+}
+
+static void
+get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ struct sset *nat_address_keys,
+ struct sset *local_l3gw_ports,
+ const struct sbrec_chassis *chassis,
+ struct shash *nat_addresses,
+ struct sset *non_local_lports,
+ struct sset *local_lports)
+{
+ const char *gw_port;
+ SSET_FOR_EACH (gw_port, local_l3gw_ports) {
+ const struct sbrec_port_binding *pb;
+
+ pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
+ if (!pb) {
+ continue;
+ }
+
+ if (pb->n_nat_addresses) {
+ for (size_t i = 0; i < pb->n_nat_addresses; i++) {
+ consider_nat_address(sbrec_port_binding_by_name,
+ pb->nat_addresses[i], pb,
+ nat_address_keys, chassis,
+ nat_addresses,
+ non_local_lports,
+ local_lports);
+ }
+ } else {
+ /* Continue to support options:nat-addresses for version
+ * upgrade. */
+ const char *nat_addresses_options = smap_get(&pb->options,
+ "nat-addresses");
+ if (nat_addresses_options) {
+ consider_nat_address(sbrec_port_binding_by_name,
+ nat_addresses_options, pb,
+ nat_address_keys, chassis,
+ nat_addresses,
+ non_local_lports,
+ local_lports);
+ }
+ }
+ }
+}
+
+static uint32_t
+garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
+ uint32_t port_key)
+{
+ return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
+}
+
+static uint32_t
+garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
+{
+ return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
+}
+
+/* Searches for a given garp_rarp_node in a hmap. Ignores the announce_time
+ * and backoff field since they might be different based on runtime. */
+static struct garp_rarp_node *
+garp_rarp_lookup(const struct eth_addr ea, ovs_be32 ipv4, uint32_t dp_key,
+ uint32_t port_key)
+{
+ struct garp_rarp_node *grn;
+ uint32_t hash = garp_rarp_node_hash(&ea, dp_key, port_key);
+ CMAP_FOR_EACH_WITH_HASH (grn, cmap_node, hash, &garp_rarp_data.data) {
+ if (!eth_addr_equals(ea, grn->ea)) {
+ continue;
+ }
+
+ if (ipv4 != grn->ipv4) {
+ continue;
+ }
+
+ if (dp_key != grn->dp_key) {
+ continue;
+ }
+
+ if (port_key != grn->port_key) {
+ continue;
+ }
+
+ return grn;
+ }
+ return NULL;
+}
+
+static void
+garp_rarp_node_add(const struct eth_addr ea, ovs_be32 ip,
+ uint32_t dp_key, uint32_t port_key)
+{
+ struct garp_rarp_node *grn = garp_rarp_lookup(ea, ip, dp_key, port_key);
+ if (grn) {
+ grn->stale = false;
+ return;
+ }
+
+ grn = xmalloc(sizeof *grn);
+ grn->ea = ea;
+ grn->ipv4 = ip;
+ atomic_store(&grn->announce_time, time_msec() + 1000);
+ atomic_store(&grn->backoff, 1000); /* msec. */
+ grn->dp_key = dp_key;
+ grn->port_key = port_key;
+ grn->stale = false;
+ cmap_insert(&garp_rarp_data.data, &grn->cmap_node,
+ garp_rarp_node_hash_struct(grn));
+ garp_rarp_data_has_changed = true;
+}
+
+/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
+ * on peer router datapaths.
+ */
+static void
+send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
+ const struct sbrec_port_binding *in_pb,
+ struct eth_addr ea, ovs_be32 ip)
+{
+ if (!r_ctx_in->ovnsb_idl_txn) {
+ return;
+ }
+
+ const struct local_datapath *ldp =
+ get_local_datapath(r_ctx_in->local_datapaths,
+ in_pb->datapath->tunnel_key);
+
+ ovs_assert(ldp);
+ const struct peer_ports *peers;
+ VECTOR_FOR_EACH_PTR (&ldp->peer_ports, peers) {
+ const struct sbrec_port_binding *local = peers->local;
+ const struct sbrec_port_binding *remote = peers->remote;
+
+ /* Skip "ingress" port. */
+ if (local == in_pb) {
+ continue;
+ }
+
+ bool update_only = !smap_get_bool(&remote->datapath->external_ids,
+ "always_learn_from_arp_request",
+ true);
+
+ struct ds ip_s = DS_EMPTY_INITIALIZER;
+
+ ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
+ mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
+ r_ctx_in->sbrec_mac_binding_by_lport_ip,
+ remote->logical_port, remote->datapath,
+ ea, ds_cstr(&ip_s), update_only);
+ ds_destroy(&ip_s);
+ }
+}
+
+/* Add or update a vif for which GARPs need to be announced. */
+static void
+send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
+ const struct sbrec_port_binding *binding_rec,
+ struct shash *nat_addresses)
+{
+ /* Skip localports as they don't need to be announced */
+ if (!strcmp(binding_rec->type, "localport")) {
+ return;
+ }
+
+ /* Update GARP for NAT IP if it exists. Consider port bindings with type
+ * "l3gateway" for logical switch ports attached to gateway routers, and
+ * port bindings with type "patch" for logical switch ports attached to
+ * distributed gateway ports. */
+ if (!strcmp(binding_rec->type, "l3gateway")
+ || !strcmp(binding_rec->type, "patch")) {
+ struct lport_addresses *laddrs = NULL;
+ while ((laddrs = shash_find_and_delete(nat_addresses,
+ binding_rec->logical_port))) {
+ for (size_t i = 0; i < laddrs->n_ipv4_addrs; i++) {
+ garp_rarp_node_add(laddrs->ea, laddrs->ipv4_addrs[i].addr,
+ binding_rec->datapath->tunnel_key,
+ binding_rec->tunnel_key);
+ send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
+ laddrs->ipv4_addrs[i].addr);
+ }
+ /*
+ * Send RARPs even if we do not have a ipv4 address as it e.g.
+ * happens on ipv6 only ports.
+ */
+ if (laddrs->n_ipv4_addrs == 0) {
+ garp_rarp_node_add(laddrs->ea, 0,
+ binding_rec->datapath->tunnel_key,
+ binding_rec->tunnel_key);
+ }
+ destroy_lport_addresses(laddrs);
+ free(laddrs);
+ }
+ return;
+ }
+
+ /* Add GARP for new vif. */
+ for (size_t i = 0; i < binding_rec->n_mac; i++) {
+ struct lport_addresses laddrs;
+ ovs_be32 ip = 0;
+ if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
+ continue;
+ }
+
+ if (laddrs.n_ipv4_addrs) {
+ ip = laddrs.ipv4_addrs[0].addr;
+ }
+
+ garp_rarp_node_add(laddrs.ea, ip,
+ binding_rec->datapath->tunnel_key,
+ binding_rec->tunnel_key);
+ if (ip) {
+ send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
+ }
+
+ destroy_lport_addresses(&laddrs);
+ break;
+ }
+}
+
+static void
+garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in)
+{
+ sset_clear(&r_ctx_in->data->non_local_lports);
+ sset_clear(&r_ctx_in->data->local_lports);
+}
+
+void
+garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in)
+{
+ garp_rarp_clear(r_ctx_in);
+
+ struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
+ struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
+ struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
+ struct shash nat_addresses = SHASH_INITIALIZER(&nat_addresses);
+
+ struct garp_rarp_node *grn;
+ CMAP_FOR_EACH (grn, cmap_node, &garp_rarp_data.data) {
+ grn->stale = true;
+ }
+
+ get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
+ r_ctx_in->chassis,
+ r_ctx_in->local_datapaths,
+ &localnet_vifs, &local_l3gw_ports);
+
+ get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
+ &nat_ip_keys, &local_l3gw_ports,
+ r_ctx_in->chassis, &nat_addresses,
+ &r_ctx_in->data->non_local_lports,
+ &r_ctx_in->data->local_lports);
+
+ /* Update send_garp_rarp_data. */
+ const char *iface_id;
+ SSET_FOR_EACH (iface_id, &localnet_vifs) {
+ const struct sbrec_port_binding *pb = lport_lookup_by_name(
+ r_ctx_in->sbrec_port_binding_by_name, iface_id);
+ if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
+ send_garp_rarp_update(r_ctx_in, pb, &nat_addresses);
+ }
+ }
+
+ /* Update send_garp_rarp_data for nat-addresses. */
+ const char *gw_port;
+ SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
+ const struct sbrec_port_binding *pb = lport_lookup_by_name(
+ r_ctx_in->sbrec_port_binding_by_name, gw_port);
+ if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
+ send_garp_rarp_update(r_ctx_in, pb, &nat_addresses);
+ }
+ }
+
+ sset_destroy(&localnet_vifs);
+ sset_destroy(&local_l3gw_ports);
+
+ struct shash_node *iter;
+ SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
+ struct lport_addresses *laddrs = iter->data;
+ destroy_lport_addresses(laddrs);
+ shash_delete(&nat_addresses, iter);
+ free(laddrs);
+ }
+ shash_destroy(&nat_addresses);
+
+ sset_destroy(&nat_ip_keys);
+
+ unsigned long long garp_rarp_max_timeout = smap_get_ullong(
+ &r_ctx_in->cfg->external_ids,
+ "garp-max-timeout-sec", 0) * 1000;
+ bool garp_rarp_continuous = !!garp_rarp_max_timeout;
+ if (!garp_rarp_max_timeout) {
+ garp_rarp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
+ }
+
+ bool reset_timers = (
+ garp_rarp_max_timeout != garp_rarp_data.max_timeout ||
+ garp_rarp_continuous != garp_rarp_data.continuous);
+
+ CMAP_FOR_EACH (grn, cmap_node, &garp_rarp_data.data) {
+ if (grn->stale) {
+ cmap_remove(&garp_rarp_data.data, &grn->cmap_node,
+ garp_rarp_node_hash_struct(grn));
+ ovsrcu_postpone(garp_rarp_node_free, grn);
+ } else if (reset_timers) {
+ atomic_store(&grn->announce_time, time_msec() + 1000);
+ atomic_store(&grn->backoff, 1000);
+ }
+ }
+
+ garp_rarp_data.max_timeout = garp_rarp_max_timeout;
+ garp_rarp_data.continuous = garp_rarp_continuous;
+}
+
+const struct garp_rarp_data *
+garp_rarp_get_data(void)
+{
+ return &garp_rarp_data;
+}
+
+bool
+garp_rarp_data_changed(void) {
+ bool ret = garp_rarp_data_has_changed;
+ garp_rarp_data_has_changed = true;
+ return ret;
+}
+
+void
+garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
+{
+ free(garp_rarp);
+}
+
+struct ed_type_garp_rarp *
+garp_rarp_init(void)
+{
+ cmap_init(&garp_rarp_data.data);
+ garp_rarp_data.max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
+ garp_rarp_data.continuous = false;
+
+ struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
+ sset_init(&gr->non_local_lports);
+ sset_init(&gr->local_lports);
+ return gr;
+}
+
+void
+garp_rarp_cleanup(struct ed_type_garp_rarp *data)
+{
+ struct garp_rarp_node *grn;
+ CMAP_FOR_EACH (grn, cmap_node, &garp_rarp_data.data) {
+ cmap_remove(&garp_rarp_data.data, &grn->cmap_node,
+ garp_rarp_node_hash_struct(grn));
+ garp_rarp_node_free(grn);
+ }
+ sset_destroy(&data->non_local_lports);
+ sset_destroy(&data->local_lports);
+}
new file mode 100644
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2025, STACKIT GmbH & Co. KG
+ *
+ * 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 "vswitch-idl.h"
+#ifndef GARP_RARP_H
+#define GARP_RARP_H 1
+
+#include "cmap.h"
+#include "sset.h"
+#include "openvswitch/types.h"
+
+/* Contains a single mac and ip address that should be announced. */
+struct garp_rarp_node {
+ struct cmap_node cmap_node;
+ struct eth_addr ea; /* Ethernet address of port. */
+ ovs_be32 ipv4; /* Ipv4 address of port. */
+ atomic_llong announce_time; /* Next announcement in ms.
+ * If LLONG_MAX there should be no
+ * annoucement. */
+ atomic_int backoff; /* Backoff timeout for the next
+ * announcement (in msecs). */
+ uint32_t dp_key; /* Datapath used to output this GARP. */
+ uint32_t port_key; /* Port to inject the GARP into. */
+ bool stale; /* Used during sync to remove stale
+ * information. */
+};
+
+/* Contains all required data for pinctrl to actually send garps. */
+struct garp_rarp_data {
+ struct cmap data;
+
+ long long int max_timeout;
+ bool continuous;
+};
+
+struct garp_rarp_ctx_in {
+ struct ovsdb_idl_txn *ovnsb_idl_txn;
+ const struct ovsrec_open_vswitch *cfg;
+ struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
+ struct ovsdb_idl_index *sbrec_port_binding_by_name;
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
+ const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
+ const struct sbrec_chassis *chassis;
+ const struct hmap *local_datapaths;
+ const struct sset *active_tunnels;
+ struct ed_type_garp_rarp *data;
+};
+
+struct ed_type_garp_rarp {
+ /* non_local_lports and local_lports are used in the incremental handlers
+ * to trigger updates if such a port changes. */
+ struct sset non_local_lports; /* lports that we did not consider because
+ they where not local. */
+ struct sset local_lports; /* lports where we did consider the addresses
+ because they where local. */
+};
+
+void garp_rarp_run(struct garp_rarp_ctx_in *);
+void garp_rarp_node_free(struct garp_rarp_node *);
+const struct garp_rarp_data *garp_rarp_get_data(void);
+bool garp_rarp_data_changed(void);
+
+struct ed_type_garp_rarp *garp_rarp_init(void);
+void garp_rarp_cleanup(struct ed_type_garp_rarp *);
+
+#endif /* GARP_RARP_H */
@@ -92,6 +92,7 @@
#include "route.h"
#include "route-exchange.h"
#include "route-table-notify.h"
+#include "garp_rarp.h"
VLOG_DEFINE_THIS_MODULE(main);
@@ -4975,6 +4976,13 @@ controller_output_route_exchange_handler(struct engine_node *node OVS_UNUSED,
return EN_HANDLED_UPDATED;
}
+static enum engine_input_handler_result
+controller_output_garp_rarp_handler(struct engine_node *node OVS_UNUSED,
+ void *data OVS_UNUSED)
+{
+ return EN_HANDLED_UPDATED;
+}
+
/* Handles sbrec_chassis changes.
* If a new chassis is added or removed return false, so that
* flows are recomputed. For any updates, there is no need for
@@ -5432,6 +5440,189 @@ en_route_exchange_status_cleanup(void *data OVS_UNUSED)
{
}
+static enum engine_node_state
+en_garp_rarp_run(struct engine_node *node, void *data_)
+{
+ struct ed_type_garp_rarp *data = data_;
+
+ const struct ovsrec_open_vswitch_table *ovs_table =
+ EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
+ const char *chassis_id = get_ovs_chassis_id(ovs_table);
+ ovs_assert(chassis_id);
+
+ struct ovsdb_idl_index *sbrec_chassis_by_name =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_chassis", node),
+ "name");
+ const struct sbrec_chassis *chassis
+ = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+ ovs_assert(chassis);
+
+ const struct ovsrec_open_vswitch *cfg
+ = ovsrec_open_vswitch_table_first(ovs_table);
+
+ struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_port_binding", node),
+ "datapath");
+ struct ovsdb_idl_index *sbrec_port_binding_by_name =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_port_binding", node),
+ "name");
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_mac_binding", node),
+ "lport_ip");
+
+ struct ovsdb_idl_txn *ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn;
+
+ const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
+ sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
+
+ struct ed_type_runtime_data *rt_data =
+ engine_get_input_data("runtime_data", node);
+
+ struct garp_rarp_ctx_in r_ctx_in = {
+ .ovnsb_idl_txn = ovnsb_idl_txn,
+ .cfg = cfg,
+ .sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath,
+ .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
+ .sbrec_mac_binding_by_lport_ip = sbrec_mac_binding_by_lport_ip,
+ .ecmp_nh_table = ecmp_nh_table,
+ .chassis = chassis,
+ .active_tunnels = &rt_data->active_tunnels,
+ .local_datapaths = &rt_data->local_datapaths,
+ .data = data,
+ };
+
+ garp_rarp_run(&r_ctx_in);
+ return EN_UPDATED;
+}
+
+
+static void *
+en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
+ struct engine_arg *arg OVS_UNUSED)
+{
+ return garp_rarp_init();
+}
+
+static void
+en_garp_rarp_cleanup(void *data)
+{
+ garp_rarp_cleanup(data);
+}
+
+static enum engine_input_handler_result
+garp_rarp_sb_port_binding_handler(struct engine_node *node,
+ void *data_)
+{
+ /* We need to handle a change if there was change on a datapath with
+ * a localnet port.
+ * Also the ha_chassis status of a port binding might change. */
+ struct ed_type_garp_rarp *data = data_;
+
+ const struct ovsrec_open_vswitch_table *ovs_table =
+ EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
+ const char *chassis_id = get_ovs_chassis_id(ovs_table);
+ ovs_assert(chassis_id);
+
+ struct ovsdb_idl_index *sbrec_chassis_by_name =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_chassis", node),
+ "name");
+ const struct sbrec_chassis *chassis
+ = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+ ovs_assert(chassis);
+
+ struct ed_type_runtime_data *rt_data =
+ engine_get_input_data("runtime_data", node);
+ const struct sbrec_port_binding_table *port_binding_table =
+ EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
+ struct ovsdb_idl_index *sbrec_port_binding_by_name =
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_port_binding", node),
+ "name");
+
+ const struct sbrec_port_binding *pb;
+ SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, port_binding_table) {
+ struct local_datapath *ld = get_local_datapath(
+ &rt_data->local_datapaths, pb->datapath->tunnel_key);
+
+ if (!ld || ld->localnet_port) {
+ /* XXX: actually handle this incrementally. */
+ return EN_UNHANDLED;
+ }
+
+ if (sset_contains(&data->non_local_lports, pb->logical_port) &&
+ lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
+ &rt_data->active_tunnels,
+ pb->logical_port)) {
+ /* XXX: actually handle this incrementally. */
+ return EN_UNHANDLED;
+ }
+
+ if (sset_contains(&data->local_lports, pb->logical_port) &&
+ !lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
+ &rt_data->active_tunnels,
+ pb->logical_port)) {
+ /* XXX: actually handle this incrementally. */
+ return EN_UNHANDLED;
+ }
+ }
+
+ return EN_HANDLED_UNCHANGED;
+}
+
+static enum engine_input_handler_result
+garp_rarp_runtime_data_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+ /* We use two elements from rt_data:
+ * 1. active_tunnels: There is currently not incremental processing for
+ * this in runtime_data. So we just fall back to a recompute.
+ * 2. local_datapaths: This has incremental processing on the runtime_data
+ * side. We are only interested in datapaths with a localnet port so
+ * we just recompute if there is one in there. Otherwise the change is
+ * irrelevant for us. */
+
+ struct ed_type_runtime_data *rt_data =
+ engine_get_input_data("runtime_data", node);
+
+ /* There are no tracked data. Fall back to full recompute. */
+ if (!rt_data->tracked) {
+ return EN_UNHANDLED;
+ }
+
+ struct tracked_datapath *tdp;
+ HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
+ if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
+ /* This is currently not handled incrementally in runtime_data
+ * so it should never happen. Recompute just in case. */
+ return EN_UNHANDLED;
+ }
+
+ struct local_datapath *ld = get_local_datapath(
+ &rt_data->local_datapaths, tdp->dp->tunnel_key);
+
+ if (!ld || ld->localnet_port) {
+ /* XXX: actually handle this incrementally. */
+ return EN_UNHANDLED;
+ }
+
+ /* The localnet port might also have been removed. */
+ struct tracked_lport *tlp;
+ struct shash_node *sn;
+ SHASH_FOR_EACH (sn, &tdp->lports) {
+ tlp = sn->data;
+ if (!strcmp(tlp->pb->type, "localnet")) {
+ return EN_UNHANDLED;
+ }
+ }
+ }
+
+ return EN_HANDLED_UNCHANGED;
+}
+
/* Returns false if the northd internal version stored in SB_Global
* and ovn-controller internal version don't match.
*/
@@ -5740,6 +5931,7 @@ main(int argc, char *argv[])
ENGINE_NODE(route_table_notify);
ENGINE_NODE(route_exchange);
ENGINE_NODE(route_exchange_status);
+ ENGINE_NODE(garp_rarp);
#define SB_NODE(NAME) ENGINE_NODE_SB(NAME);
SB_NODES
@@ -5953,6 +6145,16 @@ main(int argc, char *argv[])
engine_add_input(&en_dns_cache, &en_sb_dns,
dns_cache_sb_dns_handler);
+ engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
+ engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
+ engine_add_input(&en_garp_rarp, &en_sb_port_binding,
+ garp_rarp_sb_port_binding_handler);
+ /* The mac_binding data is just used in an index to filter duplicates when
+ * inserting data to the southbound. */
+ engine_add_input(&en_garp_rarp, &en_sb_mac_binding, engine_noop_handler);
+ engine_add_input(&en_garp_rarp, &en_runtime_data,
+ garp_rarp_runtime_data_handler);
+
engine_add_input(&en_controller_output, &en_dns_cache,
NULL);
engine_add_input(&en_controller_output, &en_lflow_output,
@@ -5965,6 +6167,8 @@ main(int argc, char *argv[])
controller_output_bfd_chassis_handler);
engine_add_input(&en_controller_output, &en_route_exchange,
controller_output_route_exchange_handler);
+ engine_add_input(&en_controller_output, &en_garp_rarp,
+ controller_output_garp_rarp_handler);
engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
engine_add_input(&en_controller_output, &en_acl_id,
@@ -6001,6 +6205,8 @@ main(int argc, char *argv[])
sbrec_chassis_template_var_index_by_chassis);
engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
sbrec_learned_route_index_by_datapath);
+ engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
+ sbrec_mac_binding_by_lport_ip);
engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
ovsrec_flow_sample_collector_set_by_id);
engine_ovsdb_node_add_index(&en_ovs_port, "qos", ovsrec_port_by_qos);
@@ -6427,7 +6633,6 @@ main(int argc, char *argv[])
pinctrl_update(ovnsb_idl_loop.idl);
pinctrl_run(ovnsb_idl_txn,
sbrec_datapath_binding_by_key,
- sbrec_port_binding_by_datapath,
sbrec_port_binding_by_key,
sbrec_port_binding_by_name,
sbrec_mac_binding_by_lport_ip,
@@ -6443,7 +6648,7 @@ main(int argc, char *argv[])
sbrec_bfd_table_get(ovnsb_idl_loop.idl),
sbrec_ecmp_nexthop_table_get(
ovnsb_idl_loop.idl),
- br_int, chassis,
+ chassis,
&runtime_data->local_datapaths,
&runtime_data->active_tunnels,
&runtime_data->local_active_ports_ipv6_pd,
@@ -64,6 +64,7 @@
#include "ip-mcast.h"
#include "ovn-sb-idl.h"
#include "ovn-dns.h"
+#include "garp_rarp.h"
VLOG_DEFINE_THIS_MODULE(pinctrl);
@@ -161,11 +162,9 @@ static struct ovs_mutex pinctrl_mutex = OVS_MUTEX_INITIALIZER;
static struct seq *pinctrl_handler_seq;
static struct seq *pinctrl_main_seq;
-#define GARP_RARP_DEF_MAX_TIMEOUT 16000
-static long long int garp_rarp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
-static bool garp_rarp_continuous;
+#define ARP_ND_DEF_MAX_TIMEOUT 16000
-static long long int arp_nd_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
+static long long int arp_nd_max_timeout = ARP_ND_DEF_MAX_TIMEOUT;
static bool arp_nd_continuous;
static void *pinctrl_handler(void *arg);
@@ -227,26 +226,17 @@ static void run_activated_ports(
struct ovsdb_idl_index *sbrec_port_binding_by_name,
const struct sbrec_chassis *chassis);
-static void init_send_garps_rarps(void);
static void init_send_arps_nds(void);
-static void destroy_send_garps_rarps(void);
static void destroy_send_arps_nds(void);
static void send_garp_rarp_wait(long long int send_garp_rarp_time);
static void send_arp_nd_wait(long long int send_arp_nd_time);
static void send_garp_rarp_prepare(
- struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
- const struct ovsrec_bridge *,
- const struct sbrec_chassis *,
- const struct hmap *local_datapaths,
+ const struct sbrec_chassis *chassis,
const struct ovsrec_open_vswitch_table *ovs_table)
OVS_REQUIRES(pinctrl_mutex);
static void send_garp_rarp_run(struct rconn *swconn,
- long long int *send_garp_rarp_time)
- OVS_REQUIRES(pinctrl_mutex);
+ long long int *send_garp_rarp_time);
static void send_arp_nd_run(struct rconn *swconn,
long long int *send_arp_nd_time)
OVS_REQUIRES(pinctrl_mutex);
@@ -560,7 +550,6 @@ void
pinctrl_init(void)
{
init_put_mac_bindings();
- init_send_garps_rarps();
init_send_arps_nds();
init_ipv6_ras();
init_ipv6_prefixd();
@@ -3959,7 +3948,6 @@ pinctrl_handler(void *arg_)
if (may_inject_pkts()) {
if (!ovs_mutex_trylock(&pinctrl_mutex)) {
- send_garp_rarp_run(swconn, &send_garp_rarp_time);
send_arp_nd_run(swconn, &send_arp_nd_time);
send_ipv6_ras(swconn, &send_ipv6_ra_time);
send_ipv6_prefixd(swconn, &send_prefixd_time);
@@ -3969,6 +3957,7 @@ pinctrl_handler(void *arg_)
} else {
lock_failed = true;
}
+ send_garp_rarp_run(swconn, &send_garp_rarp_time);
ip_mcast_querier_run(swconn, &send_mcast_query_time);
}
@@ -4068,7 +4057,6 @@ pinctrl_update(const struct ovsdb_idl *idl)
void
pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
struct ovsdb_idl_index *sbrec_port_binding_by_key,
struct ovsdb_idl_index *sbrec_port_binding_by_name,
struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
@@ -4080,7 +4068,6 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
const struct sbrec_mac_binding_table *mac_binding_table,
const struct sbrec_bfd_table *bfd_table,
const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
- const struct ovsrec_bridge *br_int,
const struct sbrec_chassis *chassis,
const struct hmap *local_datapaths,
const struct sset *active_tunnels,
@@ -4095,10 +4082,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
sbrec_mac_binding_by_lport_ip);
run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
sbrec_port_binding_by_key, chassis, cur_cfg);
- send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath,
- sbrec_port_binding_by_name,
- sbrec_mac_binding_by_lport_ip, ecmp_nh_table,
- br_int, chassis, local_datapaths, ovs_table);
+ send_garp_rarp_prepare(ecmp_nh_table, chassis, ovs_table);
prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
local_active_ports_ipv6_pd, chassis,
@@ -4648,7 +4632,6 @@ pinctrl_destroy(void)
pthread_join(pinctrl.pinctrl_thread, NULL);
latch_destroy(&pinctrl.pinctrl_thread_exit);
rconn_destroy(pinctrl.swconn);
- destroy_send_garps_rarps();
destroy_send_arps_nds();
destroy_ipv6_ras();
destroy_ipv6_prefixd();
@@ -4763,89 +4746,6 @@ send_mac_binding_buffered_pkts(struct rconn *swconn)
}
}
-/* Update or add an IP-MAC binding for 'logical_port'.
- * Caller should make sure that 'ovnsb_idl_txn' is valid. */
-static void
-mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- const char *logical_port,
- const struct sbrec_datapath_binding *dp,
- struct eth_addr ea, const char *ip,
- bool update_only)
-{
- /* Convert ethernet argument to string form for database. */
- char mac_string[ETH_ADDR_STRLEN + 1];
- snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
-
- const struct sbrec_mac_binding *b =
- mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
- logical_port, ip);
- if (!b) {
- if (update_only) {
- return;
- }
- b = sbrec_mac_binding_insert(ovnsb_idl_txn);
- sbrec_mac_binding_set_logical_port(b, logical_port);
- sbrec_mac_binding_set_ip(b, ip);
- sbrec_mac_binding_set_datapath(b, dp);
- }
-
- if (strcmp(b->mac, mac_string)) {
- sbrec_mac_binding_set_mac(b, mac_string);
-
- /* For backward compatibility check if timestamp column is available
- * in SB DB. */
- if (pinctrl.mac_binding_can_timestamp) {
- VLOG_DBG("Setting MAC binding timestamp for "
- "ip:%s mac:%s port:%s to %lld",
- b->ip, b->mac, logical_port, time_wall_msec());
- sbrec_mac_binding_set_timestamp(b, time_wall_msec());
- }
- }
-}
-
-/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
- * on peer router datapaths.
- */
-static void
-send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- const struct hmap *local_datapaths,
- const struct sbrec_port_binding *in_pb,
- struct eth_addr ea, ovs_be32 ip)
-{
- if (!ovnsb_idl_txn) {
- return;
- }
-
- const struct local_datapath *ldp =
- get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
-
- ovs_assert(ldp);
- const struct peer_ports *peers;
- VECTOR_FOR_EACH_PTR (&ldp->peer_ports, peers) {
- const struct sbrec_port_binding *local = peers->local;
- const struct sbrec_port_binding *remote = peers->remote;
-
- /* Skip "ingress" port. */
- if (local == in_pb) {
- continue;
- }
-
- bool update_only = !smap_get_bool(&remote->datapath->external_ids,
- "always_learn_from_arp_request",
- true);
-
- struct ds ip_s = DS_EMPTY_INITIALIZER;
-
- ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
- mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
- remote->logical_port, remote->datapath,
- ea, ds_cstr(&ip_s), update_only);
- ds_destroy(&ip_s);
- }
-}
-
static void
run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
@@ -4997,174 +4897,6 @@ wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
* are needed for switches and routers on the broadcast segment to update
* their port-mac and ARP tables.
*/
-struct garp_rarp_data {
- struct eth_addr ea; /* Ethernet address of port. */
- ovs_be32 ipv4; /* Ipv4 address of port. */
- long long int announce_time; /* Next announcement in ms. */
- int backoff; /* Backoff timeout for the next
- * announcement (in msecs). */
- uint32_t dp_key; /* Datapath used to output this GARP. */
- uint32_t port_key; /* Port to inject the GARP into. */
-};
-
-/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
-static struct shash send_garp_rarp_data;
-
-static void
-init_send_garps_rarps(void)
-{
- shash_init(&send_garp_rarp_data);
-}
-
-static void
-destroy_send_garps_rarps(void)
-{
- shash_destroy_free_data(&send_garp_rarp_data);
-}
-
-/* Runs with in the main ovn-controller thread context. */
-static void
-add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
- uint32_t dp_key, uint32_t port_key)
-{
- struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
- garp_rarp->ea = ea;
- garp_rarp->ipv4 = ip;
- garp_rarp->announce_time = time_msec() + 1000;
- garp_rarp->backoff = 1000; /* msec. */
- garp_rarp->dp_key = dp_key;
- garp_rarp->port_key = port_key;
- shash_add(&send_garp_rarp_data, name, garp_rarp);
-
- /* Notify pinctrl_handler so that it can wakeup and process
- * these GARP/RARP requests. */
- notify_pinctrl_handler();
-}
-
-/* Add or update a vif for which GARPs need to be announced. */
-static void
-send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- const struct hmap *local_datapaths,
- const struct sbrec_port_binding *binding_rec,
- struct shash *nat_addresses,
- long long int garp_max_timeout,
- bool garp_continuous)
-{
- struct garp_rarp_data *garp_rarp = NULL;
-
- /* Skip localports as they don't need to be announced */
- if (!strcmp(binding_rec->type, "localport")) {
- return;
- }
-
- /* Update GARP for NAT IP if it exists. Consider port bindings with type
- * "l3gateway" for logical switch ports attached to gateway routers, and
- * port bindings with type "patch" for logical switch ports attached to
- * distributed gateway ports. */
- if (!strcmp(binding_rec->type, "l3gateway")
- || !strcmp(binding_rec->type, "patch")) {
- struct lport_addresses *laddrs = NULL;
- while ((laddrs = shash_find_and_delete(nat_addresses,
- binding_rec->logical_port))) {
- int i;
- for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
- char *name = xasprintf("%s-%s", binding_rec->logical_port,
- laddrs->ipv4_addrs[i].addr_s);
- garp_rarp = shash_find_data(&send_garp_rarp_data, name);
- if (garp_rarp) {
- garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
- garp_rarp->port_key = binding_rec->tunnel_key;
- if (garp_max_timeout != garp_rarp_max_timeout ||
- garp_continuous != garp_rarp_continuous) {
- /* reset backoff */
- garp_rarp->announce_time = time_msec() + 1000;
- garp_rarp->backoff = 1000; /* msec. */
- }
- } else if (ovnsb_idl_txn) {
- add_garp_rarp(name, laddrs->ea,
- laddrs->ipv4_addrs[i].addr,
- binding_rec->datapath->tunnel_key,
- binding_rec->tunnel_key);
- send_garp_locally(ovnsb_idl_txn,
- sbrec_mac_binding_by_lport_ip,
- local_datapaths, binding_rec, laddrs->ea,
- laddrs->ipv4_addrs[i].addr);
-
- }
- free(name);
- }
- /*
- * Send RARPs even if we do not have a ipv4 address as it e.g.
- * happens on ipv6 only ports.
- */
- if (laddrs->n_ipv4_addrs == 0) {
- char *name = xasprintf("%s-noip",
- binding_rec->logical_port);
- garp_rarp = shash_find_data(&send_garp_rarp_data, name);
- if (garp_rarp) {
- garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
- garp_rarp->port_key = binding_rec->tunnel_key;
- if (garp_max_timeout != garp_rarp_max_timeout ||
- garp_continuous != garp_rarp_continuous) {
- /* reset backoff */
- garp_rarp->announce_time = time_msec() + 1000;
- garp_rarp->backoff = 1000; /* msec. */
- }
- } else {
- add_garp_rarp(name, laddrs->ea,
- 0, binding_rec->datapath->tunnel_key,
- binding_rec->tunnel_key);
- }
- free(name);
- }
- destroy_lport_addresses(laddrs);
- free(laddrs);
- }
- return;
- }
-
- /* Update GARP for vif if it exists. */
- garp_rarp = shash_find_data(&send_garp_rarp_data,
- binding_rec->logical_port);
- if (garp_rarp) {
- garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
- garp_rarp->port_key = binding_rec->tunnel_key;
- if (garp_max_timeout != garp_rarp_max_timeout ||
- garp_continuous != garp_rarp_continuous) {
- /* reset backoff */
- garp_rarp->announce_time = time_msec() + 1000;
- garp_rarp->backoff = 1000; /* msec. */
- }
- return;
- }
-
- /* Add GARP for new vif. */
- int i;
- for (i = 0; i < binding_rec->n_mac; i++) {
- struct lport_addresses laddrs;
- ovs_be32 ip = 0;
- if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
- continue;
- }
-
- if (laddrs.n_ipv4_addrs) {
- ip = laddrs.ipv4_addrs[0].addr;
- }
-
- add_garp_rarp(binding_rec->logical_port,
- laddrs.ea, ip,
- binding_rec->datapath->tunnel_key,
- binding_rec->tunnel_key);
- if (ip) {
- send_garp_locally(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
- local_datapaths, binding_rec, laddrs.ea, ip);
- }
-
- destroy_lport_addresses(&laddrs);
- break;
- }
-}
struct arp_nd_data {
struct hmap_node hmap_node;
@@ -5309,16 +5041,6 @@ send_arp_nd_update(const struct sbrec_port_binding *pb, const char *nexthop,
}
}
-/* Remove a vif from GARP announcements. */
-static void
-send_garp_rarp_delete(const char *lport)
-{
- struct garp_rarp_data *garp_rarp = shash_find_and_delete
- (&send_garp_rarp_data, lport);
- free(garp_rarp);
- notify_pinctrl_handler();
-}
-
void
send_self_originated_neigh_packet(struct rconn *swconn,
uint32_t dp_key, uint32_t port_key,
@@ -5370,12 +5092,19 @@ send_self_originated_neigh_packet(struct rconn *swconn,
/* Called with in the pinctrl_handler thread context. */
static long long int
-send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
- long long int current_time)
- OVS_REQUIRES(pinctrl_mutex)
+send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
+ long long int current_time, long long int max_timeout,
+ bool continuous)
{
- if (current_time < garp_rarp->announce_time) {
- return garp_rarp->announce_time;
+ long long int announce_time, old_announce_time;
+ int backoff, old_backoff;
+
+ atomic_read(&garp_rarp->announce_time, &announce_time);
+ atomic_read(&garp_rarp->backoff, &backoff);
+ old_announce_time = announce_time;
+ old_backoff = backoff;
+ if (current_time < announce_time) {
+ return announce_time;
}
/* Compose and inject a GARP request packet. */
@@ -5398,14 +5127,19 @@ send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
/* Set the next announcement. At most 5 announcements are sent for a
* vif if garp_rarp_max_timeout is not specified otherwise cap the max
* timeout to garp_rarp_max_timeout. */
- if (garp_rarp_continuous || garp_rarp->backoff < garp_rarp_max_timeout) {
- garp_rarp->announce_time = current_time + garp_rarp->backoff;
+ if (continuous || backoff < max_timeout) {
+ announce_time = current_time + backoff;
} else {
- garp_rarp->announce_time = LLONG_MAX;
+ announce_time = LLONG_MAX;
}
- garp_rarp->backoff = MIN(garp_rarp_max_timeout, garp_rarp->backoff * 2);
+ backoff = MIN(max_timeout, backoff * 2);
- return garp_rarp->announce_time;
+ atomic_compare_exchange_strong(&garp_rarp->announce_time,
+ &old_announce_time, announce_time);
+ atomic_compare_exchange_strong(&garp_rarp->backoff, &old_backoff,
+ backoff);
+
+ return announce_time;
}
static void
@@ -6439,241 +6173,12 @@ ip_mcast_querier_wait(long long int query_time)
}
}
-/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
-static void
-get_localnet_vifs_l3gwports(
- struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const struct ovsrec_bridge *br_int,
- const struct sbrec_chassis *chassis,
- const struct hmap *local_datapaths,
- struct sset *localnet_vifs,
- struct sset *local_l3gw_ports)
-{
- for (int i = 0; i < br_int->n_ports; i++) {
- const struct ovsrec_port *port_rec = br_int->ports[i];
- if (!strcmp(port_rec->name, br_int->name)) {
- continue;
- }
- const char *tunnel_id = smap_get(&port_rec->external_ids,
- "ovn-chassis-id");
- if (tunnel_id &&
- encaps_tunnel_id_match(tunnel_id, chassis->name, NULL, NULL)) {
- continue;
- }
- const char *localnet = smap_get(&port_rec->external_ids,
- "ovn-localnet-port");
- if (localnet) {
- continue;
- }
- for (int j = 0; j < port_rec->n_interfaces; j++) {
- const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
- if (!iface_rec->n_ofport) {
- continue;
- }
- /* Get localnet vif. */
- const char *iface_id = smap_get(&iface_rec->external_ids,
- "iface-id");
- if (!iface_id) {
- continue;
- }
- const struct sbrec_port_binding *pb
- = lport_lookup_by_name(sbrec_port_binding_by_name, iface_id);
- if (!pb || pb->chassis != chassis) {
- continue;
- }
- if (!iface_rec->link_state ||
- strcmp(iface_rec->link_state, "up")) {
- continue;
- }
- struct local_datapath *ld
- = get_local_datapath(local_datapaths,
- pb->datapath->tunnel_key);
- if (ld && ld->localnet_port) {
- sset_add(localnet_vifs, iface_id);
- }
- }
- }
-
- struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
- sbrec_port_binding_by_datapath);
-
- const struct local_datapath *ld;
- HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
- const struct sbrec_port_binding *pb;
-
- if (!ld->localnet_port) {
- continue;
- }
-
- /* Get l3gw ports. Consider port bindings with type "l3gateway"
- * that connect to gateway routers (if local), and consider port
- * bindings of type "patch" since they might connect to
- * distributed gateway ports with NAT addresses. */
-
- sbrec_port_binding_index_set_datapath(target, ld->datapath);
- SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
- sbrec_port_binding_by_datapath) {
- if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
- || !strcmp(pb->type, "patch")) {
- sset_add(local_l3gw_ports, pb->logical_port);
- }
- }
- }
- sbrec_port_binding_index_destroy_row(target);
-}
-
-
-/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
- * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
- * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
- * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
- * fields of 'laddrs'. The logical port name is stored in 'lport'.
- *
- * Returns true if at least 'MAC' is found in 'address', false otherwise.
- *
- * The caller must call destroy_lport_addresses() and free(*lport). */
-static bool
-extract_addresses_with_port(const char *addresses,
- struct lport_addresses *laddrs,
- char **lport)
-{
- int ofs;
- if (!extract_addresses(addresses, laddrs, &ofs)) {
- return false;
- } else if (!addresses[ofs]) {
- return true;
- }
-
- struct lexer lexer;
- lexer_init(&lexer, addresses + ofs);
- lexer_get(&lexer);
-
- if (lexer.error || lexer.token.type != LEX_T_ID
- || !lexer_match_id(&lexer, "is_chassis_resident")) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
- lexer_destroy(&lexer);
- return true;
- }
-
- if (!lexer_match(&lexer, LEX_T_LPAREN)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
- "'is_chassis_resident' in address '%s'", addresses);
- lexer_destroy(&lexer);
- return false;
- }
-
- if (lexer.token.type != LEX_T_STRING) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_INFO_RL(&rl,
- "Syntax error: expecting quoted string after "
- "'is_chassis_resident' in address '%s'", addresses);
- lexer_destroy(&lexer);
- return false;
- }
-
- *lport = xstrdup(lexer.token.s);
-
- lexer_get(&lexer);
- if (!lexer_match(&lexer, LEX_T_RPAREN)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in "
- "'is_chassis_resident()' in address '%s'",
- addresses);
- lexer_destroy(&lexer);
- return false;
- }
-
- lexer_destroy(&lexer);
- return true;
-}
-
-static void
-consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const char *nat_address,
- const struct sbrec_port_binding *pb,
- struct sset *nat_address_keys,
- const struct sbrec_chassis *chassis,
- struct shash *nat_addresses)
-{
- struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
- char *lport = NULL;
- const struct sbrec_port_binding *cr_pb = NULL;
- bool rc = extract_addresses_with_port(nat_address, laddrs, &lport);
- if (lport) {
- cr_pb = lport_lookup_by_name(sbrec_port_binding_by_name, lport);
- }
- if (!rc
- || (!lport && !strcmp(pb->type, "patch"))
- || (lport && (!cr_pb || (cr_pb->chassis != chassis)))) {
- destroy_lport_addresses(laddrs);
- free(laddrs);
- free(lport);
- return;
- }
- free(lport);
-
- int i;
- for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
- char *name = xasprintf("%s-%s", pb->logical_port,
- laddrs->ipv4_addrs[i].addr_s);
- sset_add(nat_address_keys, name);
- free(name);
- }
- if (laddrs->n_ipv4_addrs == 0) {
- char *name = xasprintf("%s-noip", pb->logical_port);
- sset_add(nat_address_keys, name);
- free(name);
- }
- shash_add(nat_addresses, pb->logical_port, laddrs);
-}
-
-static void
-get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- struct sset *nat_address_keys,
- struct sset *local_l3gw_ports,
- const struct sbrec_chassis *chassis,
- struct shash *nat_addresses)
-{
- const char *gw_port;
- SSET_FOR_EACH(gw_port, local_l3gw_ports) {
- const struct sbrec_port_binding *pb;
-
- pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
- if (!pb) {
- continue;
- }
-
- if (pb->n_nat_addresses) {
- for (int i = 0; i < pb->n_nat_addresses; i++) {
- consider_nat_address(sbrec_port_binding_by_name,
- pb->nat_addresses[i], pb,
- nat_address_keys, chassis,
- nat_addresses);
- }
- } else {
- /* Continue to support options:nat-addresses for version
- * upgrade. */
- const char *nat_addresses_options = smap_get(&pb->options,
- "nat-addresses");
- if (nat_addresses_options) {
- consider_nat_address(sbrec_port_binding_by_name,
- nat_addresses_options, pb,
- nat_address_keys, chassis,
- nat_addresses);
- }
- }
- }
-}
-
static void
send_garp_rarp_wait(long long int send_garp_rarp_time)
{
/* Set the poll timer for next garp/rarp only if there is data to
* be sent. */
- if (!shash_is_empty(&send_garp_rarp_data)) {
+ if (!cmap_is_empty(&garp_rarp_get_data()->data)) {
poll_timer_wait_until(send_garp_rarp_time);
}
}
@@ -6691,19 +6196,20 @@ send_arp_nd_wait(long long int send_arp_nd_time)
/* Called with in the pinctrl_handler thread context. */
static void
send_garp_rarp_run(struct rconn *swconn, long long int *send_garp_rarp_time)
- OVS_REQUIRES(pinctrl_mutex)
{
- if (shash_is_empty(&send_garp_rarp_data)) {
+ const struct garp_rarp_data *garp_rarp_data = garp_rarp_get_data();
+ if (cmap_is_empty(&garp_rarp_data->data)) {
return;
}
/* Send GARPs, and update the next announcement. */
- struct shash_node *iter;
long long int current_time = time_msec();
*send_garp_rarp_time = LLONG_MAX;
- SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
- long long int next_announce = send_garp_rarp(swconn, iter->data,
- current_time);
+ struct garp_rarp_node *garp;
+ CMAP_FOR_EACH (garp, cmap_node, &garp_rarp_data->data) {
+ long long int next_announce = send_garp_rarp(
+ swconn, garp, current_time, garp_rarp_data->max_timeout,
+ garp_rarp_data->continuous);
if (*send_garp_rarp_time > next_announce) {
*send_garp_rarp_time = next_announce;
}
@@ -6762,84 +6268,28 @@ send_arp_nd_run(struct rconn *swconn, long long int *send_arp_nd_time)
/* Called by pinctrl_run(). Runs with in the main ovn-controller
* thread context. */
static void
-send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
- const struct ovsrec_bridge *br_int,
+send_garp_rarp_prepare(const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
const struct sbrec_chassis *chassis,
- const struct hmap *local_datapaths,
const struct ovsrec_open_vswitch_table *ovs_table)
OVS_REQUIRES(pinctrl_mutex)
{
- struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
- struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
- struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
- struct shash nat_addresses;
- unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
- unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
- bool garp_continuous = false, continuous_arp_nd = true;
+ unsigned long long max_arp_nd_timeout = ARP_ND_DEF_MAX_TIMEOUT;
+ bool continuous_arp_nd = true;
const struct ovsrec_open_vswitch *cfg =
ovsrec_open_vswitch_table_first(ovs_table);
if (cfg) {
- garp_max_timeout = smap_get_ullong(
- &cfg->external_ids, "garp-max-timeout-sec", 0) * 1000;
- garp_continuous = !!garp_max_timeout;
- if (!garp_max_timeout) {
- garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
- }
max_arp_nd_timeout = smap_get_ullong(
&cfg->external_ids, "arp-nd-max-timeout-sec",
- GARP_RARP_DEF_MAX_TIMEOUT / 1000) * 1000;
+ ARP_ND_DEF_MAX_TIMEOUT / 1000) * 1000;
continuous_arp_nd = !!max_arp_nd_timeout;
}
- shash_init(&nat_addresses);
- get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
- sbrec_port_binding_by_name,
- br_int, chassis, local_datapaths,
- &localnet_vifs, &local_l3gw_ports);
-
- get_nat_addresses_and_keys(sbrec_port_binding_by_name,
- &nat_ip_keys, &local_l3gw_ports,
- chassis, &nat_addresses);
-
- /* For deleted ports and deleted nat ips, remove from
- * send_garp_rarp_data. */
- struct shash_node *iter;
- SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
- if (!sset_contains(&localnet_vifs, iter->name) &&
- !sset_contains(&nat_ip_keys, iter->name)) {
- send_garp_rarp_delete(iter->name);
- }
- }
-
- /* Update send_garp_rarp_data. */
- const char *iface_id;
- SSET_FOR_EACH (iface_id, &localnet_vifs) {
- const struct sbrec_port_binding *pb = lport_lookup_by_name(
- sbrec_port_binding_by_name, iface_id);
- if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
- send_garp_rarp_update(ovnsb_idl_txn,
- sbrec_mac_binding_by_lport_ip,
- local_datapaths, pb, &nat_addresses,
- garp_max_timeout, garp_continuous);
- }
- }
-
- /* Update send_garp_rarp_data for nat-addresses. */
- const char *gw_port;
- SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
- const struct sbrec_port_binding *pb
- = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
- if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
- send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
- local_datapaths, pb, &nat_addresses,
- garp_max_timeout, garp_continuous);
- }
+ if (garp_rarp_data_changed()) {
+ /* Notify pinctrl_handler so that it can wakeup and process
+ * these GARP/RARP requests. */
+ notify_pinctrl_handler();
}
arp_nd_sync_data(ecmp_nh_table);
@@ -6853,24 +6303,6 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
}
}
- /* pinctrl_handler thread will send the GARPs. */
-
- sset_destroy(&localnet_vifs);
- sset_destroy(&local_l3gw_ports);
-
- SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
- struct lport_addresses *laddrs = iter->data;
- destroy_lport_addresses(laddrs);
- shash_delete(&nat_addresses, iter);
- free(laddrs);
- }
- shash_destroy(&nat_addresses);
-
- sset_destroy(&nat_ip_keys);
-
- garp_rarp_max_timeout = garp_max_timeout;
- garp_rarp_continuous = garp_continuous;
-
arp_nd_max_timeout = max_arp_nd_timeout;
arp_nd_continuous = continuous_arp_nd;
}
@@ -6879,7 +6311,7 @@ static bool
may_inject_pkts(void)
{
return (!shash_is_empty(&ipv6_ras) ||
- !shash_is_empty(&send_garp_rarp_data) ||
+ !cmap_is_empty(&garp_rarp_get_data()->data) ||
ipv6_prefixd_should_inject() ||
!ovs_list_is_empty(&mcast_query_list) ||
buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx) ||
@@ -44,7 +44,6 @@ struct sbrec_mac_binding_table;
void pinctrl_init(void);
void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
struct ovsdb_idl_index *sbrec_port_binding_by_key,
struct ovsdb_idl_index *sbrec_port_binding_by_name,
struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
@@ -56,7 +55,7 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
const struct sbrec_mac_binding_table *,
const struct sbrec_bfd_table *,
const struct sbrec_ecmp_nexthop_table *,
- const struct ovsrec_bridge *, const struct sbrec_chassis *,
+ const struct sbrec_chassis *chassis,
const struct hmap *local_datapaths,
const struct sset *active_tunnels,
const struct shash *local_active_ports_ipv6_pd,
@@ -15,9 +15,12 @@
#include <config.h>
+#include "openvswitch/vlog.h"
#include "lib/mac-binding-index.h"
#include "lib/ovn-sb-idl.h"
+VLOG_DEFINE_THIS_MODULE(mac_binding_index);
+
struct ovsdb_idl_index *
mac_binding_by_datapath_index_create(struct ovsdb_idl *idl)
{
@@ -47,3 +50,45 @@ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
return retval;
}
+
+/* Update or add an IP-MAC binding for 'logical_port'.
+ * Caller should make sure that 'ovnsb_idl_txn' is valid. */
+void
+mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ const char *logical_port,
+ const struct sbrec_datapath_binding *dp,
+ struct eth_addr ea, const char *ip,
+ bool update_only)
+{
+ /* Convert ethernet argument to string form for database. */
+ char mac_string[ETH_ADDR_STRLEN + 1];
+ snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
+
+ const struct sbrec_mac_binding *b =
+ mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
+ logical_port, ip);
+ if (!b) {
+ if (update_only) {
+ return;
+ }
+ b = sbrec_mac_binding_insert(ovnsb_idl_txn);
+ sbrec_mac_binding_set_logical_port(b, logical_port);
+ sbrec_mac_binding_set_ip(b, ip);
+ sbrec_mac_binding_set_datapath(b, dp);
+ }
+
+ if (strcmp(b->mac, mac_string)) {
+ sbrec_mac_binding_set_mac(b, mac_string);
+
+ /* For backward compatibility check if timestamp column is available
+ * in SB DB. */
+ if (sbrec_server_has_mac_binding_table_col_timestamp(
+ ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
+ VLOG_DBG("Setting MAC binding timestamp for "
+ "ip:%s mac:%s port:%s to %lld",
+ b->ip, b->mac, logical_port, time_wall_msec());
+ sbrec_mac_binding_set_timestamp(b, time_wall_msec());
+ }
+ }
+}
@@ -27,4 +27,10 @@ const struct sbrec_mac_binding *
mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
const char *logical_port, const char *ip);
+void mac_binding_add_to_sb(
+ struct ovsdb_idl_txn *,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ const char *logical_port, const struct sbrec_datapath_binding *,
+ struct eth_addr, const char *ip, bool update_only);
+
#endif /* lib/mac-binding-index.h */