DPDK patches and discussions
 help / color / mirror / Atom feed
From: Bill Zhou <dongz@mellanox.com>
To: ferruh.yigit@intel.com, matan@mellanox.com, wenzhuo.lu@intel.com,
	beilei.xing@intel.com, bernard.iremonger@intel.com,
	orika@mellanox.com, john.mcnamara@intel.com,
	marko.kovacevic@intel.com
Cc: dev@dpdk.org
Subject: [dpdk-dev] [PATCH v3] app/testpmd: support flow aging
Date: Sat,  2 May 2020 17:00:23 +0300
Message-ID: <20200502140023.5274-1-dongz@mellanox.com> (raw)
In-Reply-To: <20200430155345.1384-1-dongz@mellanox.com>

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:
- Registering aging event when the testpmd application start, add one new
  command to set verbose bitmaps for all events. If RTE_ETH_EVENT_FLOW_AGED
  bit is set, when new flow be checked age out, there will be output log
  for it.
- 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.
---
 app/test-pmd/cmdline.c                      |  49 +++++++++
 app/test-pmd/cmdline_flow.c                 |  62 +++++++++++
 app/test-pmd/config.c                       | 108 ++++++++++++++++++--
 app/test-pmd/testpmd.c                      |  12 +++
 app/test-pmd/testpmd.h                      |   4 +
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  79 ++++++++++++++
 6 files changed, 303 insertions(+), 11 deletions(-)

diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 1375f223eb..a257f25e01 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -263,6 +263,9 @@ static void cmd_help_long_parsed(void *parsed_result,
 			"set verbose (level)\n"
 			"    Set the debug verbosity level X.\n\n"
 
+			"set event_verbose (bitmap)\n"
+			"    Set the event debug verbose 32 bits bitmap X.\n\n"
+
 			"set log global|(type) (level)\n"
 			"    Set the log level.\n\n"
 
@@ -1125,6 +1128,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"
@@ -19387,6 +19394,47 @@ cmdline_parse_inst_t cmd_showport_macs = {
 	},
 };
 
+/* Set event verbose bitmap*/
+struct cmd_set_event_verbose_result {
+	cmdline_fixed_string_t set;
+	cmdline_fixed_string_t keyword;
+	uint32_t bitmap;
+};
+cmdline_parse_token_string_t cmd_set_event_verbose_set =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_event_verbose_result,
+		set, "set");
+cmdline_parse_token_string_t cmd_set_event_verbose_keyword =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_event_verbose_result,
+		keyword, "event_verbose");
+cmdline_parse_token_num_t cmd_set_event_verbose_bitmap =
+	TOKEN_NUM_INITIALIZER(struct cmd_set_event_verbose_result,
+		bitmap, UINT32);
+
+static void
+cmd_set_event_verbose_parsed(void *parsed_result,
+				__rte_unused struct cmdline *cl,
+				__rte_unused void *data)
+{
+	struct cmd_set_event_verbose_result *res = parsed_result;
+
+	printf("Change event verbose bitmap from 0x%x to 0x%x\n",
+	       (unsigned int) event_verbose_bitmap,
+	       (unsigned int) res->bitmap);
+	event_verbose_bitmap = res->bitmap;
+}
+
+cmdline_parse_inst_t cmd_set_event_verbose = {
+	.f = cmd_set_event_verbose_parsed,
+	.data = NULL,
+	.help_str = "set event_verbose (bitmap)",
+	.tokens = {
+		(void *)&cmd_set_event_verbose_set,
+		(void *)&cmd_set_event_verbose_keyword,
+		(void *)&cmd_set_event_verbose_bitmap,
+		NULL,
+	},
+};
+
 /* ******************************************************************************** */
 
 /* list of instructions */
@@ -19684,6 +19732,7 @@ cmdline_parse_ctx_t main_ctx[] = {
 	(cmdline_parse_inst_t *)&cmd_show_set_raw,
 	(cmdline_parse_inst_t *)&cmd_show_set_raw_all,
 	(cmdline_parse_inst_t *)&cmd_config_tx_dynf_specific,
+	(cmdline_parse_inst_t *)&cmd_set_event_verbose,
 	NULL,
 };
 
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/testpmd.c b/app/test-pmd/testpmd.c
index 99bacddbfd..689467f736 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -81,6 +81,7 @@
 #define EXTBUF_ZONE_SIZE RTE_PGSIZE_2M
 
 uint16_t verbose_level = 0; /**< Silent by default. */
+uint32_t event_verbose_bitmap; /**< Verbose bitmap for all events */
 int testpmd_logtype; /**< Log type for testpmd logs */
 
 /* use master core for command line ? */
@@ -3068,6 +3069,15 @@ rmv_port_callback(void *arg)
 		start_packet_forwarding(0);
 }
 
+static void
+aging_event_output(uint16_t portid)
+{
+	if (event_verbose_bitmap & (1 << RTE_ETH_EVENT_FLOW_AGED)) {
+		printf("port %u RTE_ETH_EVENT_FLOW_AGED triggered\n", portid);
+		fflush(stdout);
+	}
+}
+
 /* This function is used by the interrupt thread */
 static int
 eth_event_callback(portid_t port_id, enum rte_eth_event_type type, void *param,
@@ -3098,6 +3108,8 @@ eth_event_callback(portid_t port_id, enum rte_eth_event_type type, void *param,
 				rmv_port_callback, (void *)(intptr_t)port_id))
 			fprintf(stderr, "Could not set up deferred device removal\n");
 		break;
+	case RTE_ETH_EVENT_FLOW_AGED:
+		aging_event_output(port_id);
 	default:
 		break;
 	}
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 7ff4c5dba3..674c3670f9 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -323,6 +323,7 @@ extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
 /* globals used for configuration */
 extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */
+extern uint32_t event_verbose_bitmap; /**< Verbose bitmap for all events */
 extern int testpmd_logtype; /**< Log type for testpmd logs */
 extern uint8_t  interactive;
 extern uint8_t  auto_start;
@@ -747,12 +748,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..b5eb59ba46 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -632,6 +632,23 @@ Available levels are as following:
 * ``2`` fully verbose except for Rx packets.
 * ``> 2`` fully verbose.
 
+set event_verbose
+~~~~~~~~~~~~~~~~~
+
+Set the debug verbosity bitmaps for all events defined in enum rte_eth_event_type,
+the maximum bits of the bitmap is 32::
+
+   testpmd> set event_verbose (bitmap)
+
+For examine, to start the event:RTE_ETH_EVENT_FLOW_AGED log::
+
+   testpmd> set event_verbose 0x400
+   Change event verbose bitmap from 0x0 to 0x400
+
+When aged flow be checkout, there will be one output log for it::
+
+   testpmd> port 0 RTE_ETH_EVENT_FLOW_AGED triggered
+
 set log
 ~~~~~~~
 
@@ -3616,6 +3633,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 +4524,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-02 14:00 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   ` Bill Zhou [this message]
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
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=20200502140023.5274-1-dongz@mellanox.com \
    --to=dongz@mellanox.com \
    --cc=beilei.xing@intel.com \
    --cc=bernard.iremonger@intel.com \
    --cc=dev@dpdk.org \
    --cc=ferruh.yigit@intel.com \
    --cc=john.mcnamara@intel.com \
    --cc=marko.kovacevic@intel.com \
    --cc=matan@mellanox.com \
    --cc=orika@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