DPDK patches and discussions
 help / color / mirror / Atom feed
From: Ori Kam <orika@mellanox.com>
To: Bill Zhou <dongz@mellanox.com>,
	"ferruh.yigit@intel.com" <ferruh.yigit@intel.com>,
	Matan Azrad <matan@mellanox.com>,
	"wenzhuo.lu@intel.com" <wenzhuo.lu@intel.com>,
	"beilei.xing@intel.com" <beilei.xing@intel.com>,
	"bernard.iremonger@intel.com" <bernard.iremonger@intel.com>,
	"john.mcnamara@intel.com" <john.mcnamara@intel.com>,
	"marko.kovacevic@intel.com" <marko.kovacevic@intel.com>
Cc: "dev@dpdk.org" <dev@dpdk.org>
Subject: Re: [dpdk-dev] [PATCH v4] app/testpmd: support flow aging
Date: Sun, 3 May 2020 14:58:54 +0000
Message-ID: <AM6PR05MB5176726F089731759C6C94DDDBA90@AM6PR05MB5176.eurprd05.prod.outlook.com> (raw)
In-Reply-To: <20200503085948.27167-1-dongz@mellanox.com>



> -----Original Message-----
> From: Bill Zhou <dongz@mellanox.com>
> Subject: [PATCH v4] app/testpmd: support flow aging
> 
> Currently, there is no way to check the aging event or to get the current
> aged flows in testpmd, this patch include those implements, it's included:
> 
> - Add new item "flow_aged" to the current print event command arguments.
> - Add new command to list all aged flows, meanwhile, we can set parameter
>   to destroy it.
> 
> Signed-off-by: Bill Zhou <dongz@mellanox.com>
> ---
> v2: Update the way of registering aging event, add new command to control
> if the event need be print or not. Update the output of the delete aged
> flow command format.
> v3: Change the command from only set aged flow output to set one gloable
> verbose bitmap for all events output.
> v4: Add the event output to current global print event arguments.
> ---

Acked-by: Ori Kam <orika@mellanox.com>
Thanks,
Ori

>  app/test-pmd/cmdline.c                      |   4 +
>  app/test-pmd/cmdline_flow.c                 |  62 +++++++++++
>  app/test-pmd/config.c                       | 108 ++++++++++++++++++--
>  app/test-pmd/parameters.c                   |   6 +-
>  app/test-pmd/testpmd.c                      |   4 +-
>  app/test-pmd/testpmd.h                      |   3 +
>  doc/guides/testpmd_app_ug/testpmd_funcs.rst |  62 +++++++++++
>  7 files changed, 235 insertions(+), 14 deletions(-)
> 
> diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
> index 1375f223eb..bcf9080c48 100644
> --- a/app/test-pmd/cmdline.c
> +++ b/app/test-pmd/cmdline.c
> @@ -1125,6 +1125,10 @@ static void cmd_help_long_parsed(void
> *parsed_result,
>  			"    Restrict ingress traffic to the defined"
>  			" flow rules\n\n"
> 
> +			"flow aged {port_id} [destroy]\n"
> +			"    List and destroy aged flows"
> +			" flow rules\n\n"
> +
>  			"set vxlan ip-version (ipv4|ipv6) vni (vni) udp-src"
>  			" (udp-src) udp-dst (udp-dst) ip-src (ip-src) ip-dst"
>  			" (ip-dst) eth-src (eth-src) eth-dst (eth-dst)\n"
> diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
> index 45bcff3cf5..4e2006c543 100644
> --- a/app/test-pmd/cmdline_flow.c
> +++ b/app/test-pmd/cmdline_flow.c
> @@ -67,6 +67,7 @@ enum index {
>  	DUMP,
>  	QUERY,
>  	LIST,
> +	AGED,
>  	ISOLATE,
> 
>  	/* Destroy arguments. */
> @@ -78,6 +79,9 @@ enum index {
>  	/* List arguments. */
>  	LIST_GROUP,
> 
> +	/* Destroy aged flow arguments. */
> +	AGED_DESTROY,
> +
>  	/* Validate/create arguments. */
>  	GROUP,
>  	PRIORITY,
> @@ -664,6 +668,9 @@ struct buffer {
>  		struct {
>  			int set;
>  		} isolate; /**< Isolated mode arguments. */
> +		struct {
> +			int destroy;
> +		} aged; /**< Aged arguments. */
>  	} args; /**< Command arguments. */
>  };
> 
> @@ -719,6 +726,12 @@ static const enum index next_list_attr[] = {
>  	ZERO,
>  };
> 
> +static const enum index next_aged_attr[] = {
> +	AGED_DESTROY,
> +	END,
> +	ZERO,
> +};
> +
>  static const enum index item_param[] = {
>  	ITEM_PARAM_IS,
>  	ITEM_PARAM_SPEC,
> @@ -1466,6 +1479,9 @@ static int parse_action(struct context *, const struct
> token *,
>  static int parse_list(struct context *, const struct token *,
>  		      const char *, unsigned int,
>  		      void *, unsigned int);
> +static int parse_aged(struct context *, const struct token *,
> +		      const char *, unsigned int,
> +		      void *, unsigned int);
>  static int parse_isolate(struct context *, const struct token *,
>  			 const char *, unsigned int,
>  			 void *, unsigned int);
> @@ -1649,6 +1665,7 @@ static const struct token token_list[] = {
>  			      FLUSH,
>  			      DUMP,
>  			      LIST,
> +			      AGED,
>  			      QUERY,
>  			      ISOLATE)),
>  		.call = parse_init,
> @@ -1708,6 +1725,13 @@ static const struct token token_list[] = {
>  		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
>  		.call = parse_list,
>  	},
> +	[AGED] = {
> +		.name = "aged",
> +		.help = "list and destroy aged flows",
> +		.next = NEXT(next_aged_attr, NEXT_ENTRY(PORT_ID)),
> +		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
> +		.call = parse_aged,
> +	},
>  	[ISOLATE] = {
>  		.name = "isolate",
>  		.help = "restrict ingress traffic to the defined flow rules",
> @@ -1741,6 +1765,12 @@ static const struct token token_list[] = {
>  		.args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.list.group)),
>  		.call = parse_list,
>  	},
> +	[AGED_DESTROY] = {
> +		.name = "destroy",
> +		.help = "specify aged flows need be destroyed",
> +		.call = parse_aged,
> +		.comp = comp_none,
> +	},
>  	/* Validate/create attributes. */
>  	[GROUP] = {
>  		.name = "group",
> @@ -5367,6 +5397,35 @@ parse_list(struct context *ctx, const struct token
> *token,
>  	return len;
>  }
> 
> +/** Parse tokens for list all aged flows command. */
> +static int
> +parse_aged(struct context *ctx, const struct token *token,
> +	   const char *str, unsigned int len,
> +	   void *buf, unsigned int size)
> +{
> +	struct buffer *out = buf;
> +
> +	/* Token name must match. */
> +	if (parse_default(ctx, token, str, len, NULL, 0) < 0)
> +		return -1;
> +	/* Nothing else to do if there is no buffer. */
> +	if (!out)
> +		return len;
> +	if (!out->command) {
> +		if (ctx->curr != AGED)
> +			return -1;
> +		if (sizeof(*out) > size)
> +			return -1;
> +		out->command = ctx->curr;
> +		ctx->objdata = 0;
> +		ctx->object = out;
> +		ctx->objmask = NULL;
> +	}
> +	if (ctx->curr == AGED_DESTROY)
> +		out->args.aged.destroy = 1;
> +	return len;
> +}
> +
>  /** Parse tokens for isolate command. */
>  static int
>  parse_isolate(struct context *ctx, const struct token *token,
> @@ -6367,6 +6426,9 @@ cmd_flow_parsed(const struct buffer *in)
>  	case ISOLATE:
>  		port_flow_isolate(in->port, in->args.isolate.set);
>  		break;
> +	case AGED:
> +		port_flow_aged(in->port, in->args.aged.destroy);
> +		break;
>  	default:
>  		break;
>  	}
> diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
> index 72f25d1521..035d336ab5 100644
> --- a/app/test-pmd/config.c
> +++ b/app/test-pmd/config.c
> @@ -1367,6 +1367,26 @@ port_flow_validate(portid_t port_id,
>  	return 0;
>  }
> 
> +/** Update age action context by port_flow pointer. */
> +void
> +update_age_action_context(const struct rte_flow_action *actions,
> +			struct port_flow *pf)
> +{
> +	struct rte_flow_action_age *age = NULL;
> +
> +	for (; actions->type != RTE_FLOW_ACTION_TYPE_END; actions++) {
> +		switch (actions->type) {
> +		case RTE_FLOW_ACTION_TYPE_AGE:
> +			age = (struct rte_flow_action_age *)
> +				(uintptr_t)actions->conf;
> +			age->context = pf;
> +			return;
> +		default:
> +			break;
> +		}
> +	}
> +}
> +
>  /** Create flow rule. */
>  int
>  port_flow_create(portid_t port_id,
> @@ -1377,28 +1397,27 @@ port_flow_create(portid_t port_id,
>  	struct rte_flow *flow;
>  	struct rte_port *port;
>  	struct port_flow *pf;
> -	uint32_t id;
> +	uint32_t id = 0;
>  	struct rte_flow_error error;
> 
> -	/* Poisoning to make sure PMDs update it in case of error. */
> -	memset(&error, 0x22, sizeof(error));
> -	flow = rte_flow_create(port_id, attr, pattern, actions, &error);
> -	if (!flow)
> -		return port_flow_complain(&error);
>  	port = &ports[port_id];
>  	if (port->flow_list) {
>  		if (port->flow_list->id == UINT32_MAX) {
>  			printf("Highest rule ID is already assigned, delete"
>  			       " it first");
> -			rte_flow_destroy(port_id, flow, NULL);
>  			return -ENOMEM;
>  		}
>  		id = port->flow_list->id + 1;
> -	} else
> -		id = 0;
> +	}
>  	pf = port_flow_new(attr, pattern, actions, &error);
> -	if (!pf) {
> -		rte_flow_destroy(port_id, flow, NULL);
> +	if (!pf)
> +		return port_flow_complain(&error);
> +	update_age_action_context(actions, pf);
> +	/* Poisoning to make sure PMDs update it in case of error. */
> +	memset(&error, 0x22, sizeof(error));
> +	flow = rte_flow_create(port_id, attr, pattern, actions, &error);
> +	if (!flow) {
> +		free(pf);
>  		return port_flow_complain(&error);
>  	}
>  	pf->next = port->flow_list;
> @@ -1570,6 +1589,73 @@ port_flow_query(portid_t port_id, uint32_t rule,
>  	return 0;
>  }
> 
> +/** List simply and destroy all aged flows. */
> +void
> +port_flow_aged(portid_t port_id, uint8_t destroy)
> +{
> +	void **contexts;
> +	int nb_context, total = 0, idx;
> +	struct rte_flow_error error;
> +	struct port_flow *pf;
> +
> +	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
> +	    port_id == (portid_t)RTE_PORT_ALL)
> +		return;
> +	total = rte_flow_get_aged_flows(port_id, NULL, 0, &error);
> +	printf("Port %u total aged flows: %d\n", port_id, total);
> +	if (total < 0) {
> +		port_flow_complain(&error);
> +		return;
> +	}
> +	if (total == 0)
> +		return;
> +	contexts = malloc(sizeof(void *) * total);
> +	if (contexts == NULL) {
> +		printf("Cannot allocate contexts for aged flow\n");
> +		return;
> +	}
> +	printf("ID\tGroup\tPrio\tAttr\n");
> +	nb_context = rte_flow_get_aged_flows(port_id, contexts, total,
> &error);
> +	if (nb_context != total) {
> +		printf("Port:%d get aged flows count(%d) != total(%d)\n",
> +			port_id, nb_context, total);
> +		free(contexts);
> +		return;
> +	}
> +	for (idx = 0; idx < nb_context; idx++) {
> +		pf = (struct port_flow *)contexts[idx];
> +		if (!pf) {
> +			printf("Error: get Null context in port %u\n", port_id);
> +			continue;
> +		}
> +		printf("%" PRIu32 "\t%" PRIu32 "\t%" PRIu32 "\t%c%c%c\t\n",
> +		       pf->id,
> +		       pf->rule.attr->group,
> +		       pf->rule.attr->priority,
> +		       pf->rule.attr->ingress ? 'i' : '-',
> +		       pf->rule.attr->egress ? 'e' : '-',
> +		       pf->rule.attr->transfer ? 't' : '-');
> +	}
> +	if (destroy) {
> +		int ret;
> +		uint32_t flow_id;
> +
> +		total = 0;
> +		printf("\n");
> +		for (idx = 0; idx < nb_context; idx++) {
> +			pf = (struct port_flow *)contexts[idx];
> +			if (!pf)
> +				continue;
> +			flow_id = pf->id;
> +			ret = port_flow_destroy(port_id, 1, &flow_id);
> +			if (!ret)
> +				total++;
> +		}
> +		printf("%d flows be destroyed\n", total);
> +	}
> +	free(contexts);
> +}
> +
>  /** List flow rules. */
>  void
>  port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
> diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
> index 30c1753c32..92b5575626 100644
> --- a/app/test-pmd/parameters.c
> +++ b/app/test-pmd/parameters.c
> @@ -187,9 +187,9 @@ usage(char* progname)
>  	printf("  --no-rmv-interrupt: disable device removal interrupt.\n");
>  	printf("  --bitrate-stats=N: set the logical core N to perform "
>  		"bit-rate calculation.\n");
> -	printf("  --print-event
> <unknown|intr_lsc|queue_state|intr_reset|vf_mbox|macsec|intr_rmv|all>: "
> +	printf("  --print-event
> <unknown|intr_lsc|queue_state|intr_reset|vf_mbox|macsec|intr_rmv|flow_a
> ged|all>: "
>  	       "enable print of designated event or all of them.\n");
> -	printf("  --mask-event
> <unknown|intr_lsc|queue_state|intr_reset|vf_mbox|macsec|intr_rmv|all>: "
> +	printf("  --mask-event
> <unknown|intr_lsc|queue_state|intr_reset|vf_mbox|macsec|intr_rmv|flow_a
> ged|all>: "
>  	       "disable print of designated event or all of them.\n");
>  	printf("  --flow-isolate-all: "
>  	       "requests flow API isolated mode on all ports at initialization
> time.\n");
> @@ -545,6 +545,8 @@ parse_event_printing_config(const char *optarg, int
> enable)
>  		mask = UINT32_C(1) << RTE_ETH_EVENT_NEW;
>  	else if (!strcmp(optarg, "dev_released"))
>  		mask = UINT32_C(1) << RTE_ETH_EVENT_DESTROY;
> +	else if (!strcmp(optarg, "flow_aged"))
> +		mask = UINT32_C(1) << RTE_ETH_EVENT_FLOW_AGED;
>  	else if (!strcmp(optarg, "all"))
>  		mask = ~UINT32_C(0);
>  	else {
> diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
> index 99bacddbfd..a2d0be56b3 100644
> --- a/app/test-pmd/testpmd.c
> +++ b/app/test-pmd/testpmd.c
> @@ -375,6 +375,7 @@ static const char * const eth_event_desc[] = {
>  	[RTE_ETH_EVENT_INTR_RMV] = "device removal",
>  	[RTE_ETH_EVENT_NEW] = "device probed",
>  	[RTE_ETH_EVENT_DESTROY] = "device released",
> +	[RTE_ETH_EVENT_FLOW_AGED] = "flow aged",
>  	[RTE_ETH_EVENT_MAX] = NULL,
>  };
> 
> @@ -388,7 +389,8 @@ uint32_t event_print_mask = (UINT32_C(1) <<
> RTE_ETH_EVENT_UNKNOWN) |
>  			    (UINT32_C(1) << RTE_ETH_EVENT_INTR_RESET) |
>  			    (UINT32_C(1) << RTE_ETH_EVENT_IPSEC) |
>  			    (UINT32_C(1) << RTE_ETH_EVENT_MACSEC) |
> -			    (UINT32_C(1) << RTE_ETH_EVENT_INTR_RMV);
> +			    (UINT32_C(1) << RTE_ETH_EVENT_INTR_RMV) |
> +			    (UINT32_C(1) << RTE_ETH_EVENT_FLOW_AGED);
>  /*
>   * Decide if all memory are locked for performance.
>   */
> diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
> index 7ff4c5dba3..fb391672a8 100644
> --- a/app/test-pmd/testpmd.h
> +++ b/app/test-pmd/testpmd.h
> @@ -747,12 +747,15 @@ int port_flow_create(portid_t port_id,
>  		     const struct rte_flow_attr *attr,
>  		     const struct rte_flow_item *pattern,
>  		     const struct rte_flow_action *actions);
> +void update_age_action_context(const struct rte_flow_action *actions,
> +		     struct port_flow *pf);
>  int port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule);
>  int port_flow_flush(portid_t port_id);
>  int port_flow_dump(portid_t port_id, const char *file_name);
>  int port_flow_query(portid_t port_id, uint32_t rule,
>  		    const struct rte_flow_action *action);
>  void port_flow_list(portid_t port_id, uint32_t n, const uint32_t *group);
> +void port_flow_aged(portid_t port_id, uint8_t destroy);
>  int port_flow_isolate(portid_t port_id, int set);
> 
>  void rx_ring_desc_display(portid_t port_id, queueid_t rxq_id, uint16_t rxd_id);
> diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> index a360ecccfd..19260cc2d9 100644
> --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> @@ -3616,6 +3616,10 @@ following sections.
> 
>     flow dump {port_id} {output_file}
> 
> +- List and destroy aged flow rules::
> +
> +   flow aged {port_id} [destroy]
> +
>  Validating flow rules
>  ~~~~~~~~~~~~~~~~~~~~~
> 
> @@ -4503,6 +4507,64 @@ Otherwise, it will complain error occurred::
> 
>     Caught error type [...] ([...]): [...]
> 
> +Listing and destroying aged flow rules
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +``flow aged`` simply lists aged flow rules be get from api
> ``rte_flow_get_aged_flows``,
> +and ``destroy`` parameter can be used to destroy those flow rules in PMD.
> +
> +   flow aged {port_id} [destroy]
> +
> +Listing current aged flow rules::
> +
> +   testpmd> flow aged 0
> +   Port 0 total aged flows: 0
> +   testpmd> flow create 0 ingress pattern eth / ipv4 src is 2.2.2.14 / end
> +      actions age timeout 5 / queue index 0 /  end
> +   Flow rule #0 created
> +   testpmd> flow create 0 ingress pattern eth / ipv4 src is 2.2.2.15 / end
> +      actions age timeout 4 / queue index 0 /  end
> +   Flow rule #1 created
> +   testpmd> flow create 0 ingress pattern eth / ipv4 src is 2.2.2.16 / end
> +      actions age timeout 2 / queue index 0 /  end
> +   Flow rule #2 created
> +   testpmd> flow create 0 ingress pattern eth / ipv4 src is 2.2.2.17 / end
> +      actions age timeout 3 / queue index 0 /  end
> +   Flow rule #3 created
> +
> +
> +Aged Rules are simply list as command ``flow list {port_id}``, but strip the
> detail rule
> +information, all the aged flows are sorted by the longest timeout time. For
> example, if
> +those rules be configured in the same time, ID 2 will be the first aged out rule,
> the next
> +will be ID 3, ID 1, ID 0::
> +
> +   testpmd> flow aged 0
> +   Port 0 total aged flows: 4
> +   ID      Group   Prio    Attr
> +   2       0       0       i--
> +   3       0       0       i--
> +   1       0       0       i--
> +   0       0       0       i--
> +
> +If attach ``destroy`` parameter, the command will destroy all the list aged
> flow rules.
> +
> +   testpmd> flow aged 0 destroy
> +   Port 0 total aged flows: 4
> +   ID      Group   Prio    Attr
> +   2       0       0       i--
> +   3       0       0       i--
> +   1       0       0       i--
> +   0       0       0       i--
> +
> +   Flow rule #2 destroyed
> +   Flow rule #3 destroyed
> +   Flow rule #1 destroyed
> +   Flow rule #0 destroyed
> +   4 flows be destroyed
> +   testpmd> flow aged 0
> +   Port 0 total aged flows: 0
> +
> +
>  Sample QinQ flow rules
>  ~~~~~~~~~~~~~~~~~~~~~~
> 
> --
> 2.21.0


  parent reply	other threads:[~2020-05-03 14:58 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-24 10:55 [dpdk-dev] [PATCH] " Bill Zhou
2020-04-24 16:25 ` Ferruh Yigit
2020-04-26  7:23   ` Bill Zhou
2020-04-27 14:13     ` Ferruh Yigit
2020-04-27 15:12       ` Matan Azrad
2020-04-30 22:26         ` Ferruh Yigit
2020-04-30 15:53 ` [dpdk-dev] [PATCH v2] " Bill Zhou
2020-04-30 22:43   ` Ferruh Yigit
2020-05-01  6:51     ` Matan Azrad
2020-05-01  9:27       ` Ferruh Yigit
2020-05-01 11:28         ` Matan Azrad
2020-05-01 11:54           ` Ferruh Yigit
2020-05-01 12:45             ` Matan Azrad
2020-05-01 13:38               ` Ferruh Yigit
2020-05-01 15:14                 ` Matan Azrad
2020-05-01 15:44                   ` Ferruh Yigit
2020-05-02 14:00   ` [dpdk-dev] [PATCH v3] " Bill Zhou
2020-05-03  8:59     ` [dpdk-dev] [PATCH v4] " Bill Zhou
2020-05-03  9:46       ` Matan Azrad
2020-05-03 14:58       ` Ori Kam [this message]
2020-05-05  8:37       ` Ferruh Yigit
2020-05-05  9:11         ` Matan Azrad
2020-05-05  9:23           ` Ferruh Yigit
2020-05-05  9:49       ` [dpdk-dev] [PATCH v5] " Bill Zhou
2020-05-05 10:09         ` Ori Kam
2020-05-05 15:11           ` Ferruh Yigit
2020-05-06  8:04             ` Matan Azrad

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=AM6PR05MB5176726F089731759C6C94DDDBA90@AM6PR05MB5176.eurprd05.prod.outlook.com \
    --to=orika@mellanox.com \
    --cc=beilei.xing@intel.com \
    --cc=bernard.iremonger@intel.com \
    --cc=dev@dpdk.org \
    --cc=dongz@mellanox.com \
    --cc=ferruh.yigit@intel.com \
    --cc=john.mcnamara@intel.com \
    --cc=marko.kovacevic@intel.com \
    --cc=matan@mellanox.com \
    --cc=wenzhuo.lu@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

DPDK patches and discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://inbox.dpdk.org/dev/0 dev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 dev dev/ https://inbox.dpdk.org/dev \
		dev@dpdk.org
	public-inbox-index dev

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://inbox.dpdk.org/inbox.dpdk.dev


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git