[PATCH 22.11] net/mlx5: fix LACP redirection in Rx domain
Xueming(Steven) Li
xuemingl at nvidia.com
Tue Dec 19 14:11:12 CET 2023
Hi Bing,
Applied to 22.11 LTS branch, thanks for the backporting!
> -----Original Message-----
> From: Bing Zhao <bingz at nvidia.com>
> Sent: 12/18/2023 17:21
> To: Xueming(Steven) Li <xuemingl at nvidia.com>
> Cc: stable at dpdk.org; Suanming Mou <suanmingm at nvidia.com>
> Subject: [PATCH 22.11] net/mlx5: fix LACP redirection in Rx domain
>
> [ upstream commit 49dffadf4b0c4ad1292ffc877b33109cd35ffce4 ]
>
> When the "lacp_by_user" is not set from the application in bond mode, the LACP
> traffic should be handled by the kernel driver by default.
>
> This commit adds the missing support in the template API when "dv_flow_en=2".
> The behavior will be the same as that in the DV mode with "dv_flow_en=1". The
> LACP packets will be redirected to the kernel when starting the steering in the
> NIC Rx domain.
>
> With this commit, the DEFAULT_MISS action usage is refactored a bit.
> In the HWS, one unique action can be created with supported bits set in the "flag"
> per port. The *ROOT_FDB and *HWS_FDB flag bits will only be set when the port
> is in switchdev mode and working as the E-Switch manager proxy port. The SF/VF
> and all other representors won't have the FDB flag bits when creating the
> DEFAULT_MISS action.
>
> Fixes: 9fa7c1cddb85 ("net/mlx5: create control flow rules with HWS")
> Cc: stable at dpdk.org
>
> Signed-off-by: Bing Zhao <bingz at nvidia.com>
> Acked-by: Suanming Mou <suanmingm at nvidia.com>
> ---
> drivers/net/mlx5/linux/mlx5_os.c | 8 +-
> drivers/net/mlx5/mlx5.h | 3 +
> drivers/net/mlx5/mlx5_flow.h | 1 +
> drivers/net/mlx5/mlx5_flow_hw.c | 247 ++++++++++++++++++++++++++++++-
> drivers/net/mlx5/mlx5_trigger.c | 3 +
> 5 files changed, 253 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
> index 438b832a40..28bf7211e4 100644
> --- a/drivers/net/mlx5/linux/mlx5_os.c
> +++ b/drivers/net/mlx5/linux/mlx5_os.c
> @@ -474,6 +474,10 @@ mlx5_alloc_shared_dr(struct mlx5_priv *priv)
> err = mlx5_alloc_table_hash_list(priv);
> if (err)
> goto error;
> + sh->default_miss_action =
> + mlx5_glue->dr_create_flow_action_default_miss();
> + if (!sh->default_miss_action)
> + DRV_LOG(WARNING, "Default miss action is not supported.");
> if (priv->sh->config.dv_flow_en == 2)
> return 0;
> /* The resources below are only valid with DV support. */ @@ -597,10
> +601,6 @@ mlx5_alloc_shared_dr(struct mlx5_priv *priv)
>
> __mlx5_discovery_misc5_cap(priv);
> #endif /* HAVE_MLX5DV_DR */
> - sh->default_miss_action =
> - mlx5_glue->dr_create_flow_action_default_miss();
> - if (!sh->default_miss_action)
> - DRV_LOG(WARNING, "Default miss action is not supported.");
> LIST_INIT(&sh->shared_rxqs);
> return 0;
> error:
> diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h index
> ba7c4441e5..96a269ccd0 100644
> --- a/drivers/net/mlx5/mlx5.h
> +++ b/drivers/net/mlx5/mlx5.h
> @@ -1735,6 +1735,7 @@ struct mlx5_priv {
> struct rte_flow_template_table *hw_esw_sq_miss_tbl;
> struct rte_flow_template_table *hw_esw_zero_tbl;
> struct rte_flow_template_table *hw_tx_meta_cpy_tbl;
> + struct rte_flow_template_table *hw_lacp_rx_tbl;
> struct rte_flow_pattern_template *hw_tx_repr_tagging_pt;
> struct rte_flow_actions_template *hw_tx_repr_tagging_at;
> struct rte_flow_template_table *hw_tx_repr_tagging_tbl; @@ -1814,6
> +1815,8 @@ struct mlx5_priv {
> struct mlx5dr_action *hw_drop[2];
> /* HW steering global tag action. */
> struct mlx5dr_action *hw_tag[2];
> + /* HW steering global default miss action. */
> + struct mlx5dr_action *hw_def_miss;
> /* HW steering create ongoing rte flow table list header. */
> LIST_HEAD(flow_hw_tbl_ongo, rte_flow_template_table)
> flow_hw_tbl_ongo;
> struct mlx5_indexed_pool *acts_ipool; /* Action data indexed pool. */
> diff --git a/drivers/net/mlx5/mlx5_flow.h b/drivers/net/mlx5/mlx5_flow.h index
> 1192735750..eb87f84166 100644
> --- a/drivers/net/mlx5/mlx5_flow.h
> +++ b/drivers/net/mlx5/mlx5_flow.h
> @@ -2587,6 +2587,7 @@ int mlx5_flow_hw_esw_destroy_sq_miss_flow(struct
> rte_eth_dev *dev, int mlx5_flow_hw_esw_create_default_jump_flow(struct
> rte_eth_dev *dev); int
> mlx5_flow_hw_create_tx_default_mreg_copy_flow(struct rte_eth_dev *dev);
> int mlx5_flow_hw_tx_repr_matching_flow(struct rte_eth_dev *dev, uint32_t sqn,
> bool external);
> +int mlx5_flow_hw_lacp_rx_flow(struct rte_eth_dev *dev);
> int mlx5_flow_actions_validate(struct rte_eth_dev *dev,
> const struct rte_flow_actions_template_attr *attr,
> const struct rte_flow_action actions[], diff --git
> a/drivers/net/mlx5/mlx5_flow_hw.c b/drivers/net/mlx5/mlx5_flow_hw.c index
> 28d0bbecc4..6b889e9f81 100644
> --- a/drivers/net/mlx5/mlx5_flow_hw.c
> +++ b/drivers/net/mlx5/mlx5_flow_hw.c
> @@ -1363,7 +1363,7 @@ __flow_hw_actions_translate(struct rte_eth_dev
> *dev,
> else
> type = MLX5DR_TABLE_TYPE_NIC_RX;
> for (; !actions_end; actions++, masks++) {
> - switch (actions->type) {
> + switch ((int)actions->type) {
> case RTE_FLOW_ACTION_TYPE_INDIRECT:
> action_pos = at->actions_off[actions - at->actions];
> if (!attr->group) {
> @@ -1667,6 +1667,16 @@ __flow_hw_actions_translate(struct rte_eth_dev
> *dev,
> action_pos))
> goto err;
> break;
> + case MLX5_RTE_FLOW_ACTION_TYPE_DEFAULT_MISS:
> + /* Internal, can be skipped. */
> + if (!!attr->group) {
> + DRV_LOG(ERR, "DEFAULT MISS action is only"
> + " supported in root table.");
> + goto err;
> + }
> + action_pos = at->actions_off[actions - at->actions];
> + acts->rule_acts[action_pos].action = priv-
> >hw_def_miss;
> + break;
> case RTE_FLOW_ACTION_TYPE_END:
> actions_end = true;
> break;
> @@ -3869,6 +3879,34 @@ flow_hw_validate_action_push_vlan(struct
> rte_eth_dev *dev, #undef X_FIELD }
>
> +static int
> +flow_hw_validate_action_default_miss(struct rte_eth_dev *dev,
> + const struct rte_flow_actions_template_attr
> *attr,
> + uint64_t action_flags,
> + struct rte_flow_error *error)
> +{
> + /*
> + * The private DEFAULT_MISS action is used internally for LACP in control
> + * flows. So this validation can be ignored. It can be kept right now since
> + * the validation will be done only once.
> + */
> + struct mlx5_priv *priv = dev->data->dev_private;
> +
> + if (!attr->ingress || attr->egress || attr->transfer)
> + return rte_flow_error_set(error, EINVAL,
> + RTE_FLOW_ERROR_TYPE_ACTION,
> NULL,
> + "DEFAULT MISS is only supported in
> ingress.");
> + if (!priv->hw_def_miss)
> + return rte_flow_error_set(error, EINVAL,
> + RTE_FLOW_ERROR_TYPE_ACTION,
> NULL,
> + "DEFAULT MISS action does not
> exist.");
> + if (action_flags & MLX5_FLOW_FATE_ACTIONS)
> + return rte_flow_error_set(error, EINVAL,
> + RTE_FLOW_ERROR_TYPE_ACTION,
> NULL,
> + "DEFAULT MISS should be the only
> termination.");
> + return 0;
> +}
> +
> static int
> mlx5_flow_hw_actions_validate(struct rte_eth_dev *dev,
> const struct rte_flow_actions_template_attr *attr,
> @@ -3902,7 +3940,7 @@ mlx5_flow_hw_actions_validate(struct rte_eth_dev
> *dev,
>
> RTE_FLOW_ERROR_TYPE_ACTION,
> action,
> "mask type does not match
> action type");
> - switch (action->type) {
> + switch ((int)action->type) {
> case RTE_FLOW_ACTION_TYPE_VOID:
> break;
> case RTE_FLOW_ACTION_TYPE_INDIRECT:
> @@ -4028,6 +4066,13 @@ mlx5_flow_hw_actions_validate(struct rte_eth_dev
> *dev,
> case RTE_FLOW_ACTION_TYPE_END:
> actions_end = true;
> break;
> + case MLX5_RTE_FLOW_ACTION_TYPE_DEFAULT_MISS:
> + ret = flow_hw_validate_action_default_miss(dev, attr,
> + action_flags,
> error);
> + if (ret < 0)
> + return ret;
> + action_flags |= MLX5_FLOW_ACTION_DEFAULT_MISS;
> + break;
> default:
> return rte_flow_error_set(error, ENOTSUP,
>
> RTE_FLOW_ERROR_TYPE_ACTION,
> @@ -4047,8 +4092,7 @@ flow_hw_actions_validate(struct rte_eth_dev *dev,
> const struct rte_flow_action masks[],
> struct rte_flow_error *error)
> {
> - return mlx5_flow_hw_actions_validate(dev, attr, actions, masks, NULL,
> - error);
> + return mlx5_flow_hw_actions_validate(dev, attr, actions, masks, NULL,
> +error);
> }
>
>
> @@ -4149,7 +4193,7 @@ flow_hw_dr_actions_template_create(struct
> rte_flow_actions_template *at)
>
> if (curr_off >= MLX5_HW_MAX_ACTS)
> goto err_actions_num;
> - switch (at->actions[i].type) {
> + switch ((int)at->actions[i].type) {
> case RTE_FLOW_ACTION_TYPE_VOID:
> break;
> case RTE_FLOW_ACTION_TYPE_INDIRECT:
> @@ -4227,6 +4271,10 @@ flow_hw_dr_actions_template_create(struct
> rte_flow_actions_template *at)
> }
> at->actions_off[i] = cnt_off;
> break;
> + case MLX5_RTE_FLOW_ACTION_TYPE_DEFAULT_MISS:
> + at->actions_off[i] = curr_off;
> + action_types[curr_off++] =
> MLX5DR_ACTION_TYP_MISS;
> + break;
> default:
> type = mlx5_hw_dr_action_types[at->actions[i].type];
> at->actions_off[i] = curr_off;
> @@ -5747,6 +5795,42 @@
> flow_hw_create_tx_default_mreg_copy_pattern_template(struct rte_eth_dev
> *dev,
> return flow_hw_pattern_template_create(dev, &tx_pa_attr, eth_all,
> error); }
>
> +/*
> + * Creating a flow pattern template with all LACP packets matching,
> +only for NIC
> + * ingress domain.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param error
> + * Pointer to error structure.
> + *
> + * @return
> + * Pointer to flow pattern template on success, NULL otherwise.
> + */
> +static struct rte_flow_pattern_template *
> +flow_hw_create_lacp_rx_pattern_template(struct rte_eth_dev *dev, struct
> +rte_flow_error *error) {
> + struct rte_flow_pattern_template_attr pa_attr = {
> + .relaxed_matching = 0,
> + .ingress = 1,
> + };
> + struct rte_flow_item_eth lacp_mask = {
> + .dst.addr_bytes = "\x00\x00\x00\x00\x00\x00",
> + .src.addr_bytes = "\x00\x00\x00\x00\x00\x00",
> + .type = 0xFFFF,
> + };
> + struct rte_flow_item eth_all[] = {
> + [0] = {
> + .type = RTE_FLOW_ITEM_TYPE_ETH,
> + .mask = &lacp_mask,
> + },
> + [1] = {
> + .type = RTE_FLOW_ITEM_TYPE_END,
> + },
> + };
> + return flow_hw_pattern_template_create(dev, &pa_attr, eth_all, error);
> +}
> +
> /**
> * Creates a flow actions template with modify field action and masked jump
> action.
> * Modify field action sets the least significant bit of REG_C_0 (usable by user-
> space) @@ -6013,6 +6097,38 @@
> flow_hw_create_tx_default_mreg_copy_actions_template(struct rte_eth_dev
> *dev,
> masks, error);
> }
>
> +/*
> + * Creating an actions template to use default miss to re-route packets
> +to the
> + * kernel driver stack.
> + * On root table, only DEFAULT_MISS action can be used.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param error
> + * Pointer to error structure.
> + *
> + * @return
> + * Pointer to flow actions template on success, NULL otherwise.
> + */
> +static struct rte_flow_actions_template *
> +flow_hw_create_lacp_rx_actions_template(struct rte_eth_dev *dev, struct
> +rte_flow_error *error) {
> + struct rte_flow_actions_template_attr act_attr = {
> + .ingress = 1,
> + };
> + const struct rte_flow_action actions[] = {
> + [0] = {
> + .type = (enum rte_flow_action_type)
> +
> MLX5_RTE_FLOW_ACTION_TYPE_DEFAULT_MISS,
> + },
> + [1] = {
> + .type = RTE_FLOW_ACTION_TYPE_END,
> + },
> + };
> +
> + return flow_hw_actions_template_create(dev, &act_attr, actions,
> +actions, error); }
> +
> /**
> * Creates a control flow table used to transfer traffic from E-Switch Manager
> * and TX queues from group 0 to group 1.
> @@ -6171,6 +6287,43 @@ flow_hw_create_ctrl_jump_table(struct rte_eth_dev
> *dev,
> return flow_hw_table_create(dev, &cfg, &it, 1, &at, 1, error); }
>
> +/*
> + * Create a table on the root group to for the LACP traffic redirecting.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param it
> + * Pointer to flow pattern template.
> + * @param at
> + * Pointer to flow actions template.
> + *
> + * @return
> + * Pointer to flow table on success, NULL otherwise.
> + */
> +static struct rte_flow_template_table *
> +flow_hw_create_lacp_rx_table(struct rte_eth_dev *dev,
> + struct rte_flow_pattern_template *it,
> + struct rte_flow_actions_template *at,
> + struct rte_flow_error *error)
> +{
> + struct rte_flow_template_table_attr attr = {
> + .flow_attr = {
> + .group = 0,
> + .priority = 0,
> + .ingress = 1,
> + .egress = 0,
> + .transfer = 0,
> + },
> + .nb_flows = 1,
> + };
> + struct mlx5_flow_template_table_cfg cfg = {
> + .attr = attr,
> + .external = false,
> + };
> +
> + return flow_hw_table_create(dev, &cfg, &it, 1, &at, 1, error); }
> +
> /**
> * Creates a set of flow tables used to create control flows used
> * when E-Switch is engaged.
> @@ -6191,10 +6344,12 @@ flow_hw_create_ctrl_tables(struct rte_eth_dev
> *dev, struct rte_flow_error *error
> struct rte_flow_pattern_template *regc_sq_items_tmpl = NULL;
> struct rte_flow_pattern_template *port_items_tmpl = NULL;
> struct rte_flow_pattern_template *tx_meta_items_tmpl = NULL;
> + struct rte_flow_pattern_template *lacp_rx_items_tmpl = NULL;
> struct rte_flow_actions_template *regc_jump_actions_tmpl = NULL;
> struct rte_flow_actions_template *port_actions_tmpl = NULL;
> struct rte_flow_actions_template *jump_one_actions_tmpl = NULL;
> struct rte_flow_actions_template *tx_meta_actions_tmpl = NULL;
> + struct rte_flow_actions_template *lacp_rx_actions_tmpl = NULL;
> uint32_t xmeta = priv->sh->config.dv_xmeta_en;
> uint32_t repr_matching = priv->sh->config.repr_matching;
> int ret;
> @@ -6290,6 +6445,28 @@ flow_hw_create_ctrl_tables(struct rte_eth_dev *dev,
> struct rte_flow_error *error
> goto err;
> }
> }
> + /* Create LACP default miss table. */
> + if (!priv->sh->config.lacp_by_user && priv->pf_bond >= 0) {
> + lacp_rx_items_tmpl =
> flow_hw_create_lacp_rx_pattern_template(dev, error);
> + if (!lacp_rx_items_tmpl) {
> + DRV_LOG(ERR, "port %u failed to create pattern
> template"
> + " for LACP Rx traffic", dev->data->port_id);
> + goto err;
> + }
> + lacp_rx_actions_tmpl =
> flow_hw_create_lacp_rx_actions_template(dev, error);
> + if (!lacp_rx_actions_tmpl) {
> + DRV_LOG(ERR, "port %u failed to create actions
> template"
> + " for LACP Rx traffic", dev->data->port_id);
> + goto err;
> + }
> + priv->hw_lacp_rx_tbl = flow_hw_create_lacp_rx_table(dev,
> lacp_rx_items_tmpl,
> +
> lacp_rx_actions_tmpl, error);
> + if (!priv->hw_lacp_rx_tbl) {
> + DRV_LOG(ERR, "port %u failed to create template table
> for"
> + " for LACP Rx traffic", dev->data->port_id);
> + goto err;
> + }
> + }
> return 0;
> err:
> /* Do not overwrite the rte_errno. */
> @@ -6298,6 +6475,10 @@ flow_hw_create_ctrl_tables(struct rte_eth_dev *dev,
> struct rte_flow_error *error
> ret = rte_flow_error_set(error, EINVAL,
>
> RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
> "Failed to create control tables.");
> + if (priv->hw_tx_meta_cpy_tbl) {
> + flow_hw_table_destroy(dev, priv->hw_tx_meta_cpy_tbl, NULL);
> + priv->hw_tx_meta_cpy_tbl = NULL;
> + }
> if (priv->hw_esw_zero_tbl) {
> flow_hw_table_destroy(dev, priv->hw_esw_zero_tbl, NULL);
> priv->hw_esw_zero_tbl = NULL;
> @@ -6310,6 +6491,8 @@ flow_hw_create_ctrl_tables(struct rte_eth_dev *dev,
> struct rte_flow_error *error
> flow_hw_table_destroy(dev, priv->hw_esw_sq_miss_root_tbl,
> NULL);
> priv->hw_esw_sq_miss_root_tbl = NULL;
> }
> + if (lacp_rx_actions_tmpl)
> + flow_hw_actions_template_destroy(dev, lacp_rx_actions_tmpl,
> NULL);
> if (tx_meta_actions_tmpl)
> flow_hw_actions_template_destroy(dev, tx_meta_actions_tmpl,
> NULL);
> if (jump_one_actions_tmpl)
> @@ -6318,6 +6501,8 @@ flow_hw_create_ctrl_tables(struct rte_eth_dev *dev,
> struct rte_flow_error *error
> flow_hw_actions_template_destroy(dev, port_actions_tmpl,
> NULL);
> if (regc_jump_actions_tmpl)
> flow_hw_actions_template_destroy(dev,
> regc_jump_actions_tmpl, NULL);
> + if (lacp_rx_items_tmpl)
> + flow_hw_pattern_template_destroy(dev, lacp_rx_items_tmpl,
> NULL);
> if (tx_meta_items_tmpl)
> flow_hw_pattern_template_destroy(dev, tx_meta_items_tmpl,
> NULL);
> if (port_items_tmpl)
> @@ -6881,6 +7066,7 @@ flow_hw_configure(struct rte_eth_dev *dev,
> struct rte_flow_queue_attr ctrl_queue_attr = {0};
> bool is_proxy = !!(priv->sh->config.dv_esw_en && priv->master);
> int ret = 0;
> + uint32_t action_flags;
>
> if (!port_attr || !nb_queue || !queue_attr) {
> rte_errno = EINVAL;
> @@ -7030,6 +7216,20 @@ flow_hw_configure(struct rte_eth_dev *dev,
> if (ret)
> goto err;
> }
> + /*
> + * DEFAULT_MISS action have different behaviors in different domains.
> + * In FDB, it will steering the packets to the E-switch manager.
> + * In NIC Rx root, it will steering the packet to the kernel driver stack.
> + * An action with all bits set in the flag can be created and the HWS
> + * layer will translate it properly when being used in different rules.
> + */
> + action_flags = MLX5DR_ACTION_FLAG_ROOT_RX |
> MLX5DR_ACTION_FLAG_HWS_RX |
> + MLX5DR_ACTION_FLAG_ROOT_TX |
> MLX5DR_ACTION_FLAG_HWS_TX;
> + if (is_proxy)
> + action_flags |= (MLX5DR_ACTION_FLAG_ROOT_FDB |
> MLX5DR_ACTION_FLAG_HWS_FDB);
> + priv->hw_def_miss = mlx5dr_action_create_default_miss(priv->dr_ctx,
> action_flags);
> + if (!priv->hw_def_miss)
> + goto err;
> if (is_proxy) {
> ret = flow_hw_create_vport_actions(priv);
> if (ret) {
> @@ -9052,6 +9252,43 @@ mlx5_flow_hw_tx_repr_matching_flow(struct
> rte_eth_dev *dev, uint32_t sqn, bool e
> items, 0, actions, 0, &flow_info,
> external); }
>
> +int
> +mlx5_flow_hw_lacp_rx_flow(struct rte_eth_dev *dev) {
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct rte_flow_item_eth lacp_item = {
> + .type = RTE_BE16(RTE_ETHER_TYPE_SLOW),
> + };
> + struct rte_flow_item eth_lacp[] = {
> + [0] = {
> + .type = RTE_FLOW_ITEM_TYPE_ETH,
> + .spec = &lacp_item,
> + .mask = &lacp_item,
> + },
> + [1] = {
> + .type = RTE_FLOW_ITEM_TYPE_END,
> + },
> + };
> + struct rte_flow_action miss_action[] = {
> + [0] = {
> + .type = (enum rte_flow_action_type)
> +
> MLX5_RTE_FLOW_ACTION_TYPE_DEFAULT_MISS,
> + },
> + [1] = {
> + .type = RTE_FLOW_ACTION_TYPE_END,
> + },
> + };
> + struct mlx5_hw_ctrl_flow_info flow_info = {
> + .type = MLX5_HW_CTRL_FLOW_TYPE_LACP_RX,
> + };
> +
> + MLX5_ASSERT(priv->master);
> + if (!priv->dr_ctx || !priv->hw_lacp_rx_tbl)
> + return 0;
> + return flow_hw_create_ctrl_flow(dev, dev, priv->hw_lacp_rx_tbl,
> eth_lacp, 0,
> + miss_action, 0, &flow_info, false); }
> +
> static uint32_t
> __calc_pattern_flags(const enum mlx5_flow_ctrl_rx_eth_pattern_type
> eth_pattern_type) { diff --git a/drivers/net/mlx5/mlx5_trigger.c
> b/drivers/net/mlx5/mlx5_trigger.c index c6742cb47e..b12a1dc1c7 100644
> --- a/drivers/net/mlx5/mlx5_trigger.c
> +++ b/drivers/net/mlx5/mlx5_trigger.c
> @@ -1524,6 +1524,9 @@ mlx5_traffic_enable_hws(struct rte_eth_dev *dev)
> }
> if (priv->isolated)
> return 0;
> + if (!priv->sh->config.lacp_by_user && priv->pf_bond >= 0)
> + if (mlx5_flow_hw_lacp_rx_flow(dev))
> + goto error;
> if (dev->data->promiscuous)
> flags |= MLX5_CTRL_PROMISCUOUS;
> if (dev->data->all_multicast)
> --
> 2.34.1
More information about the stable
mailing list