On Wed, Oct 14, 2026 at 1:16 PM Jacob Tanenbaum <[email protected]> wrote:
> The IGMP/MLD querier injects a single query through the > multicast flood group. The flood group's OpenFlow flow > contains one "load OUTPORT; resubmit(CHECK_LOOPBACK)" > action per local port, and OVS translates the entire thing > in a single packet_xlate(). The translation's resubmit > counter accumulates across all of those ports, so on a node > with many local ports (~330) the translation exceeds OVS's > MAX_RESUBMITS (4096) budget, packet_xlate() returns > OFPFMFC_UNKNOWN, and the query is dropped. > > Rather than relying on the flood group, have the querier > inject the query directly to the flood group's local ports. > consider_mc_group() already determines exactly which local > ports the flood group would deliver to (and each port's > conntrack zone); publish that list to pinctrl so the querier > can fan the query out per port. Publish an empty list when > the flood group has no local ports on this chassis (including > datapaths with no local ports at all), so the querier can > distinguish that from a list that has not been published > yet. > > The querier sends one OpenFlow PACKET_OUT per batch of up to > 100 ports. OVS executes every resubmit in an action list > (the flood group relies on this), and a batch of 100 ports > uses ~1900 of the 4096-resubmit budget because the per-port > egress path is a fixed ~19 resubmits. The single flood > query of ~330 ports used ~6300 resubmits. > > Each port's query still traverses the per-port egress > pipeline, so multicast ACLs still filter which ports > receive it. If the port list has not been published > yet (first transient cycles), the querier skips the query > and retries every second; once published, an empty list > means the flood group has no local ports, so the query is > skipped on the regular query interval. A flood-group query > would not be delivered locally in either state. > > Tested with the IGMP/MLD snoop/querier/relay, external > querier, and multicast relay unit tests (real > ovn-controller + vswitchd, pcap-validated query delivery). > > Reported-at: https://redhat.atlassian.net/browse/FDP-4438 sorry that is the epic the correct tag is Reported-at: https://redhat.atlassian.net/browse/FDP-4445 will fix if/when a v2 is needed > > Assisted-by: Qwen3.8-27B-FP8, opencode > Signed-off-by: Jacob Tanenbaum <[email protected]> > > diff --git a/controller/physical.c b/controller/physical.c > index 8c5d9b940..aef5357bc 100644 > --- a/controller/physical.c > +++ b/controller/physical.c > @@ -2922,6 +2922,23 @@ mc_ofctrl_add_flow(const struct > sbrec_multicast_group *mc, > ofpbuf_clear(&ctx->ofpacts); > } > > +/* Appends (port_key, ct_zone) to 'flood_ports' ('flood_ports' must have > room > + * for one more entry; 'n_flood_ports' is the current count). */ > +static void > +flood_querier_port_add(struct mcast_querier_port *flood_ports, > + size_t *n_flood_ports, uint32_t port_key, > + int zone_id) > +{ > + if (!flood_ports) { > + return; > + } > + size_t i = *n_flood_ports; > + > + flood_ports[i].port_key = port_key; > + flood_ports[i].ct_zone = zone_id; > + (*n_flood_ports)++; > +} > + > static void > consider_mc_group(const struct physical_ctx *ctx, > const struct sbrec_multicast_group *mc, > @@ -2930,12 +2947,31 @@ consider_mc_group(const struct physical_ctx *ctx, > struct local_datapath *ldp = get_local_datapath(ctx->local_datapaths, > > mc->datapath->tunnel_key); > if (!ldp) { > + /* The datapath has no ports local to this chassis, so its flood > + * group would not deliver queries locally. Publish an empty list > + * so the querier can tell this apart from a list that has not > been > + * published yet. */ > + if (mc->tunnel_key == OVN_MCAST_FLOOD_TUNNEL_KEY) { > + pinctrl_mcast_querier_set_ports(mc->datapath->tunnel_key, > NULL, > + 0); > + } > return; > } > > struct sset remote_chassis = SSET_INITIALIZER(&remote_chassis); > struct sset vtep_chassis = SSET_INITIALIZER(&vtep_chassis); > > + /* The querier delivers the query to each local port of the flood > group > + * (in batches of up to MCAST_QUERIER_PORT_BATCH per OpenFlow message) > + * instead of a single query that the flood group fans out per port > (see > + * pinctrl_mcast_querier_set_ports). Collect the local ports of the > flood > + * group to publish them to the querier. */ > + struct mcast_querier_port *flood_ports = NULL; > + size_t n_flood_ports = 0; > + if (mc->tunnel_key == OVN_MCAST_FLOOD_TUNNEL_KEY) { > + flood_ports = xzalloc(mc->n_ports * sizeof *flood_ports); > + } > + > /* Go through all of the ports in the multicast group: > * > * - For remote ports, add the chassis to 'remote_chassis' or > @@ -3005,6 +3041,8 @@ consider_mc_group(const struct physical_ctx *ctx, > if (ldp->is_transit_switch) { > local_set_ct_zone_and_output_pb(port->tunnel_key, zone_id, > &local_ctx->ofpacts); > + flood_querier_port_add(flood_ports, &n_flood_ports, > + port->tunnel_key, zone_id); > } else { > local_output_pb(port->tunnel_key, &remote_ctx->ofpacts); > local_output_pb(port->tunnel_key, &ramp_ctx->ofpacts); > @@ -3026,9 +3064,13 @@ consider_mc_group(const struct physical_ctx *ctx, > || type == LP_L3GATEWAY)) { > local_set_ct_zone_and_output_pb(port->tunnel_key, zone_id, > &local_ctx->ofpacts); > + flood_querier_port_add(flood_ports, &n_flood_ports, > + port->tunnel_key, zone_id); > } else if (simap_contains(ctx->patch_ofports, > port->logical_port)) { > local_set_ct_zone_and_output_pb(port->tunnel_key, zone_id, > &local_ctx->ofpacts); > + flood_querier_port_add(flood_ports, &n_flood_ports, > + port->tunnel_key, zone_id); > } else if (type == LP_CHASSISREDIRECT > && port->chassis == ctx->chassis) { > const char *distributed_port = smap_get(&port->options, > @@ -3042,6 +3084,9 @@ consider_mc_group(const struct physical_ctx *ctx, > local_set_ct_zone_and_output_pb( > distributed_binding->tunnel_key, zone_id, > &local_ctx->ofpacts); > + flood_querier_port_add(flood_ports, &n_flood_ports, > + > distributed_binding->tunnel_key, > + zone_id); > } > } > } else if (!get_localnet_port(ctx->local_datapaths, > @@ -3068,6 +3113,12 @@ consider_mc_group(const struct physical_ctx *ctx, > } > } > > + if (flood_ports) { > + pinctrl_mcast_querier_set_ports(mc->datapath->tunnel_key, > flood_ports, > + n_flood_ports); > + free(flood_ports); > + } > + > bool local_lports = local_ctx->ofpacts.size > 0; > bool remote_ports = remote_ctx->ofpacts.size > 0; > bool ramp_ports = ramp_ctx->ofpacts.size > 0; > diff --git a/controller/pinctrl.c b/controller/pinctrl.c > index 5c9634b24..19f5bb8bf 100644 > --- a/controller/pinctrl.c > +++ b/controller/pinctrl.c > @@ -291,6 +291,9 @@ static void ip_mcast_snoop_run(void) > static void ip_mcast_querier_run(struct rconn *swconn, > long long int *query_time); > static void ip_mcast_querier_wait(long long int query_time); > +static size_t ip_mcast_querier_get_ports(int64_t dp_key, > + struct mcast_querier_port *ports, > + size_t *n_ports); > static void ip_mcast_sync( > struct ovsdb_idl_txn *ovnsb_idl_txn, > const struct sbrec_chassis *chassis, > @@ -5436,6 +5439,33 @@ static struct hmap mcast_snoop_map > OVS_GUARDED_BY(pinctrl_mutex); > */ > static struct ovs_list mcast_query_list; > > +/* > + * Local-port fanout for the multicast querier. > + * > + * physical_run() (main thread) records, for each datapath, the local > ports > + * the multicast flood group would deliver to. The querier (run in the > + * pinctrl_handler thread) then sends the query in batches of a bounded > number > + * of those ports instead of a single query via the flood group, so that > OVS > + * does not translate an unbounded per-port resubmit fan-out (which > exceeds > + * OVS's resubmit limit on nodes with many local ports and causes the > query to > + * be rejected with OFPFMFC_UNKNOWN). > + * An entry exists once physical_run() has considered the datapath's flood > + * group at least once, and may be empty when the flood group has no local > + * ports on this chassis. > + * > + * 'mcast_querier_ports' (dp_key -> struct mcast_querier_port_list) is > written > + * by the main thread and read by the handler thread, both while holding > + * pinctrl_mutex. > + */ > +struct mcast_querier_port_list { > + struct hmap_node hmap_node; > + uint32_t dp_key; > + size_t n_ports; > + struct mcast_querier_port *ports; > +}; > + > +static struct hmap mcast_querier_ports OVS_GUARDED_BY(pinctrl_mutex); > + > /* Multicast config information stored independently by datapath key. > * Protected by pinctrl_mutex. pinctrl_handler has RO access and > pinctrl_main > * has RW access. Read accesses from pinctrl_ip_mcast_handle() can be > @@ -5754,6 +5784,7 @@ ip_mcast_snoop_init(void) > hmap_init(&mcast_snoop_map); > ovs_list_init(&mcast_query_list); > hmap_init(&mcast_cfg_map); > + hmap_init(&mcast_querier_ports); > } > > static void > @@ -5772,6 +5803,14 @@ ip_mcast_snoop_destroy(void) > HMAP_FOR_EACH_POP (ip_ms_state, hmap_node, &mcast_cfg_map) { > free(ip_ms_state); > } > + > + struct mcast_querier_port_list *port_list; > + > + HMAP_FOR_EACH_POP (port_list, hmap_node, &mcast_querier_ports) { > + free(port_list->ports); > + free(port_list); > + } > + hmap_destroy(&mcast_querier_ports); > } > > static void > @@ -6204,10 +6243,12 @@ pinctrl_ip_mcast_handle(struct rconn *swconn, > } > } > > -static void > -ip_mcast_querier_send_igmp(struct rconn *swconn, struct ip_mcast_snoop > *ip_ms) > +/* Composes an IGMPv3 multicast query into 'buf' (up to 'buf_size' bytes) > and > + * returns its length in bytes. */ > +static size_t > +ip_mcast_querier_compose_igmp(struct ip_mcast_snoop *ip_ms, void *buf, > + size_t buf_size) > { > - /* Compose a multicast query. */ > uint64_t packet_stub[128 / 8]; > struct dp_packet packet; > > @@ -6223,35 +6264,21 @@ ip_mcast_querier_send_igmp(struct rconn *swconn, > struct ip_mcast_snoop *ip_ms) > uint8_t qqic = max_response; > packet_set_igmp3_query(&packet, max_response, 0, false, 0, qqic); > > - /* Inject multicast query. */ > - uint64_t ofpacts_stub[4096 / 8]; > - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > - enum ofp_version version = rconn_get_version(swconn); > - put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); > - put_load(OVN_MCAST_FLOOD_TUNNEL_KEY, MFF_LOG_OUTPORT, 0, 32, > &ofpacts); > - put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts); > - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); > - resubmit->in_port = OFPP_CONTROLLER; > - resubmit->table_id = OFTABLE_LOCAL_OUTPUT; > - > - struct ofputil_packet_out po = { > - .packet = dp_packet_data(&packet), > - .packet_len = dp_packet_size(&packet), > - .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)); > + size_t size = dp_packet_size(&packet); > + if (size > buf_size) { > + size = buf_size; > + } > + memcpy(buf, dp_packet_data(&packet), size); > dp_packet_uninit(&packet); > - ofpbuf_uninit(&ofpacts); > + return size; > } > > -static void > -ip_mcast_querier_send_mld(struct rconn *swconn, struct ip_mcast_snoop > *ip_ms) > +/* Composes an MLDv2 multicast query into 'buf' (up to 'buf_size' bytes) > and > + * returns its length in bytes. */ > +static size_t > +ip_mcast_querier_compose_mld(struct ip_mcast_snoop *ip_ms, void *buf, > + size_t buf_size) > { > - /* Compose a multicast query. */ > uint64_t packet_stub[128 / 8]; > struct dp_packet packet; > > @@ -6273,31 +6300,162 @@ ip_mcast_querier_send_mld(struct rconn *swconn, > struct ip_mcast_snoop *ip_ms) > struct in6_addr unspecified = { { { 0 } } }; > packet_set_mld_query(&packet, max_response, &unspecified, false, 0, > qqic); > > - /* Inject multicast query. */ > - uint64_t ofpacts_stub[4096 / 8]; > - struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > - enum ofp_version version = rconn_get_version(swconn); > - put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); > - put_load(OVN_MCAST_FLOOD_TUNNEL_KEY, MFF_LOG_OUTPORT, 0, 32, > &ofpacts); > - put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts); > - struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts); > - resubmit->in_port = OFPP_CONTROLLER; > - resubmit->table_id = OFTABLE_LOCAL_OUTPUT; > - > - struct ofputil_packet_out po = { > - .packet = dp_packet_data(&packet), > - .packet_len = dp_packet_size(&packet), > - .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)); > + size_t size = dp_packet_size(&packet); > + if (size > buf_size) { > + size = buf_size; > + } > + memcpy(buf, dp_packet_data(&packet), size); > dp_packet_uninit(&packet); > - ofpbuf_uninit(&ofpacts); > + return size; > +} > + > +/* Maximum number of local ports bundled into a single querier PACKET_OUT. > + * > + * Each port in a batch is emitted as (load CT_ZONE, if set) + load > OUTPORT + > + * resubmit(CHECK_LOOPBACK). OVS translates a batch by resubmitting once > per > + * port through the egress pipeline, and the translation's resubmit > counter > + * accumulates across all ports in the batch against ofproto's > MAX_RESUBMITS > + * (4096) budget. The per-port egress path is a fixed ~19 resubmits, so a > + * batch of 100 ports uses ~1900 of the 4096 budget, safely under the > limit. > + * Bundling keeps each OVS translation bounded (a single flood-group > query of > + * all ~330 ports would use ~6300 resubmits and be rejected) while > reducing the > + * number of OpenFlow PACKET_OUT messages to ceil(n_ports / 100). */ > +#define MCAST_QUERIER_PORT_BATCH 100 > + > +/* Sends 'query' ('query_len' bytes) to each port in 'ports' (0 to > + * 'n_ports'), in batches of at most MCAST_QUERIER_PORT_BATCH ports per > + * PACKET_OUT. Each port in a batch uses the same per-port actions the > flood > + * group would apply for that port (load the port's conntrack zone, set > the > + * logical output port, resubmit to the CHECK_LOOPBACK table), so every > port's > + * query traverses the per-port egress pipeline exactly like the flood > group > + * delivery would. Bounding each PACKET_OUT to a fixed number of ports > keeps > + * each OVS translation under OVS's resubmit limit (which a single > flood-group > + * query of all local ports would exceed) while reducing the number of > + * OpenFlow messages. */ > +static void > +ip_mcast_querier_send_port_fanout(struct rconn *swconn, > + struct ip_mcast_snoop *ip_ms, > + const void *query, size_t query_len, > + const struct mcast_querier_port *ports, > + size_t n_ports) > +{ > + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version( > + rconn_get_version(swconn)); > + > + for (size_t start = 0; start < n_ports; > + start += MCAST_QUERIER_PORT_BATCH) { > + size_t end = MIN(start + MCAST_QUERIER_PORT_BATCH, n_ports); > + > + struct ofpbuf ofpacts; > + ofpbuf_init(&ofpacts, 0); > + put_load(ip_ms->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts); > + for (size_t i = start; i < end; i++) { > + const struct mcast_querier_port *port = &ports[i]; > + > + if (port->ct_zone) { > + put_load(port->ct_zone, MFF_LOG_CT_ZONE, 0, 16, &ofpacts); > + } > + put_load(port->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_CHECK_LOOPBACK; > + } > + > + struct ofputil_packet_out po = { > + .packet = query, > + .packet_len = query_len, > + .buffer_id = UINT32_MAX, > + .ofpacts = ofpacts.data, > + .ofpacts_len = ofpacts.size, > + }; > + match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER); > + queue_msg(swconn, ofputil_encode_packet_out(&po, proto)); > + ofpbuf_uninit(&ofpacts); > + } > +} > + > +/* Fetches the local ports published by physical_run() for datapath > 'dp_key'. > + * Returns a newly allocated array of 'n_ports' entries (caller must > free), or > + * NULL when the list has not been published yet or is empty. */ > +static struct mcast_querier_port * > +ip_mcast_querier_fetch_ports(int64_t dp_key, size_t *n_ports) > +{ > + size_t n = ip_mcast_querier_get_ports(dp_key, NULL, NULL); > + if (!n) { > + *n_ports = 0; > + return NULL; > + } > + struct mcast_querier_port *ports = xmalloc(n * sizeof *ports); > + ip_mcast_querier_get_ports(dp_key, ports, &n); > + *n_ports = n; > + return ports; > } > > +/* Composes and sends the IGMPv3 and MLDv2 queries (if ipv4 or ipv6 is > + * enabled) to all local ports of the datapath's multicast flood group, in > + * batches of a set number of ports per OpenFlow message (see > + * ip_mcast_querier_send_port_fanout). Called only when > + * ip_mcast_querier_send() has verified that the per-port list published > by > + * physical_run() is non-empty; if the list is emptied before the fetch > below, > + * the queries are skipped and sent on the next interval. */ > +static void > +ip_mcast_querier_send_query(struct rconn *swconn, struct ip_mcast_snoop > *ip_ms) > +{ > + size_t n_ports; > + struct mcast_querier_port *ports = > + ip_mcast_querier_fetch_ports(ip_ms->dp_key, &n_ports); > + if (!ports) { > + return; > + } > + > + char query_buf[128]; > + if (ip_ms->cfg.querier_v4_enabled) { > + size_t query_len = ip_mcast_querier_compose_igmp(ip_ms, query_buf, > + sizeof > query_buf); > + ip_mcast_querier_send_port_fanout(swconn, ip_ms, query_buf, > + query_len, ports, n_ports); > + } > + if (ip_ms->cfg.querier_v6_enabled) { > + size_t query_len = ip_mcast_querier_compose_mld(ip_ms, query_buf, > + sizeof query_buf); > + ip_mcast_querier_send_port_fanout(swconn, ip_ms, query_buf, > + query_len, ports, n_ports); > + } > + free(ports); > +} > + > +/* Returns whether physical_run() has published the local ports of the > + * datapath's multicast flood group for datapath 'dp_key' yet (the list > may > + * be empty), and, when 'n_ports' is non-NULL, the number of ports in the > + * list. > + * > + * Called from the pinctrl_handler thread. Takes pinctrl_mutex > internally. > + */ > +static bool > +ip_mcast_querier_ports_published(int64_t dp_key, size_t *n_ports) > +{ > + ovs_mutex_lock(&pinctrl_mutex); > + > + struct mcast_querier_port_list *list; > + bool published = false; > + HMAP_FOR_EACH (list, hmap_node, &mcast_querier_ports) { > + if (list->dp_key == dp_key) { > + if (n_ports) { > + *n_ports = list->n_ports; > + } > + published = true; > + break; > + } > + } > + > + ovs_mutex_unlock(&pinctrl_mutex); > + return published; > +} > + > +/* Retry interval (ms) the querier uses while physical_run() has not yet > + * published the local ports of the datapath's multicast flood group. */ > +#define MCAST_QUERIER_INITIAL_RETRY_MS 1000 > + > static long long int > ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms, > long long int current_time) > @@ -6306,16 +6464,30 @@ ip_mcast_querier_send(struct rconn *swconn, struct > ip_mcast_snoop *ip_ms, > return ip_ms->query_time_ms; > } > > - if (ip_ms->cfg.querier_v4_enabled) { > - ip_mcast_querier_send_igmp(swconn, ip_ms); > - } > + size_t n_ports; > + bool published = > + ip_mcast_querier_ports_published(ip_ms->dp_key, &n_ports); > > - if (ip_ms->cfg.querier_v6_enabled) { > - ip_mcast_querier_send_mld(swconn, ip_ms); > - } > + /* While the port list has not been published yet, retry at a short > + * backoff so that the first query is not delayed by a full query > + * interval; afterwards, retry on the regular query interval. */ > + long long int interval_ms = > + published ? ip_ms->cfg.query_interval_s * 1000 > + : MCAST_QUERIER_INITIAL_RETRY_MS; > > /* Set the next query time. */ > - ip_ms->query_time_ms = current_time + ip_ms->cfg.query_interval_s * > 1000; > + ip_ms->query_time_ms = current_time + interval_ms; > + > + /* Skip the query if the local ports of the datapath's multicast flood > + * group have not been published by physical_run() yet, or the flood > + * group has no local ports. A flood-group query would not be > delivered > + * locally in either state, so just retry on the next interval. */ > + if (!published || !n_ports) { > + return ip_ms->query_time_ms; > + } > + > + ip_mcast_querier_send_query(swconn, ip_ms); > + > return ip_ms->query_time_ms; > } > > @@ -6349,6 +6521,80 @@ ip_mcast_querier_wait(long long int query_time) > } > } > > +/* Records, for datapath 'dp_key', the local ports that the multicast > flood > + * group delivers to. 'ports' may be NULL, in which case an empty list is > + * stored: this both avoids keeping a stale list from a previous pass and > + * lets the querier distinguish "the flood group has no local ports on > this > + * chassis" from "the list has not been published yet". > + * > + * Called from the main thread (physical_run and the mc group changes > + * handler) while it iterates the multicast groups. Takes pinctrl_mutex > + * internally, so the caller must not already hold it. > + */ > +void > +pinctrl_mcast_querier_set_ports(int64_t dp_key, > + const struct mcast_querier_port *ports, > + size_t n_ports) > +{ > + ovs_mutex_lock(&pinctrl_mutex); > + > + struct mcast_querier_port_list *list; > + HMAP_FOR_EACH (list, hmap_node, &mcast_querier_ports) { > + if (list->dp_key == dp_key) { > + break; > + } > + } > + if (!list) { > + list = xzalloc(sizeof *list); > + list->dp_key = dp_key; > + hmap_insert(&mcast_querier_ports, &list->hmap_node, > + ip_mcast_snoop_hash(dp_key)); > + } > + > + free(list->ports); > + if (n_ports) { > + list->ports = xmalloc(n_ports * sizeof *list->ports); > + memcpy(list->ports, ports, n_ports * sizeof *list->ports); > + } else { > + list->ports = NULL; > + } > + list->n_ports = n_ports; > + > + ovs_mutex_unlock(&pinctrl_mutex); > +} > + > +/* Returns the number of local ports the querier should fan out to for > + * datapath 'dp_key' (0 if none are known yet), and, when 'ports' and > + * 'n_ports' are non-NULL, copies up to *n_ports of them into 'ports' and > + * updates *n_ports to the number copied. > + * > + * Called from the pinctrl_handler thread. Takes pinctrl_mutex > internally. > + */ > +static size_t > +ip_mcast_querier_get_ports(int64_t dp_key, struct mcast_querier_port > *ports, > + size_t *n_ports) > +{ > + ovs_mutex_lock(&pinctrl_mutex); > + > + size_t n = 0; > + struct mcast_querier_port_list *list; > + HMAP_FOR_EACH (list, hmap_node, &mcast_querier_ports) { > + if (list->dp_key != dp_key) { > + continue; > + } > + n = list->n_ports; > + if (ports && n_ports) { > + size_t count = MIN(n, *n_ports); > + memcpy(ports, list->ports, count * sizeof *ports); > + *n_ports = count; > + } > + break; > + } > + > + ovs_mutex_unlock(&pinctrl_mutex); > + return n; > +} > + > static void > send_garp_rarp_wait(long long int send_garp_rarp_time) > { > diff --git a/controller/pinctrl.h b/controller/pinctrl.h > index a638fe29f..bd69539ca 100644 > --- a/controller/pinctrl.h > +++ b/controller/pinctrl.h > @@ -86,4 +86,35 @@ void send_self_originated_neigh_packet(struct rconn > *swconn, > struct in6_addr *local, > struct in6_addr *target, > uint8_t table_id); > + > +/* A local port that the multicast flood group would deliver a query to, > along > + * with the conntrack zone that the flood group's per-port action loads. > */ > +struct mcast_querier_port { > + uint32_t port_key; > + uint16_t ct_zone; > +}; > + > +/* Multicast querier local-port fanout. > + * > + * Instead of the querier injecting a single IGMP/MLD query via the > multicast > + * flood group (which OVS translates by resubmitting once per local port, > + * exceeding OVS's resubmit limit on nodes with many local ports and > causing > + * the query to be rejected with OFPFMFC_UNKNOWN), the querier injects the > + * query in batches of a bounded number of local ports per OpenFlow > message. > + * Each port in a batch is injected the same way a single port of the > flood > + * group would be: load the port's conntrack zone, set the logical output > port, > + * and resubmit to the CHECK_LOOPBACK table. Bounding each message to a > fixed > + * number of ports keeps each OVS translation under OVS's resubmit limit > while > + * reducing the number of OpenFlow messages. > + * > + * The physical node (physical_run and the mc group changes handler) knows > + * exactly which local ports the flood group would deliver to; it > publishes > + * them here so the pinctrl_handler thread can fan the query out per port. > + * > + * Called from the main thread; read by the handler thread (the querier). > + * Takes pinctrl_mutex internally. > + */ > +void pinctrl_mcast_querier_set_ports(int64_t dp_key, > + const struct mcast_querier_port > *ports, > + size_t n_ports); > #endif /* controller/pinctrl.h */ > -- > 2.56.0 > > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
