On Thu, Jul 7, 2022 at 12:24 PM Lucas Alvares Gomes <lucasago...@gmail.com> wrote: > > Hi, > > I tested this series of patches with OpenStack upstream [0] since we > have some automated tests for IGMP and it seems good: > > test_multicast_between_vms_on_same_network[id-113486fc-24c9-4be4-8361-03b1c9892867] > pass (link: > https://98794ab76263ee253bc7-6b6bf6aa2495d781bdba3c8c61916451.ssl.cf1.rackcdn.com/848934/2/check/neutron-tempest-plugin-ovn/b3191bf/testr_results.html, > this will expire at some point) > > What I did was to change ML2/OVN to set mcast_flood to False on the > LSPs, apply the series of patches in OVN and then run the tests. > > Tested-By: > https://review.opendev.org/c/openstack/neutron-tempest-plugin/+/848934/ >
Tested-By: Lucas Alvares Gomes <lucasago...@gmail.com> > [0] https://review.opendev.org/q/topic:test-igmp > > Cheers, > Lucas > > On Tue, Jul 5, 2022 at 1:13 PM Dumitru Ceara <dce...@redhat.com> wrote: > > > > RFC 4541 "Considerations for Internet Group Management Protocol (IGMP) > > and Multicast Listener Discovery (MLD) Snooping Switches" [0] states: > > > > For IGMP packets: > > 2.1.1. IGMP Forwarding Rules > > 1) A snooping switch should forward IGMP Membership Reports only to > > those ports where multicast routers are attached. > > > > Alternatively stated: a snooping switch should not forward IGMP > > Membership Reports to ports on which only hosts are attached. An > > administrative control may be provided to override this > > restriction, allowing the report messages to be flooded to other > > ports. > > > > This is the main IGMP snooping functionality for the control path. > > > > Sending membership reports to other hosts can result, for IGMPv1 > > and IGMPv2, in unintentionally preventing a host from joining a > > specific multicast group. > > > > When an IGMPv1 or IGMPv2 host receives a membership report for a > > group address that it intends to join, the host will suppress its > > own membership report for the same group. This join or message > > suppression is a requirement for IGMPv1 and IGMPv2 hosts. > > However, if a switch does not receive a membership report from the > > host it will not forward multicast data to it. > > > > In OVN this translates to forwarding reports only on: > > a. ports where mrouters have been learned (IGMP queries were received on > > those ports) > > b. ports connecting a multicast enabled logical switch to a multicast > > relay enabled logical router (OVN mrouter) > > c. ports configured with mcast_flood_reports=true > > > > 2.1.2. Data Forwarding Rules > > > > 1) Packets with a destination IP address outside 224.0.0.X which are > > not IGMP should be forwarded according to group-based port > > membership tables and must also be forwarded on router ports. > > > > In OVN this translates to forwarding traffic for a group G to: > > a. all ports where G was learned > > b. all ports where an (external or OVN) mrouter was learned. > > c. all ports configured with mcast_flood=true > > > > IGMP queries are already snooped by ovn-controller. Just propagate the > > information about where mrouters are to the Southbound DB through means > > of a custom IGMP_Group (name="mrouters"). > > > > Snooped queries are then re-injected in the OVS pipeline with outport > > set to MC_FLOOD_L2 (only flood them in the L2 domain). > > > > Snooped reports are re-injected in the OVS pipeline with outport set to > > MC_MROUTER_FLOOD (flood them to snooped mrouters and OVN mrouters). > > > > The same behavior applies to IPv6 too (MLD). > > > > [0] https://datatracker.ietf.org/doc/html/rfc4541 > > > > Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1933990 > > Reported-at: https://github.com/ovn-org/ovn/issues/126 > > Signed-off-by: Dumitru Ceara <dce...@redhat.com> > > --- > > controller/ip-mcast.c | 129 ++++++++++++++++++++++++++++++++----- > > controller/ip-mcast.h | 14 ++++ > > controller/pinctrl.c | 129 +++++++++++++++++++++++++++++++++---- > > lib/ip-mcast-index.h | 5 + > > lib/mcast-group-index.h | 4 - > > northd/northd.c | 54 +++++++++++---- > > northd/ovn-northd.8.xml | 6 -- > > tests/ovn-macros.at | 25 +++++++ > > tests/ovn.at | 164 > > ++++++++++++++++++++++++++++++++++++++++++++++- > > 9 files changed, 472 insertions(+), 58 deletions(-) > > > > diff --git a/controller/ip-mcast.c b/controller/ip-mcast.c > > index 9b0b4465a..a870fb29e 100644 > > --- a/controller/ip-mcast.c > > +++ b/controller/ip-mcast.c > > @@ -16,6 +16,7 @@ > > #include <config.h> > > > > #include "ip-mcast.h" > > +#include "ip-mcast-index.h" > > #include "lport.h" > > #include "lib/ovn-sb-idl.h" > > > > @@ -27,6 +28,18 @@ struct igmp_group_port { > > const struct sbrec_port_binding *port; > > }; > > > > +static const struct sbrec_igmp_group * > > +igmp_group_lookup_(struct ovsdb_idl_index *igmp_groups, > > + const char *addr_str, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis); > > + > > +static struct sbrec_igmp_group * > > +igmp_group_create_(struct ovsdb_idl_txn *idl_txn, > > + const char *addr_str, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis); > > + > > struct ovsdb_idl_index * > > igmp_group_index_create(struct ovsdb_idl *idl) > > { > > @@ -54,17 +67,16 @@ igmp_group_lookup(struct ovsdb_idl_index *igmp_groups, > > return NULL; > > } > > > > - struct sbrec_igmp_group *target = > > - sbrec_igmp_group_index_init_row(igmp_groups); > > - > > - sbrec_igmp_group_index_set_address(target, addr_str); > > - sbrec_igmp_group_index_set_datapath(target, datapath); > > - sbrec_igmp_group_index_set_chassis(target, chassis); > > + return igmp_group_lookup_(igmp_groups, addr_str, datapath, chassis); > > +} > > > > - const struct sbrec_igmp_group *g = > > - sbrec_igmp_group_index_find(igmp_groups, target); > > - sbrec_igmp_group_index_destroy_row(target); > > - return g; > > +const struct sbrec_igmp_group * > > +igmp_mrouter_lookup(struct ovsdb_idl_index *igmp_groups, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis) > > +{ > > + return igmp_group_lookup_(igmp_groups, OVN_IGMP_GROUP_MROUTERS, > > + datapath, chassis); > > } > > > > /* Creates and returns a new IGMP group based on an IPv4 (mapped in IPv6) > > or > > @@ -82,13 +94,16 @@ igmp_group_create(struct ovsdb_idl_txn *idl_txn, > > return NULL; > > } > > > > - struct sbrec_igmp_group *g = sbrec_igmp_group_insert(idl_txn); > > - > > - sbrec_igmp_group_set_address(g, addr_str); > > - sbrec_igmp_group_set_datapath(g, datapath); > > - sbrec_igmp_group_set_chassis(g, chassis); > > + return igmp_group_create_(idl_txn, addr_str, datapath, chassis); > > +} > > > > - return g; > > +struct sbrec_igmp_group * > > +igmp_mrouter_create(struct ovsdb_idl_txn *idl_txn, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis) > > +{ > > + return igmp_group_create_(idl_txn, OVN_IGMP_GROUP_MROUTERS, datapath, > > + chassis); > > } > > > > void > > @@ -140,6 +155,54 @@ igmp_group_update_ports(const struct sbrec_igmp_group > > *g, > > hmap_destroy(&old_ports); > > } > > > > +void > > +igmp_mrouter_update_ports(const struct sbrec_igmp_group *g, > > + struct ovsdb_idl_index *datapaths, > > + struct ovsdb_idl_index *port_bindings, > > + const struct mcast_snooping *ms) > > + OVS_REQ_RDLOCK(ms->rwlock) > > +{ > > + struct igmp_group_port *old_ports_storage = > > + (g->n_ports ? xmalloc(g->n_ports * sizeof *old_ports_storage) : > > NULL); > > + > > + struct hmap old_ports = HMAP_INITIALIZER(&old_ports); > > + > > + for (size_t i = 0; i < g->n_ports; i++) { > > + struct igmp_group_port *old_port = &old_ports_storage[i]; > > + > > + old_port->port = g->ports[i]; > > + hmap_insert(&old_ports, &old_port->hmap_node, > > + old_port->port->tunnel_key); > > + } > > + > > + struct mcast_mrouter_bundle *bundle; > > + uint64_t dp_key = g->datapath->tunnel_key; > > + > > + LIST_FOR_EACH (bundle, mrouter_node, &ms->mrouter_lru) { > > + uint32_t port_key = (uintptr_t)bundle->port; > > + const struct sbrec_port_binding *sbrec_port = > > + lport_lookup_by_key(datapaths, port_bindings, dp_key, > > port_key); > > + if (!sbrec_port) { > > + continue; > > + } > > + > > + struct hmap_node *node = hmap_first_with_hash(&old_ports, > > port_key); > > + if (!node) { > > + sbrec_igmp_group_update_ports_addvalue(g, sbrec_port); > > + } else { > > + hmap_remove(&old_ports, node); > > + } > > + } > > + > > + struct igmp_group_port *igmp_port; > > + HMAP_FOR_EACH_POP (igmp_port, hmap_node, &old_ports) { > > + sbrec_igmp_group_update_ports_delvalue(g, igmp_port->port); > > + } > > + > > + free(old_ports_storage); > > + hmap_destroy(&old_ports); > > +} > > + > > void > > igmp_group_delete(const struct sbrec_igmp_group *g) > > { > > @@ -162,3 +225,37 @@ igmp_group_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, > > > > return true; > > } > > + > > +static const struct sbrec_igmp_group * > > +igmp_group_lookup_(struct ovsdb_idl_index *igmp_groups, > > + const char *addr_str, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis) > > +{ > > + struct sbrec_igmp_group *target = > > + sbrec_igmp_group_index_init_row(igmp_groups); > > + > > + sbrec_igmp_group_index_set_address(target, addr_str); > > + sbrec_igmp_group_index_set_datapath(target, datapath); > > + sbrec_igmp_group_index_set_chassis(target, chassis); > > + > > + const struct sbrec_igmp_group *g = > > + sbrec_igmp_group_index_find(igmp_groups, target); > > + sbrec_igmp_group_index_destroy_row(target); > > + return g; > > +} > > + > > +static struct sbrec_igmp_group * > > +igmp_group_create_(struct ovsdb_idl_txn *idl_txn, > > + const char *addr_str, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis) > > +{ > > + struct sbrec_igmp_group *g = sbrec_igmp_group_insert(idl_txn); > > + > > + sbrec_igmp_group_set_address(g, addr_str); > > + sbrec_igmp_group_set_datapath(g, datapath); > > + sbrec_igmp_group_set_chassis(g, chassis); > > + > > + return g; > > +} > > diff --git a/controller/ip-mcast.h b/controller/ip-mcast.h > > index b3447d4c7..326f39db1 100644 > > --- a/controller/ip-mcast.h > > +++ b/controller/ip-mcast.h > > @@ -30,12 +30,20 @@ const struct sbrec_igmp_group *igmp_group_lookup( > > const struct in6_addr *address, > > const struct sbrec_datapath_binding *datapath, > > const struct sbrec_chassis *chassis); > > +const struct sbrec_igmp_group *igmp_mrouter_lookup( > > + struct ovsdb_idl_index *igmp_groups, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis); > > > > struct sbrec_igmp_group *igmp_group_create( > > struct ovsdb_idl_txn *idl_txn, > > const struct in6_addr *address, > > const struct sbrec_datapath_binding *datapath, > > const struct sbrec_chassis *chassis); > > +struct sbrec_igmp_group *igmp_mrouter_create( > > + struct ovsdb_idl_txn *idl_txn, > > + const struct sbrec_datapath_binding *datapath, > > + const struct sbrec_chassis *chassis); > > > > void igmp_group_update_ports(const struct sbrec_igmp_group *g, > > struct ovsdb_idl_index *datapaths, > > @@ -43,6 +51,12 @@ void igmp_group_update_ports(const struct > > sbrec_igmp_group *g, > > const struct mcast_snooping *ms, > > const struct mcast_group *mc_group) > > OVS_REQ_RDLOCK(ms->rwlock); > > +void > > +igmp_mrouter_update_ports(const struct sbrec_igmp_group *g, > > + struct ovsdb_idl_index *datapaths, > > + struct ovsdb_idl_index *port_bindings, > > + const struct mcast_snooping *ms) > > + OVS_REQ_RDLOCK(ms->rwlock); > > > > void igmp_group_delete(const struct sbrec_igmp_group *g); > > > > diff --git a/controller/pinctrl.c b/controller/pinctrl.c > > index f954362b7..4e1478102 100644 > > --- a/controller/pinctrl.c > > +++ b/controller/pinctrl.c > > @@ -624,6 +624,39 @@ set_actions_and_enqueue_msg(struct rconn *swconn, > > ofpbuf_uninit(&ofpacts); > > } > > > > +/* Forwards a packet to 'out_port_key' even if that's on a remote > > + * hypervisor, i.e., the packet is re-injected in table > > OFTABLE_REMOTE_OUTPUT. > > + */ > > +static void > > +pinctrl_forward_pkt(struct rconn *swconn, int64_t dp_key, > > + int64_t in_port_key, int64_t out_port_key, > > + const struct dp_packet *pkt) > > +{ > > + /* Reinject the packet and flood it to all registered mrouters. */ > > + uint64_t ofpacts_stub[4096 / 8]; > > + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > > + enum ofp_version version = rconn_get_version(swconn); > > + put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); > > + put_load(in_port_key, MFF_LOG_INPORT, 0, 32, &ofpacts); > > + put_load(out_port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); > > + > > + struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); > > + resubmit->in_port = OFPP_CONTROLLER; > > + resubmit->table_id = OFTABLE_REMOTE_OUTPUT; > > + > > + struct ofputil_packet_out po = { > > + .packet = dp_packet_data(pkt), > > + .packet_len = dp_packet_size(pkt), > > + .buffer_id = UINT32_MAX, > > + .ofpacts = ofpacts.data, > > + .ofpacts_len = ofpacts.size, > > + }; > > + match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); > > + enum ofputil_protocol proto = > > ofputil_protocol_from_ofp_version(version); > > + queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); > > + ofpbuf_uninit(&ofpacts); > > +} > > + > > static struct shash ipv6_prefixd; > > > > enum { > > @@ -5139,6 +5172,7 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, > > } > > } > > > > + const struct sbrec_igmp_group *sbrec_ip_mrouter; > > const struct sbrec_igmp_group *sbrec_igmp; > > > > /* Then flush any IGMP_Group entries that are not needed anymore: > > @@ -5174,7 +5208,9 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, > > continue; > > } > > > > - if (ip_parse(sbrec_igmp->address, &group_v4_addr)) { > > + if (!strcmp(sbrec_igmp->address, OVN_IGMP_GROUP_MROUTERS)) { > > + continue; > > + } else if (ip_parse(sbrec_igmp->address, &group_v4_addr)) { > > group_addr = in6_addr_mapped_ipv4(group_v4_addr); > > } else if (!ipv6_parse(sbrec_igmp->address, &group_addr)) { > > continue; > > @@ -5213,6 +5249,8 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, > > struct mcast_group *mc_group; > > > > ovs_rwlock_rdlock(&ip_ms->ms->rwlock); > > + > > + /* Groups. */ > > LIST_FOR_EACH (mc_group, group_node, &ip_ms->ms->group_lru) { > > if (ovs_list_is_empty(&mc_group->bundle_lru)) { > > continue; > > @@ -5228,6 +5266,20 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, > > sbrec_port_binding_by_key, ip_ms->ms, > > mc_group); > > } > > + > > + /* Mrouters. */ > > + sbrec_ip_mrouter = igmp_mrouter_lookup(sbrec_igmp_groups, > > + local_dp->datapath, > > + chassis); > > + if (!sbrec_ip_mrouter) { > > + sbrec_ip_mrouter = igmp_mrouter_create(ovnsb_idl_txn, > > + local_dp->datapath, > > + chassis); > > + } > > + igmp_mrouter_update_ports(sbrec_ip_mrouter, > > + sbrec_datapath_binding_by_key, > > + sbrec_port_binding_by_key, ip_ms->ms); > > + > > ovs_rwlock_unlock(&ip_ms->ms->rwlock); > > } > > > > @@ -5236,12 +5288,35 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn, > > } > > } > > > > +/* Reinject the packet and flood it to all registered mrouters (also those > > + * who are not local to this chassis). */ > > +static void > > +ip_mcast_forward_report(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, > > + uint32_t orig_in_port_key, > > + const struct dp_packet *report) > > +{ > > + pinctrl_forward_pkt(swconn, ip_ms->dp_key, orig_in_port_key, > > + OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY, report); > > +} > > + > > + > > +static void > > +ip_mcast_forward_query(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, > > + uint32_t orig_in_port_key, > > + const struct dp_packet *query) > > +{ > > + pinctrl_forward_pkt(swconn, ip_ms->dp_key, orig_in_port_key, > > + OVN_MCAST_FLOOD_L2_TUNNEL_KEY, query); > > +} > > + > > static bool > > -pinctrl_ip_mcast_handle_igmp(struct ip_mcast_snoop *ip_ms, > > +pinctrl_ip_mcast_handle_igmp(struct rconn *swconn, > > + struct ip_mcast_snoop *ip_ms, > > const struct flow *ip_flow, > > struct dp_packet *pkt_in, > > - void *port_key_data) > > + uint32_t in_port_key) > > { > > + void *port_key_data = (void *)(uintptr_t)in_port_key; > > const struct igmp_header *igmp; > > size_t offset; > > > > @@ -5255,6 +5330,8 @@ pinctrl_ip_mcast_handle_igmp(struct ip_mcast_snoop > > *ip_ms, > > > > ovs_be32 ip4 = ip_flow->igmp_group_ip4; > > bool group_change = false; > > + bool report = false; > > + bool query = false; > > > > /* Only default VLAN is supported for now. */ > > ovs_rwlock_wrlock(&ip_ms->ms->rwlock); > > @@ -5264,36 +5341,48 @@ pinctrl_ip_mcast_handle_igmp(struct ip_mcast_snoop > > *ip_ms, > > group_change = > > mcast_snooping_add_group4(ip_ms->ms, ip4, IP_MCAST_VLAN, > > port_key_data); > > + report = true; > > break; > > case IGMP_HOST_LEAVE_MESSAGE: > > group_change = > > mcast_snooping_leave_group4(ip_ms->ms, ip4, IP_MCAST_VLAN, > > port_key_data); > > + report = true; > > break; > > case IGMP_HOST_MEMBERSHIP_QUERY: > > - /* Shouldn't be receiving any of these since we are the multicast > > - * router. Store them for now. > > - */ > > group_change = > > mcast_snooping_add_mrouter(ip_ms->ms, IP_MCAST_VLAN, > > port_key_data); > > + query = true; > > break; > > case IGMPV3_HOST_MEMBERSHIP_REPORT: > > group_change = > > mcast_snooping_add_report(ip_ms->ms, pkt_in, IP_MCAST_VLAN, > > port_key_data); > > + report = true; > > break; > > } > > ovs_rwlock_unlock(&ip_ms->ms->rwlock); > > + > > + /* Forward reports to all registered mrouters and flood queries to > > + * the whole L2 domain. > > + */ > > + if (report) { > > + ip_mcast_forward_report(swconn, ip_ms, in_port_key, pkt_in); > > + } else if (query) { > > + ip_mcast_forward_query(swconn, ip_ms, in_port_key, pkt_in); > > + } > > return group_change; > > } > > > > static bool > > -pinctrl_ip_mcast_handle_mld(struct ip_mcast_snoop *ip_ms, > > +pinctrl_ip_mcast_handle_mld(struct rconn *swconn, > > + struct ip_mcast_snoop *ip_ms, > > const struct flow *ip_flow, > > struct dp_packet *pkt_in, > > - void *port_key_data) > > + uint32_t in_port_key) > > { > > + void *port_key_data = (void *)(uintptr_t)in_port_key; > > const struct mld_header *mld; > > size_t offset; > > > > @@ -5310,6 +5399,8 @@ pinctrl_ip_mcast_handle_mld(struct ip_mcast_snoop > > *ip_ms, > > } > > > > bool group_change = false; > > + bool report = false; > > + bool query = false; > > > > /* Only default VLAN is supported for now. */ > > ovs_rwlock_wrlock(&ip_ms->ms->rwlock); > > @@ -5323,6 +5414,7 @@ pinctrl_ip_mcast_handle_mld(struct ip_mcast_snoop > > *ip_ms, > > mcast_snooping_add_mrouter(ip_ms->ms, IP_MCAST_VLAN, > > port_key_data); > > } > > + query = true; > > break; > > case MLD_REPORT: > > case MLD_DONE: > > @@ -5330,14 +5422,24 @@ pinctrl_ip_mcast_handle_mld(struct ip_mcast_snoop > > *ip_ms, > > group_change = > > mcast_snooping_add_mld(ip_ms->ms, pkt_in, IP_MCAST_VLAN, > > port_key_data); > > + report = true; > > break; > > } > > ovs_rwlock_unlock(&ip_ms->ms->rwlock); > > + > > + /* Forward reports to all registered mrouters and flood queries to > > + * the whole L2 domain. > > + */ > > + if (report) { > > + ip_mcast_forward_report(swconn, ip_ms, in_port_key, pkt_in); > > + } else if (query) { > > + ip_mcast_forward_query(swconn, ip_ms, in_port_key, pkt_in); > > + } > > return group_change; > > } > > > > static void > > -pinctrl_ip_mcast_handle(struct rconn *swconn OVS_UNUSED, > > +pinctrl_ip_mcast_handle(struct rconn *swconn, > > const struct flow *ip_flow, > > struct dp_packet *pkt_in, > > const struct match *md, > > @@ -5366,18 +5468,17 @@ pinctrl_ip_mcast_handle(struct rconn *swconn > > OVS_UNUSED, > > } > > > > uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0]; > > - void *port_key_data = (void *)(uintptr_t)port_key; > > > > switch (dl_type) { > > case ETH_TYPE_IP: > > - if (pinctrl_ip_mcast_handle_igmp(ip_ms, ip_flow, pkt_in, > > - port_key_data)) { > > + if (pinctrl_ip_mcast_handle_igmp(swconn, ip_ms, ip_flow, pkt_in, > > + port_key)) { > > notify_pinctrl_main(); > > } > > break; > > case ETH_TYPE_IPV6: > > - if (pinctrl_ip_mcast_handle_mld(ip_ms, ip_flow, pkt_in, > > - port_key_data)) { > > + if (pinctrl_ip_mcast_handle_mld(swconn, ip_ms, ip_flow, pkt_in, > > + port_key)) { > > notify_pinctrl_main(); > > } > > break; > > diff --git a/lib/ip-mcast-index.h b/lib/ip-mcast-index.h > > index 3ac65798b..87a7bf28d 100644 > > --- a/lib/ip-mcast-index.h > > +++ b/lib/ip-mcast-index.h > > @@ -20,6 +20,11 @@ struct ovsdb_idl; > > > > struct sbrec_datapath_binding; > > > > +/* Fixed group name to denote an IGMP_Group that actually stores > > + * a list of learned mrouters. > > + */ > > +#define OVN_IGMP_GROUP_MROUTERS "mrouters" > > + > > #define OVN_MCAST_MIN_IDLE_TIMEOUT_S 15 > > #define OVN_MCAST_MAX_IDLE_TIMEOUT_S 3600 > > #define OVN_MCAST_DEFAULT_IDLE_TIMEOUT_S 300 > > diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h > > index e8d82b126..9664a94dd 100644 > > --- a/lib/mcast-group-index.h > > +++ b/lib/mcast-group-index.h > > @@ -32,10 +32,6 @@ enum ovn_mcast_tunnel_keys { > > OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY, /* For L3 multicast traffic that > > must > > * be relayed (multicast routed). > > */ > > - OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY, /* For multicast reports that > > need to > > - * be forwarded statically towards > > - * mrouters. > > - */ > > OVN_MCAST_STATIC_TUNNEL_KEY, /* For switches: > > * - for L3 multicast traffic that > > * needs to be forwarded > > diff --git a/northd/northd.c b/northd/northd.c > > index 964af992f..690a86104 100644 > > --- a/northd/northd.c > > +++ b/northd/northd.c > > @@ -4506,10 +4506,6 @@ static const struct multicast_group mc_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 }; > > @@ -4803,6 +4799,22 @@ ovn_igmp_group_allocate_id(struct ovn_igmp_group > > *igmp_group) > > return true; > > } > > > > +static void > > +ovn_igmp_mrouter_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, > > + &mc_mrouter_flood, entry->ports, > > + entry->n_ports); > > + > > + ovn_igmp_group_destroy_entry(entry); > > + free(entry); > > + } > > +} > > + > > static void > > ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group, > > struct hmap *mcast_groups) > > @@ -8287,13 +8299,6 @@ build_lswitch_destination_lookup_bmcast(struct > > ovn_datapath *od, > > > > if (mcast_sw_info->enabled) { > > ds_clear(actions); > > - if (mcast_sw_info->flood_reports) { > > - ds_put_cstr(actions, > > - "clone { " > > - "outport = \""MC_MROUTER_STATIC"\"; " > > - "output; " > > - "};"); > > - } > > ds_put_cstr(actions, "igmp;"); > > /* Punt IGMP traffic to controller. */ > > ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100, > > @@ -15091,10 +15096,11 @@ build_mcast_groups(struct lflow_input *input_data, > > } > > > > /* If this port is configured to always flood multicast reports > > - * add it to the MC_MROUTER_STATIC group. > > + * add it to the MC_MROUTER_FLOOD group (all reports must be > > + * flooded to statically configured or learned mrouters). > > */ > > if (op->mcast_info.flood_reports) { > > - ovn_multicast_add(mcast_groups, &mc_mrouter_static, op); > > + ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op); > > op->od->mcast_info.sw.flood_reports = true; > > } > > > > @@ -15130,7 +15136,10 @@ build_mcast_groups(struct lflow_input *input_data, > > } > > > > struct in6_addr group_address; > > - if (!ovn_igmp_group_get_address(sb_igmp, &group_address)) { > > + if (!strcmp(sb_igmp->address, OVN_IGMP_GROUP_MROUTERS)) { > > + /* Use all-zeros IP to denote a group corresponding to > > mrouters. */ > > + memset(&group_address, 0, sizeof group_address); > > + } else if (!ovn_igmp_group_get_address(sb_igmp, &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->address); > > @@ -15188,6 +15197,12 @@ build_mcast_groups(struct lflow_input *input_data, > > LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { > > struct in6_addr *address = &igmp_group->address; > > > > + /* Skip mrouter entries. */ > > + if (!strcmp(igmp_group->mcgroup.name, > > + OVN_IGMP_GROUP_MROUTERS)) { > > + continue; > > + } > > + > > /* For IPv6 only relay routable multicast groups > > * (RFC 4291 2.7). > > */ > > @@ -15215,10 +15230,21 @@ build_mcast_groups(struct lflow_input *input_data, > > > > /* Walk the aggregated IGMP groups and allocate IDs for new entries. > > * Then store the ports in the associated multicast group. > > + * Mrouter entries are also stored as IGMP groups, deal with those > > + * explicitly. > > */ > > struct ovn_igmp_group *igmp_group; > > HMAP_FOR_EACH_SAFE (igmp_group, hmap_node, igmp_groups) { > > > > + /* If this is a mrouter entry just aggregate the mrouter ports > > + * into the MC_MROUTER mcast_group and destroy the igmp_group; > > + * no more processing needed. */ > > + if (!strcmp(igmp_group->mcgroup.name, OVN_IGMP_GROUP_MROUTERS)) { > > + ovn_igmp_mrouter_aggregate_ports(igmp_group, mcast_groups); > > + ovn_igmp_group_destroy(igmp_groups, igmp_group); > > + continue; > > + } > > + > > if (!ovn_igmp_group_allocate_id(igmp_group)) { > > /* If we ran out of keys just destroy the entry. */ > > ovn_igmp_group_destroy(igmp_groups, igmp_group); > > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml > > index a87df24bd..3d1e7357d 100644 > > --- a/northd/ovn-northd.8.xml > > +++ b/northd/ovn-northd.8.xml > > @@ -1662,11 +1662,7 @@ output; > > <li> > > A priority-100 flow that punts all IGMP/MLD packets to > > <code>ovn-controller</code> if multicast snooping is enabled on the > > - logical switch. The flow also forwards the IGMP/MLD packets to the > > - <code>MC_MROUTER_STATIC</code> multicast group, which > > - <code>ovn-northd</code> populates with all the logical ports that > > - have <ref column="options" table="Logical_Switch_Port"/> > > - <code>:mcast_flood_reports='true'</code>. > > + logical switch. > > </li> > > > > <li> > > diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at > > index 77e89f6b4..bc7f53fd9 100644 > > --- a/tests/ovn-macros.at > > +++ b/tests/ovn-macros.at > > @@ -624,6 +624,31 @@ store_igmp_v3_query() { > > echo ${packet} >> ${outfile} > > } > > > > +# send_igmp_v3_query INPORT HV ETH_SRC IP_SRC IP_CSUM OUTFILE > > +# > > +# This shell function builds and sends an IGMPv3 general query from > > +# ETH_SRC and IP_SRC and stores the hexdump of the packet in OUTFILE. > > +# > > +send_igmp_v3_query() { > > + local inport=$1 hv=$2 eth_src=$3 ip_src=$4 ip_chksum=$5 outfile=$6 > > + > > + 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} > > + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} > > +} > > + > > # send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN TTL > > # IP_CHKSUM IP_PROTO DATA > > # > > diff --git a/tests/ovn.at b/tests/ovn.at > > index 9512fd033..b552dd246 100644 > > --- a/tests/ovn.at > > +++ b/tests/ovn.at > > @@ -21214,8 +21214,8 @@ send_igmp_v3_report hv1-vif2 hv1 \ > > $(ip_to_hex 239 0 1 68) 04 e9b9 \ > > expected_reports > > > > -# Check that the IGMP Group is learned. > > -wait_row_count IGMP_Group 1 address=239.0.1.68 > > +# Check that the IGMP Group is learned (on sw1-p12 and on sw2-rtr). > > +wait_row_count IGMP_Group 2 address=239.0.1.68 > > check ovn-nbctl --wait=hv sync > > > > AS_BOX([IGMP traffic test 8]) > > @@ -21276,7 +21276,7 @@ ch=$(fetch_column Chassis _uuid name=hv3) > > ovn-sbctl create IGMP_Group address=239.0.1.42 datapath=$dp chassis=$ch > > > > ovn-nbctl --wait=hv sync > > -wait_row_count IGMP_Group 1 address=239.0.1.68 > > +wait_row_count IGMP_Group 2 address=239.0.1.68 > > wait_row_count IGMP_Group 1 address=239.0.1.42 > > > > OVN_CLEANUP([hv1], [hv2], [hv3]) > > @@ -21971,8 +21971,8 @@ send_mld_v2_report hv1-vif2 hv1 \ > > ff0adeadbeef00000000000000000001 04 c0e4 \ > > expected_reports > > > > -# Check that the IP multicast group is learned. > > -wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"' > > +# Check that the IP multicast group is learned (on sw1-p12 and on sw2-rtr). > > +wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"' > > check ovn-nbctl --wait=hv sync > > > > # Send traffic from sw1-p21 > > @@ -22018,6 +22018,160 @@ OVN_CLEANUP([hv1], [hv2]) > > AT_CLEANUP > > ]) > > > > +OVN_FOR_EACH_NORTHD([ > > +AT_SETUP([IGMP external querier]) > > +AT_KEYWORDS([IP-multicast]) > > + > > +ovn_start > > + > > +# Logical network: > > +# A logical switch (mcast snoop enabled) connected to a provider (localnet) > > +# network. > > +# One external multicast querier, connected to the switch via the localnet > > +# port. > > + > > +check ovn-nbctl \ > > + -- ls-add sw1 \ > > + -- lsp-add sw1 sw1-ln \ > > + -- lsp-set-type sw1-ln localnet \ > > + -- lsp-set-options sw1-ln network_name=phys-1 \ > > + -- lsp-add sw1 sw1-p1 \ > > + -- lsp-add sw1 sw1-p2 \ > > + -- lsp-add sw1 sw1-p3 \ > > + -- lsp-add sw1 sw1-p4 \ > > + -- set logical_switch sw1 other_config:mcast_snoop=true \ > > + -- set logical_switch sw1 other_config:mcast_querier=false > > + > > +net_add n1 > > + > > +sim_add hv1 > > +as hv1 > > +check ovs-vsctl add-br br-phys-1 > > +ovn_attach n1 br-phys-1 192.168.1.1 > > +check ovs-vsctl set open . > > external-ids:ovn-bridge-mappings=phys-1:br-phys-1 > > + > > +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ > > + set interface hv1-vif1 external-ids:iface-id=sw1-p1 \ > > + options:tx_pcap=hv1/vif1-tx.pcap \ > > + options:rxq_pcap=hv1/vif1-rx.pcap > > +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ > > + set interface hv1-vif2 external-ids:iface-id=sw1-p2 \ > > + options:tx_pcap=hv1/vif2-tx.pcap \ > > + options:rxq_pcap=hv1/vif2-rx.pcap > > + > > + > > +sim_add hv2 > > +as hv2 > > +check ovs-vsctl add-br br-phys-1 > > +ovn_attach n1 br-phys-1 192.168.1.2 > > +check ovs-vsctl set open . > > external-ids:ovn-bridge-mappings=phys-1:br-phys-1 > > + > > +check ovs-vsctl -- add-port br-int hv2-vif1 -- \ > > + set interface hv2-vif1 external-ids:iface-id=sw1-p3 \ > > + options:tx_pcap=hv2/vif1-tx.pcap \ > > + options:rxq_pcap=hv2/vif1-rx.pcap > > +check ovs-vsctl -- add-port br-int hv2-vif2 -- \ > > + set interface hv2-vif2 external-ids:iface-id=sw1-p4 \ > > + options:tx_pcap=hv2/vif2-tx.pcap \ > > + options:rxq_pcap=hv2/vif2-rx.pcap > > + > > +wait_for_ports_up > > +check ovn-nbctl --wait=hv sync > > + > > +AT_CAPTURE_FILE([exp]) > > +AT_CAPTURE_FILE([rcv]) > > +check_packets() { > > + > exp > > + > rcv > > + if test "$1" = --uniq; then > > + sort="sort -u"; shift > > + else > > + sort=sort > > + fi > > + for tuple in "$@"; do > > + set $tuple; pcap=$1 type=$2 > > + echo "--- $pcap" | tee -a exp >> rcv > > + $sort "$type" >> exp > > + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv > > + echo | tee -a exp >> rcv > > + done > > + > > + $at_diff exp rcv >/dev/null > > +} > > + > > +OVN_POPULATE_ARP > > + > > +sw1=$(fetch_column Datapath_Binding _uuid external_ids:name=sw1) > > + > > +dnl Send IGMP query from localnet1 (send on both hypervisors to simulate > > +dnl the multicast query packet in the fabric). > > +send_igmp_v3_query br-phys-1_n1 hv1 000000000142 $(ip_to_hex 10 0 0 42) > > 8fb1 > > +send_igmp_v3_query br-phys-1_n1 hv2 000000000142 $(ip_to_hex 10 0 0 42) > > 8fb1 expected > > + > > +dnl Wait for the mrouter to be learned by both chassis. > > +wait_row_count IGMP_Group 2 address=mrouters datapath=$sw1 > > + > > +dnl The query should have been flooded in the broadcast domain. > > +: > expected_empty > > + > > +OVS_WAIT_UNTIL( > > + [check_packets 'hv1/vif1-tx.pcap expected' \ > > + 'hv1/vif2-tx.pcap expected' \ > > + 'hv1/br-phys-1_n1-tx.pcap expected_empty' \ > > + 'hv2/vif1-tx.pcap expected' \ > > + 'hv2/vif2-tx.pcap expected' \ > > + 'hv2/br-phys-1_n1-tx.pcap expected_empty'], > > + [$at_diff -F'^---' exp rcv]) > > + > > +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 > > +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 > > +as hv1 reset_pcap_file br-phys-1_n1 hv1/br-phys-1_n1 > > +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 > > +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 > > +as hv2 reset_pcap_file br-phys-1_n1 hv2/br-phys-1_n1 > > + > > +: > expected > > + > > +dnl Send IGMP query from sw1-p4. > > +send_igmp_v3_query hv2-vif2 hv2 000000000002 $(ip_to_hex 10 0 0 2) 8fb1 > > expected > > + > > +OVS_WAIT_UNTIL( > > + [check_packets 'hv1/vif1-tx.pcap expected' \ > > + 'hv1/vif2-tx.pcap expected' \ > > + 'hv1/br-phys-1_n1-tx.pcap expected_empty' \ > > + 'hv2/vif1-tx.pcap expected' \ > > + 'hv2/vif2-tx.pcap expected_empty' \ > > + 'hv2/br-phys-1_n1-tx.pcap expected'], > > + [$at_diff -F'^---' exp rcv]) > > + > > +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 > > +as hv1 reset_pcap_file hv1-vif2 hv1/vif2 > > +as hv1 reset_pcap_file br-phys-1_n1 hv1/br-phys-1_n1 > > +as hv2 reset_pcap_file hv2-vif1 hv2/vif1 > > +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 > > +as hv2 reset_pcap_file br-phys-1_n1 hv2/br-phys-1_n1 > > + > > +: > expected > > + > > +dnl Send a join from sw1-p1: should be forwarded to sw1-ln and sw1-p4. > > +send_igmp_v3_report hv1-vif1 hv1 \ > > + 000000000001 $(ip_to_hex 10 0 0 2) f9f7 \ > > + $(ip_to_hex 239 0 1 68) 04 e9b9 \ > > + expected > > + > > +OVS_WAIT_UNTIL( > > + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ > > + 'hv1/vif2-tx.pcap expected_empty' \ > > + 'hv1/br-phys-1_n1-tx.pcap expected' \ > > + 'hv2/vif1-tx.pcap expected_empty' \ > > + 'hv2/vif2-tx.pcap expected' \ > > + 'hv2/br-phys-1_n1-tx.pcap expected_empty' ], > > + [$at_diff -F'^---' exp rcv]) > > + > > +OVN_CLEANUP([hv1], [hv2]) > > +AT_CLEANUP > > +]) > > + > > OVN_FOR_EACH_NORTHD([ > > AT_SETUP([unixctl socket]) > > ovn_start > > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev