[ovs-dev] [PATCH v4 3/3] OVN: Add ovn-northd IGMP support
Dumitru Ceara
dceara at redhat.com
Mon Jul 15 20:25:03 UTC 2019
New IP Multicast Snooping Options are added to the Northbound DB
Logical_Switch:other_config column. These allow enabling IGMP snooping and
querier on the logical switch and get translated by ovn-northd to rows in
the IP_Multicast Southbound DB table.
ovn-northd monitors for changes done by ovn-controllers in the Southbound DB
IGMP_Group table. Based on the entries in IGMP_Group ovn-northd creates
Multicast_Group entries in the Southbound DB, one per IGMP_Group address X,
containing the list of logical switch ports (aggregated from all controllers)
that have IGMP_Group entries for that datapath and address X. ovn-northd
also creates a logical flow that matches on IP multicast traffic destined
to address X and outputs it on the tunnel key of the corresponding
Multicast_Group entry.
Signed-off-by: Dumitru Ceara <dceara at redhat.com>
Acked-by: Mark Michelson <mmichels at redhat.com>
---
ovn/controller/ovn-controller.c | 5
ovn/controller/pinctrl.c | 1
ovn/lib/automake.mk | 2
ovn/lib/ip-mcast-index.h | 3
ovn/lib/mcast-group-index.c | 43 +++
ovn/lib/mcast-group-index.h | 32 ++
ovn/northd/ovn-northd.c | 548 ++++++++++++++++++++++++++++++++++++---
ovn/ovn-nb.xml | 54 ++++
tests/ovn.at | 270 +++++++++++++++++++
tests/system-ovn.at | 119 ++++++++
10 files changed, 1033 insertions(+), 44 deletions(-)
create mode 100644 ovn/lib/mcast-group-index.c
create mode 100644 ovn/lib/mcast-group-index.h
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 080a357..cf6c8ae 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -45,6 +45,7 @@
#include "ovn/lib/chassis-index.h"
#include "ovn/lib/extend-table.h"
#include "ovn/lib/ip-mcast-index.h"
+#include "ovn/lib/mcast-group-index.h"
#include "ovn/lib/ovn-sb-idl.h"
#include "ovn/lib/ovn-util.h"
#include "patch.h"
@@ -1743,9 +1744,7 @@ main(int argc, char *argv[])
struct ovsdb_idl_index *sbrec_chassis_by_name
= chassis_index_create(ovnsb_idl_loop.idl);
struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath
- = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
- &sbrec_multicast_group_col_name,
- &sbrec_multicast_group_col_datapath);
+ = mcast_group_index_create(ovnsb_idl_loop.idl);
struct ovsdb_idl_index *sbrec_port_binding_by_name
= ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
&sbrec_port_binding_col_logical_port);
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index f9730a6..d857067 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -45,6 +45,7 @@
#include "ovn/lex.h"
#include "ovn/lib/acl-log.h"
#include "ovn/lib/ip-mcast-index.h"
+#include "ovn/lib/mcast-group-index.h"
#include "ovn/lib/ovn-l7.h"
#include "ovn/lib/ovn-util.h"
#include "ovn/logical-fields.h"
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index 8f69982..7cac67f 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -14,6 +14,8 @@ ovn_lib_libovn_la_SOURCES = \
ovn/lib/extend-table.c \
ovn/lib/ip-mcast-index.c \
ovn/lib/ip-mcast-index.h \
+ ovn/lib/mcast-group-index.c \
+ ovn/lib/mcast-group-index.h \
ovn/lib/lex.c \
ovn/lib/ovn-l7.h \
ovn/lib/ovn-util.c \
diff --git a/ovn/lib/ip-mcast-index.h b/ovn/lib/ip-mcast-index.h
index 15141ea..a23b4a7 100644
--- a/ovn/lib/ip-mcast-index.h
+++ b/ovn/lib/ip-mcast-index.h
@@ -28,9 +28,6 @@ struct sbrec_datapath_binding;
#define OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S 1
#define OVN_MCAST_DEFAULT_MAX_ENTRIES 2048
-#define OVN_MCAST_FLOOD_TUNNEL_KEY 65535
-#define OVN_MCAST_UNKNOWN_TUNNEL_KEY (OVN_MCAST_FLOOD_TUNNEL_KEY - 1)
-
struct ovsdb_idl_index *ip_mcast_index_create(struct ovsdb_idl *);
const struct sbrec_ip_multicast *ip_mcast_lookup(
struct ovsdb_idl_index *ip_mcast_index,
diff --git a/ovn/lib/mcast-group-index.c b/ovn/lib/mcast-group-index.c
new file mode 100644
index 0000000..740311e
--- /dev/null
+++ b/ovn/lib/mcast-group-index.c
@@ -0,0 +1,43 @@
+/* Copyright (c) 2019, Red Hat, Inc.
+ *
+ * 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 "ovn/lib/mcast-group-index.h"
+#include "ovn/lib/ovn-sb-idl.h"
+
+struct ovsdb_idl_index *
+mcast_group_index_create(struct ovsdb_idl *idl)
+{
+ return ovsdb_idl_index_create2(idl, &sbrec_multicast_group_col_name,
+ &sbrec_multicast_group_col_datapath);
+}
+
+const struct sbrec_multicast_group *
+mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index,
+ const char *name,
+ const struct sbrec_datapath_binding *datapath)
+{
+ struct sbrec_multicast_group *target =
+ sbrec_multicast_group_index_init_row(mcgroup_index);
+ sbrec_multicast_group_index_set_name(target, name);
+ sbrec_multicast_group_index_set_datapath(target, datapath);
+
+ struct sbrec_multicast_group *mcgroup =
+ sbrec_multicast_group_index_find(mcgroup_index, target);
+ sbrec_multicast_group_index_destroy_row(target);
+
+ return mcgroup;
+}
diff --git a/ovn/lib/mcast-group-index.h b/ovn/lib/mcast-group-index.h
new file mode 100644
index 0000000..859e6a7
--- /dev/null
+++ b/ovn/lib/mcast-group-index.h
@@ -0,0 +1,32 @@
+/* Copyright (c) 2019, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_MCAST_GROUP_INDEX_H
+#define OVN_MCAST_GROUP_INDEX_H 1
+
+struct ovsdb_idl;
+
+struct sbrec_datapath_binding;
+
+#define OVN_MCAST_FLOOD_TUNNEL_KEY 65535
+#define OVN_MCAST_UNKNOWN_TUNNEL_KEY (OVN_MCAST_FLOOD_TUNNEL_KEY - 1)
+
+struct ovsdb_idl_index *mcast_group_index_create(struct ovsdb_idl *);
+const struct sbrec_multicast_group *
+mcast_group_lookup(struct ovsdb_idl_index *mcgroup_index,
+ const char *name,
+ const struct sbrec_datapath_binding *datapath);
+
+#endif /* ovn/lib/mcast-group-index.h */
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 4929fb6..c83894b 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -29,6 +29,8 @@
#include "openvswitch/json.h"
#include "ovn/lex.h"
#include "ovn/lib/chassis-index.h"
+#include "ovn/lib/ip-mcast-index.h"
+#include "ovn/lib/mcast-group-index.h"
#include "ovn/lib/ovn-l7.h"
#include "ovn/lib/ovn-nb-idl.h"
#include "ovn/lib/ovn-sb-idl.h"
@@ -57,6 +59,8 @@ struct northd_context {
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;
};
static const char *ovnnb_db;
@@ -304,17 +308,17 @@ tnlid_in_use(const struct hmap *set, uint32_t tnlid)
}
static uint32_t
-next_tnlid(uint32_t tnlid, uint32_t max)
+next_tnlid(uint32_t tnlid, uint32_t min, uint32_t max)
{
- return tnlid + 1 <= max ? tnlid + 1 : 1;
+ return tnlid + 1 <= max ? tnlid + 1 : min;
}
static uint32_t
-allocate_tnlid(struct hmap *set, const char *name, uint32_t max,
+allocate_tnlid(struct hmap *set, const char *name, uint32_t min, uint32_t max,
uint32_t *hint)
{
- for (uint32_t tnlid = next_tnlid(*hint, max); tnlid != *hint;
- tnlid = next_tnlid(tnlid, max)) {
+ 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;
@@ -426,6 +430,42 @@ struct ipam_info {
bool mac_only;
};
+#define OVN_MIN_MULTICAST 32768
+#define OVN_MAX_MULTICAST OVN_MCAST_FLOOD_TUNNEL_KEY
+BUILD_ASSERT_DECL(OVN_MIN_MULTICAST < OVN_MAX_MULTICAST);
+
+#define OVN_MIN_IP_MULTICAST OVN_MIN_MULTICAST
+#define OVN_MAX_IP_MULTICAST (OVN_MCAST_UNKNOWN_TUNNEL_KEY - 1)
+BUILD_ASSERT_DECL(OVN_MAX_IP_MULTICAST >= OVN_MIN_MULTICAST);
+
+/*
+ * Multicast snooping and querier per datapath configuration.
+ */
+struct mcast_info {
+ bool enabled;
+ bool querier;
+ bool flood_unregistered;
+
+ int64_t table_size;
+ int64_t idle_timeout;
+ int64_t query_interval;
+ char *eth_src;
+ char *ipv4_src;
+ int64_t query_max_response;
+
+ struct hmap group_tnlids;
+ uint32_t group_tnlid_hint;
+ uint32_t active_flows;
+};
+
+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 {
@@ -450,6 +490,9 @@ struct ovn_datapath {
/* 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
@@ -526,6 +569,13 @@ ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
bitmap_free(od->ipam_info.allocated_ipv4s);
free(od->router_ports);
ovn_ls_port_group_destroy(&od->nb_pgs);
+
+ if (od->nbs) {
+ free(od->mcast_info.eth_src);
+ free(od->mcast_info.ipv4_src);
+ destroy_tnlids(&od->mcast_info.group_tnlids);
+ }
+
free(od);
}
}
@@ -661,6 +711,86 @@ init_ipam_info_for_datapath(struct ovn_datapath *od)
}
static void
+init_mcast_info_for_datapath(struct ovn_datapath *od)
+{
+ if (!od->nbs) {
+ return;
+ }
+
+ struct mcast_info *mcast_info = &od->mcast_info;
+
+ mcast_info->enabled =
+ smap_get_bool(&od->nbs->other_config, "mcast_snoop", false);
+ mcast_info->querier =
+ smap_get_bool(&od->nbs->other_config, "mcast_querier", true);
+ mcast_info->flood_unregistered =
+ smap_get_bool(&od->nbs->other_config, "mcast_flood_unregistered",
+ false);
+
+ mcast_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_info->idle_timeout = idle_timeout;
+
+ uint32_t query_interval =
+ smap_get_ullong(&od->nbs->other_config, "mcast_query_interval",
+ mcast_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_info->query_interval = query_interval;
+
+ mcast_info->eth_src =
+ nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_eth_src"));
+ mcast_info->ipv4_src =
+ nullable_xstrdup(smap_get(&od->nbs->other_config, "mcast_ip4_src"));
+
+ mcast_info->query_max_response =
+ smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response",
+ OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S);
+
+ hmap_init(&mcast_info->group_tnlids);
+ mcast_info->group_tnlid_hint = OVN_MIN_IP_MULTICAST;
+ mcast_info->active_flows = 0;
+}
+
+static void
+store_mcast_info_for_datapath(const struct sbrec_ip_multicast *sb,
+ struct ovn_datapath *od)
+{
+ struct mcast_info *mcast_info = &od->mcast_info;
+
+ sbrec_ip_multicast_set_datapath(sb, od->sb);
+ sbrec_ip_multicast_set_enabled(sb, &mcast_info->enabled, 1);
+ sbrec_ip_multicast_set_querier(sb, &mcast_info->querier, 1);
+ sbrec_ip_multicast_set_table_size(sb, &mcast_info->table_size, 1);
+ sbrec_ip_multicast_set_idle_timeout(sb, &mcast_info->idle_timeout, 1);
+ sbrec_ip_multicast_set_query_interval(sb,
+ &mcast_info->query_interval, 1);
+ sbrec_ip_multicast_set_query_max_resp(sb,
+ &mcast_info->query_max_response, 1);
+
+ if (mcast_info->eth_src) {
+ sbrec_ip_multicast_set_eth_src(sb, mcast_info->eth_src);
+ }
+
+ if (mcast_info->ipv4_src) {
+ sbrec_ip_multicast_set_ip4_src(sb, mcast_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
@@ -743,6 +873,7 @@ join_datapaths(struct northd_context *ctx, struct hmap *datapaths,
}
init_ipam_info_for_datapath(od);
+ init_mcast_info_for_datapath(od);
}
const struct nbrec_logical_router *nbr;
@@ -780,7 +911,7 @@ static uint32_t
ovn_datapath_allocate_key(struct hmap *dp_tnlids)
{
static uint32_t hint;
- return allocate_tnlid(dp_tnlids, "datapath", (1u << 24) - 1, &hint);
+ return allocate_tnlid(dp_tnlids, "datapath", 1, (1u << 24) - 1, &hint);
}
/* Updates the southbound Datapath_Binding table so that it contains the
@@ -912,7 +1043,7 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
}
static struct ovn_port *
-ovn_port_find(struct hmap *ports, const char *name)
+ovn_port_find(const struct hmap *ports, const char *name)
{
struct ovn_port *op;
@@ -928,7 +1059,7 @@ static uint32_t
ovn_port_allocate_key(struct ovn_datapath *od)
{
return allocate_tnlid(&od->port_tnlids, "port",
- (1u << 15) - 1, &od->port_key_hint);
+ 1, (1u << 15) - 1, &od->port_key_hint);
}
static char *
@@ -2702,9 +2833,6 @@ build_ports(struct northd_context *ctx,
cleanup_sb_ha_chassis_groups(ctx, &active_ha_chassis_grps);
sset_destroy(&active_ha_chassis_grps);
}
-
-#define OVN_MIN_MULTICAST 32768
-#define OVN_MAX_MULTICAST 65535
struct multicast_group {
const char *name;
@@ -2712,10 +2840,12 @@ struct multicast_group {
};
#define MC_FLOOD "_MC_flood"
-static const struct multicast_group mc_flood = { MC_FLOOD, 65535 };
+static const struct multicast_group mc_flood =
+ { MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY };
#define MC_UNKNOWN "_MC_unknown"
-static const struct multicast_group mc_unknown = { MC_UNKNOWN, 65534 };
+static const struct multicast_group mc_unknown =
+ { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
static bool
multicast_group_equal(const struct multicast_group *a,
@@ -2758,10 +2888,10 @@ ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath,
}
static void
-ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
- struct ovn_port *port)
+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_datapath *od = port->od;
struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group);
if (!mc) {
mc = xmalloc(sizeof *mc);
@@ -2772,11 +2902,27 @@ ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
mc->allocated_ports = 4;
mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports);
}
- if (mc->n_ports >= mc->allocated_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);
}
- mc->ports[mc->n_ports++] = port;
+
+ 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
@@ -2800,7 +2946,144 @@ ovn_multicast_update_sbrec(const struct ovn_multicast *mc,
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. */
+ const struct sbrec_igmp_group *sb;
+};
+
+/*
+ * 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 ovn_datapath *datapath;
+ struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */
+ struct multicast_group mcgroup;
+
+ struct ovs_list sb_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 void
+ovn_igmp_group_add(struct northd_context *ctx, struct hmap *igmp_groups,
+ struct ovn_datapath *datapath,
+ const struct sbrec_igmp_group *sb_igmp_group)
+{
+ struct in6_addr group_address;
+ ovs_be32 ipv4;
+
+ if (ip_parse(sb_igmp_group->address, &ipv4)) {
+ group_address = in6_addr_mapped_ipv4(ipv4);
+ } else if (!ipv6_parse(sb_igmp_group->address, &group_address)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_WARN_RL(&rl, "invalid IGMP group address: %s",
+ sb_igmp_group->address);
+ return;
+ }
+
+ struct ovn_igmp_group *igmp_group =
+ ovn_igmp_group_find(igmp_groups, datapath, &group_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,
+ sb_igmp_group->address, datapath->sb);
+
+ igmp_group->datapath = datapath;
+ igmp_group->address = group_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 = sb_igmp_group->address;
+ ovs_list_init(&igmp_group->sb_entries);
+
+ hmap_insert(igmp_groups, &igmp_group->hmap_node,
+ ovn_igmp_group_hash(datapath, &group_address));
+ }
+
+ struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry);
+
+ entry->sb = sb_igmp_group;
+ ovs_list_push_back(&igmp_group->sb_entries , &entry->list_node);
+}
+
+static void
+ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
+ struct hmap *ovn_ports,
+ struct hmap *mcast_groups)
+{
+ struct ovn_igmp_group_entry *entry;
+
+ LIST_FOR_EACH_POP (entry, list_node, &igmp_group->sb_entries) {
+ size_t n_oports = 0;
+ struct ovn_port **oports =
+ xmalloc(entry->sb->n_ports * sizeof *oports);
+
+ for (size_t i = 0; i < entry->sb->n_ports; i++) {
+ oports[n_oports] =
+ ovn_port_find(ovn_ports, entry->sb->ports[i]->logical_port);
+ if (oports[n_oports]) {
+ n_oports++;
+ }
+ }
+
+ ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
+ &igmp_group->mcgroup, oports, n_oports);
+ free(oports);
+ 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->sb_entries) {
+ free(entry);
+ }
+ hmap_remove(igmp_groups, &igmp_group->hmap_node);
+ free(igmp_group);
+ }
+}
+
/* Logical flow generation.
*
* This code generates the Logical_Flow table in the southbound database, as a
@@ -4474,7 +4757,7 @@ build_lrouter_groups(struct hmap *ports, struct ovs_list *lr_list)
static void
build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
struct hmap *port_groups, struct hmap *lflows,
- struct hmap *mcgroups)
+ struct hmap *mcgroups, struct hmap *igmp_groups)
{
/* This flow table structure is documented in ovn-northd(8), so please
* update ovn-northd.8.xml if you change anything. */
@@ -4938,24 +5221,63 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
}
+
/* Ingress table 17: Destination lookup, broadcast and multicast handling
- * (priority 100). */
- HMAP_FOR_EACH (op, key_node, ports) {
- if (!op->nbsp) {
+ * (priority 70 - 100). */
+ HMAP_FOR_EACH (od, key_node, datapaths) {
+ if (!od->nbs) {
continue;
}
- if (lsp_is_enabled(op->nbsp)) {
- ovn_multicast_add(mcgroups, &mc_flood, op);
+ if (od->mcast_info.enabled) {
+ /* Punt IGMP traffic to controller. */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
+ "ip4 && ip.proto == 2", "igmp;");
+
+ /* Flood all IP multicast traffic destined to 224.0.0.X to all
+ * ports - RFC 4541, section 2.1.2, item 2.
+ */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 85,
+ "ip4 && ip4.dst == 224.0.0.0/24",
+ "outport = \""MC_FLOOD"\"; output;");
+
+ /* Drop unregistered IP multicast if not allowed. */
+ if (!od->mcast_info.flood_unregistered) {
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
+ "ip4 && ip4.mcast", "drop;");
+ }
}
+
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast",
+ "outport = \""MC_FLOOD"\"; output;");
}
- HMAP_FOR_EACH (od, key_node, datapaths) {
- if (!od->nbs) {
+
+ /* Ingress table 17: Add IP multicast flows learnt from IGMP
+ * (priority 90). */
+ struct ovn_igmp_group *igmp_group, *next_igmp_group;
+
+ HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, igmp_groups) {
+ ds_clear(&match);
+ ds_clear(&actions);
+
+ if (!igmp_group->datapath) {
continue;
}
- ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100, "eth.mcast",
- "outport = \""MC_FLOOD"\"; output;");
+ struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info;
+
+ if (mcast_info->active_flows >= mcast_info->table_size) {
+ continue;
+ }
+ mcast_info->active_flows++;
+
+ ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
+ igmp_group->mcgroup.name);
+ ds_put_format(&actions, "outport = \"%s\"; output; ",
+ igmp_group->mcgroup.name);
+
+ ovn_lflow_add(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP, 90,
+ ds_cstr(&match), ds_cstr(&actions));
}
/* Ingress table 17: Destination lookup, unicast handling (priority 50), */
@@ -7556,12 +7878,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
* constructing their contents based on the OVN_NB database. */
static void
build_lflows(struct northd_context *ctx, struct hmap *datapaths,
- struct hmap *ports, struct hmap *port_groups)
+ struct hmap *ports, struct hmap *port_groups,
+ struct hmap *mcgroups, struct hmap *igmp_groups)
{
struct hmap lflows = HMAP_INITIALIZER(&lflows);
- struct hmap mcgroups = HMAP_INITIALIZER(&mcgroups);
- build_lswitch_flows(datapaths, ports, port_groups, &lflows, &mcgroups);
+ build_lswitch_flows(datapaths, ports, port_groups, &lflows, mcgroups,
+ igmp_groups);
build_lrouter_flows(datapaths, ports, &lflows);
/* Push changes to the Logical_Flow table to database. */
@@ -7636,24 +7959,27 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
struct multicast_group group = { .name = sbmc->name,
.key = sbmc->tunnel_key };
- struct ovn_multicast *mc = ovn_multicast_find(&mcgroups, od, &group);
+ struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, &group);
if (mc) {
ovn_multicast_update_sbrec(mc, sbmc);
- ovn_multicast_destroy(&mcgroups, mc);
+ ovn_multicast_destroy(mcgroups, mc);
} else {
sbrec_multicast_group_delete(sbmc);
}
}
struct ovn_multicast *mc, *next_mc;
- HMAP_FOR_EACH_SAFE (mc, next_mc, hmap_node, &mcgroups) {
+ HMAP_FOR_EACH_SAFE (mc, next_mc, hmap_node, mcgroups) {
+ if (!mc->datapath) {
+ ovn_multicast_destroy(mcgroups, mc);
+ continue;
+ }
sbmc = sbrec_multicast_group_insert(ctx->ovnsb_txn);
sbrec_multicast_group_set_datapath(sbmc, mc->datapath->sb);
sbrec_multicast_group_set_name(sbmc, mc->group->name);
sbrec_multicast_group_set_tunnel_key(sbmc, mc->group->key);
ovn_multicast_update_sbrec(mc, sbmc);
- ovn_multicast_destroy(&mcgroups, mc);
+ ovn_multicast_destroy(mcgroups, mc);
}
- hmap_destroy(&mcgroups);
}
static void
@@ -8073,6 +8399,105 @@ destroy_datapaths_and_ports(struct hmap *datapaths, struct hmap *ports,
}
static void
+build_ip_mcast(struct northd_context *ctx, struct hmap *datapaths)
+{
+ struct ovn_datapath *od;
+
+ HMAP_FOR_EACH (od, key_node, datapaths) {
+ if (!od->nbs) {
+ continue;
+ }
+
+ const struct sbrec_ip_multicast *ip_mcast =
+ ip_mcast_lookup(ctx->sbrec_ip_mcast_by_dp, od->sb);
+
+ if (!ip_mcast) {
+ ip_mcast = sbrec_ip_multicast_insert(ctx->ovnsb_txn);
+ }
+ store_mcast_info_for_datapath(ip_mcast, od);
+ }
+
+ /* Delete southbound records without northbound matches. */
+ const struct sbrec_ip_multicast *sb, *sb_next;
+
+ SBREC_IP_MULTICAST_FOR_EACH_SAFE (sb, sb_next, ctx->ovnsb_idl) {
+ if (!sb->datapath ||
+ !ovn_datapath_from_sbrec(datapaths, sb->datapath)) {
+ sbrec_ip_multicast_delete(sb);
+ }
+ }
+}
+
+static void
+build_mcast_groups(struct northd_context *ctx,
+ struct hmap *datapaths, struct hmap *ports,
+ struct hmap *mcast_groups,
+ struct hmap *igmp_groups)
+{
+ struct ovn_port *op;
+
+ hmap_init(mcast_groups);
+ hmap_init(igmp_groups);
+
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (!op->nbsp) {
+ continue;
+ }
+
+ if (lsp_is_enabled(op->nbsp)) {
+ ovn_multicast_add(mcast_groups, &mc_flood, op);
+ }
+ }
+
+ const struct sbrec_igmp_group *sb_igmp, *sb_igmp_next;
+
+ SBREC_IGMP_GROUP_FOR_EACH_SAFE (sb_igmp, sb_igmp_next, ctx->ovnsb_idl) {
+ /* If this is a stale group (e.g., controller had crashed,
+ * purge it).
+ */
+ if (!sb_igmp->chassis || !sb_igmp->datapath) {
+ sbrec_igmp_group_delete(sb_igmp);
+ continue;
+ }
+
+ /* If the datapath value is stale, purge the group. */
+ struct ovn_datapath *od =
+ ovn_datapath_from_sbrec(datapaths, sb_igmp->datapath);
+ if (!od) {
+ sbrec_igmp_group_delete(sb_igmp);
+ continue;
+ }
+
+ /* Add the IGMP group entry. Will also try to allocate an ID for it
+ * if the multicast group already exists.
+ */
+ ovn_igmp_group_add(ctx, igmp_groups, od, sb_igmp);
+ }
+
+ /* Walk the aggregated IGMP groups and allocate IDs for new entries.
+ * Then store the ports in the associated multicast group.
+ */
+ struct ovn_igmp_group *igmp_group, *igmp_group_next;
+ HMAP_FOR_EACH_SAFE (igmp_group, igmp_group_next, hmap_node, igmp_groups) {
+ 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 we ran out of keys just destroy the entry. */
+ if (igmp_group->mcgroup.key == 0) {
+ ovn_igmp_group_destroy(igmp_groups, igmp_group);
+ continue;
+ }
+
+ /* Aggregate the ports from all SB entries corresponding to this
+ * group.
+ */
+ ovn_igmp_group_aggregate_ports(igmp_group, ports, mcast_groups);
+ }
+}
+
+static void
ovnnb_db_run(struct northd_context *ctx,
struct ovsdb_idl_index *sbrec_chassis_by_name,
struct ovsdb_idl_loop *sb_loop,
@@ -8083,23 +8508,36 @@ ovnnb_db_run(struct northd_context *ctx,
return;
}
struct hmap port_groups;
+ struct hmap mcast_groups;
+ struct hmap igmp_groups;
build_datapaths(ctx, datapaths, lr_list);
build_ports(ctx, sbrec_chassis_by_name, datapaths, ports);
build_ipam(datapaths, ports);
build_port_group_lswitches(ctx, &port_groups, ports);
build_lrouter_groups(ports, lr_list);
- build_lflows(ctx, datapaths, ports, &port_groups);
+ build_ip_mcast(ctx, datapaths);
+ build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
+ build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
+ &igmp_groups);
sync_address_sets(ctx);
sync_port_groups(ctx);
sync_meters(ctx);
sync_dns_entries(ctx, datapaths);
+ struct ovn_igmp_group *igmp_group, *next_igmp_group;
+
+ HMAP_FOR_EACH_SAFE (igmp_group, next_igmp_group, hmap_node, &igmp_groups) {
+ ovn_igmp_group_destroy(&igmp_groups, igmp_group);
+ }
+
struct ovn_port_group *pg, *next_pg;
HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) {
ovn_port_group_destroy(&port_groups, pg);
}
+ hmap_destroy(&igmp_groups);
+ hmap_destroy(&mcast_groups);
hmap_destroy(&port_groups);
/* Sync ipsec configuration.
@@ -8899,12 +9337,44 @@ main(int argc, char *argv[])
add_column_noalert(ovnsb_idl_loop.idl,
&sbrec_ha_chassis_group_col_ref_chassis);
+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_igmp_group);
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_address);
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_datapath);
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_chassis);
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_igmp_group_col_ports);
+
+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_ip_multicast);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_datapath);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_enabled);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_querier);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_eth_src);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_ip4_src);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_table_size);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_idle_timeout);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_query_interval);
+ add_column_noalert(ovnsb_idl_loop.idl,
+ &sbrec_ip_multicast_col_query_max_resp);
+
struct ovsdb_idl_index *sbrec_chassis_by_name
= chassis_index_create(ovnsb_idl_loop.idl);
struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name
= ha_chassis_group_index_create(ovnsb_idl_loop.idl);
+ struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp
+ = mcast_group_index_create(ovnsb_idl_loop.idl);
+
+ struct ovsdb_idl_index *sbrec_ip_mcast_by_dp
+ = ip_mcast_index_create(ovnsb_idl_loop.idl);
+
/* Ensure that only a single ovn-northd is active in the deployment by
* acquiring a lock called "ovn_northd" on the southbound database
* and then only performing DB transactions if the lock is held. */
@@ -8920,6 +9390,8 @@ main(int argc, char *argv[])
.ovnsb_idl = ovnsb_idl_loop.idl,
.ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
.sbrec_ha_chassis_grp_by_name = sbrec_ha_chassis_grp_by_name,
+ .sbrec_mcast_group_by_name_dp = sbrec_mcast_group_by_name_dp,
+ .sbrec_ip_mcast_by_dp = sbrec_ip_mcast_by_dp,
};
if (!had_lock && ovsdb_idl_has_lock(ovnsb_idl_loop.idl)) {
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index b028756..57b6edb 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -285,6 +285,60 @@
</column>
</group>
+ <group title="IP Multicast Snooping Options">
+ <p>
+ These options control IP Multicast Snooping configuration of the
+ logical switch. To enable IP Multicast Snooping set
+ <ref column="other_config" key="mcast_snoop"/> to true. To enable IP
+ Multicast Querier set <ref column="other_config" key="mcast_snoop"/>
+ to true. If IP Multicast Querier is enabled
+ <ref column="other_config" key="mcast_eth_src"/> and
+ <ref column="other_config" key="mcast_ip4_src"/> must be set.
+ </p>
+ <column name="other_config" key="mcast_snoop"
+ type='{"type": "boolean"}'>
+ Enables/disables IP Multicast Snooping on the logical switch.
+ </column>
+ <column name="other_config" key="mcast_querier"
+ type='{"type": "boolean"}'>
+ Enables/disables IP Multicast Querier on the logical switch.
+ </column>
+ <column name="other_config" key="mcast_flood_unregistered"
+ type='{"type": "boolean"}'>
+ Determines whether unregistered multicast traffic should be flooded
+ or not. Only applicable if
+ <ref column="other_config" key="mcast_snoop"/> is enabled.
+ </column>
+ <column name="other_config" key="mcast_table_size"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 32766}'>
+ Number of multicast groups to be stored. Default: 2048.
+ </column>
+ <column name="other_config" key="mcast_idle_timeout"
+ type='{"type": "integer", "minInteger": 15, "maxInteger": 3600}'>
+ Configures the IP Multicast Snooping group idle timeout (in seconds).
+ Default: 300 seconds.
+ </column>
+ <column name="other_config" key="mcast_query_interval"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 3600}'>
+ Configures the IP Multicast Querier interval between queries (in
+ seconds). Default:
+ <ref column="other_config" key="mcast_idle_timeout"/> / 2.
+ </column>
+ <column name="other_config" key="mcast_query_max_response"
+ type='{"type": "integer", "minInteger": 1, "maxInteger": 10}'>
+ Configures the value of the "max-response" field in the multicast
+ queries originated by the logical switch. Default: 1 second.
+ </column>
+ <column name="other_config" key="mcast_eth_src">
+ Configures the source Ethernet address for queries originated by the
+ logical switch.
+ </column>
+ <column name="other_config" key="mcast_ip4_src">
+ Configures the source IPv4 address for queries originated by the
+ logical switch.
+ </column>
+ </group>
+
<group title="Common Columns">
<column name="external_ids">
See <em>External IDs</em> at the beginning of this document.
diff --git a/tests/ovn.at b/tests/ovn.at
index fb05f8b..cb380d2 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -14430,3 +14430,273 @@ AT_CHECK([ovn-sbctl get controller_event $uuid seq_num], [0], [dnl
OVN_CLEANUP([hv1], [hv2])
AT_CLEANUP
+
+AT_SETUP([ovn -- IGMP snoop/querier])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Logical network:
+# Two independent logical switches (sw1 and sw2).
+# sw1:
+# - subnet 10.0.0.0/8
+# - 2 ports bound on hv1 (sw1-p11, sw1-p12)
+# - 2 ports bound on hv2 (sw1-p21, sw1-p22)
+# sw2:
+# - subnet 20.0.0.0/8
+# - 1 port bound on hv1 (sw2-p1)
+# - 1 port bound on hv2 (sw2-p2)
+# - IGMP Querier from 20.0.0.254
+
+reset_pcap_file() {
+ local iface=$1
+ local pcap_file=$2
+ ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+ rm -f ${pcap_file}*.pcap
+ ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "$@"
+}
+
+#
+# send_igmp_v3_report INPORT HV ETH_SRC IP_SRC IP_CSUM GROUP REC_TYPE
+# IGMP_CSUM OUTFILE
+#
+# This shell function causes an IGMPv3 report to be received on INPORT of HV.
+# The packet's content has Ethernet destination 01:00:5E:00:00:22 and source
+# ETH_SRC (exactly 12 hex digits). Ethernet type is set to IP.
+# GROUP is the IP multicast group to be joined/to leave (based on REC_TYPE).
+# REC_TYPE == 04: join GROUP
+# REC_TYPE == 03: leave GROUP
+# The packet hexdump is also stored in OUTFILE.
+#
+send_igmp_v3_report() {
+ local inport=$1 hv=$2 eth_src=$3 ip_src=$4 ip_chksum=$5 group=$6
+ local rec_type=$7 igmp_chksum=$8 outfile=$9
+
+ local eth_dst=01005e000016
+ local ip_dst=$(ip_to_hex 224 0 0 22)
+ local ip_ttl=01
+ local ip_ra_opt=94040000
+
+ local igmp_type=2200
+ local num_rec=00000001
+ local aux_dlen=00
+ local num_src=0000
+
+ local eth=${eth_dst}${eth_src}0800
+ local ip=46c0002800004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}${ip_ra_opt}
+ local igmp=${igmp_type}${igmp_chksum}${num_rec}${rec_type}${aux_dlen}${num_src}${group}
+ local packet=${eth}${ip}${igmp}
+
+ echo ${packet} >> ${outfile}
+ as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
+}
+
+#
+# store_igmp_v3_query ETH_SRC IP_SRC IP_CSUM OUTFILE
+#
+# This shell function builds an IGMPv3 general query from ETH_SRC and IP_SRC
+# and stores the hexdump of the packet in OUTFILE.
+#
+store_igmp_v3_query() {
+ local eth_src=$1 ip_src=$2 ip_chksum=$3 outfile=$4
+
+ local eth_dst=01005e000001
+ local ip_dst=$(ip_to_hex 224 0 0 1)
+ local ip_ttl=01
+ local igmp_type=11
+ local max_resp=0a
+ local igmp_chksum=eeeb
+ local addr=00000000
+
+ local eth=${eth_dst}${eth_src}0800
+ local ip=4500002000004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}
+ local igmp=${igmp_type}${max_resp}${igmp_chksum}${addr}000a0000
+ local packet=${eth}${ip}${igmp}
+
+ echo ${packet} >> ${outfile}
+}
+
+#
+# send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN
+# IP_PROTO DATA OUTFILE
+#
+# This shell function causes an IP multicast packet to be received on INPORT
+# of HV.
+# The hexdump of the packet is stored in OUTFILE.
+#
+send_ip_multicast_pkt() {
+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ip_src=$5 ip_dst=$6
+ local ip_len=$7 ip_chksum=$8 proto=$9 data=${10} outfile=${11}
+
+ local ip_ttl=20
+
+ local eth=${eth_dst}${eth_src}0800
+ local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst}
+ local packet=${eth}${ip}${data}
+
+ as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
+ echo ${packet} >> ${outfile}
+}
+
+ovn-nbctl ls-add sw1
+ovn-nbctl ls-add sw2
+
+ovn-nbctl lsp-add sw1 sw1-p11
+ovn-nbctl lsp-add sw1 sw1-p12
+ovn-nbctl lsp-add sw1 sw1-p21
+ovn-nbctl lsp-add sw1 sw1-p22
+ovn-nbctl lsp-add sw2 sw2-p1
+ovn-nbctl lsp-add sw2 sw2-p2
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p12 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=sw2-p1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+ options:rxq_pcap=hv1/vif3-rx.pcap \
+ ofport-request=1
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p21 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+ options:rxq_pcap=hv2/vif1-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw1-p22 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+ options:rxq_pcap=hv2/vif2-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=sw2-p2 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+ options:rxq_pcap=hv2/vif3-rx.pcap \
+ ofport-request=1
+
+OVN_POPULATE_ARP
+
+# Enable IGMP snooping on sw1.
+ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false"
+ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true"
+
+# No IGMP query should be generated by sw1 (mcast_querier="false").
+truncate -s 0 expected
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected])
+
+ovn-nbctl --wait=hv sync
+
+# Inject IGMP Join for 239.0.1.68 on sw1-p11.
+send_igmp_v3_report hv1-vif1 hv1 \
+ 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+ $(ip_to_hex 239 0 1 68) 04 e9b9 \
+ /dev/null
+# Inject IGMP Join for 239.0.1.68 on sw1-p21.
+send_igmp_v3_report hv2-vif1 hv2 000000000002 $(ip_to_hex 10 0 0 2) f9f9 \
+ $(ip_to_hex 239 0 1 68) 04 e9b9 \
+ /dev/null
+
+# Check that the IGMP Group is learned on both hv.
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ test "${total_entries}" = "2"
+])
+
+# Send traffic and make sure it gets forwarded only on the two ports that
+# joined.
+truncate -s 0 expected
+truncate -s 0 expected_empty
+send_ip_multicast_pkt hv1-vif2 hv1 \
+ 000000000001 01005e000144 \
+ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \
+ e518e518000a3b3a0000 \
+ expected
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
+
+# Inject IGMP Leave for 239.0.1.68 on sw1-p11.
+send_igmp_v3_report hv1-vif1 hv1 \
+ 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+ $(ip_to_hex 239 0 1 68) 03 eab9 \
+ /dev/null
+
+# Check IGMP_Group table on both HV.
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ test "${total_entries}" = "1"
+])
+
+# Send traffic traffic and make sure it gets forwarded only on the port that
+# joined.
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+truncate -s 0 expected
+truncate -s 0 expected_empty
+send_ip_multicast_pkt hv1-vif2 hv1 \
+ 000000000001 01005e000144 \
+ $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e ca70 11 \
+ e518e518000a3b3a0000 \
+ expected
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
+
+# Flush IGMP groups.
+ovn-sbctl ip-multicast-flush sw1
+ovn-nbctl --wait=hv -t 3 sync
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ test "${total_entries}" = "0"
+])
+
+# Enable IGMP snooping and querier on sw2 and set query interval to minimum.
+ovn-nbctl set Logical_Switch sw2 \
+ other_config:mcast_snoop="true" \
+ other_config:mcast_querier="true" \
+ other_config:mcast_query_interval=1 \
+ other_config:mcast_eth_src="00:00:00:00:02:fe" \
+ other_config:mcast_ip4_src="20.0.0.254"
+
+# Wait for 1 query interval (1 sec) and check that two queries are generated.
+truncate -s 0 expected
+store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
+store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
+
+sleep 1
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index b7e2d77..10fbd26 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -1542,3 +1542,122 @@ as
OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
/connection dropped.*/d"])
AT_CLEANUP
+
+AT_SETUP([ovn -- 2 LSs IGMP])
+AT_KEYWORDS([ovnigmp])
+
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+# Logical network:
+# Two independent logical switches (sw1 and sw2).
+# sw1:
+# - subnet 10.0.0.0/8
+# - 2 ports (sw1-p1 - sw1-p2)
+# sw2:
+# - subnet 20.0.0.0/8
+# - 2 port (sw2-p1 - sw2-p2)
+# - IGMP Querier from 20.0.0.254
+
+ovn-nbctl ls-add sw1
+ovn-nbctl ls-add sw2
+
+for i in `seq 1 2`
+do
+ ADD_NAMESPACES(sw1-p$i)
+ ADD_VETH(sw1-p$i, sw1-p$i, br-int, "10.0.0.$i/24", "00:00:00:00:01:0$i", \
+ "10.0.0.254")
+ ovn-nbctl lsp-add sw1 sw1-p$i \
+ -- lsp-set-addresses sw1-p$i "00:00:00:00:01:0$i 10.0.0.$i"
+done
+
+for i in `seq 1 2`
+do
+ ADD_NAMESPACES(sw2-p$i)
+ ADD_VETH(sw2-p$i, sw2-p$i, br-int, "20.0.0.$i/24", "00:00:00:00:02:0$i", \
+ "20.0.0.254")
+ ovn-nbctl lsp-add sw2 sw2-p$i \
+ -- lsp-set-addresses sw2-p$i "00:00:00:00:02:0$i 20.0.0.$i"
+done
+
+# Enable IGMP snooping on sw1.
+ovn-nbctl set Logical_Switch sw1 other_config:mcast_querier="false"
+ovn-nbctl set Logical_Switch sw1 other_config:mcast_snoop="true"
+
+# Inject IGMP Join for 239.0.1.68 on sw1-p1.
+NS_CHECK_EXEC([sw1-p1], [ip addr add dev sw1-p1 239.0.1.68/32 autojoin], [0])
+
+# Inject IGMP Join for 239.0.1.68 on sw1-p2
+NS_CHECK_EXEC([sw1-p2], [ip addr add dev sw1-p2 239.0.1.68/32 autojoin], [0])
+
+# Check that the IGMP Group is learned.
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w`
+ test "${total_entries}" = "1"
+ test "${ports}" = "2"
+])
+
+# Inject IGMP Leave for 239.0.1.68 on sw1-p2.
+NS_CHECK_EXEC([sw1-p2], [ip addr del dev sw1-p2 239.0.1.68/32], [0])
+
+# Check that only one port is left in the group.
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ ports=`ovn-sbctl find IGMP_Group | grep ports | cut -f 2 -d ":" | wc -w`
+ test "${total_entries}" = "1"
+ test "${ports}" = "1"
+])
+
+# Flush IGMP groups.
+ovn-sbctl ip-multicast-flush sw1
+ovn-nbctl --wait=hv -t 3 sync
+OVS_WAIT_UNTIL([
+ total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+ test "${total_entries}" = "0"
+])
+
+# Enable IGMP snooping and querier on sw2 and set query interval to minimum.
+ovn-nbctl set Logical_Switch sw2 \
+ other_config:mcast_snoop="true" \
+ other_config:mcast_querier="true" \
+ other_config:mcast_query_interval=1 \
+ other_config:mcast_eth_src="00:00:00:00:02:fe" \
+ other_config:mcast_ip4_src="20.0.0.254"
+
+# Check that queries are generated.
+NS_CHECK_EXEC([sw2-p1], [tcpdump -n -c 2 -i sw2-p1 igmp > sw2-p1.pcap &])
+
+OVS_WAIT_UNTIL([
+ total_queries=`cat sw2-p1.pcap | grep "igmp query" | wc -l`
+ test "${total_queries}" = "2"
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
More information about the dev
mailing list