Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
11271 lines (9974 sloc) 424 KB
/*
* 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 <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include "bitmap.h"
#include "command-line.h"
#include "daemon.h"
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
#include "fatal-signal.h"
#include "hash.h"
#include "openvswitch/hmap.h"
#include "openvswitch/json.h"
#include "ovn/lex.h"
#include "lib/chassis-index.h"
#include "lib/ip-mcast-index.h"
#include "lib/mcast-group-index.h"
#include "lib/ovn-l7.h"
#include "lib/ovn-nb-idl.h"
#include "lib/ovn-sb-idl.h"
#include "lib/ovn-util.h"
#include "ovn/actions.h"
#include "ovn/logical-fields.h"
#include "packets.h"
#include "openvswitch/poll-loop.h"
#include "smap.h"
#include "sset.h"
#include "svec.h"
#include "stream.h"
#include "stream-ssl.h"
#include "unixctl.h"
#include "util.h"
#include "uuid.h"
#include "openvswitch/vlog.h"
VLOG_DEFINE_THIS_MODULE(ovn_northd);
static unixctl_cb_func ovn_northd_exit;
static unixctl_cb_func ovn_northd_pause;
static unixctl_cb_func ovn_northd_resume;
static unixctl_cb_func ovn_northd_is_paused;
static unixctl_cb_func ovn_northd_status;
struct northd_context {
struct ovsdb_idl *ovnnb_idl;
struct ovsdb_idl *ovnsb_idl;
struct ovsdb_idl_txn *ovnnb_txn;
struct ovsdb_idl_txn *ovnsb_txn;
struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp;
struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
};
struct northd_state {
bool had_lock;
bool paused;
};
/* An IPv4 or IPv6 address */
struct v46_ip {
int family;
union {
ovs_be32 ipv4;
struct in6_addr ipv6;
};
};
static const char *ovnnb_db;
static const char *ovnsb_db;
static const char *unixctl_path;
#define MAC_ADDR_SPACE 0xffffff
/* MAC address management (macam) table of "struct eth_addr"s, that holds the
* MAC addresses allocated by the OVN ipam module. */
static struct hmap macam = HMAP_INITIALIZER(&macam);
static struct eth_addr mac_prefix;
static bool controller_event_en;
/* MAC allocated for service monitor usage. Just one mac is allocated
* for this purpose and ovn-controller's on each chassis will make use
* of this mac when sending out the packets to monitor the services
* defined in Service_Monitor Southbound table. Since these packets
* all locally handled, having just one mac is good enough. */
static char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
#define MAX_OVN_TAGS 4096
/* Pipeline stages. */
/* The two pipelines in an OVN logical flow table. */
enum ovn_pipeline {
P_IN, /* Ingress pipeline. */
P_OUT /* Egress pipeline. */
};
/* The two purposes for which ovn-northd uses OVN logical datapaths. */
enum ovn_datapath_type {
DP_SWITCH, /* OVN logical switch. */
DP_ROUTER /* OVN logical router. */
};
/* Returns an "enum ovn_stage" built from the arguments.
*
* (It's better to use ovn_stage_build() for type-safety reasons, but inline
* functions can't be used in enums or switch cases.) */
#define OVN_STAGE_BUILD(DP_TYPE, PIPELINE, TABLE) \
(((DP_TYPE) << 9) | ((PIPELINE) << 8) | (TABLE))
/* A stage within an OVN logical switch or router.
*
* An "enum ovn_stage" indicates whether the stage is part of a logical switch
* or router, whether the stage is part of the ingress or egress pipeline, and
* the table within that pipeline. The first three components are combined to
* form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2,
* S_ROUTER_OUT_DELIVERY. */
enum ovn_stage {
#define PIPELINE_STAGES \
/* Logical switch ingress stages. */ \
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \
PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \
PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \
PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \
PIPELINE_STAGE(SWITCH, IN, ACL, 6, "ls_in_acl") \
PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 7, "ls_in_qos_mark") \
PIPELINE_STAGE(SWITCH, IN, QOS_METER, 8, "ls_in_qos_meter") \
PIPELINE_STAGE(SWITCH, IN, LB, 9, "ls_in_lb") \
PIPELINE_STAGE(SWITCH, IN, STATEFUL, 10, "ls_in_stateful") \
PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 11, "ls_in_arp_rsp") \
PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 12, "ls_in_dhcp_options") \
PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 14, "ls_in_dns_lookup") \
PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 15, "ls_in_dns_response") \
PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 16, "ls_in_external_port") \
PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 17, "ls_in_l2_lkup") \
\
/* Logical switch egress stages. */ \
PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \
PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful") \
PIPELINE_STAGE(SWITCH, OUT, LB, 3, "ls_out_lb") \
PIPELINE_STAGE(SWITCH, OUT, ACL, 4, "ls_out_acl") \
PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 5, "ls_out_qos_mark") \
PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 6, "ls_out_qos_meter") \
PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 7, "ls_out_stateful") \
PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 8, "ls_out_port_sec_ip") \
PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 9, "ls_out_port_sec_l2") \
\
/* Logical router ingress stages. */ \
PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \
PIPELINE_STAGE(ROUTER, IN, LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \
PIPELINE_STAGE(ROUTER, IN, LEARN_NEIGHBOR, 2, "lr_in_learn_neighbor") \
PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 3, "lr_in_ip_input") \
PIPELINE_STAGE(ROUTER, IN, DEFRAG, 4, "lr_in_defrag") \
PIPELINE_STAGE(ROUTER, IN, UNSNAT, 5, "lr_in_unsnat") \
PIPELINE_STAGE(ROUTER, IN, DNAT, 6, "lr_in_dnat") \
PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 7, "lr_in_nd_ra_options") \
PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 8, "lr_in_nd_ra_response") \
PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 9, "lr_in_ip_routing") \
PIPELINE_STAGE(ROUTER, IN, POLICY, 10, "lr_in_policy") \
PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 11, "lr_in_arp_resolve") \
PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 12, "lr_in_chk_pkt_len") \
PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 13,"lr_in_larger_pkts") \
PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 14, "lr_in_gw_redirect") \
PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 15, "lr_in_arp_request") \
\
/* Logical router egress stages. */ \
PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \
PIPELINE_STAGE(ROUTER, OUT, SNAT, 1, "lr_out_snat") \
PIPELINE_STAGE(ROUTER, OUT, EGR_LOOP, 2, "lr_out_egr_loop") \
PIPELINE_STAGE(ROUTER, OUT, DELIVERY, 3, "lr_out_delivery")
#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \
S_##DP_TYPE##_##PIPELINE##_##STAGE \
= OVN_STAGE_BUILD(DP_##DP_TYPE, P_##PIPELINE, TABLE),
PIPELINE_STAGES
#undef PIPELINE_STAGE
};
/* Due to various hard-coded priorities need to implement ACLs, the
* northbound database supports a smaller range of ACL priorities than
* are available to logical flows. This value is added to an ACL
* priority to determine the ACL's logical flow priority. */
#define OVN_ACL_PRI_OFFSET 1000
/* Register definitions specific to switches. */
#define REGBIT_CONNTRACK_DEFRAG "reg0[0]"
#define REGBIT_CONNTRACK_COMMIT "reg0[1]"
#define REGBIT_CONNTRACK_NAT "reg0[2]"
#define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]"
/* Register definitions for switches and routers. */
#define REGBIT_NAT_REDIRECT "reg9[0]"
/* Indicate that this packet has been recirculated using egress
* loopback. This allows certain checks to be bypassed, such as a
* logical router dropping packets with source IP address equals
* one of the logical router's own IP addresses. */
#define REGBIT_EGRESS_LOOPBACK "reg9[1]"
#define REGBIT_DISTRIBUTED_NAT "reg9[2]"
/* Register to store the result of check_pkt_larger action. */
#define REGBIT_PKT_LARGER "reg9[3]"
#define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[4]"
#define REGBIT_SKIP_LOOKUP_NEIGHBOR "reg9[5]"
#define FLAGBIT_NOT_VXLAN "flags[1] == 0"
/* Returns an "enum ovn_stage" built from the arguments. */
static enum ovn_stage
ovn_stage_build(enum ovn_datapath_type dp_type, enum ovn_pipeline pipeline,
uint8_t table)
{
return OVN_STAGE_BUILD(dp_type, pipeline, table);
}
/* Returns the pipeline to which 'stage' belongs. */
static enum ovn_pipeline
ovn_stage_get_pipeline(enum ovn_stage stage)
{
return (stage >> 8) & 1;
}
/* Returns the pipeline name to which 'stage' belongs. */
static const char *
ovn_stage_get_pipeline_name(enum ovn_stage stage)
{
return ovn_stage_get_pipeline(stage) == P_IN ? "ingress" : "egress";
}
/* Returns the table to which 'stage' belongs. */
static uint8_t
ovn_stage_get_table(enum ovn_stage stage)
{
return stage & 0xff;
}
/* Returns a string name for 'stage'. */
static const char *
ovn_stage_to_str(enum ovn_stage stage)
{
switch (stage) {
#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \
case S_##DP_TYPE##_##PIPELINE##_##STAGE: return NAME;
PIPELINE_STAGES
#undef PIPELINE_STAGE
default: return "<unknown>";
}
}
/* Returns the type of the datapath to which a flow with the given 'stage' may
* be added. */
static enum ovn_datapath_type
ovn_stage_to_datapath_type(enum ovn_stage stage)
{
switch (stage) {
#define PIPELINE_STAGE(DP_TYPE, PIPELINE, STAGE, TABLE, NAME) \
case S_##DP_TYPE##_##PIPELINE##_##STAGE: return DP_##DP_TYPE;
PIPELINE_STAGES
#undef PIPELINE_STAGE
default: OVS_NOT_REACHED();
}
}
static void
usage(void)
{
printf("\
%s: OVN northbound management daemon\n\
usage: %s [OPTIONS]\n\
\n\
Options:\n\
--ovnnb-db=DATABASE connect to ovn-nb database at DATABASE\n\
(default: %s)\n\
--ovnsb-db=DATABASE connect to ovn-sb database at DATABASE\n\
(default: %s)\n\
--unixctl=SOCKET override default control socket name\n\
-h, --help display this help message\n\
-o, --options list available options\n\
-V, --version display version information\n\
", program_name, program_name, default_nb_db(), default_sb_db());
daemon_usage();
vlog_usage();
stream_usage("database", true, true, false);
}
struct tnlid_node {
struct hmap_node hmap_node;
uint32_t tnlid;
};
static void
destroy_tnlids(struct hmap *tnlids)
{
struct tnlid_node *node;
HMAP_FOR_EACH_POP (node, hmap_node, tnlids) {
free(node);
}
hmap_destroy(tnlids);
}
static void
add_tnlid(struct hmap *set, uint32_t tnlid)
{
struct tnlid_node *node = xmalloc(sizeof *node);
hmap_insert(set, &node->hmap_node, hash_int(tnlid, 0));
node->tnlid = tnlid;
}
static bool
tnlid_in_use(const struct hmap *set, uint32_t tnlid)
{
const struct tnlid_node *node;
HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash_int(tnlid, 0), set) {
if (node->tnlid == tnlid) {
return true;
}
}
return false;
}
static uint32_t
next_tnlid(uint32_t tnlid, uint32_t min, uint32_t max)
{
return tnlid + 1 <= max ? tnlid + 1 : min;
}
static uint32_t
allocate_tnlid(struct hmap *set, const char *name, uint32_t min, uint32_t max,
uint32_t *hint)
{
for (uint32_t tnlid = next_tnlid(*hint, min, max); tnlid != *hint;
tnlid = next_tnlid(tnlid, min, max)) {
if (!tnlid_in_use(set, tnlid)) {
add_tnlid(set, tnlid);
*hint = tnlid;
return tnlid;
}
}
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "all %s tunnel ids exhausted", name);
return 0;
}
struct ovn_chassis_qdisc_queues {
struct hmap_node key_node;
uint32_t queue_id;
struct uuid chassis_uuid;
};
static uint32_t
hash_chassis_queue(const struct uuid *chassis_uuid, uint32_t queue_id)
{
return hash_2words(uuid_hash(chassis_uuid), queue_id);
}
static void
destroy_chassis_queues(struct hmap *set)
{
struct ovn_chassis_qdisc_queues *node;
HMAP_FOR_EACH_POP (node, key_node, set) {
free(node);
}
hmap_destroy(set);
}
static void
add_chassis_queue(struct hmap *set, const struct uuid *chassis_uuid,
uint32_t queue_id)
{
struct ovn_chassis_qdisc_queues *node = xmalloc(sizeof *node);
node->queue_id = queue_id;
node->chassis_uuid = *chassis_uuid;
hmap_insert(set, &node->key_node,
hash_chassis_queue(chassis_uuid, queue_id));
}
static bool
chassis_queueid_in_use(const struct hmap *set, const struct uuid *chassis_uuid,
uint32_t queue_id)
{
const struct ovn_chassis_qdisc_queues *node;
HMAP_FOR_EACH_WITH_HASH (node, key_node,
hash_chassis_queue(chassis_uuid, queue_id), set) {
if (uuid_equals(chassis_uuid, &node->chassis_uuid)
&& node->queue_id == queue_id) {
return true;
}
}
return false;
}
static uint32_t
allocate_chassis_queueid(struct hmap *set, const struct uuid *uuid, char *name)
{
if (!uuid) {
return 0;
}
for (uint32_t queue_id = QDISC_MIN_QUEUE_ID + 1;
queue_id <= QDISC_MAX_QUEUE_ID;
queue_id++) {
if (!chassis_queueid_in_use(set, uuid, queue_id)) {
add_chassis_queue(set, uuid, queue_id);
return queue_id;
}
}
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "all %s queue ids exhausted", name);
return 0;
}
static void
free_chassis_queueid(struct hmap *set, const struct uuid *uuid,
uint32_t queue_id)
{
if (!uuid) {
return;
}
struct ovn_chassis_qdisc_queues *node;
HMAP_FOR_EACH_WITH_HASH (node, key_node,
hash_chassis_queue(uuid, queue_id), set) {
if (uuid_equals(uuid, &node->chassis_uuid)
&& node->queue_id == queue_id) {
hmap_remove(set, &node->key_node);
free(node);
break;
}
}
}
static inline bool
port_has_qos_params(const struct smap *opts)
{
return (smap_get(opts, "qos_max_rate") ||
smap_get(opts, "qos_burst"));
}
struct ipam_info {
uint32_t start_ipv4;
size_t total_ipv4s;
unsigned long *allocated_ipv4s; /* A bitmap of allocated IPv4s */
bool ipv6_prefix_set;
struct in6_addr ipv6_prefix;
bool mac_only;
};
/*
* Multicast snooping and querier per datapath configuration.
*/
struct mcast_switch_info {
bool enabled; /* True if snooping enabled. */
bool querier; /* True if querier enabled. */
bool flood_unregistered; /* True if unregistered multicast should be
* flooded.
*/
bool flood_relay; /* True if the switch is connected to a
* multicast router and unregistered multicast
* should be flooded to the mrouter. Only
* applicable if flood_unregistered == false.
*/
bool flood_reports; /* True if the switch has at least one port
* configured to flood reports.
*/
bool flood_static; /* True if the switch has at least one port
* configured to flood traffic.
*/
int64_t table_size; /* Max number of IP multicast groups. */
int64_t idle_timeout; /* Timeout after which an idle group is
* flushed.
*/
int64_t query_interval; /* Interval between multicast queries. */
char *eth_src; /* ETH src address of the multicast queries. */
char *ipv4_src; /* IP src address of the multicast queries. */
int64_t query_max_response; /* Expected time after which reports should
* be received for queries that were sent out.
*/
uint32_t active_flows; /* Current number of active IP multicast
* flows.
*/
};
struct mcast_router_info {
bool relay; /* True if the router should relay IP multicast. */
bool flood_static; /* True if the router has at least one port configured
* to flood traffic.
*/
};
struct mcast_info {
struct hmap group_tnlids; /* Group tunnel IDs in use on this DP. */
uint32_t group_tnlid_hint; /* Hint for allocating next group tunnel ID. */
struct ovs_list groups; /* List of groups learnt on this DP. */
union {
struct mcast_switch_info sw; /* Switch specific multicast info. */
struct mcast_router_info rtr; /* Router specific multicast info. */
};
};
struct mcast_port_info {
bool flood; /* True if the port should flood IP multicast traffic
* regardless if it's registered or not. */
bool flood_reports; /* True if the port should flood IP multicast reports
* (e.g., IGMP join/leave). */
};
static void
init_mcast_port_info(struct mcast_port_info *mcast_info,
const struct nbrec_logical_switch_port *nbsp,
const struct nbrec_logical_router_port *nbrp)
{
if (nbsp) {
mcast_info->flood =
smap_get_bool(&nbsp->options, "mcast_flood", false);
mcast_info->flood_reports =
smap_get_bool(&nbsp->options, "mcast_flood_reports",
false);
} else if (nbrp) {
/* We don't process multicast reports in any special way on logical
* routers so just treat them as regular multicast traffic.
*/
mcast_info->flood =
smap_get_bool(&nbrp->options, "mcast_flood", false);
mcast_info->flood_reports = mcast_info->flood;
}
}
static uint32_t
ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
{
return allocate_tnlid(&mcast_info->group_tnlids, "multicast group",
OVN_MIN_IP_MULTICAST, OVN_MAX_IP_MULTICAST,
&mcast_info->group_tnlid_hint);
}
/* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or
* sb->external_ids:logical-switch. */
struct ovn_datapath {
struct hmap_node key_node; /* Index on 'key'. */
struct uuid key; /* (nbs/nbr)->header_.uuid. */
const struct nbrec_logical_switch *nbs; /* May be NULL. */
const struct nbrec_logical_router *nbr; /* May be NULL. */
const struct sbrec_datapath_binding *sb; /* May be NULL. */
struct ovs_list list; /* In list of similar records. */
/* 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;
/* IPAM data. */
struct ipam_info ipam_info;
/* Multicast data. */
struct mcast_info mcast_info;
/* OVN northd only needs to know about the logical router gateway port for
* NAT on a distributed router. This "distributed gateway port" is
* populated only when there is a "redirect-chassis" specified for one of
* the ports on the logical router. Otherwise this will be NULL. */
struct ovn_port *l3dgw_port;
/* The "derived" OVN port representing the instance of l3dgw_port on
* the "redirect-chassis". */
struct ovn_port *l3redirect_port;
struct ovn_port *localnet_port;
struct ovs_list lr_list; /* In list of logical router datapaths. */
/* The logical router group to which this datapath belongs.
* Valid only if it is logical router datapath. NULL otherwise. */
struct lrouter_group *lr_group;
/* Port groups related to the datapath, used only when nbs is NOT NULL. */
struct hmap nb_pgs;
};
/* A group of logical router datapaths which are connected - either
* directly or indirectly.
* Each logical router can belong to only one group. */
struct lrouter_group {
struct ovn_datapath **router_dps;
int n_router_dps;
/* Set of ha_chassis_groups which are associated with the router dps. */
struct sset ha_chassis_groups;
};
struct macam_node {
struct hmap_node hmap_node;
struct eth_addr mac_addr; /* Allocated MAC address. */
};
static void
cleanup_macam(struct hmap *macam_)
{
struct macam_node *node;
HMAP_FOR_EACH_POP (node, hmap_node, macam_) {
free(node);
}
}
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);
hmap_init(&od->nb_pgs);
od->port_key_hint = 0;
hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key));
od->lr_group = NULL;
return od;
}
static void ovn_ls_port_group_destroy(struct hmap *nb_pgs);
static void destroy_mcast_info_for_datapath(struct ovn_datapath *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);
bitmap_free(od->ipam_info.allocated_ipv4s);
free(od->router_ports);
ovn_ls_port_group_destroy(&od->nb_pgs);
destroy_mcast_info_for_datapath(od);
free(od);
}
}
/* Returns 'od''s datapath type. */
static enum ovn_datapath_type
ovn_datapath_get_type(const struct ovn_datapath *od)
{
return od->nbs ? DP_SWITCH : DP_ROUTER;
}
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 bool
lrouter_is_enabled(const struct nbrec_logical_router *lrouter)
{
return !lrouter->enabled || *lrouter->enabled;
}
static void
init_ipam_info_for_datapath(struct ovn_datapath *od)
{
if (!od->nbs) {
return;
}
const char *subnet_str = smap_get(&od->nbs->other_config, "subnet");
const char *ipv6_prefix = smap_get(&od->nbs->other_config, "ipv6_prefix");
if (ipv6_prefix) {
od->ipam_info.ipv6_prefix_set = ipv6_parse(
ipv6_prefix, &od->ipam_info.ipv6_prefix);
}
if (!subnet_str) {
if (!ipv6_prefix) {
od->ipam_info.mac_only = smap_get_bool(&od->nbs->other_config,
"mac_only", false);
}
return;
}
ovs_be32 subnet, mask;
char *error = ip_parse_masked(subnet_str, &subnet, &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 'subnet' %s", subnet_str);
free(error);
return;
}
od->ipam_info.start_ipv4 = ntohl(subnet) + 1;
od->ipam_info.total_ipv4s = ~ntohl(mask);
od->ipam_info.allocated_ipv4s =
bitmap_allocate(od->ipam_info.total_ipv4s);
/* Mark first IP as taken */
bitmap_set1(od->ipam_info.allocated_ipv4s, 0);
/* Check if there are any reserver IPs (list) to be excluded from IPAM */
const char *exclude_ip_list = smap_get(&od->nbs->other_config,
"exclude_ips");
if (!exclude_ip_list) {
return;
}
struct lexer lexer;
lexer_init(&lexer, exclude_ip_list);
/* exclude_ip_list could be in the format -
* "10.0.0.4 10.0.0.10 10.0.0.20..10.0.0.50 10.0.0.100..10.0.0.110".
*/
lexer_get(&lexer);
while (lexer.token.type != LEX_T_END) {
if (lexer.token.type != LEX_T_INTEGER) {
lexer_syntax_error(&lexer, "expecting address");
break;
}
uint32_t start = ntohl(lexer.token.value.ipv4);
lexer_get(&lexer);
uint32_t end = start + 1;
if (lexer_match(&lexer, LEX_T_ELLIPSIS)) {
if (lexer.token.type != LEX_T_INTEGER) {
lexer_syntax_error(&lexer, "expecting address range");
break;
}
end = ntohl(lexer.token.value.ipv4) + 1;
lexer_get(&lexer);
}
/* Clamp start...end to fit the subnet. */
start = MAX(od->ipam_info.start_ipv4, start);
end = MIN(od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s, end);
if (end > start) {
bitmap_set_multiple(od->ipam_info.allocated_ipv4s,
start - od->ipam_info.start_ipv4,
end - start, 1);
} else {
lexer_error(&lexer, "excluded addresses not in subnet");
}
}
if (lexer.error) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "logical switch "UUID_FMT": bad exclude_ips (%s)",
UUID_ARGS(&od->key), lexer.error);
}
lexer_destroy(&lexer);
}
static void
init_mcast_info_for_router_datapath(struct ovn_datapath *od)
{
struct mcast_router_info *mcast_rtr_info = &od->mcast_info.rtr;
mcast_rtr_info->relay = smap_get_bool(&od->nbr->options, "mcast_relay",
false);
}
static void
init_mcast_info_for_switch_datapath(struct ovn_datapath *od)
{
struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
mcast_sw_info->enabled =
smap_get_bool(&od->nbs->other_config, "mcast_snoop", false);
mcast_sw_info->querier =
smap_get_bool(&od->nbs->other_config, "mcast_querier", true);
mcast_sw_info->flood_unregistered =
smap_get_bool(&od->nbs->other_config, "mcast_flood_unregistered",
false);
mcast_sw_info->table_size =
smap_get_ullong(&od->nbs->other_config, "mcast_table_size",
OVN_MCAST_DEFAULT_MAX_ENTRIES);
uint32_t idle_timeout =
smap_get_ullong(&od->nbs->other_config, "mcast_idle_timeout",
OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S);
if (idle_timeout < OVN_MCAST_MIN_IDLE_TIMEOUT_S) {
idle_timeout = OVN_MCAST_MIN_IDLE_TIMEOUT_S;
} else if (idle_timeout > OVN_MCAST_MAX_IDLE_TIMEOUT_S) {
idle_timeout = OVN_MCAST_MAX_IDLE_TIMEOUT_S;
}
mcast_sw_info->idle_timeout = idle_timeout;
uint32_t query_interval =
smap_get_ullong(&od->nbs->other_config, "mcast_query_interval",
mcast_sw_info->idle_timeout / 2);
if (query_interval < OVN_MCAST_MIN_QUERY_INTERVAL_S) {
query_interval = OVN_MCAST_MIN_QUERY_INTERVAL_S;
} else if (query_interval > OVN_MCAST_MAX_QUERY_INTERVAL_S) {
query_interval = OVN_MCAST_MAX_QUERY_INTERVAL_S;
}
mcast_sw_info->query_interval = query_interval;
mcast_sw_info->eth_src =
nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_eth_src"));
mcast_sw_info->ipv4_src =
nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip4_src"));
mcast_sw_info->query_max_response =
smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response",
OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S);
mcast_sw_info->active_flows = 0;
}
static void
init_mcast_info_for_datapath(struct ovn_datapath *od)
{
if (!od->nbr && !od->nbs) {
return;
}
hmap_init(&od->mcast_info.group_tnlids);
od->mcast_info.group_tnlid_hint = OVN_MIN_IP_MULTICAST;
ovs_list_init(&od->mcast_info.groups);
if (od->nbs) {
init_mcast_info_for_switch_datapath(od);
} else {
init_mcast_info_for_router_datapath(od);
}
}
static void
destroy_mcast_info_for_switch_datapath(struct ovn_datapath *od)
{
struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
free(mcast_sw_info->eth_src);
free(mcast_sw_info->ipv4_src);
}
static void
destroy_mcast_info_for_datapath(struct ovn_datapath *od)
{
if (!od->nbr && !od->nbs) {
return;
}
if (od->nbs) {
destroy_mcast_info_for_switch_datapath(od);
}
destroy_tnlids(&od->mcast_info.group_tnlids);
}
static void
store_mcast_info_for_switch_datapath(const struct sbrec_ip_multicast *sb,
struct ovn_datapath *od)
{
struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
sbrec_ip_multicast_set_datapath(sb, od->sb);
sbrec_ip_multicast_set_enabled(sb, &mcast_sw_info->enabled, 1);
sbrec_ip_multicast_set_querier(sb, &mcast_sw_info->querier, 1);
sbrec_ip_multicast_set_table_size(sb, &mcast_sw_info->table_size, 1);
sbrec_ip_multicast_set_idle_timeout(sb, &mcast_sw_info->idle_timeout, 1);
sbrec_ip_multicast_set_query_interval(sb,
&mcast_sw_info->query_interval, 1);
sbrec_ip_multicast_set_query_max_resp(sb,
&mcast_sw_info->query_max_response,
1);
if (mcast_sw_info->eth_src) {
sbrec_ip_multicast_set_eth_src(sb, mcast_sw_info->eth_src);
}
if (mcast_sw_info->ipv4_src) {
sbrec_ip_multicast_set_ip4_src(sb, mcast_sw_info->ipv4_src);
}
}
static void
ovn_datapath_update_external_ids(struct ovn_datapath *od)
{
/* Get the logical-switch or logical-router UUID to set in
* external-ids. */
char uuid_s[UUID_LEN + 1];
sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key));
const char *key = od->nbs ? "logical-switch" : "logical-router";
/* Get names to set in external-ids. */
const char *name = od->nbs ? od->nbs->name : od->nbr->name;
const char *name2 = (od->nbs
? smap_get(&od->nbs->external_ids,
"neutron:network_name")
: smap_get(&od->nbr->external_ids,
"neutron:router_name"));
/* Set external-ids. */
struct smap ids = SMAP_INITIALIZER(&ids);
smap_add(&ids, key, uuid_s);
smap_add(&ids, "name", name);
if (name2 && name2[0]) {
smap_add(&ids, "name2", name2);
}
sbrec_datapath_binding_set_external_ids(od->sb, &ids);
smap_destroy(&ids);
}
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, struct ovs_list *lr_list)
{
ovs_list_init(sb_only);
ovs_list_init(nb_only);
ovs_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);
ovs_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;
ovs_list_remove(&od->list);
ovs_list_push_back(both, &od->list);
ovn_datapath_update_external_ids(od);
} else {
od = ovn_datapath_create(datapaths, &nbs->header_.uuid,
nbs, NULL, NULL);
ovs_list_push_back(nb_only, &od->list);
}
init_ipam_info_for_datapath(od);
init_mcast_info_for_datapath(od);
}
const struct nbrec_logical_router *nbr;
NBREC_LOGICAL_ROUTER_FOR_EACH (nbr, ctx->ovnnb_idl) {
if (!lrouter_is_enabled(nbr)) {
continue;
}
struct ovn_datapath *od = ovn_datapath_find(datapaths,
&nbr->header_.uuid);
if (od) {
if (!od->nbs) {
od->nbr = nbr;
ovs_list_remove(&od->list);
ovs_list_push_back(both, &od->list);
ovn_datapath_update_external_ids(od);
} 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);
ovs_list_push_back(nb_only, &od->list);
}
init_mcast_info_for_datapath(od);
ovs_list_push_back(lr_list, &od->lr_list);
}
}
static uint32_t
ovn_datapath_allocate_key(struct hmap *dp_tnlids)
{
static uint32_t hint;
return allocate_tnlid(dp_tnlids, "datapath", 1, (1u << 24) - 1, &hint);
}
/* Updates the southbound Datapath_Binding table so that it contains the
* logical switches and routers specified by the northbound database.
*
* Initializes 'datapaths' to contain a "struct ovn_datapath" for every logical
* switch and router. */
static void
build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
struct ovs_list *lr_list)
{
struct ovs_list sb_only, nb_only, both;
join_datapaths(ctx, datapaths, &sb_only, &nb_only, &both, lr_list);
if (!ovs_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) {
uint32_t tunnel_key = ovn_datapath_allocate_key(&dp_tnlids);
if (!tunnel_key) {
break;
}
od->sb = sbrec_datapath_binding_insert(ctx->ovnsb_txn);
ovn_datapath_update_external_ids(od);
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) {
ovs_list_remove(&od->list);
sbrec_datapath_binding_delete(od->sb);
ovn_datapath_destroy(datapaths, od);
}
}
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 sbrec_port_binding *sb; /* May be NULL. */
/* Logical switch port data. */
const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */
struct lport_addresses *lsp_addrs; /* Logical switch port addresses. */
unsigned int n_lsp_addrs;
struct lport_addresses *ps_addrs; /* Port security addresses. */
unsigned int n_ps_addrs;
/* Logical router port data. */
const struct nbrec_logical_router_port *nbrp; /* May be NULL. */
struct lport_addresses lrp_networks;
/* Logical port multicast data. */
struct mcast_port_info mcast_info;
bool derived; /* Indicates whether this is an additional port
* derived from nbsp or nbrp. */
/* The port's peer:
*
* - A switch port S of type "router" has a router port R as a peer,
* and R in turn has S has its peer.
*
* - Two connected logical router ports have each other as peer. */
struct ovn_port *peer;
struct ovn_datapath *od;
struct ovs_list list; /* In list of similar records. */
};
static void
ovn_port_set_sb(struct ovn_port *op,
const struct sbrec_port_binding *sb)
{
op->sb = sb;
}
static void
ovn_port_set_nb(struct ovn_port *op,
const struct nbrec_logical_switch_port *nbsp,
const struct nbrec_logical_router_port *nbrp)
{
op->nbsp = nbsp;
op->nbrp = nbrp;
init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
}
static struct ovn_port *
ovn_port_create(struct hmap *ports, const char *key,
const struct nbrec_logical_switch_port *nbsp,
const struct nbrec_logical_router_port *nbrp,
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);
ovn_port_set_sb(op, sb);
ovn_port_set_nb(op, nbsp, nbrp);
op->derived = false;
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);
for (int i = 0; i < port->n_lsp_addrs; i++) {
destroy_lport_addresses(&port->lsp_addrs[i]);
}
free(port->lsp_addrs);
for (int i = 0; i < port->n_ps_addrs; i++) {
destroy_lport_addresses(&port->ps_addrs[i]);
}
free(port->ps_addrs);
destroy_lport_addresses(&port->lrp_networks);
free(port->json_key);
free(port->key);
free(port);
}
}
static struct ovn_port *
ovn_port_find(const 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",
1, (1u << 15) - 1, &od->port_key_hint);
}
/* Returns true if the logical switch port 'enabled' column is empty or
* set to true. Otherwise, returns false. */
static bool
lsp_is_enabled(const struct nbrec_logical_switch_port *lsp)
{
return !lsp->n_enabled || *lsp->enabled;
}
/* Returns true only if the logical switch port 'up' column is set to true.
* Otherwise, if the column is not set or set to false, returns false. */
static bool
lsp_is_up(const struct nbrec_logical_switch_port *lsp)
{
return lsp->n_up && *lsp->up;
}
static bool
lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
{
return !strcmp(nbsp->type, "external");
}
static bool
lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
{
return !lrport->enabled || *lrport->enabled;
}
static char *
chassis_redirect_name(const char *port_name)
{
return xasprintf("cr-%s", port_name);
}
static bool
ipam_is_duplicate_mac(struct eth_addr *ea, uint64_t mac64, bool warn)
{
struct macam_node *macam_node;
HMAP_FOR_EACH_WITH_HASH (macam_node, hmap_node, hash_uint64(mac64),
&macam) {
if (eth_addr_equals(*ea, macam_node->mac_addr)) {
if (warn) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Duplicate MAC set: "ETH_ADDR_FMT,
ETH_ADDR_ARGS(macam_node->mac_addr));
}
return true;
}
}
return false;
}
static void
ipam_insert_mac(struct eth_addr *ea, bool check)
{
if (!ea) {
return;
}
uint64_t mac64 = eth_addr_to_uint64(*ea);
uint64_t prefix = eth_addr_to_uint64(mac_prefix);
/* If the new MAC was not assigned by this address management system or
* check is true and the new MAC is a duplicate, do not insert it into the
* macam hmap. */
if (((mac64 ^ prefix) >> 24)
|| (check && ipam_is_duplicate_mac(ea, mac64, true))) {
return;
}
struct macam_node *new_macam_node = xmalloc(sizeof *new_macam_node);
new_macam_node->mac_addr = *ea;
hmap_insert(&macam, &new_macam_node->hmap_node, hash_uint64(mac64));
}
static void
ipam_insert_ip(struct ovn_datapath *od, uint32_t ip)
{
if (!od || !od->ipam_info.allocated_ipv4s) {
return;
}
if (ip >= od->ipam_info.start_ipv4 &&
ip < (od->ipam_info.start_ipv4 + od->ipam_info.total_ipv4s)) {
if (bitmap_is_set(od->ipam_info.allocated_ipv4s,
ip - od->ipam_info.start_ipv4)) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Duplicate IP set on switch %s: "IP_FMT,
od->nbs->name, IP_ARGS(htonl(ip)));
}
bitmap_set1(od->ipam_info.allocated_ipv4s,
ip - od->ipam_info.start_ipv4);
}
}
static void
ipam_insert_lsp_addresses(struct ovn_datapath *od, struct ovn_port *op,
char *address)
{
if (!od || !op || !address || !strcmp(address, "unknown")
|| !strcmp(address, "router") || is_dynamic_lsp_address(address)) {
return;
}
struct lport_addresses laddrs;
if (!extract_lsp_addresses(address, &laddrs)) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Extract addresses failed.");
return;
}
ipam_insert_mac(&laddrs.ea, true);
/* IP is only added to IPAM if the switch's subnet option
* is set, whereas MAC is always added to MACAM. */
if (!od->ipam_info.allocated_ipv4s) {
destroy_lport_addresses(&laddrs);
return;
}
for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
uint32_t ip = ntohl(laddrs.ipv4_addrs[j].addr);
ipam_insert_ip(od, ip);
}
destroy_lport_addresses(&laddrs);
}
static void
ipam_add_port_addresses(struct ovn_datapath *od, struct ovn_port *op)
{
if (!od || !op) {
return;
}
if (op->nbsp) {
/* Add all the port's addresses to address data structures. */
for (size_t i = 0; i < op->nbsp->n_addresses; i++) {
ipam_insert_lsp_addresses(od, op, op->nbsp->addresses[i]);
}
} else if (op->nbrp) {
struct lport_addresses lrp_networks;
if (!extract_lrp_networks(op->nbrp, &lrp_networks)) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Extract addresses failed.");
return;
}
ipam_insert_mac(&lrp_networks.ea, true);
if (!op->peer || !op->peer->nbsp || !op->peer->od || !op->peer->od->nbs
|| !smap_get(&op->peer->od->nbs->other_config, "subnet")) {
destroy_lport_addresses(&lrp_networks);
return;
}
for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) {
uint32_t ip = ntohl(lrp_networks.ipv4_addrs[i].addr);
/* If the router has the first IP address of the subnet, don't add
* it to IPAM. We already added this when we initialized IPAM for
* the datapath. This will just result in an erroneous message
* about a duplicate IP address.
*/
if (ip != op->peer->od->ipam_info.start_ipv4) {
ipam_insert_ip(op->peer->od, ip);
}
}
destroy_lport_addresses(&lrp_networks);
}
}
static uint64_t
ipam_get_unused_mac(ovs_be32 ip)
{
uint32_t mac_addr_suffix, i, base_addr = ntohl(ip) & MAC_ADDR_SPACE;
struct eth_addr mac;
uint64_t mac64;
for (i = 0; i < MAC_ADDR_SPACE - 1; i++) {
/* The tentative MAC's suffix will be in the interval (1, 0xfffffe). */
mac_addr_suffix = ((base_addr + i) % (MAC_ADDR_SPACE - 1)) + 1;
mac64 = eth_addr_to_uint64(mac_prefix) | mac_addr_suffix;
eth_addr_from_uint64(mac64, &mac);
if (!ipam_is_duplicate_mac(&mac, mac64, false)) {
break;
}
}
if (i == MAC_ADDR_SPACE) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "MAC address space exhausted.");
mac64 = 0;
}
return mac64;
}
static uint32_t
ipam_get_unused_ip(struct ovn_datapath *od)
{
if (!od || !od->ipam_info.allocated_ipv4s) {
return 0;
}
size_t new_ip_index = bitmap_scan(od->ipam_info.allocated_ipv4s, 0, 0,
od->ipam_info.total_ipv4s - 1);
if (new_ip_index == od->ipam_info.total_ipv4s - 1) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL( &rl, "Subnet address space has been exhausted.");
return 0;
}
return od->ipam_info.start_ipv4 + new_ip_index;
}
enum dynamic_update_type {
NONE, /* No change to the address */
REMOVE, /* Address is no longer dynamic */
STATIC, /* Use static address (MAC only) */
DYNAMIC, /* Assign a new dynamic address */
};
struct dynamic_address_update {
struct ovs_list node; /* In build_ipam()'s list of updates. */
struct ovn_datapath *od;
struct ovn_port *op;
struct lport_addresses current_addresses;
struct eth_addr static_mac;
ovs_be32 static_ip;
struct in6_addr static_ipv6;
enum dynamic_update_type mac;
enum dynamic_update_type ipv4;
enum dynamic_update_type ipv6;
};
static enum dynamic_update_type
dynamic_mac_changed(const char *lsp_addresses,
struct dynamic_address_update *update)
{
struct eth_addr ea;
if (ovs_scan(lsp_addresses, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) {
if (eth_addr_equals(ea, update->current_addresses.ea)) {
return NONE;
} else {
/* MAC is still static, but it has changed */
update->static_mac = ea;
return STATIC;
}
}
uint64_t mac64 = eth_addr_to_uint64(update->current_addresses.ea);
uint64_t prefix = eth_addr_to_uint64(mac_prefix);
if ((mac64 ^ prefix) >> 24) {
return DYNAMIC;
} else {
return NONE;
}
}
static enum dynamic_update_type
dynamic_ip4_changed(const char *lsp_addrs,
struct dynamic_address_update *update)
{
const struct ipam_info *ipam = &update->op->od->ipam_info;
const struct lport_addresses *cur_addresses = &update->current_addresses;
bool dynamic_ip4 = ipam->allocated_ipv4s != NULL;
if (!dynamic_ip4) {
if (update->current_addresses.n_ipv4_addrs) {
return REMOVE;
} else {
return NONE;
}
}
if (!cur_addresses->n_ipv4_addrs) {
/* IPv4 was previously static but now is dynamic */
return DYNAMIC;
}
uint32_t ip4 = ntohl(cur_addresses->ipv4_addrs[0].addr);
if (ip4 < ipam->start_ipv4) {
return DYNAMIC;
}
uint32_t index = ip4 - ipam->start_ipv4;
if (index > ipam->total_ipv4s ||
bitmap_is_set(ipam->allocated_ipv4s, index)) {
/* Previously assigned dynamic IPv4 address can no longer be used.
* It's either outside the subnet, conflicts with an excluded IP,
* or conflicts with a statically-assigned address on the switch
*/
return DYNAMIC;
} else {
char ipv6_s[IPV6_SCAN_LEN + 1];
ovs_be32 new_ip;
int n = 0;
if ((ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT"%n",
IP_SCAN_ARGS(&new_ip), &n)
&& lsp_addrs[n] == '\0') ||
(ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
IP_SCAN_ARGS(&new_ip), ipv6_s, &n)
&& lsp_addrs[n] == '\0')) {
index = ntohl(new_ip) - ipam->start_ipv4;
if (ntohl(new_ip) < ipam->start_ipv4 ||
index > ipam->total_ipv4s ||
bitmap_is_set(ipam->allocated_ipv4s, index)) {
/* new static ip is not valid */
return DYNAMIC;
} else if (cur_addresses->ipv4_addrs[0].addr != new_ip) {
update->ipv4 = STATIC;
update->static_ip = new_ip;
return STATIC;
}
}
return NONE;
}
}
static enum dynamic_update_type
dynamic_ip6_changed(const char *lsp_addrs,
struct dynamic_address_update *update)
{
bool dynamic_ip6 = update->op->od->ipam_info.ipv6_prefix_set;
struct eth_addr ea;
if (!dynamic_ip6) {
if (update->current_addresses.n_ipv6_addrs) {
/* IPv6 was dynamic but now is not */
return REMOVE;
} else {
/* IPv6 has never been dynamic */
return NONE;
}
}
if (!update->current_addresses.n_ipv6_addrs ||
ovs_scan(lsp_addrs, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) {
/* IPv6 was previously static but now is dynamic */
return DYNAMIC;
}
const struct lport_addresses *cur_addresses;
char ipv6_s[IPV6_SCAN_LEN + 1];
ovs_be32 new_ip;
int n = 0;
if ((ovs_scan(lsp_addrs, "dynamic "IPV6_SCAN_FMT"%n",
ipv6_s, &n) && lsp_addrs[n] == '\0') ||
(ovs_scan(lsp_addrs, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
IP_SCAN_ARGS(&new_ip), ipv6_s, &n)
&& lsp_addrs[n] == '\0')) {
struct in6_addr ipv6;
if (!ipv6_parse(ipv6_s, &ipv6)) {
return DYNAMIC;
}
struct in6_addr masked = ipv6_addr_bitand(&ipv6,
&update->op->od->ipam_info.ipv6_prefix);
if (!IN6_ARE_ADDR_EQUAL(&masked,
&update->op->od->ipam_info.ipv6_prefix)) {
return DYNAMIC;
}
cur_addresses = &update->current_addresses;
if (!IN6_ARE_ADDR_EQUAL(&cur_addresses->ipv6_addrs[0].addr,
&ipv6)) {
update->static_ipv6 = ipv6;
return STATIC;
}
} else if (update->mac != NONE) {
return DYNAMIC;
}
return NONE;
}
/* Check previously assigned dynamic addresses for validity. This will
* check if the assigned addresses need to change.
*
* Returns true if any changes to dynamic addresses are required
*/
static bool
dynamic_addresses_check_for_updates(const char *lsp_addrs,
struct dynamic_address_update *update)
{
update->mac = dynamic_mac_changed(lsp_addrs, update);
update->ipv4 = dynamic_ip4_changed(lsp_addrs, update);
update->ipv6 = dynamic_ip6_changed(lsp_addrs, update);
if (update->mac == NONE &&
update->ipv4 == NONE &&
update->ipv6 == NONE) {
return false;
} else {
return true;
}
}
/* For addresses that do not need to be updated, go ahead and insert them
* into IPAM. This way, their addresses will be claimed and cannot be assigned
* elsewhere later.
*/
static void
update_unchanged_dynamic_addresses(struct dynamic_address_update *update)
{
if (update->mac == NONE) {
ipam_insert_mac(&update->current_addresses.ea, false);
}
if (update->ipv4 == NONE && update->current_addresses.n_ipv4_addrs) {
ipam_insert_ip(update->op->od,
ntohl(update->current_addresses.ipv4_addrs[0].addr));
}
}
static void
set_lsp_dynamic_addresses(const char *dynamic_addresses, struct ovn_port *op)
{
extract_lsp_addresses(dynamic_addresses, &op->lsp_addrs[op->n_lsp_addrs]);
op->n_lsp_addrs++;
}
/* Determines which components (MAC, IPv4, and IPv6) of dynamic
* addresses need to be assigned. This is used exclusively for
* ports that do not have dynamic addresses already assigned.
*/
static void
set_dynamic_updates(const char *addrspec,
struct dynamic_address_update *update)
{
bool has_ipv4 = false, has_ipv6 = false;
char ipv6_s[IPV6_SCAN_LEN + 1];
struct eth_addr mac;
ovs_be32 ip;
int n = 0;
if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n",
ETH_ADDR_SCAN_ARGS(mac), &n)
&& addrspec[n] == '\0') {
update->mac = STATIC;
update->static_mac = mac;
} else {
update->mac = DYNAMIC;
}
if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT"%n",
IP_SCAN_ARGS(&ip), &n) && addrspec[n] == '\0')) {
has_ipv4 = true;
} else if ((ovs_scan(addrspec, "dynamic "IPV6_SCAN_FMT"%n",
ipv6_s, &n) && addrspec[n] == '\0')) {
has_ipv6 = true;
} else if ((ovs_scan(addrspec, "dynamic "IP_SCAN_FMT" "IPV6_SCAN_FMT"%n",
IP_SCAN_ARGS(&ip), ipv6_s, &n)
&& addrspec[n] == '\0')) {
has_ipv4 = has_ipv6 = true;
}
if (has_ipv4) {
update->ipv4 = STATIC;
update->static_ip = ip;
} else if (update->op->od->ipam_info.allocated_ipv4s) {
update->ipv4 = DYNAMIC;
} else {
update->ipv4 = NONE;
}
if (has_ipv6 && ipv6_parse(ipv6_s, &update->static_ipv6)) {
update->ipv6 = STATIC;
} else if (update->op->od->ipam_info.ipv6_prefix_set) {
update->ipv6 = DYNAMIC;
} else {
update->ipv6 = NONE;
}
}
static void
update_dynamic_addresses(struct dynamic_address_update *update)
{
ovs_be32 ip4 = 0;
switch (update->ipv4) {
case NONE:
if (update->current_addresses.n_ipv4_addrs) {
ip4 = update->current_addresses.ipv4_addrs[0].addr;
}
break;
case REMOVE:
break;
case STATIC:
ip4 = update->static_ip;
break;
case DYNAMIC:
ip4 = htonl(ipam_get_unused_ip(update->od));
VLOG_INFO("Assigned dynamic IPv4 address '"IP_FMT"' to port '%s'",
IP_ARGS(ip4), update->op->nbsp->name);
}
struct eth_addr mac;
switch (update->mac) {
case NONE:
mac = update->current_addresses.ea;
break;
case REMOVE:
OVS_NOT_REACHED();
case STATIC:
mac = update->static_mac;
break;
case DYNAMIC:
eth_addr_from_uint64(ipam_get_unused_mac(ip4), &mac);
VLOG_INFO("Assigned dynamic MAC address '"ETH_ADDR_FMT"' to port '%s'",
ETH_ADDR_ARGS(mac), update->op->nbsp->name);
break;
}
struct in6_addr ip6 = in6addr_any;
switch (update->ipv6) {
case NONE:
if (update->current_addresses.n_ipv6_addrs) {
ip6 = update->current_addresses.ipv6_addrs[0].addr;
}
break;
case REMOVE:
break;
case STATIC:
ip6 = update->static_ipv6;
break;
case DYNAMIC:
in6_generate_eui64(mac, &update->od->ipam_info.ipv6_prefix, &ip6);
struct ds ip6_ds = DS_EMPTY_INITIALIZER;
ipv6_format_addr(&ip6, &ip6_ds);
VLOG_INFO("Assigned dynamic IPv6 address '%s' to port '%s'",
ip6_ds.string, update->op->nbsp->name);
ds_destroy(&ip6_ds);
break;
}
struct ds new_addr = DS_EMPTY_INITIALIZER;
ds_put_format(&new_addr, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
ipam_insert_mac(&mac, true);
if (ip4) {
ipam_insert_ip(update->od, ntohl(ip4));
ds_put_format(&new_addr, " "IP_FMT, IP_ARGS(ip4));
}
if (!IN6_ARE_ADDR_EQUAL(&ip6, &in6addr_any)) {
char ip6_s[INET6_ADDRSTRLEN + 1];
ipv6_string_mapped(ip6_s, &ip6);
ds_put_format(&new_addr, " %s", ip6_s);
}
nbrec_logical_switch_port_set_dynamic_addresses(update->op->nbsp,
ds_cstr(&new_addr));
set_lsp_dynamic_addresses(ds_cstr(&new_addr), update->op);
ds_destroy(&new_addr);
}
static void
build_ipam(struct hmap *datapaths, struct hmap *ports)
{
/* IPAM generally stands for IP address management. In non-virtualized
* world, MAC addresses come with the hardware. But, with virtualized
* workloads, they need to be assigned and managed. This function
* does both IP address management (ipam) and MAC address management
* (macam). */
/* If the switch's other_config:subnet is set, allocate new addresses for
* ports that have the "dynamic" keyword in their addresses column. */
struct ovn_datapath *od;
struct ovs_list updates;
ovs_list_init(&updates);
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
continue;
}
for (size_t i = 0; i < od->nbs->n_ports; i++) {
const struct nbrec_logical_switch_port *nbsp = od->nbs->ports[i];
if (!od->ipam_info.allocated_ipv4s &&
!od->ipam_info.ipv6_prefix_set &&
!od->ipam_info.mac_only) {
if (nbsp->dynamic_addresses) {
nbrec_logical_switch_port_set_dynamic_addresses(nbsp,
NULL);
}
continue;
}
struct ovn_port *op = ovn_port_find(ports, nbsp->name);
if (!op || op->nbsp != nbsp || op->peer) {
/* Do not allocate addresses for logical switch ports that
* have a peer. */
continue;
}
int num_dynamic_addresses = 0;
for (size_t j = 0; j < nbsp->n_addresses; j++) {
if (!is_dynamic_lsp_address(nbsp->addresses[j])) {
continue;
}
if (num_dynamic_addresses) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "More than one dynamic address "
"configured for logical switch port '%s'",
nbsp->name);
continue;
}
num_dynamic_addresses++;
struct dynamic_address_update *update
= xzalloc(sizeof *update);
update->op = op;
update->od = od;
if (nbsp->dynamic_addresses) {
bool any_changed;
extract_lsp_addresses(nbsp->dynamic_addresses,
&update->current_addresses);
any_changed = dynamic_addresses_check_for_updates(
nbsp->addresses[j], update);
update_unchanged_dynamic_addresses(update);
if (any_changed) {
ovs_list_push_back(&updates, &update->node);
} else {
/* No changes to dynamic addresses */
set_lsp_dynamic_addresses(nbsp->dynamic_addresses, op);
destroy_lport_addresses(&update->current_addresses);
free(update);
}
} else {
set_dynamic_updates(nbsp->addresses[j], update);
ovs_list_push_back(&updates, &update->node);
}
}
if (!num_dynamic_addresses && nbsp->dynamic_addresses) {
nbrec_logical_switch_port_set_dynamic_addresses(nbsp, NULL);
}
}
}
/* After retaining all unchanged dynamic addresses, now assign
* new ones.
*/
struct dynamic_address_update *update;
LIST_FOR_EACH_POP (update, node, &updates) {
update_dynamic_addresses(update);
destroy_lport_addresses(&update->current_addresses);
free(update);
}
}
/* Tag allocation for nested containers.
*
* For a logical switch port with 'parent_name' and a request to allocate tags,
* keeps a track of all allocated tags. */
struct tag_alloc_node {
struct hmap_node hmap_node;
char *parent_name;
unsigned long *allocated_tags; /* A bitmap to track allocated tags. */
};
static void
tag_alloc_destroy(struct hmap *tag_alloc_table)
{
struct tag_alloc_node *node;
HMAP_FOR_EACH_POP (node, hmap_node, tag_alloc_table) {
bitmap_free(node->allocated_tags);
free(node->parent_name);
free(node);
}
hmap_destroy(tag_alloc_table);
}
static struct tag_alloc_node *
tag_alloc_get_node(struct hmap *tag_alloc_table, const char *parent_name)
{
/* If a node for the 'parent_name' exists, return it. */
struct tag_alloc_node *tag_alloc_node;
HMAP_FOR_EACH_WITH_HASH (tag_alloc_node, hmap_node,
hash_string(parent_name, 0),
tag_alloc_table) {
if (!strcmp(tag_alloc_node->parent_name, parent_name)) {
return tag_alloc_node;
}
}
/* Create a new node. */
tag_alloc_node = xmalloc(sizeof *tag_alloc_node);
tag_alloc_node->parent_name = xstrdup(parent_name);
tag_alloc_node->allocated_tags = bitmap_allocate(MAX_OVN_TAGS);
/* Tag 0 is invalid for nested containers. */
bitmap_set1(tag_alloc_node->allocated_tags, 0);
hmap_insert(tag_alloc_table, &tag_alloc_node->hmap_node,
hash_string(parent_name, 0));
return tag_alloc_node;
}
static void
tag_alloc_add_existing_tags(struct hmap *tag_alloc_table,
const struct nbrec_logical_switch_port *nbsp)
{
/* Add the tags of already existing nested containers. If there is no
* 'nbsp->parent_name' or no 'nbsp->tag' set, there is nothing to do. */
if (!nbsp->parent_name || !nbsp->parent_name[0] || !nbsp->tag) {
return;
}
struct tag_alloc_node *tag_alloc_node;
tag_alloc_node = tag_alloc_get_node(tag_alloc_table, nbsp->parent_name);
bitmap_set1(tag_alloc_node->allocated_tags, *nbsp->tag);
}
static void
tag_alloc_create_new_tag(struct hmap *tag_alloc_table,
const struct nbrec_logical_switch_port *nbsp)
{
if (!nbsp->tag_request) {
return;
}
if (nbsp->parent_name && nbsp->parent_name[0]
&& *nbsp->tag_request == 0) {
/* For nested containers that need allocation, do the allocation. */
if (nbsp->tag) {
/* This has already been allocated. */
return;
}
struct tag_alloc_node *tag_alloc_node;
int64_t tag;
tag_alloc_node = tag_alloc_get_node(tag_alloc_table,
nbsp->parent_name);
tag = bitmap_scan(tag_alloc_node->allocated_tags, 0, 1, MAX_OVN_TAGS);
if (tag == MAX_OVN_TAGS) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_ERR_RL(&rl, "out of vlans for logical switch ports with "
"parent %s", nbsp->parent_name);
return;
}
bitmap_set1(tag_alloc_node->allocated_tags, tag);
nbrec_logical_switch_port_set_tag(nbsp, &tag, 1);
} else if (*nbsp->tag_request != 0) {
/* For everything else, copy the contents of 'tag_request' to 'tag'. */
nbrec_logical_switch_port_set_tag(nbsp, nbsp->tag_request, 1);
}
}
static void
join_logical_ports(struct northd_context *ctx,
struct hmap *datapaths, struct hmap *ports,
struct hmap *chassis_qdisc_queues,
struct hmap *tag_alloc_table, struct ovs_list *sb_only,
struct ovs_list *nb_only, struct ovs_list *both)
{
ovs_list_init(sb_only);
ovs_list_init(nb_only);
ovs_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);
ovs_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_switch_port *nbsp
= od->nbs->ports[i];
struct ovn_port *op = ovn_port_find(ports, nbsp->name);
if (op) {
if (op->nbsp || op->nbrp) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "duplicate logical port %s",
nbsp->name);
continue;
}
ovn_port_set_nb(op, nbsp, NULL);
ovs_list_remove(&op->list);
uint32_t queue_id = smap_get_int(&op->sb->options,
"qdisc_queue_id", 0);
if (queue_id && op->sb->chassis) {
add_chassis_queue(
chassis_qdisc_queues, &op->sb->chassis->header_.uuid,
queue_id);
}
ovs_list_push_back(both, &op->list);
/* This port exists due to a SB binding, but should
* not have been initialized fully. */
ovs_assert(!op->n_lsp_addrs && !op->n_ps_addrs);
} else {
op = ovn_port_create(ports, nbsp->name, nbsp, NULL, NULL);
ovs_list_push_back(nb_only, &op->list);
}
if (!strcmp(nbsp->type, "localnet")) {
od->localnet_port = op;
}
op->lsp_addrs
= xmalloc(sizeof *op->lsp_addrs * nbsp->n_addresses);
for (size_t j = 0; j < nbsp->n_addresses; j++) {
if (!strcmp(nbsp->addresses[j], "unknown")
|| !strcmp(nbsp->addresses[j], "router")) {
continue;
}
if (is_dynamic_lsp_address(nbsp->addresses[j])) {
continue;
} else if (!extract_lsp_addresses(nbsp->addresses[j],
&op->lsp_addrs[op->n_lsp_addrs])) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_INFO_RL(&rl, "invalid syntax '%s' in logical "
"switch port addresses. No MAC "
"address found",
op->nbsp->addresses[j]);
continue;
}
op->n_lsp_addrs++;
}
op->ps_addrs
= xmalloc(sizeof *op->ps_addrs * nbsp->n_port_security);
for (size_t j = 0; j < nbsp->n_port_security; j++) {
if (!extract_lsp_addresses(nbsp->port_security[j],
&op->ps_addrs[op->n_ps_addrs])) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_INFO_RL(&rl, "invalid syntax '%s' in port "
"security. No MAC address found",
op->nbsp->port_security[j]);
continue;
}
op->n_ps_addrs++;
}
op->od = od;
tag_alloc_add_existing_tags(tag_alloc_table, nbsp);
}
} else {
for (size_t i = 0; i < od->nbr->n_ports; i++) {
const struct nbrec_logical_router_port *nbrp
= od->nbr->ports[i];
struct lport_addresses lrp_networks;
if (!extract_lrp_networks(nbrp, &lrp_networks)) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "bad 'mac' %s", nbrp->mac);
continue;
}
if (!lrp_networks.n_ipv4_addrs && !lrp_networks.n_ipv6_addrs) {
continue;
}
struct ovn_port *op = ovn_port_find(ports, nbrp->name);
if (op) {
if (op->nbsp || op->nbrp) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "duplicate logical router port %s",
nbrp->name);
continue;
}
ovn_port_set_nb(op, NULL, nbrp);
ovs_list_remove(&op->list);
ovs_list_push_back(both, &op->list);
/* This port exists but should not have been
* initialized fully. */
ovs_assert(!op->lrp_networks.n_ipv4_addrs
&& !op->lrp_networks.n_ipv6_addrs);
} else {
op = ovn_port_create(ports, nbrp->name, NULL, nbrp, NULL);
ovs_list_push_back(nb_only, &op->list);
}
op->lrp_networks = lrp_networks;
op->od = od;
const char *redirect_chassis = smap_get(&op->nbrp->options,
"redirect-chassis");
if (op->nbrp->ha_chassis_group || redirect_chassis ||
op->nbrp->n_gateway_chassis) {
/* Additional "derived" ovn_port crp represents the
* instance of op on the "redirect-chassis". */
const char *gw_chassis = smap_get(&op->od->nbr->options,
"chassis");
if (gw_chassis) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Bad configuration: "
"redirect-chassis configured on port %s "
"on L3 gateway router", nbrp->name);
continue;
}
if (od->l3dgw_port || od->l3redirect_port) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Bad configuration: multiple ports "
"with redirect-chassis on same logical "
"router %s", od->nbr->name);
continue;
}
char *redirect_name = chassis_redirect_name(nbrp->name);
struct ovn_port *crp = ovn_port_find(ports, redirect_name);
if (crp) {
crp->derived = true;
ovn_port_set_nb(crp, NULL, nbrp);
ovs_list_remove(&crp->list);
ovs_list_push_back(both, &crp->list);
} else {
crp = ovn_port_create(ports, redirect_name,
NULL, nbrp, NULL);
crp->derived = true;
ovs_list_push_back(nb_only, &crp->list);
}
crp->od = od;
free(redirect_name);
/* Set l3dgw_port and l3redirect_port in od, for later
* use during flow creation. */
od->l3dgw_port = op;
od->l3redirect_port = crp;
}
}
}
}
/* 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->nbsp && !strcmp(op->nbsp->type, "router") && !op->derived) {
const char *peer_name = smap_get(&op->nbsp->options, "router-port");
if (!peer_name) {
continue;
}
struct ovn_port *peer = ovn_port_find(ports, peer_name);
if (!peer || !peer->nbrp) {
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;
/* Fill op->lsp_addrs for op->nbsp->addresses[] with
* contents "router", which was skipped in the loop above. */
for (size_t j = 0; j < op->nbsp->n_addresses; j++) {
if (!strcmp(op->nbsp->addresses[j], "router")) {
if (extract_lrp_networks(peer->nbrp,
&op->lsp_addrs[op->n_lsp_addrs])) {
op->n_lsp_addrs++;
}
break;
}
}
/* If the router is multicast enabled then set relay on the switch
* datapath.
*/
if (peer->od && peer->od->mcast_info.rtr.relay) {
op->od->mcast_info.sw.flood_relay = true;
}
} else if (op->nbrp && op->nbrp->peer && !op->derived) {
struct ovn_port *peer = ovn_port_find(ports, op->nbrp->peer);
if (peer) {
if (peer->nbrp) {
op->peer = peer;
} else if (peer->nbsp) {
/* An ovn_port for a switch port of type "router" does have
* a router port as its peer (see the case above for
* "router" ports), but this is set via options:router-port
* in Logical_Switch_Port and does not involve the
* Logical_Router_Port's 'peer' column. */
static struct vlog_rate_limit rl =
VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "Bad configuration: The peer of router "
"port %s is a switch port", op->key);
}
}
}
}
/* Wait until all ports have been connected to add to IPAM since
* it relies on proper peers to be set
*/
HMAP_FOR_EACH (op, key_node, ports) {
ipam_add_port_addresses(op->od, op);
}
}
static void
ip_address_and_port_from_lb_key(const char *key, char **ip_address,
uint16_t *port, int *addr_family);
static void
get_router_load_balancer_ips(const struct ovn_datapath *od,
struct sset *all_ips_v4, struct sset *all_ips_v6)
{
if (!od->nbr) {
return;
}
for (int i = 0; i < od->nbr->n_load_balancer; i++) {
struct nbrec_load_balancer *lb = od->nbr->load_balancer[i];
struct smap *vips = &lb->vips;
struct smap_node *node;
SMAP_FOR_EACH (node, vips) {
/* node->key contains IP:port or just IP. */
char *ip_address = NULL;
uint16_t port;
int addr_family;
ip_address_and_port_from_lb_key(node->key, &ip_address, &port,
&addr_family);
if (!ip_address) {
continue;
}
struct sset *all_ips;
if (addr_family == AF_INET) {
all_ips = all_ips_v4;
} else {
all_ips = all_ips_v6;
}
if (!sset_contains(all_ips, ip_address)) {
sset_add(all_ips, ip_address);
}
free(ip_address);
}
}
}
/* Returns an array of strings, each consisting of a MAC address followed
* by one or more IP addresses, and if the port is a distributed gateway
* port, followed by 'is_chassis_resident("LPORT_NAME")', where the
* LPORT_NAME is the name of the L3 redirect port or the name of the
* logical_port specified in a NAT rule. These strings include the
* external IP addresses of all NAT rules defined on that router, and all
* of the IP addresses used in load balancer VIPs defined on that router.
*
* The caller must free each of the n returned strings with free(),
* and must free the returned array when it is no longer needed. */
static char **
get_nat_addresses(const struct ovn_port *op, size_t *n)
{
size_t n_nats = 0;
struct eth_addr mac;
if (!op->nbrp || !op->od || !op->od->nbr
|| (!op->od->nbr->n_nat && !op->od->nbr->n_load_balancer)
|| !eth_addr_from_string(op->nbrp->mac, &mac)) {
*n = n_nats;
return NULL;
}
struct ds c_addresses = DS_EMPTY_INITIALIZER;
ds_put_format(&c_addresses, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
bool central_ip_address = false;
char **addresses;
addresses = xmalloc(sizeof *addresses * (op->od->nbr->n_nat + 1));
/* Get NAT IP addresses. */
for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
const struct nbrec_nat *nat = op->od->nbr->nat[i];
ovs_be32 ip, mask;
char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
if (error || mask != OVS_BE32_MAX) {
free(error);
continue;
}
/* Determine whether this NAT rule satisfies the conditions for
* distributed NAT processing. */
if (op->od->l3redirect_port && !strcmp(nat->type, "dnat_and_snat")
&& nat->logical_port && nat->external_mac) {
/* Distributed NAT rule. */
if (eth_addr_from_string(nat->external_mac, &mac)) {
struct ds address = DS_EMPTY_INITIALIZER;
ds_put_format(&address, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
ds_put_format(&address, " %s", nat->external_ip);
ds_put_format(&address, " is_chassis_resident(\"%s\")",
nat->logical_port);
addresses[n_nats++] = ds_steal_cstr(&address);
}
} else {
/* Centralized NAT rule, either on gateway router or distributed
* router.
* Check if external_ip is same as router ip. If so, then there
* is no need to add this to the nat_addresses. The router IPs
* will be added separately. */
bool is_router_ip = false;
for (size_t j = 0; j < op->lrp_networks.n_ipv4_addrs; j++) {
if (!strcmp(nat->external_ip,
op->lrp_networks.ipv4_addrs[j].addr_s)) {
is_router_ip = true;
break;
}
}
if (!is_router_ip) {
for (size_t j = 0; j < op->lrp_networks.n_ipv6_addrs; j++) {
if (!strcmp(nat->external_ip,
op->lrp_networks.ipv6_addrs[j].addr_s)) {
is_router_ip = true;
break;
}
}
}
if (!is_router_ip) {
ds_put_format(&c_addresses, " %s", nat->external_ip);
central_ip_address = true;
}
}
}
/* Two sets to hold all load-balancer vips. */
struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
const char *ip_address;
SSET_FOR_EACH (ip_address, &all_ips_v4) {
ds_put_format(&c_addresses, " %s", ip_address);
central_ip_address = true;
}
SSET_FOR_EACH (ip_address, &all_ips_v6) {
ds_put_format(&c_addresses, " %s", ip_address);
central_ip_address = true;
}
sset_destroy(&all_ips_v4);
sset_destroy(&all_ips_v6);
if (central_ip_address) {
/* Gratuitous ARP for centralized NAT rules on distributed gateway
* ports should be restricted to the "redirect-chassis". */
if (op->od->l3redirect_port) {
ds_put_format(&c_addresses, " is_chassis_resident(%s)",
op->od->l3redirect_port->json_key);
}
addresses[n_nats++] = ds_steal_cstr(&c_addresses);
}
*n = n_nats;
return addresses;
}
static bool
sbpb_gw_chassis_needs_update(
const struct sbrec_port_binding *pb,
const struct nbrec_logical_router_port *lrp,
struct ovsdb_idl_index *sbrec_chassis_by_name)
{
if (!lrp || !pb) {
return false;
}
if (lrp->n_gateway_chassis && !pb->ha_chassis_group) {
/* If there are gateway chassis in the NB DB, but there is
* no corresponding HA chassis group in SB DB we need to
* create the HA chassis group in SB DB for this lrp. */
return true;
}
if (strcmp(pb->ha_chassis_group->name, lrp->name)) {
/* Name doesn't match. */
return true;
}
if (lrp->n_gateway_chassis != pb->ha_chassis_group->n_ha_chassis) {
return true;
}
for (size_t i = 0; i < lrp->n_gateway_chassis; i++) {
struct nbrec_gateway_chassis *nbgw_ch = lrp->gateway_chassis[i];
bool found = false;
for (size_t j = 0; j < pb->ha_chassis_group->n_ha_chassis; j++) {
struct sbrec_ha_chassis *sbha_ch =
pb->ha_chassis_group->ha_chassis[j];
const char *chassis_name = smap_get(&sbha_ch->external_ids,
"chassis-name");
if (!chassis_name) {
return true;
}
if (strcmp(chassis_name, nbgw_ch->chassis_name)) {
continue;
}
found = true;
if (nbgw_ch->priority != sbha_ch->priority) {
return true;
}
if (sbha_ch->chassis &&
strcmp(nbgw_ch->chassis_name, sbha_ch->chassis->name)) {
/* sbha_ch->chassis's name is different from the one
* in sbha_ch->external_ids:chassis-name. */
return true;
}
if (!sbha_ch->chassis &&
chassis_lookup_by_name(sbrec_chassis_by_name,
nbgw_ch->chassis_name)) {
/* sbha_ch->chassis is NULL, but the chassis is
* present in Chassis table. */
return true;
}
}
if (!found) {
return true;
}
}
/* No need to update SB DB. Its in sync. */
return false;
}
static struct sbrec_ha_chassis *
create_sb_ha_chassis(struct northd_context *ctx,
const struct sbrec_chassis *chassis,
const char *chassis_name, int priority)
{
struct sbrec_ha_chassis *sb_ha_chassis =
sbrec_ha_chassis_insert(ctx->ovnsb_txn);
sbrec_ha_chassis_set_chassis(sb_ha_chassis, chassis);
sbrec_ha_chassis_set_priority(sb_ha_chassis, priority);
/* Store the chassis_name in external_ids. If the chassis
* entry doesn't exist in the Chassis table then we can
* figure out the chassis to which this ha_chassis
* maps to. */
const struct smap external_ids =
SMAP_CONST1(&external_ids, "chassis-name", chassis_name);
sbrec_ha_chassis_set_external_ids(sb_ha_chassis, &external_ids);
return sb_ha_chassis;
}
static bool
chassis_group_list_changed(
const struct nbrec_ha_chassis_group *nb_ha_grp,
const struct sbrec_ha_chassis_group *sb_ha_grp,
struct ovsdb_idl_index *sbrec_chassis_by_name)
{
if (nb_ha_grp->n_ha_chassis != sb_ha_grp->n_ha_chassis) {
return true;
}
struct shash nb_ha_chassis_list = SHASH_INITIALIZER(&nb_ha_chassis_list);
for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) {
shash_add(&nb_ha_chassis_list,
nb_ha_grp->ha_chassis[i]->chassis_name,
nb_ha_grp->ha_chassis[i]);
}
bool changed = false;
const struct sbrec_ha_chassis *sb_ha_chassis;
const struct nbrec_ha_chassis *nb_ha_chassis;
for (size_t i = 0; i < sb_ha_grp->n_ha_chassis; i++) {
sb_ha_chassis = sb_ha_grp->ha_chassis[i];
const char *chassis_name = smap_get(&sb_ha_chassis->external_ids,
"chassis-name");
if (!chassis_name) {
changed = true;
break;
}
nb_ha_chassis = shash_find_and_delete(&nb_ha_chassis_list,
chassis_name);
if (!nb_ha_chassis ||
nb_ha_chassis->priority != sb_ha_chassis->priority) {
changed = true;
break;
}
if (sb_ha_chassis->chassis &&
strcmp(sb_ha_chassis->chassis->name, chassis_name)) {
/* sb_ha_chassis->chassis's name is different from the one
* in sb_ha_chassis->external_ids:chassis-name. */
changed = true;
break;
}
if (!sb_ha_chassis->chassis &&
chassis_lookup_by_name(sbrec_chassis_by_name,
chassis_name)) {
/* sb_ha_chassis->chassis is NULL, but the chassis is
* present in Chassis table. */
changed = true;
break;
}
}
struct shash_node *node, *next;
SHASH_FOR_EACH_SAFE (node, next, &nb_ha_chassis_list) {
shash_delete(&nb_ha_chassis_list, node);
changed = true;
}
shash_destroy(&nb_ha_chassis_list);
return changed;
}
static void
sync_ha_chassis_group_for_sbpb(struct northd_context *ctx,
const struct nbrec_ha_chassis_group *nb_ha_grp,
struct ovsdb_idl_index *sbrec_chassis_by_name,
const struct sbrec_port_binding *pb)
{
bool new_sb_chassis_group = false;
const struct sbrec_ha_chassis_group *sb_ha_grp =
ha_chassis_group_lookup_by_name(
ctx->sbrec_ha_chassis_grp_by_name, nb_ha_grp->name);
if (!sb_ha_grp) {
sb_ha_grp = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
sbrec_ha_chassis_group_set_name(sb_ha_grp, nb_ha_grp->name);
new_sb_chassis_group = true;
}
if (new_sb_chassis_group ||
chassis_group_list_changed(nb_ha_grp, sb_ha_grp,
sbrec_chassis_by_name)) {
struct sbrec_ha_chassis **sb_ha_chassis = NULL;
size_t n_ha_chassis = nb_ha_grp->n_ha_chassis;
sb_ha_chassis = xcalloc(n_ha_chassis, sizeof *sb_ha_chassis);
for (size_t i = 0; i < nb_ha_grp->n_ha_chassis; i++) {
const struct nbrec_ha_chassis *nb_ha_chassis
= nb_ha_grp->ha_chassis[i];
const struct sbrec_chassis *chassis =
chassis_lookup_by_name(sbrec_chassis_by_name,
nb_ha_chassis->chassis_name);
sb_ha_chassis[i] = sbrec_ha_chassis_insert(ctx->ovnsb_txn);
/* It's perfectly ok if the chassis is NULL. This could
* happen when ovn-controller exits and removes its row
* from the chassis table in OVN SB DB. */
sbrec_ha_chassis_set_chassis(sb_ha_chassis[i], chassis);
sbrec_ha_chassis_set_priority(sb_ha_chassis[i],
nb_ha_chassis->priority);
const struct smap external_ids =
SMAP_CONST1(&external_ids, "chassis-name",
nb_ha_chassis->chassis_name);
sbrec_ha_chassis_set_external_ids(sb_ha_chassis[i], &external_ids);
}
sbrec_ha_chassis_group_set_ha_chassis(sb_ha_grp, sb_ha_chassis,
n_ha_chassis);
free(sb_ha_chassis);
}
sbrec_port_binding_set_ha_chassis_group(pb, sb_ha_grp);
}
/* This functions translates the gw chassis on the nb database
* to HA chassis group in the sb database entries.
*/
static void
copy_gw_chassis_from_nbrp_to_sbpb(
struct northd_context *ctx,
struct ovsdb_idl_index *sbrec_chassis_by_name,
const struct nbrec_logical_router_port *lrp,
const struct sbrec_port_binding *port_binding)
{
/* Make use of the new HA chassis group table to support HA
* for the distributed gateway router port. */
const struct sbrec_ha_chassis_group *sb_ha_chassis_group =
ha_chassis_group_lookup_by_name(
ctx->sbrec_ha_chassis_grp_by_name, lrp->name);
if (!sb_ha_chassis_group) {
sb_ha_chassis_group = sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
sbrec_ha_chassis_group_set_name(sb_ha_chassis_group, lrp->name);
}
struct sbrec_ha_chassis **sb_ha_chassis = xcalloc(lrp->n_gateway_chassis,
sizeof *sb_ha_chassis);
size_t n_sb_ha_ch = 0;
for (size_t n = 0; n < lrp->n_gateway_chassis; n++) {
struct nbrec_gateway_chassis *lrp_gwc = lrp->gateway_chassis[n];
if (!lrp_gwc->chassis_name) {
continue;
}
const struct sbrec_chassis *chassis =
chassis_lookup_by_name(sbrec_chassis_by_name,
lrp_gwc->chassis_name);
sb_ha_chassis[n_sb_ha_ch] =
create_sb_ha_chassis(ctx, chassis, lrp_gwc->chassis_name,
lrp_gwc->priority);
n_sb_ha_ch++;
}
sbrec_ha_chassis_group_set_ha_chassis(sb_ha_chassis_group,
sb_ha_chassis, n_sb_ha_ch);
sbrec_port_binding_set_ha_chassis_group(port_binding, sb_ha_chassis_group);
free(sb_ha_chassis);
}
static void
ovn_port_update_sbrec(struct northd_context *ctx,
struct ovsdb_idl_index *sbrec_chassis_by_name,
const struct ovn_port *op,
struct hmap *chassis_qdisc_queues,
struct sset *active_ha_chassis_grps)
{
sbrec_port_binding_set_datapath(op->sb, op->od->sb);
if (op->nbrp) {
/* If the router is for l3 gateway, it resides on a chassis
* and its port type is "l3gateway". */
const char *chassis_name = smap_get(&op->od->nbr->options, "chassis");
if (op->derived) {
sbrec_port_binding_set_type(op->sb, "chassisredirect");
} else if (chassis_name) {
sbrec_port_binding_set_type(op->sb, "l3gateway");
} else {
sbrec_port_binding_set_type(op->sb, "patch");
}
struct smap new;
smap_init(&new);
if (op->derived) {
const char *redirect_chassis = smap_get(&op->nbrp->options,
"redirect-chassis");
const char *redirect_type = smap_get(&op->nbrp->options,
"redirect-type");
int n_gw_options_set = 0;
if (op->nbrp->ha_chassis_group) {
n_gw_options_set++;
}
if (op->nbrp->n_gateway_chassis) {
n_gw_options_set++;
}
if (redirect_chassis) {
n_gw_options_set++;
}
if (n_gw_options_set > 1) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(
&rl, "Multiple gatway options set for the logical router "
"port %s. The first preferred option is "
"ha_chassis_group; the second is gateway_chassis; "
"and the last is redirect-chassis.", op->nbrp->name);
}
if (op->nbrp->ha_chassis_group) {
/* HA Chassis group is set. Ignore 'gateway_chassis'
* column and redirect-chassis option. */
sync_ha_chassis_group_for_sbpb(ctx, op->nbrp->ha_chassis_group,
sbrec_chassis_by_name, op->sb);
sset_add(active_ha_chassis_grps,
op->nbrp->ha_chassis_group->name);
} else if (op->nbrp->n_gateway_chassis) {
/* Legacy gateway_chassis support.
* Create ha_chassis_group for the Northbound gateway_chassis
* associated with the lrp. */
if (sbpb_gw_chassis_needs_update(op->sb, op->nbrp,
sbrec_chassis_by_name)) {
copy_gw_chassis_from_nbrp_to_sbpb(ctx,
sbrec_chassis_by_name,
op->nbrp, op->sb);
}
sset_add(active_ha_chassis_grps, op->nbrp->name);
} else if (redirect_chassis) {
/* Handle ports that had redirect-chassis option attached
* to them, and for backwards compatibility convert them
* to a single HA Chassis group entry */
const struct sbrec_chassis *chassis =
chassis_lookup_by_name(sbrec_chassis_by_name,
redirect_chassis);
if (chassis) {
/* If we found the chassis, and the gw chassis on record
* differs from what we expect go ahead and update */
char *gwc_name = xasprintf("%s_%s", op->nbrp->name,
chassis->name);
const struct sbrec_ha_chassis_group *sb_ha_ch_grp;
sb_ha_ch_grp = ha_chassis_group_lookup_by_name(
ctx->sbrec_ha_chassis_grp_by_name, gwc_name);
if (!sb_ha_ch_grp) {
sb_ha_ch_grp =
sbrec_ha_chassis_group_insert(ctx->ovnsb_txn);
sbrec_ha_chassis_group_set_name(sb_ha_ch_grp,
gwc_name);
}
if (sb_ha_ch_grp->n_ha_chassis != 1) {
struct sbrec_ha_chassis *sb_ha_ch =
create_sb_ha_chassis(ctx, chassis,
chassis->name, 0);
sbrec_ha_chassis_group_set_ha_chassis(sb_ha_ch_grp,
&sb_ha_ch, 1);
}
sbrec_port_binding_set_ha_chassis_group(op->sb,
sb_ha_ch_grp);
sset_add(active_ha_chassis_grps, gwc_name);
free(gwc_name);
} else {
VLOG_WARN("chassis name '%s' from redirect from logical "
" router port '%s' redirect-chassis not found",
redirect_chassis, op->nbrp->name);
if (op->sb->ha_chassis_group) {
sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
}
}
} else {
/* Nothing is set. Clear ha_chassis_group from pb. */
if (op->sb->ha_chassis_group) {
sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
}
}
if (op->sb->n_gateway_chassis) {
/* Delete the legacy gateway_chassis from the pb. */
sbrec_port_binding_set_gateway_chassis(op->sb, NULL, 0);
}
smap_add(&new, "distributed-port", op->nbrp->name);
if (redirect_type) {
smap_add(&new, "redirect-type", redirect_type);
}
} else {
if (op->peer) {
smap_add(&new, "peer", op->peer->key);
}
if (chassis_name) {
smap_add(&new, "l3gateway-chassis", chassis_name);
}
}
sbrec_port_binding_set_options(op->sb, &new);
smap_destroy(&new);
sbrec_port_binding_set_parent_port(op->sb, NULL);
sbrec_port_binding_set_tag(op->sb, NULL, 0);
struct ds s = DS_EMPTY_INITIALIZER;
ds_put_cstr(&s, op->nbrp->mac);
for (int i = 0; i < op->nbrp->n_networks; ++i) {
ds_put_format(&s, " %s", op->nbrp->networks[i]);
}
const char *addresses = ds_cstr(&s);
sbrec_port_binding_set_mac(op->sb, &addresses, 1);
ds_destroy(&s);
struct smap ids = SMAP_INITIALIZER(&ids);
sbrec_port_binding_set_external_ids(op->sb, &ids);
sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
} else {
if (strcmp(op->nbsp->type, "router")) {
uint32_t queue_id = smap_get_int(
&op->sb->options, "qdisc_queue_id", 0);
bool has_qos = port_has_qos_params(&op->nbsp->options);
const struct uuid *uuid = NULL;
struct smap options;
char *name = "";
if (!strcmp(op->nbsp->type, "localnet")) {
uuid = &op->sb->header_.uuid;
name = "localnet";
} else if (op->sb->chassis) {
uuid = &op->sb->chassis->header_.uuid;
name = op->sb->chassis->name;
}
if (has_qos && !queue_id) {
queue_id = allocate_chassis_queueid(chassis_qdisc_queues,
uuid, name);
} else if (!has_qos && queue_id) {
free_chassis_queueid(chassis_qdisc_queues, uuid, queue_id);
queue_id = 0;
}
smap_clone(&options, &op->nbsp->options);
if (queue_id) {
smap_add_format(&options,
"qdisc_queue_id", "%d", queue_id);
}
sbrec_port_binding_set_options(op->sb, &options);
smap_destroy(&options);
if (ovn_is_known_nb_lsp_type(op->nbsp->type)) {
sbrec_port_binding_set_type(op->sb, op->nbsp->type);
} else {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(
&rl, "Unknown port type '%s' set on logical switch '%s'.",
op->nbsp->type, op->nbsp->name);
}
sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
if (!strcmp(op->nbsp->type, "external")) {
if (op->nbsp->ha_chassis_group) {
sync_ha_chassis_group_for_sbpb(
ctx, op->nbsp->ha_chassis_group,
sbrec_chassis_by_name, op->sb);
sset_add(active_ha_chassis_grps,
op->nbsp->ha_chassis_group->name);
} else {
sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
}
}
} else {
const char *chassis = NULL;
if (op->peer && op->peer->od && op->peer->od->nbr) {
chassis = smap_get(&op->peer->od->nbr->options, "chassis");
}
/* A switch port connected to a gateway router is also of
* type "l3gateway". */
if (chassis) {
sbrec_port_binding_set_type(op->sb, "l3gateway");
} else {
sbrec_port_binding_set_type(op->sb, "patch");
}
const char *router_port = smap_get(&op->nbsp->options,
"router-port");
if (router_port || chassis) {
struct smap new;
smap_init(&new);
if (router_port) {
smap_add(&new, "peer", router_port);
}
if (chassis) {
smap_add(&new, "l3gateway-chassis", chassis);
}
sbrec_port_binding_set_options(op->sb, &new);
smap_destroy(&new);
} else {
sbrec_port_binding_set_options(op->sb, NULL);
}
const char *nat_addresses = smap_get(&op->nbsp->options,
"nat-addresses");
size_t n_nats = 0;
char **nats = NULL;
if (nat_addresses && !strcmp(nat_addresses, "router")) {
if (op->peer && op->peer->od
&& (chassis || op->peer->od->l3redirect_port)) {
nats = get_nat_addresses(op->peer, &n_nats);
}
/* Only accept manual specification of ethernet address
* followed by IPv4 addresses on type "l3gateway" ports. */
} else if (nat_addresses && chassis) {
struct lport_addresses laddrs;
if (!extract_lsp_addresses(nat_addresses, &laddrs)) {
static struct vlog_rate_limit rl =
VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&rl, "Error extracting nat-addresses.");
} else {
destroy_lport_addresses(&laddrs);
n_nats = 1;
nats = xcalloc(1, sizeof *nats);
nats[0] = xstrdup(nat_addresses);
}
}
/* Add the router mac and IPv4 addresses to
* Port_Binding.nat_addresses so that GARP is sent for these
* IPs by the ovn-controller on which the distributed gateway
* router port resides if:
*
* - op->peer has 'reside-on-gateway-chassis' set and the
* the logical router datapath has distributed router port.
*
* - op->peer is distributed gateway router port.
*
* - op->peer's router is a gateway router and op has a localnet
* port.
*
* Note: Port_Binding.nat_addresses column is also used for
* sending the GARPs for the router port IPs.
* */
bool add_router_port_garp = false;
if (op->peer && op->peer->nbrp && op->peer->od->l3dgw_port &&
op->peer->od->l3redirect_port &&
(smap_get_bool(&op->peer->nbrp->options,
"reside-on-redirect-chassis", false) ||
op->peer == op->peer->od->l3dgw_port)) {
add_router_port_garp = true;
} else if (chassis && op->od->localnet_port) {
add_router_port_garp = true;
}
if (add_router_port_garp) {
struct ds garp_info = DS_EMPTY_INITIALIZER;
ds_put_format(&garp_info, "%s", op->peer->lrp_networks.ea_s);
for (size_t i = 0; i < op->peer->lrp_networks.n_ipv4_addrs;
i++) {
ds_put_format(&garp_info, " %s",
op->peer->lrp_networks.ipv4_addrs[i].addr_s);
}
if (op->peer->od->l3redirect_port) {
ds_put_format(&garp_info, " is_chassis_resident(%s)",
op->peer->od->l3redirect_port->json_key);
}
n_nats++;
nats = xrealloc(nats, (n_nats * sizeof *nats));
nats[n_nats - 1] = ds_steal_cstr(&garp_info);
ds_destroy(&garp_info);
}
sbrec_port_binding_set_nat_addresses(op->sb,
(const char **) nats, n_nats);
for (size_t i = 0; i < n_nats; i++) {
free(nats[i]);
}
free(nats);
}
sbrec_port_binding_set_parent_port(op->sb, op->nbsp->parent_name);
sbrec_port_binding_set_tag(op->sb, op->nbsp->tag, op->nbsp->n_tag);
sbrec_port_binding_set_mac(op->sb, (const char **) op->nbsp->addresses,
op->nbsp->n_addresses);
struct smap ids = SMAP_INITIALIZER(&ids);
smap_clone(&ids, &op->nbsp->external_ids);
const char *name = smap_get(&ids, "neutron:port_name");
if (name && name[0]) {
smap_add(&ids, "name", name);
}
sbrec_port_binding_set_external_ids(op->sb, &ids);
smap_destroy(&ids);
}
}
/* Remove mac_binding entries that refer to logical_ports which are
* deleted. */
static void
cleanup_mac_bindings(struct northd_context *ctx, struct hmap *ports)
{
const struct sbrec_mac_binding *b, *n;
SBREC_MAC_BINDING_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) {
if (!ovn_port_find(ports, b->logical_port)) {
sbrec_mac_binding_delete(b);
}
}
}
static void
cleanup_sb_ha_chassis_groups(struct northd_context *ctx,
struct sset *active_ha_chassis_groups)
{
const struct sbrec_ha_chassis_group *b, *n;
SBREC_HA_CHASSIS_GROUP_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) {
if (!sset_contains(active_ha_chassis_groups, b->name)) {
sbrec_ha_chassis_group_delete(b);
}
}
}
struct ovn_lb {
struct hmap_node hmap_node;
const struct nbrec_load_balancer *nlb; /* May be NULL. */
struct lb_vip *vips;
size_t n_vips;
};
struct lb_vip {
char *vip;
uint16_t vip_port;
int addr_family;
char *backend_ips;
bool health_check;
struct lb_vip_backend *backends;
size_t n_backends;
};
struct lb_vip_backend {
char *ip;
uint16_t port;
struct ovn_port *op; /* Logical port to which the ip belong to. */
bool health_check;
char *svc_mon_src_ip; /* Source IP to use for monitoring. */
const struct sbrec_service_monitor *sbrec_monitor;
};
static inline struct ovn_lb *
ovn_lb_find(struct hmap *lbs, struct uuid *uuid)
{
struct ovn_lb *lb;
size_t hash = uuid_hash(uuid);
HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
return lb;
}
}
return NULL;
}
struct service_monitor_info {
struct hmap_node hmap_node;
const struct sbrec_service_monitor *sbrec_mon;
bool required;
};
static struct service_monitor_info *
create_or_get_service_mon(struct northd_context *ctx,
struct hmap *monitor_map,
const char *ip, const char *logical_port,
uint16_t service_port, const char *protocol)
{
uint32_t hash = service_port;
hash = hash_string(ip, hash);
hash = hash_string(logical_port, hash);
struct service_monitor_info *mon_info;
HMAP_FOR_EACH_WITH_HASH (mon_info, hmap_node, hash, monitor_map) {
if (mon_info->sbrec_mon->port == service_port &&
!strcmp(mon_info->sbrec_mon->ip, ip) &&
!strcmp(mon_info->sbrec_mon->protocol, protocol) &&
!strcmp(mon_info->sbrec_mon->logical_port, logical_port)) {
return mon_info;
}
}
struct sbrec_service_monitor *sbrec_mon =
sbrec_service_monitor_insert(ctx->ovnsb_txn);
sbrec_service_monitor_set_ip(sbrec_mon, ip);
sbrec_service_monitor_set_port(sbrec_mon, service_port);
sbrec_service_monitor_set_logical_port(sbrec_mon, logical_port);
sbrec_service_monitor_set_protocol(sbrec_mon, protocol);
mon_info = xzalloc(sizeof *mon_info);
mon_info->sbrec_mon = sbrec_mon;
hmap_insert(monitor_map, &mon_info->hmap_node, hash);
return mon_info;
}
static struct ovn_lb *
ovn_lb_create(struct northd_context *ctx, struct hmap *lbs,
const struct nbrec_load_balancer *nbrec_lb,
struct hmap *ports, struct hmap *monitor_map)
{
struct ovn_lb *lb = xzalloc(sizeof *lb);
size_t hash = uuid_hash(&nbrec_lb->header_.uuid);
lb->nlb = nbrec_lb;
hmap_insert(lbs, &lb->hmap_node, hash);
lb->n_vips = smap_count(&nbrec_lb->vips);
lb->vips = xcalloc(lb->n_vips, sizeof (struct lb_vip));
struct smap_node *node;
size_t n_vips = 0;
SMAP_FOR_EACH (node, &nbrec_lb->vips) {
char *vip = NULL;
uint16_t port;
int addr_family;
ip_address_and_port_from_lb_key(node->key, &vip, &port,
&addr_family);
if (!vip) {
continue;
}
lb->vips[n_vips].vip = vip;
lb->vips[n_vips].vip_port = port;
lb->vips[n_vips].addr_family = addr_family;
lb->vips[n_vips].backend_ips = xstrdup(node->value);
struct nbrec_load_balancer_health_check *lb_health_check = NULL;
for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
if (!strcmp(nbrec_lb->health_check[i]->vip, node->key)) {
lb_health_check = nbrec_lb->health_check[i];
break;
}
}
char *tokstr = xstrdup(node->value);
char *save_ptr = NULL;
char *token;
size_t n_backends = 0;
/* Format for a backend ips : IP1:port1,IP2:port2,...". */
for (token = strtok_r(tokstr, ",", &save_ptr);
token != NULL;
token = strtok_r(NULL, ",", &save_ptr)) {
n_backends++;
}
free(tokstr);
tokstr = xstrdup(node->value);
save_ptr = NULL;
lb->vips[n_vips].n_backends = n_backends;
lb->vips[n_vips].backends = xcalloc(n_backends,
sizeof (struct lb_vip_backend));
lb->vips[n_vips].health_check = lb_health_check ? true: false;
size_t i = 0;
for (token = strtok_r(tokstr, ",", &save_ptr);
token != NULL;
token = strtok_r(NULL, ",", &save_ptr)) {
char *backend_ip;
uint16_t backend_port;
ip_address_and_port_from_lb_key(token, &backend_ip, &backend_port,
&addr_family);
if (!backend_ip) {
continue;
}
/* Get the logical port to which this ip belongs to. */
struct ovn_port *op = NULL;
char *svc_mon_src_ip = NULL;
const char *s = smap_get(&nbrec_lb->ip_port_mappings,
backend_ip);
if (s) {
char *port_name = xstrdup(s);
char *p = strstr(port_name, ":");
if (p) {
*p = 0;
p++;
op = ovn_port_find(ports, port_name);
svc_mon_src_ip = xstrdup(p);
}
free(port_name);
}
lb->vips[n_vips].backends[i].ip = backend_ip;
lb->vips[n_vips].backends[i].port = backend_port;
lb->vips[n_vips].backends[i].op = op;
lb->vips[n_vips].backends[i].svc_mon_src_ip = svc_mon_src_ip;
if (lb_health_check && op && svc_mon_src_ip) {
const char *protocol = nbrec_lb->protocol;
if (!protocol || !protocol[0]) {
protocol = "tcp";
}
lb->vips[n_vips].backends[i].health_check = true;
struct service_monitor_info *mon_info =
create_or_get_service_mon(ctx, monitor_map, backend_ip,
op->nbsp->name, backend_port,
protocol);
ovs_assert(mon_info);
sbrec_service_monitor_set_options(
mon_info->sbrec_mon, &lb_health_check->options);
if (!mon_info->sbrec_mon->src_mac ||
strcmp(mon_info->sbrec_mon->src_mac, svc_monitor_mac)) {
sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
svc_monitor_mac);
}
if (!mon_info->sbrec_mon->src_ip ||
strcmp(mon_info->sbrec_mon->src_ip, svc_mon_src_ip)) {
sbrec_service_monitor_set_src_ip(mon_info->sbrec_mon,
svc_mon_src_ip);
}
lb->vips[n_vips].backends[i].sbrec_monitor =
mon_info->sbrec_mon;
mon_info->required = true;
} else {
lb->vips[n_vips].backends[i].health_check = false;
}
i++;
}
free(tokstr);
n_vips++;
}
return lb;
}
static void
ovn_lb_destroy(struct ovn_lb *lb)
{
for (size_t i = 0; i < lb->n_vips; i++) {
free(lb->vips[i].vip);
free(lb->vips[i].backend_ips);
for (size_t j = 0; j < lb->vips[i].n_backends; j++) {
free(lb->vips[i].backends[j].ip);
free(lb->vips[i].backends[j].svc_mon_src_ip);
}
free(lb->vips[i].backends);
}
free(lb->vips);
}
static void
build_ovn_lbs(struct northd_context *ctx, struct hmap *ports,
struct hmap *lbs)
{
hmap_init(lbs);
struct hmap monitor_map = HMAP_INITIALIZER(&monitor_map);
const struct sbrec_service_monitor *sbrec_mon;
SBREC_SERVICE_MONITOR_FOR_EACH (sbrec_mon, ctx->ovnsb_idl) {
uint32_t hash = sbrec_mon->port;
hash = hash_string(sbrec_mon->ip, hash);
hash = hash_string(sbrec_mon->logical_port, hash);
struct service_monitor_info *mon_info = xzalloc(sizeof *mon_info);
mon_info->sbrec_mon = sbrec_mon;
mon_info->required = false;
hmap_insert(&monitor_map, &mon_info->hmap_node, hash);
}
const struct nbrec_load_balancer *nbrec_lb;
NBREC_LOAD_BALANCER_FOR_EACH (nbrec_lb, ctx->ovnnb_idl) {
ovn_lb_create(ctx, lbs, nbrec_lb, ports, &monitor_map);
}
struct service_monitor_info *mon_info;
HMAP_FOR_EACH_POP (mon_info, hmap_node, &monitor_map) {
if (!mon_info->required) {
sbrec_service_monitor_delete(mon_info->sbrec_mon);
}
free(mon_info);
}
hmap_destroy(&monitor_map);
}
static void
destroy_ovn_lbs(struct hmap *lbs)
{
struct ovn_lb *lb;
HMAP_FOR_EACH_POP (lb, hmap_node, lbs) {
ovn_lb_destroy(lb);
free(lb);
}
}
/* Updates the southbound Port_Binding table so that it contains the logical
* switch ports specified by the northbound database.
*
* Initializes 'ports' to contain a "struct ovn_port" for every logical port,
* using the "struct ovn_datapath"s in 'datapaths' to look up logical
* datapaths. */
static void
build_ports(struct northd_context *ctx,
struct ovsdb_idl_index *sbrec_chassis_by_name,
struct hmap *datapaths, struct hmap *ports)
{
struct ovs_list sb_only, nb_only, both;
struct hmap tag_alloc_table = HMAP_INITIALIZER(&tag_alloc_table);
struct hmap chassis_qdisc_queues = HMAP_INITIALIZER(&chassis_qdisc_queues);
/* sset which stores the set of ha chassis group names used. */
struct sset active_ha_chassis_grps =
SSET_INITIALIZER(&active_ha_chassis_grps);
join_logical_ports(ctx, datapaths, ports, &chassis_qdisc_queues,
&tag_alloc_table, &sb_only, &nb_only, &both);
struct ovn_port *op, *next;
/* For logical ports that are in both databases, update the southbound
* record based on northbound data. Also index the in-use tunnel_keys.
* For logical ports that are in NB database, do any tag allocation
* needed. */
LIST_FOR_EACH_SAFE (op, next, list, &both) {
if (op->nbsp) {
tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp);
}
ovn_port_update_sbrec(ctx, sbrec_chassis_by_name,
op, &chassis_qdisc_queues,
&active_ha_chassis_grps);
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;
}
ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn));
ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
&chassis_qdisc_queues,
&active_ha_chassis_grps);
sbrec_port_binding_set_logical_port(op->sb, op->key);
sbrec_port_binding_set_tunnel_key(op->sb, tunnel_key);
}
bool remove_mac_bindings = false;
if (!ovs_list_is_empty(&sb_only)) {
remove_mac_bindings = true;
}
/* Delete southbound records without northbound matches. */
LIST_FOR_EACH_SAFE(op, next, list, &sb_only) {
ovs_list_remove(&op->list);
sbrec_port_binding_delete(op->sb);
ovn_port_destroy(ports, op);
}
if (remove_mac_bindings) {
cleanup_mac_bindings(ctx, ports);
}
tag_alloc_destroy(&tag_alloc_table);
destroy_chassis_queues(&chassis_qdisc_queues);
cleanup_sb_ha_chassis_groups(ctx, &active_ha_chassis_grps);
sset_destroy(&active_ha_chassis_grps);
}
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, OVN_MCAST_FLOOD_TUNNEL_KEY };
#define MC_MROUTER_FLOOD "_MC_mrouter_flood"
static const struct multicast_group mc_mrouter_flood =
{ MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
#define MC_MROUTER_STATIC "_MC_mrouter_static"
static const struct multicast_group mc_mrouter_static =
{ MC_MROUTER_STATIC, OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY };
#define MC_STATIC "_MC_static"
static const struct multicast_group mc_static =
{ MC_STATIC, OVN_MCAST_STATIC_TUNNEL_KEY };
#define MC_UNKNOWN "_MC_unknown"
static const struct multicast_group mc_unknown =
{ MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
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;
}
}
return NULL;
}
static void
ovn_multicast_add_ports(struct hmap *mcgroups, struct ovn_datapath *od,
const struct multicast_group *group,
struct ovn_port **ports, size_t n_ports)
{
struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group);
if (!mc) {
mc = xmalloc(sizeof *mc);
hmap_insert(mcgroups, &mc->hmap_node, ovn_multicast_hash(od, group));
mc->datapath = od;
mc->group = group;
mc->n_ports = 0;
mc->allocated_ports = 4;
mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports);
}
size_t n_ports_total = mc->n_ports + n_ports;
if (n_ports_total > 2 * mc->allocated_ports) {
mc->allocated_ports = n_ports_total;
mc->ports = xrealloc(mc->ports,
mc->allocated_ports * sizeof *mc->ports);
} else if (n_ports_total > mc->allocated_ports) {
mc->ports = x2nrealloc(mc->ports, &mc->allocated_ports,
sizeof *mc->ports);
}
memcpy(&mc->ports[mc->n_ports], &ports[0], n_ports * sizeof *ports);
mc->n_ports += n_ports;
}
static void
ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
struct ovn_port *port)
{
ovn_multicast_add_ports(mcgroups, port->od, group, &port, 1);
}
static void
ovn_multicast_destroy(struct hmap *mcgroups, struct ovn_multicast *mc)
{
if (mc) {
hmap_remove(mcgroups, &mc->hmap_node);
free(mc->ports);
free(mc);
}
}
static void
ovn_multicast_update_sbrec(const struct ovn_multicast *mc,
const struct sbrec_multicast_group *sb)
{
struct sbrec_port_binding **ports = xmalloc(mc->n_ports * sizeof *ports);
for (size_t i = 0; i < mc->n_ports; i++) {
ports[i] = CONST_CAST(struct sbrec_port_binding *, mc->ports[i]->sb);
}
sbrec_multicast_group_set_ports(sb, ports, mc->n_ports);
free(ports);
}
/*
* IGMP group entry (1:1 mapping to SB database).
*/
struct ovn_igmp_group_entry {
struct ovs_list list_node; /* Linkage in the list of entries. */
size_t n_ports;
struct ovn_port **ports;
};
/*
* IGMP group entry (aggregate of all entries from the SB database
* corresponding to the multicast group).
*/
struct ovn_igmp_group {
struct hmap_node hmap_node; /* Index on 'datapath' and 'address'. */
struct ovs_list list_node; /* Linkage in the per-dp igmp group list. */
struct ovn_datapath *datapath;
struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */
struct multicast_group mcgroup;
struct ovs_list entries; /* List of SB entries for this group. */
};
static uint32_t
ovn_igmp_group_hash(const struct ovn_datapath *datapath,
const struct in6_addr *address)
{
return hash_pointer(datapath, hash_bytes(address, sizeof *address, 0));
}
static struct ovn_igmp_group *
ovn_igmp_group_find(struct hmap *igmp_groups,
const struct ovn_datapath *datapath,
const struct in6_addr *address)
{
struct ovn_igmp_group *group;
HMAP_FOR_EACH_WITH_HASH (group, hmap_node,
ovn_igmp_group_hash(datapath, address),
igmp_groups) {
if (group->datapath == datapath &&
ipv6_addr_equals(&group->address, address)) {
return group;
}
}
return NULL;
}
static struct ovn_igmp_group *
ovn_igmp_group_add(struct northd_context *ctx, struct hmap *igmp_groups,
struct ovn_datapath *datapath,
const struct in6_addr *address,
const char *address_s)
{
struct ovn_igmp_group *igmp_group =
ovn_igmp_group_find(igmp_groups, datapath, address);
if (!igmp_group) {
igmp_group = xmalloc(sizeof *igmp_group);
const struct sbrec_multicast_group *mcgroup =
mcast_group_lookup(ctx->sbrec_mcast_group_by_name_dp, address_s,
datapath->sb);
igmp_group->datapath = datapath;
igmp_group->address = *address;
if (mcgroup) {
igmp_group->mcgroup.key = mcgroup->tunnel_key;
add_tnlid(&datapath->mcast_info.group_tnlids, mcgroup->tunnel_key);
} else {
igmp_group->mcgroup.key = 0;
}
igmp_group->mcgroup.name = address_s;
ovs_list_init(&igmp_group->entries);
hmap_insert(igmp_groups, &igmp_group->hmap_node,
ovn_igmp_group_hash(datapath, address));
ovs_list_push_back(&datapath->mcast_info.groups,
&igmp_group->list_node);
}
return igmp_group;
}
static bool
ovn_igmp_group_get_address(const struct sbrec_igmp_group *sb_igmp_group,
struct in6_addr *address)
{
ovs_be32 ipv4;
if (ip_parse(sb_igmp_group->address, &ipv4)) {
*address = in6_addr_mapped_ipv4(ipv4);
return true;
}
if (!ipv6_parse(sb_igmp_group->address, address)) {
return false;
}
return true;
}
static struct ovn_port **
ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
size_t *n_ports, struct hmap *ovn_ports)
{
struct ovn_port **ports = xmalloc(sb_igmp_group->n_ports * sizeof *ports);
*n_ports = 0;
for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
struct ovn_port *port =
ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
/* If this is already a flood port skip it for the group. */
if (port->mcast_info.flood) {
continue;
}
/* If this is already a port of a router on which relay is enabled,
* skip it for the group. Traffic is flooded there anyway.
*/
if (port->peer && port->peer->od &&
port->peer->od->mcast_info.rtr.relay) {
continue;
}
ports[(*n_ports)] = port;
ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
if (ports[(*n_ports)]) {
(*n_ports)++;
}
}
return ports;
}
static void
ovn_igmp_group_add_entry(struct ovn_igmp_group *igmp_group,
struct ovn_port **ports, size_t n_ports)
{
struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry);
entry->ports = ports;
entry->n_ports = n_ports;
ovs_list_push_back(&igmp_group->entries, &entry->list_node);
}
static void
ovn_igmp_group_destroy_entry(struct ovn_igmp_group_entry *entry)
{
free(entry->ports);
}
static bool
ovn_igmp_group_allocate_id(struct ovn_igmp_group *igmp_group)
{
if (igmp_group->mcgroup.key == 0) {
struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info;
igmp_group->mcgroup.key = ovn_mcast_group_allocate_key(mcast_info);
}
if (igmp_group->mcgroup.key == 0) {
return false;
}
return true;
}
static void
ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
struct hmap *mcast_groups)
{
struct ovn_igmp_group_entry *entry;
LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
&igmp_group->mcgroup, entry->ports,
entry->n_ports);
ovn_igmp_group_destroy_entry(entry);
free(entry);
}
}
static void
ovn_igmp_group_destroy(struct hmap *igmp_groups,
struct ovn_igmp_group *igmp_group)
{
if (igmp_group) {
struct ovn_igmp_group_entry *entry;
LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
ovn_igmp_group_destroy_entry(entry);
free(entry);
}
hmap_remove(igmp_groups, &igmp_group->hmap_node);
ovs_list_remove(&igmp_group->list_node);
free(igmp_group);
}
}
/* Logical flow generation.
*
* This code generates the Logical_Flow table in the southbound database, as a
* function of most of the northbound database.
*/
struct ovn_lflow {
struct hmap_node hmap_node;
struct ovn_datapath *od;
enum ovn_stage stage;
uint16_t priority;
char *match;
char *actions;
char *stage_hint;
const char *where;
};
static size_t
ovn_lflow_hash(const struct ovn_lflow *lflow)
{
return ovn_logical_flow_hash(&lflow->od->sb->header_.uuid,
ovn_stage_get_table(lflow->stage),
ovn_stage_get_pipeline_name(lflow->stage),
lflow->priority, lflow->match,
lflow->actions);
}
static bool
ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_lflow *b)
{
return (a->od == b->od
&& a->stage == b->stage
&& a->priority == b->priority
&& !strcmp(a->match, b->match)
&& !strcmp(a->actions, b->actions));
}
static void
ovn_lflow_init(struct ovn_lflow *lflow, struct ovn_datapath *od,
enum ovn_stage stage, uint16_t priority,
char *match, char *actions, char *stage_hint,
const char *where)
{
lflow->od = od;
lflow->stage = stage;
lflow->priority = priority;
lflow->match = match;
lflow->actions = actions;
lflow->stage_hint = stage_hint;
lflow->where = where;
}
/* Adds a row with the specified contents to the Logical_Flow table. */
static void
ovn_lflow_add_at(struct hmap *lflow_map, struct ovn_datapath *od,
enum ovn_stage stage, uint16_t priority,
const char *match, const char *actions,
const char *stage_hint, const char *where)
{
ovs_assert(ovn_stage_to_datapath_type(stage) == ovn_datapath_get_type(od));