+ /* Logical router data (digested from nbr). */
+ ovs_be32 gateway;
+
+ /* Logical switch data. */
+ struct ovn_port **router_ports;
+ size_t n_router_ports;
+
+ struct hmap port_tnlids;
+ uint32_t port_key_hint;
+
+ bool has_unknown;
+};
+
+static struct ovn_datapath *
+ovn_datapath_create(struct hmap *datapaths, const struct uuid *key,
+ const struct nbrec_logical_switch *nbs,
+ const struct nbrec_logical_router *nbr,
+ const struct sbrec_datapath_binding *sb)
+{
+ struct ovn_datapath *od = xzalloc(sizeof *od);
+ od->key = *key;
+ od->sb = sb;
+ od->nbs = nbs;
+ od->nbr = nbr;
+ hmap_init(&od->port_tnlids);
+ od->port_key_hint = 0;
+ hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key));
+ return od;
+}
+
+static void
+ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
+{
+ if (od) {
+ /* Don't remove od->list. It is used within build_datapaths() as a
+ * private list and once we've exited that function it is not safe to
+ * use it. */
+ hmap_remove(datapaths, &od->key_node);
+ destroy_tnlids(&od->port_tnlids);
+ free(od->router_ports);
+ free(od);
+ }
+}
+
+static struct ovn_datapath *
+ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid)
+{
+ struct ovn_datapath *od;
+
+ HMAP_FOR_EACH_WITH_HASH (od, key_node, uuid_hash(uuid), datapaths) {
+ if (uuid_equals(uuid, &od->key)) {
+ return od;
+ }
+ }
+ return NULL;
+}
+
+static struct ovn_datapath *
+ovn_datapath_from_sbrec(struct hmap *datapaths,
+ const struct sbrec_datapath_binding *sb)
+{
+ struct uuid key;
+
+ if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) &&
+ !smap_get_uuid(&sb->external_ids, "logical-router", &key)) {
+ return NULL;
+ }
+ return ovn_datapath_find(datapaths, &key);
+}
+
+static void
+join_datapaths(struct northd_context *ctx, struct hmap *datapaths,
+ struct ovs_list *sb_only, struct ovs_list *nb_only,
+ struct ovs_list *both)
+{
+ hmap_init(datapaths);
+ list_init(sb_only);
+ list_init(nb_only);
+ list_init(both);
+
+ const struct sbrec_datapath_binding *sb, *sb_next;
+ SBREC_DATAPATH_BINDING_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) {
+ struct uuid key;
+ if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) &&
+ !smap_get_uuid(&sb->external_ids, "logical-router", &key)) {
+ ovsdb_idl_txn_add_comment(
+ ctx->ovnsb_txn,
+ "deleting Datapath_Binding "UUID_FMT" that lacks "
+ "external-ids:logical-switch and "
+ "external-ids:logical-router",
+ UUID_ARGS(&sb->header_.uuid));
+ sbrec_datapath_binding_delete(sb);
+ continue;
+ }
+
+ if (ovn_datapath_find(datapaths, &key)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_INFO_RL(
+ &rl, "deleting Datapath_Binding "UUID_FMT" with "
+ "duplicate external-ids:logical-switch/router "UUID_FMT,
+ UUID_ARGS(&sb->header_.uuid), UUID_ARGS(&key));
+ sbrec_datapath_binding_delete(sb);
+ continue;
+ }
+
+ struct ovn_datapath *od = ovn_datapath_create(datapaths, &key,
+ NULL, NULL, sb);
+ list_push_back(sb_only, &od->list);
+ }
+
+ const struct nbrec_logical_switch *nbs;
+ NBREC_LOGICAL_SWITCH_FOR_EACH (nbs, ctx->ovnnb_idl) {
+ struct ovn_datapath *od = ovn_datapath_find(datapaths,
+ &nbs->header_.uuid);
+ if (od) {
+ od->nbs = nbs;
+ list_remove(&od->list);
+ list_push_back(both, &od->list);
+ } else {
+ od = ovn_datapath_create(datapaths, &nbs->header_.uuid,
+ nbs, NULL, NULL);
+ list_push_back(nb_only, &od->list);
+ }
+ }
+
+ const struct nbrec_logical_router *nbr;
+ NBREC_LOGICAL_ROUTER_FOR_EACH (nbr, ctx->ovnnb_idl) {
+ struct ovn_datapath *od = ovn_datapath_find(datapaths,
+ &nbr->header_.uuid);
+ if (od) {
+ if (!od->nbs) {
+ od->nbr = nbr;
+ list_remove(&od->list);
+ list_push_back(both, &od->list);
+ } else {
+ /* Can't happen! */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl,
+ "duplicate UUID "UUID_FMT" in OVN_Northbound",
+ UUID_ARGS(&nbr->header_.uuid));
+ continue;
+ }
+ } else {
+ od = ovn_datapath_create(datapaths, &nbr->header_.uuid,
+ NULL, nbr, NULL);
+ list_push_back(nb_only, &od->list);
+ }
+
+ od->gateway = 0;
+ if (nbr->default_gw) {
+ ovs_be32 ip, mask;
+ char *error = ip_parse_masked(nbr->default_gw, &ip, &mask);
+ if (error || !ip || mask != OVS_BE32_MAX) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad 'gateway' %s", nbr->default_gw);
+ free(error);
+ } else {
+ od->gateway = ip;
+ }
+ }
+ }
+}
+
+static uint32_t
+ovn_datapath_allocate_key(struct hmap *dp_tnlids)
+{
+ static uint32_t hint;
+ return allocate_tnlid(dp_tnlids, "datapath", (1u << 24) - 1, &hint);
+}
+
+static void
+build_datapaths(struct northd_context *ctx, struct hmap *datapaths)
+{
+ struct ovs_list sb_only, nb_only, both;
+
+ join_datapaths(ctx, datapaths, &sb_only, &nb_only, &both);
+
+ if (!list_is_empty(&nb_only)) {
+ /* First index the in-use datapath tunnel IDs. */
+ struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids);
+ struct ovn_datapath *od;
+ LIST_FOR_EACH (od, list, &both) {
+ add_tnlid(&dp_tnlids, od->sb->tunnel_key);
+ }
+
+ /* Add southbound record for each unmatched northbound record. */
+ LIST_FOR_EACH (od, list, &nb_only) {
+ uint16_t tunnel_key = ovn_datapath_allocate_key(&dp_tnlids);
+ if (!tunnel_key) {
+ break;
+ }
+
+ od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn);
+
+ char uuid_s[UUID_LEN + 1];
+ sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key));
+ const char *key = od->nbs ? "logical-switch" : "logical-router";
+ const struct smap id = SMAP_CONST1(&id, key, uuid_s);
+ sbrec_datapath_binding_set_external_ids(od->sb, &id);
+
+ sbrec_datapath_binding_set_tunnel_key(od->sb, tunnel_key);
+ }
+ destroy_tnlids(&dp_tnlids);
+ }
+
+ /* Delete southbound records without northbound matches. */
+ struct ovn_datapath *od, *next;
+ LIST_FOR_EACH_SAFE (od, next, list, &sb_only) {
+ list_remove(&od->list);
+ sbrec_datapath_binding_delete(od->sb);
+ ovn_datapath_destroy(datapaths, od);
+ }
+}
+\f
+struct ovn_port {
+ struct hmap_node key_node; /* Index on 'key'. */
+ char *key; /* nbs->name, nbr->name, sb->logical_port. */
+ char *json_key; /* 'key', quoted for use in JSON. */
+
+ const struct nbrec_logical_port *nbs; /* May be NULL. */
+ const struct nbrec_logical_router_port *nbr; /* May be NULL. */
+ const struct sbrec_port_binding *sb; /* May be NULL. */
+
+ /* Logical router port data. */
+ ovs_be32 ip, mask; /* 192.168.10.123/24. */
+ ovs_be32 network; /* 192.168.10.0. */
+ ovs_be32 bcast; /* 192.168.10.255. */
+ struct eth_addr mac;
+ struct ovn_port *peer;
+
+ struct ovn_datapath *od;
+
+ struct ovs_list list; /* In list of similar records. */
+};
+
+static struct ovn_port *
+ovn_port_create(struct hmap *ports, const char *key,
+ const struct nbrec_logical_port *nbs,
+ const struct nbrec_logical_router_port *nbr,
+ const struct sbrec_port_binding *sb)
+{
+ struct ovn_port *op = xzalloc(sizeof *op);
+
+ struct ds json_key = DS_EMPTY_INITIALIZER;
+ json_string_escape(key, &json_key);
+ op->json_key = ds_steal_cstr(&json_key);
+
+ op->key = xstrdup(key);
+ op->sb = sb;
+ op->nbs = nbs;
+ op->nbr = nbr;
+ hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
+ return op;
+}
+
+static void
+ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
+{
+ if (port) {
+ /* Don't remove port->list. It is used within build_ports() as a
+ * private list and once we've exited that function it is not safe to
+ * use it. */
+ hmap_remove(ports, &port->key_node);
+ free(port->json_key);
+ free(port->key);
+ free(port);
+ }
+}
+
+static struct ovn_port *
+ovn_port_find(struct hmap *ports, const char *name)
+{
+ struct ovn_port *op;
+
+ HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) {
+ if (!strcmp(op->key, name)) {
+ return op;
+ }
+ }
+ return NULL;
+}
+
+static uint32_t
+ovn_port_allocate_key(struct ovn_datapath *od)
+{
+ return allocate_tnlid(&od->port_tnlids, "port",
+ (1u << 15) - 1, &od->port_key_hint);
+}
+
+static void
+join_logical_ports(struct northd_context *ctx,
+ struct hmap *datapaths, struct hmap *ports,
+ struct ovs_list *sb_only, struct ovs_list *nb_only,
+ struct ovs_list *both)
+{
+ hmap_init(ports);
+ list_init(sb_only);
+ list_init(nb_only);
+ list_init(both);
+
+ const struct sbrec_port_binding *sb;
+ SBREC_PORT_BINDING_FOR_EACH (sb, ctx->ovnsb_idl) {
+ struct ovn_port *op = ovn_port_create(ports, sb->logical_port,
+ NULL, NULL, sb);
+ list_push_back(sb_only, &op->list);
+ }
+
+ struct ovn_datapath *od;
+ HMAP_FOR_EACH (od, key_node, datapaths) {
+ if (od->nbs) {
+ for (size_t i = 0; i < od->nbs->n_ports; i++) {
+ const struct nbrec_logical_port *nbs = od->nbs->ports[i];
+ struct ovn_port *op = ovn_port_find(ports, nbs->name);
+ if (op) {
+ if (op->nbs || op->nbr) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "duplicate logical port %s",
+ nbs->name);
+ continue;
+ }
+ op->nbs = nbs;
+ list_remove(&op->list);
+ list_push_back(both, &op->list);
+ } else {
+ op = ovn_port_create(ports, nbs->name, nbs, NULL, NULL);
+ list_push_back(nb_only, &op->list);
+ }
+
+ op->od = od;
+ }
+ } else {
+ for (size_t i = 0; i < od->nbr->n_ports; i++) {
+ const struct nbrec_logical_router_port *nbr
+ = od->nbr->ports[i];
+
+ struct eth_addr mac;
+ if (!eth_addr_from_string(nbr->mac, &mac)) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad 'mac' %s", nbr->mac);
+ continue;
+ }
+
+ ovs_be32 ip, mask;
+ char *error = ip_parse_masked(nbr->network, &ip, &mask);
+ if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad 'network' %s", nbr->network);
+ free(error);
+ continue;
+ }
+
+ struct ovn_port *op = ovn_port_find(ports, nbr->name);
+ if (op) {
+ if (op->nbs || op->nbr) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "duplicate logical router port %s",
+ nbr->name);
+ continue;
+ }
+ op->nbr = nbr;
+ list_remove(&op->list);
+ list_push_back(both, &op->list);
+ } else {
+ op = ovn_port_create(ports, nbr->name, NULL, nbr, NULL);
+ list_push_back(nb_only, &op->list);
+ }
+
+ op->ip = ip;
+ op->mask = mask;
+ op->network = ip & mask;
+ op->bcast = ip | ~mask;
+ op->mac = mac;
+
+ op->od = od;
+ }
+ }
+ }
+
+ /* Connect logical router ports, and logical switch ports of type "router",
+ * to their peers. */
+ struct ovn_port *op;
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (op->nbs && !strcmp(op->nbs->type, "router")) {
+ const char *peer_name = smap_get(&op->nbs->options, "router-port");
+ if (!peer_name) {
+ continue;
+ }
+
+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbr) {
+ continue;
+ }
+
+ peer->peer = op;
+ op->peer = peer;
+ op->od->router_ports = xrealloc(
+ op->od->router_ports,
+ sizeof *op->od->router_ports * (op->od->n_router_ports + 1));
+ op->od->router_ports[op->od->n_router_ports++] = op;
+ } else if (op->nbr && op->nbr->peer) {
+ op->peer = ovn_port_find(ports, op->nbr->name);
+ }
+ }
+}
+
+static void
+ovn_port_update_sbrec(const struct ovn_port *op)
+{
+ sbrec_port_binding_set_datapath(op->sb, op->od->sb);
+ if (op->nbr) {
+ sbrec_port_binding_set_type(op->sb, "patch");
+
+ const char *peer = op->peer ? op->peer->key : "<error>";
+ const struct smap ids = SMAP_CONST1(&ids, "peer", peer);
+ sbrec_port_binding_set_options(op->sb, &ids);
+
+ sbrec_port_binding_set_parent_port(op->sb, NULL);
+ sbrec_port_binding_set_tag(op->sb, NULL, 0);
+ sbrec_port_binding_set_mac(op->sb, NULL, 0);
+ } else {
+ if (strcmp(op->nbs->type, "router")) {
+ sbrec_port_binding_set_type(op->sb, op->nbs->type);
+ sbrec_port_binding_set_options(op->sb, &op->nbs->options);
+ } else {
+ sbrec_port_binding_set_type(op->sb, "patch");
+
+ const char *router_port = smap_get(&op->nbs->options,
+ "router-port");
+ if (!router_port) {
+ router_port = "<error>";
+ }
+ const struct smap ids = SMAP_CONST1(&ids, "peer", router_port);
+ sbrec_port_binding_set_options(op->sb, &ids);
+ }
+ sbrec_port_binding_set_parent_port(op->sb, op->nbs->parent_name);
+ sbrec_port_binding_set_tag(op->sb, op->nbs->tag, op->nbs->n_tag);
+ sbrec_port_binding_set_mac(op->sb, (const char **) op->nbs->addresses,
+ op->nbs->n_addresses);
+ }
+}
+
+static void
+build_ports(struct northd_context *ctx, struct hmap *datapaths,
+ struct hmap *ports)
+{
+ struct ovs_list sb_only, nb_only, both;
+
+ join_logical_ports(ctx, datapaths, ports, &sb_only, &nb_only, &both);
+
+ /* For logical ports that are in both databases, update the southbound
+ * record based on northbound data. Also index the in-use tunnel_keys. */
+ struct ovn_port *op, *next;
+ LIST_FOR_EACH_SAFE (op, next, list, &both) {
+ ovn_port_update_sbrec(op);
+
+ add_tnlid(&op->od->port_tnlids, op->sb->tunnel_key);
+ if (op->sb->tunnel_key > op->od->port_key_hint) {
+ op->od->port_key_hint = op->sb->tunnel_key;
+ }
+ }
+
+ /* Add southbound record for each unmatched northbound record. */
+ LIST_FOR_EACH_SAFE (op, next, list, &nb_only) {
+ uint16_t tunnel_key = ovn_port_allocate_key(op->od);
+ if (!tunnel_key) {
+ continue;
+ }
+
+ op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
+ ovn_port_update_sbrec(op);
+
+ sbrec_port_binding_set_logical_port(op->sb, op->key);
+ sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key);
+ }
+
+ /* Delete southbound records without northbound matches. */
+ LIST_FOR_EACH_SAFE(op, next, list, &sb_only) {
+ list_remove(&op->list);
+ sbrec_port_binding_delete(op->sb);
+ ovn_port_destroy(ports, op);
+ }
+}
+\f
+#define OVN_MIN_MULTICAST 32768
+#define OVN_MAX_MULTICAST 65535
+
+struct multicast_group {
+ const char *name;
+ uint16_t key; /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */
+};
+
+#define MC_FLOOD "_MC_flood"
+static const struct multicast_group mc_flood = { MC_FLOOD, 65535 };
+
+#define MC_UNKNOWN "_MC_unknown"
+static const struct multicast_group mc_unknown = { MC_UNKNOWN, 65534 };
+
+static bool
+multicast_group_equal(const struct multicast_group *a,
+ const struct multicast_group *b)
+{
+ return !strcmp(a->name, b->name) && a->key == b->key;
+}
+
+/* Multicast group entry. */
+struct ovn_multicast {
+ struct hmap_node hmap_node; /* Index on 'datapath' and 'key'. */
+ struct ovn_datapath *datapath;
+ const struct multicast_group *group;
+
+ struct ovn_port **ports;
+ size_t n_ports, allocated_ports;
+};
+
+static uint32_t
+ovn_multicast_hash(const struct ovn_datapath *datapath,
+ const struct multicast_group *group)
+{
+ return hash_pointer(datapath, group->key);
+}
+
+static struct ovn_multicast *
+ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath,
+ const struct multicast_group *group)
+{
+ struct ovn_multicast *mc;
+
+ HMAP_FOR_EACH_WITH_HASH (mc, hmap_node,
+ ovn_multicast_hash(datapath, group), mcgroups) {
+ if (mc->datapath == datapath
+ && multicast_group_equal(mc->group, group)) {
+ return mc;