[dpdk-dev,1/3] app/testpmd: Moved cmdline_flow to librte_cmdline

Message ID 1515790887-64502-1-git-send-email-george.dit@gmail.com (mailing list archive)
State Rejected, archived
Delegated to: Thomas Monjalon
Headers

Checks

Context Check Description
ci/checkpatch warning coding style issues
ci/Intel-compilation fail apply patch file failure

Commit Message

Georgios Katsikas Jan. 12, 2018, 9:01 p.m. UTC
  Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
---
 app/test-pmd/Makefile             |    1 -
 app/test-pmd/cmdline_flow.c       | 2959 -------------------------------------
 app/test-pmd/config.c             |  541 +------
 app/test-pmd/testpmd.h            |   91 +-
 lib/Makefile                      |    4 +-
 lib/librte_cmdline/Makefile       |    3 +-
 lib/librte_cmdline/cmdline.h      |    2 +-
 lib/librte_cmdline/cmdline_flow.c | 1790 ++++++++++++++++++++++
 lib/librte_cmdline/cmdline_flow.h | 1852 +++++++++++++++++++++++
 lib/librte_ether/rte_flow.c       |   18 +-
 lib/librte_ether/rte_flow.h       |   24 +
 11 files changed, 3675 insertions(+), 3610 deletions(-)
 delete mode 100644 app/test-pmd/cmdline_flow.c
 create mode 100644 lib/librte_cmdline/cmdline_flow.c
 create mode 100644 lib/librte_cmdline/cmdline_flow.h
  

Comments

Wenzhuo Lu Jan. 15, 2018, 1:30 a.m. UTC | #1
Hi,

> -----Original Message-----
> From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> Sent: Saturday, January 13, 2018 5:01 AM
> To: olivier.matz@6wind.com
> Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> librte_cmdline
> 
> Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
Looks like a good idea to move this code to the lib.
cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
  
Olivier Matz Jan. 16, 2018, 8:39 a.m. UTC | #2
Hi Georgios,

On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> Hi,
> 
> > -----Original Message-----
> > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> > Sent: Saturday, January 13, 2018 5:01 AM
> > To: olivier.matz@6wind.com
> > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> > librte_cmdline
> > 
> > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> Looks like a good idea to move this code to the lib.
> cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.

If the command line parsing of rte_flow is something that has some
chances to be shared among multiple applications, I agree it makes sense
to move it in a library.

However, my opinion is that it would be better to have a specific
library for it, like librte_flow_cmdline, because I'm not sure that
people linking with librte_cmdline always want to pull the rte_flow
parsing code.
  
Georgios Katsikas Jan. 16, 2018, 8:45 a.m. UTC | #3
Hi Lu, Oliver,

Thanks for your feedback!
You have a point here, flow commands are only a subset of the parser.
Do you want me to create this new library and send another patch?
I guess I have to use librte_cmdline as a template/example for creating the
librte_flow_cmdline library.

Best regards,
Georgios

On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com> wrote:

> Hi Georgios,
>
> On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > Hi,
> >
> > > -----Original Message-----
> > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> > > Sent: Saturday, January 13, 2018 5:01 AM
> > > To: olivier.matz@6wind.com
> > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> > > librte_cmdline
> > >
> > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > Looks like a good idea to move this code to the lib.
> > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
>
> If the command line parsing of rte_flow is something that has some
> chances to be shared among multiple applications, I agree it makes sense
> to move it in a library.
>
> However, my opinion is that it would be better to have a specific
> library for it, like librte_flow_cmdline, because I'm not sure that
> people linking with librte_cmdline always want to pull the rte_flow
> parsing code.
>
>
  
Olivier Matz Jan. 16, 2018, 9:24 a.m. UTC | #4
Hi,

> On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com> wrote:
>
> On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
> > Hi Georgios,
> >
> > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > > Hi,
> > >
> > > > -----Original Message-----
> > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> > > > Sent: Saturday, January 13, 2018 5:01 AM
> > > > To: olivier.matz@6wind.com
> > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> > > > librte_cmdline
> > > >
> > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > > Looks like a good idea to move this code to the lib.
> > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
> >
> > If the command line parsing of rte_flow is something that has some
> > chances to be shared among multiple applications, I agree it makes sense
> > to move it in a library.
> >
> > However, my opinion is that it would be better to have a specific
> > library for it, like librte_flow_cmdline, because I'm not sure that
> > people linking with librte_cmdline always want to pull the rte_flow
> > parsing code.
> >
> >
> Hi Lu, Oliver,
> 
> Thanks for your feedback!
> You have a point here, flow commands are only a subset of the parser.
> Do you want me to create this new library and send another patch?

Let's first wait for Adrien's feedback, he can have counter-arguments.

> I guess I have to use librte_cmdline as a template/example for creating the
> librte_flow_cmdline library.

It can be used as an example for Makefile and .map file.
  
Gaëtan Rivet Jan. 16, 2018, 9:49 a.m. UTC | #5
Hi Olivier, George, thanks for following up on that,

On Tue, Jan 16, 2018 at 10:24:25AM +0100, Olivier Matz wrote:
> Hi,
> 
> > On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com> wrote:
> >
> > On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
> > > Hi Georgios,
> > >
> > > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > > > Hi,
> > > >
> > > > > -----Original Message-----
> > > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> > > > > Sent: Saturday, January 13, 2018 5:01 AM
> > > > > To: olivier.matz@6wind.com
> > > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> > > > > librte_cmdline
> > > > >
> > > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > > > Looks like a good idea to move this code to the lib.
> > > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
> > >
> > > If the command line parsing of rte_flow is something that has some
> > > chances to be shared among multiple applications, I agree it makes sense
> > > to move it in a library.
> > >
> > > However, my opinion is that it would be better to have a specific
> > > library for it, like librte_flow_cmdline, because I'm not sure that
> > > people linking with librte_cmdline always want to pull the rte_flow
> > > parsing code.
> > >

Why not a .config option to enable its build, instead of a separate
library?

> > >
> > Hi Lu, Oliver,
> > 
> > Thanks for your feedback!
> > You have a point here, flow commands are only a subset of the parser.
> > Do you want me to create this new library and send another patch?
> 
> Let's first wait for Adrien's feedback, he can have counter-arguments.
> 
> > I guess I have to use librte_cmdline as a template/example for creating the
> > librte_flow_cmdline library.
> 
> It can be used as an example for Makefile and .map file.
  
Adrien Mazarguil Jan. 16, 2018, 2:31 p.m. UTC | #6
George,

I missed the original RFC thread [1][2] (you should have used it as a cover
letter for this series BTW) please see below.

On Tue, Jan 16, 2018 at 10:24:25AM +0100, Olivier Matz wrote:
> Hi,
> 
> > On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com> wrote:
> >
> > On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
> > > Hi Georgios,
> > >
> > > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > > > Hi,
> > > >
> > > > > -----Original Message-----
> > > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios Katsikas
> > > > > Sent: Saturday, January 13, 2018 5:01 AM
> > > > > To: olivier.matz@6wind.com
> > > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow to
> > > > > librte_cmdline
> > > > >
> > > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > > > Looks like a good idea to move this code to the lib.
> > > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
> > >
> > > If the command line parsing of rte_flow is something that has some
> > > chances to be shared among multiple applications, I agree it makes sense
> > > to move it in a library.
> > >
> > > However, my opinion is that it would be better to have a specific
> > > library for it, like librte_flow_cmdline, because I'm not sure that
> > > people linking with librte_cmdline always want to pull the rte_flow
> > > parsing code.

In my opinion the entire flow command parser has nothing to do outside
testpmd, it's way too specific, even if another application finds it useful.

Code duplication being a bad thing, your application could as well compile
or even #include app/test-pmd/cmdline_flow.c directly (not pretty, I know)
since it would have the same internal layout as testpmd. Testpmd's Makefile
could be modified to spit it out as a separate library if needed.

What could make more sense would be to extract the parser engine for
librte_cmdline's dynamic tokens (e.g. struct context, struct arg, struct
token) and possibly various generic helpers (e.g. parse_string,
parse_mac_addr), but not enum index nor token_list[] and friends which
define the layout of testpmd's flow command.

This would enable other flow-like commands without duplicating the engine
every time, however I'm still not sure if making it available outside
testpmd is a good idea. Extracting and making it fully generic will require
a considerable amount of work.

> > Hi Lu, Oliver,
> > 
> > Thanks for your feedback!
> > You have a point here, flow commands are only a subset of the parser.
> > Do you want me to create this new library and send another patch?
> 
> Let's first wait for Adrien's feedback, he can have counter-arguments.
> 
> > I guess I have to use librte_cmdline as a template/example for creating the
> > librte_flow_cmdline library.
> 
> It can be used as an example for Makefile and .map file.

I'm not opposed to the idea of exporting the parser engine after making it
properly generic, but please reconsider. Testpmd is an application made to
validate PMD functionality. The flow command implementation, as neat as it
is, remains a complex wrapper on top of the cmdline_parse API which wasn't
originally designed for dynamic tokens. Its syntax may evolve without
warning if deemed necessary. Making it public will subject it to exported
API/ABI constraints and likely impede future evolution.

Assuming your application is not dragging testpmd's legacy, I think it would
be wiser to re-implement a simpler flow command look-alike on top of a more
suitable command-line handling library if you like its syntax.

[1] http://dpdk.org/ml/archives/dev/2018-January/086872.html
[2] Message-ID: CAN9HtFDz+imqbCKfs6a0NE0W7iF8C+-KiNB0nCRywimspjfEDg@mail.gmail.com
  
Georgios Katsikas Jan. 16, 2018, 2:54 p.m. UTC | #7
Hi Adrien,

Thanks for your insights and sorry for omitting the cover letter (this is
my first patch in DPDK).
I understand your concerns. The reason I proposed this patch is to
facilitate a more high level vendor agnostic API for configuring and
monitoring DPDK-based NICs.

To avoid just copying thousands of lines of code into my application, do
you think it is feasible to move at least the components (struct context,
struct arg, struct token and the parse_* helpers) you mentioned and
restructure testpmd in a way that allows applications to re-use all of its
parsing features?
I could give it a try and come back with a new patch, otherwise I am
perfectly ok if you want to do it instead.

Best regards,
Georgios

On Tue, Jan 16, 2018 at 3:31 PM, Adrien Mazarguil <
adrien.mazarguil@6wind.com> wrote:

> George,
>
> I missed the original RFC thread [1][2] (you should have used it as a cover
> letter for this series BTW) please see below.
>
> On Tue, Jan 16, 2018 at 10:24:25AM +0100, Olivier Matz wrote:
> > Hi,
> >
> > > On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com>
> wrote:
> > >
> > > On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
> > > > Hi Georgios,
> > > >
> > > > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > > > > Hi,
> > > > >
> > > > > > -----Original Message-----
> > > > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios
> Katsikas
> > > > > > Sent: Saturday, January 13, 2018 5:01 AM
> > > > > > To: olivier.matz@6wind.com
> > > > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow
> to
> > > > > > librte_cmdline
> > > > > >
> > > > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > > > > Looks like a good idea to move this code to the lib.
> > > > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
> > > >
> > > > If the command line parsing of rte_flow is something that has some
> > > > chances to be shared among multiple applications, I agree it makes
> sense
> > > > to move it in a library.
> > > >
> > > > However, my opinion is that it would be better to have a specific
> > > > library for it, like librte_flow_cmdline, because I'm not sure that
> > > > people linking with librte_cmdline always want to pull the rte_flow
> > > > parsing code.
>
> In my opinion the entire flow command parser has nothing to do outside
> testpmd, it's way too specific, even if another application finds it
> useful.
>
> Code duplication being a bad thing, your application could as well compile
> or even #include app/test-pmd/cmdline_flow.c directly (not pretty, I know)
> since it would have the same internal layout as testpmd. Testpmd's Makefile
> could be modified to spit it out as a separate library if needed.
>
> What could make more sense would be to extract the parser engine for
> librte_cmdline's dynamic tokens (e.g. struct context, struct arg, struct
> token) and possibly various generic helpers (e.g. parse_string,
> parse_mac_addr), but not enum index nor token_list[] and friends which
> define the layout of testpmd's flow command.
>
> This would enable other flow-like commands without duplicating the engine
> every time, however I'm still not sure if making it available outside
> testpmd is a good idea. Extracting and making it fully generic will require
> a considerable amount of work.
>
> > > Hi Lu, Oliver,
> > >
> > > Thanks for your feedback!
> > > You have a point here, flow commands are only a subset of the parser.
> > > Do you want me to create this new library and send another patch?
> >
> > Let's first wait for Adrien's feedback, he can have counter-arguments.
> >
> > > I guess I have to use librte_cmdline as a template/example for
> creating the
> > > librte_flow_cmdline library.
> >
> > It can be used as an example for Makefile and .map file.
>
> I'm not opposed to the idea of exporting the parser engine after making it
> properly generic, but please reconsider. Testpmd is an application made to
> validate PMD functionality. The flow command implementation, as neat as it
> is, remains a complex wrapper on top of the cmdline_parse API which wasn't
> originally designed for dynamic tokens. Its syntax may evolve without
> warning if deemed necessary. Making it public will subject it to exported
> API/ABI constraints and likely impede future evolution.
>
> Assuming your application is not dragging testpmd's legacy, I think it
> would
> be wiser to re-implement a simpler flow command look-alike on top of a more
> suitable command-line handling library if you like its syntax.
>
> [1] http://dpdk.org/ml/archives/dev/2018-January/086872.html
> [2] Message-ID: CAN9HtFDz+imqbCKfs6a0NE0W7iF8C+-KiNB0nCRywimspjfEDg@mail.
> gmail.com
>
> --
> Adrien Mazarguil
> 6WIND
>
  
Adrien Mazarguil Jan. 16, 2018, 5:54 p.m. UTC | #8
On Tue, Jan 16, 2018 at 03:54:57PM +0100, george.dit@gmail.com wrote:
> Hi Adrien,
> 
> Thanks for your insights and sorry for omitting the cover letter (this is
> my first patch in DPDK).

No problem, don't worry about that. Remember to put as much context as close
as possible to the related changes. The commit log of a patch is in fact a
more appropriate place since a cover letter is simply a summary of a
subsequent series and is discarded once applied.

> I understand your concerns. The reason I proposed this patch is to
> facilitate a more high level vendor agnostic API for configuring and
> monitoring DPDK-based NICs.
> 
> To avoid just copying thousands of lines of code into my application, do
> you think it is feasible to move at least the components (struct context,
> struct arg, struct token and the parse_* helpers) you mentioned and
> restructure testpmd in a way that allows applications to re-use all of its
> parsing features?

Yes I think it's feasible, although at the cost of great effort because
you'd need to untangle engine code from parser code to expose the former,
the flow command being a mix of both. Proper APIs must be devised for that,
hence my question: is it really worth the trouble?

Other contributors already asked me how they could reuse the flow command
parser to implement similar testpmd commands without copy/pasting the entire
file, so I'm already convinced separating at least the engine part makes
sense at the testpmd level. Moving it to librte_cmdline for external
applications seems more complex though.

> I could give it a try and come back with a new patch, otherwise I am
> perfectly ok if you want to do it instead.

While I'd certainly like to do it (at least at the testpmd level), it's
unlikely to happen anytime soon due to other priorities.

Feel free to take care of it if you're motivated enough, just keep in mind
right now I don't think this should be exposed as a public API. I can change
my mind if you manage to make it generic enough.

> On Tue, Jan 16, 2018 at 3:31 PM, Adrien Mazarguil <
> adrien.mazarguil@6wind.com> wrote:
> 
> > George,
> >
> > I missed the original RFC thread [1][2] (you should have used it as a cover
> > letter for this series BTW) please see below.
> >
> > On Tue, Jan 16, 2018 at 10:24:25AM +0100, Olivier Matz wrote:
> > > Hi,
> > >
> > > > On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com>
> > wrote:
> > > >
> > > > On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
> > > > > Hi Georgios,
> > > > >
> > > > > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
> > > > > > Hi,
> > > > > >
> > > > > > > -----Original Message-----
> > > > > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios
> > Katsikas
> > > > > > > Sent: Saturday, January 13, 2018 5:01 AM
> > > > > > > To: olivier.matz@6wind.com
> > > > > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
> > > > > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow
> > to
> > > > > > > librte_cmdline
> > > > > > >
> > > > > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
> > > > > > Looks like a good idea to move this code to the lib.
> > > > > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
> > > > >
> > > > > If the command line parsing of rte_flow is something that has some
> > > > > chances to be shared among multiple applications, I agree it makes
> > sense
> > > > > to move it in a library.
> > > > >
> > > > > However, my opinion is that it would be better to have a specific
> > > > > library for it, like librte_flow_cmdline, because I'm not sure that
> > > > > people linking with librte_cmdline always want to pull the rte_flow
> > > > > parsing code.
> >
> > In my opinion the entire flow command parser has nothing to do outside
> > testpmd, it's way too specific, even if another application finds it
> > useful.
> >
> > Code duplication being a bad thing, your application could as well compile
> > or even #include app/test-pmd/cmdline_flow.c directly (not pretty, I know)
> > since it would have the same internal layout as testpmd. Testpmd's Makefile
> > could be modified to spit it out as a separate library if needed.
> >
> > What could make more sense would be to extract the parser engine for
> > librte_cmdline's dynamic tokens (e.g. struct context, struct arg, struct
> > token) and possibly various generic helpers (e.g. parse_string,
> > parse_mac_addr), but not enum index nor token_list[] and friends which
> > define the layout of testpmd's flow command.
> >
> > This would enable other flow-like commands without duplicating the engine
> > every time, however I'm still not sure if making it available outside
> > testpmd is a good idea. Extracting and making it fully generic will require
> > a considerable amount of work.
> >
> > > > Hi Lu, Oliver,
> > > >
> > > > Thanks for your feedback!
> > > > You have a point here, flow commands are only a subset of the parser.
> > > > Do you want me to create this new library and send another patch?
> > >
> > > Let's first wait for Adrien's feedback, he can have counter-arguments.
> > >
> > > > I guess I have to use librte_cmdline as a template/example for
> > creating the
> > > > librte_flow_cmdline library.
> > >
> > > It can be used as an example for Makefile and .map file.
> >
> > I'm not opposed to the idea of exporting the parser engine after making it
> > properly generic, but please reconsider. Testpmd is an application made to
> > validate PMD functionality. The flow command implementation, as neat as it
> > is, remains a complex wrapper on top of the cmdline_parse API which wasn't
> > originally designed for dynamic tokens. Its syntax may evolve without
> > warning if deemed necessary. Making it public will subject it to exported
> > API/ABI constraints and likely impede future evolution.
> >
> > Assuming your application is not dragging testpmd's legacy, I think it
> > would
> > be wiser to re-implement a simpler flow command look-alike on top of a more
> > suitable command-line handling library if you like its syntax.
> >
> > [1] http://dpdk.org/ml/archives/dev/2018-January/086872.html
> > [2] Message-ID: CAN9HtFDz+imqbCKfs6a0NE0W7iF8C+-KiNB0nCRywimspjfEDg@mail.
> > gmail.com
> >
> > --
> > Adrien Mazarguil
> > 6WIND
> >
> 
> 
> 
> -- 
> Georgios Katsikas
> Industrial Ph.D. Student
> Network Intelligence Group
> Decision, Networks, and Analytics (DNA) Lab
> RISE SICS
> E-Mail:  georgios.katsikas@ri.se
  
Georgios Katsikas Jan. 24, 2018, 11:57 a.m. UTC | #9
Hi again,

I decided to simplify things for now, hence sent a new minimal patch
only for testpmd (not librte_cmdline), which allows external
applications to compile against it without errors.

Thanks again for you time,
Georgios

On Tue, Jan 16, 2018 at 6:54 PM, Adrien Mazarguil
<adrien.mazarguil@6wind.com> wrote:
> On Tue, Jan 16, 2018 at 03:54:57PM +0100, george.dit@gmail.com wrote:
>> Hi Adrien,
>>
>> Thanks for your insights and sorry for omitting the cover letter (this is
>> my first patch in DPDK).
>
> No problem, don't worry about that. Remember to put as much context as close
> as possible to the related changes. The commit log of a patch is in fact a
> more appropriate place since a cover letter is simply a summary of a
> subsequent series and is discarded once applied.
>
>> I understand your concerns. The reason I proposed this patch is to
>> facilitate a more high level vendor agnostic API for configuring and
>> monitoring DPDK-based NICs.
>>
>> To avoid just copying thousands of lines of code into my application, do
>> you think it is feasible to move at least the components (struct context,
>> struct arg, struct token and the parse_* helpers) you mentioned and
>> restructure testpmd in a way that allows applications to re-use all of its
>> parsing features?
>
> Yes I think it's feasible, although at the cost of great effort because
> you'd need to untangle engine code from parser code to expose the former,
> the flow command being a mix of both. Proper APIs must be devised for that,
> hence my question: is it really worth the trouble?
>
> Other contributors already asked me how they could reuse the flow command
> parser to implement similar testpmd commands without copy/pasting the entire
> file, so I'm already convinced separating at least the engine part makes
> sense at the testpmd level. Moving it to librte_cmdline for external
> applications seems more complex though.
>
>> I could give it a try and come back with a new patch, otherwise I am
>> perfectly ok if you want to do it instead.
>
> While I'd certainly like to do it (at least at the testpmd level), it's
> unlikely to happen anytime soon due to other priorities.
>
> Feel free to take care of it if you're motivated enough, just keep in mind
> right now I don't think this should be exposed as a public API. I can change
> my mind if you manage to make it generic enough.
>
>> On Tue, Jan 16, 2018 at 3:31 PM, Adrien Mazarguil <
>> adrien.mazarguil@6wind.com> wrote:
>>
>> > George,
>> >
>> > I missed the original RFC thread [1][2] (you should have used it as a cover
>> > letter for this series BTW) please see below.
>> >
>> > On Tue, Jan 16, 2018 at 10:24:25AM +0100, Olivier Matz wrote:
>> > > Hi,
>> > >
>> > > > On Tue, Jan 16, 2018 at 9:40 AM Olivier Matz <olivier.matz@6wind.com>
>> > wrote:
>> > > >
>> > > > On Tue, Jan 16, 2018 at 08:45:32AM +0000, george.dit@gmail.com wrote:
>> > > > > Hi Georgios,
>> > > > >
>> > > > > On Mon, Jan 15, 2018 at 01:30:35AM +0000, Lu, Wenzhuo wrote:
>> > > > > > Hi,
>> > > > > >
>> > > > > > > -----Original Message-----
>> > > > > > > From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Georgios
>> > Katsikas
>> > > > > > > Sent: Saturday, January 13, 2018 5:01 AM
>> > > > > > > To: olivier.matz@6wind.com
>> > > > > > > Cc: dev@dpdk.org; Georgios Katsikas <george.dit@gmail.com>
>> > > > > > > Subject: [dpdk-dev] [PATCH 1/3] app/testpmd: Moved cmdline_flow
>> > to
>> > > > > > > librte_cmdline
>> > > > > > >
>> > > > > > > Signed-off-by: Georgios Katsikas <george.dit@gmail.com>
>> > > > > > Looks like a good idea to move this code to the lib.
>> > > > > > cc Adrien the author of this file, app/test-pmd/cmdline_flow.c.
>> > > > >
>> > > > > If the command line parsing of rte_flow is something that has some
>> > > > > chances to be shared among multiple applications, I agree it makes
>> > sense
>> > > > > to move it in a library.
>> > > > >
>> > > > > However, my opinion is that it would be better to have a specific
>> > > > > library for it, like librte_flow_cmdline, because I'm not sure that
>> > > > > people linking with librte_cmdline always want to pull the rte_flow
>> > > > > parsing code.
>> >
>> > In my opinion the entire flow command parser has nothing to do outside
>> > testpmd, it's way too specific, even if another application finds it
>> > useful.
>> >
>> > Code duplication being a bad thing, your application could as well compile
>> > or even #include app/test-pmd/cmdline_flow.c directly (not pretty, I know)
>> > since it would have the same internal layout as testpmd. Testpmd's Makefile
>> > could be modified to spit it out as a separate library if needed.
>> >
>> > What could make more sense would be to extract the parser engine for
>> > librte_cmdline's dynamic tokens (e.g. struct context, struct arg, struct
>> > token) and possibly various generic helpers (e.g. parse_string,
>> > parse_mac_addr), but not enum index nor token_list[] and friends which
>> > define the layout of testpmd's flow command.
>> >
>> > This would enable other flow-like commands without duplicating the engine
>> > every time, however I'm still not sure if making it available outside
>> > testpmd is a good idea. Extracting and making it fully generic will require
>> > a considerable amount of work.
>> >
>> > > > Hi Lu, Oliver,
>> > > >
>> > > > Thanks for your feedback!
>> > > > You have a point here, flow commands are only a subset of the parser.
>> > > > Do you want me to create this new library and send another patch?
>> > >
>> > > Let's first wait for Adrien's feedback, he can have counter-arguments.
>> > >
>> > > > I guess I have to use librte_cmdline as a template/example for
>> > creating the
>> > > > librte_flow_cmdline library.
>> > >
>> > > It can be used as an example for Makefile and .map file.
>> >
>> > I'm not opposed to the idea of exporting the parser engine after making it
>> > properly generic, but please reconsider. Testpmd is an application made to
>> > validate PMD functionality. The flow command implementation, as neat as it
>> > is, remains a complex wrapper on top of the cmdline_parse API which wasn't
>> > originally designed for dynamic tokens. Its syntax may evolve without
>> > warning if deemed necessary. Making it public will subject it to exported
>> > API/ABI constraints and likely impede future evolution.
>> >
>> > Assuming your application is not dragging testpmd's legacy, I think it
>> > would
>> > be wiser to re-implement a simpler flow command look-alike on top of a more
>> > suitable command-line handling library if you like its syntax.
>> >
>> > [1] http://dpdk.org/ml/archives/dev/2018-January/086872.html
>> > [2] Message-ID: CAN9HtFDz+imqbCKfs6a0NE0W7iF8C+-KiNB0nCRywimspjfEDg@mail.
>> > gmail.com
>> >
>> > --
>> > Adrien Mazarguil
>> > 6WIND
>> >
>>
>>
>>
>> --
>> Georgios Katsikas
>> Industrial Ph.D. Student
>> Network Intelligence Group
>> Decision, Networks, and Analytics (DNA) Lab
>> RISE SICS
>> E-Mail:  georgios.katsikas@ri.se
>
> --
> Adrien Mazarguil
> 6WIND
  

Patch

diff --git a/app/test-pmd/Makefile b/app/test-pmd/Makefile
index 82b3481..ec48773 100644
--- a/app/test-pmd/Makefile
+++ b/app/test-pmd/Makefile
@@ -19,7 +19,6 @@  CFLAGS += $(WERROR_FLAGS)
 SRCS-y := testpmd.c
 SRCS-y += parameters.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline.c
-SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_flow.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_mtr.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_tm.c
 SRCS-y += config.c
diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
deleted file mode 100644
index df16d2a..0000000
--- a/app/test-pmd/cmdline_flow.c
+++ /dev/null
@@ -1,2959 +0,0 @@ 
-/*-
- *   BSD LICENSE
- *
- *   Copyright 2016 6WIND S.A.
- *   Copyright 2016 Mellanox.
- *
- *   Redistribution and use in source and binary forms, with or without
- *   modification, are permitted provided that the following conditions
- *   are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided with the
- *       distribution.
- *     * Neither the name of 6WIND S.A. nor the names of its
- *       contributors may be used to endorse or promote products derived
- *       from this software without specific prior written permission.
- *
- *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <inttypes.h>
-#include <errno.h>
-#include <ctype.h>
-#include <string.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-
-#include <rte_common.h>
-#include <rte_ethdev.h>
-#include <rte_byteorder.h>
-#include <cmdline_parse.h>
-#include <cmdline_parse_etheraddr.h>
-#include <rte_flow.h>
-
-#include "testpmd.h"
-
-/** Parser token indices. */
-enum index {
-	/* Special tokens. */
-	ZERO = 0,
-	END,
-
-	/* Common tokens. */
-	INTEGER,
-	UNSIGNED,
-	PREFIX,
-	BOOLEAN,
-	STRING,
-	MAC_ADDR,
-	IPV4_ADDR,
-	IPV6_ADDR,
-	RULE_ID,
-	PORT_ID,
-	GROUP_ID,
-	PRIORITY_LEVEL,
-
-	/* Top-level command. */
-	FLOW,
-
-	/* Sub-level commands. */
-	VALIDATE,
-	CREATE,
-	DESTROY,
-	FLUSH,
-	QUERY,
-	LIST,
-	ISOLATE,
-
-	/* Destroy arguments. */
-	DESTROY_RULE,
-
-	/* Query arguments. */
-	QUERY_ACTION,
-
-	/* List arguments. */
-	LIST_GROUP,
-
-	/* Validate/create arguments. */
-	GROUP,
-	PRIORITY,
-	INGRESS,
-	EGRESS,
-
-	/* Validate/create pattern. */
-	PATTERN,
-	ITEM_PARAM_IS,
-	ITEM_PARAM_SPEC,
-	ITEM_PARAM_LAST,
-	ITEM_PARAM_MASK,
-	ITEM_PARAM_PREFIX,
-	ITEM_NEXT,
-	ITEM_END,
-	ITEM_VOID,
-	ITEM_INVERT,
-	ITEM_ANY,
-	ITEM_ANY_NUM,
-	ITEM_PF,
-	ITEM_VF,
-	ITEM_VF_ID,
-	ITEM_PORT,
-	ITEM_PORT_INDEX,
-	ITEM_RAW,
-	ITEM_RAW_RELATIVE,
-	ITEM_RAW_SEARCH,
-	ITEM_RAW_OFFSET,
-	ITEM_RAW_LIMIT,
-	ITEM_RAW_PATTERN,
-	ITEM_ETH,
-	ITEM_ETH_DST,
-	ITEM_ETH_SRC,
-	ITEM_ETH_TYPE,
-	ITEM_VLAN,
-	ITEM_VLAN_TPID,
-	ITEM_VLAN_TCI,
-	ITEM_VLAN_PCP,
-	ITEM_VLAN_DEI,
-	ITEM_VLAN_VID,
-	ITEM_IPV4,
-	ITEM_IPV4_TOS,
-	ITEM_IPV4_TTL,
-	ITEM_IPV4_PROTO,
-	ITEM_IPV4_SRC,
-	ITEM_IPV4_DST,
-	ITEM_IPV6,
-	ITEM_IPV6_TC,
-	ITEM_IPV6_FLOW,
-	ITEM_IPV6_PROTO,
-	ITEM_IPV6_HOP,
-	ITEM_IPV6_SRC,
-	ITEM_IPV6_DST,
-	ITEM_ICMP,
-	ITEM_ICMP_TYPE,
-	ITEM_ICMP_CODE,
-	ITEM_UDP,
-	ITEM_UDP_SRC,
-	ITEM_UDP_DST,
-	ITEM_TCP,
-	ITEM_TCP_SRC,
-	ITEM_TCP_DST,
-	ITEM_TCP_FLAGS,
-	ITEM_SCTP,
-	ITEM_SCTP_SRC,
-	ITEM_SCTP_DST,
-	ITEM_SCTP_TAG,
-	ITEM_SCTP_CKSUM,
-	ITEM_VXLAN,
-	ITEM_VXLAN_VNI,
-	ITEM_E_TAG,
-	ITEM_E_TAG_GRP_ECID_B,
-	ITEM_NVGRE,
-	ITEM_NVGRE_TNI,
-	ITEM_MPLS,
-	ITEM_MPLS_LABEL,
-	ITEM_GRE,
-	ITEM_GRE_PROTO,
-	ITEM_FUZZY,
-	ITEM_FUZZY_THRESH,
-	ITEM_GTP,
-	ITEM_GTP_TEID,
-	ITEM_GTPC,
-	ITEM_GTPU,
-
-	/* Validate/create actions. */
-	ACTIONS,
-	ACTION_NEXT,
-	ACTION_END,
-	ACTION_VOID,
-	ACTION_PASSTHRU,
-	ACTION_MARK,
-	ACTION_MARK_ID,
-	ACTION_FLAG,
-	ACTION_QUEUE,
-	ACTION_QUEUE_INDEX,
-	ACTION_DROP,
-	ACTION_COUNT,
-	ACTION_DUP,
-	ACTION_DUP_INDEX,
-	ACTION_RSS,
-	ACTION_RSS_QUEUES,
-	ACTION_RSS_QUEUE,
-	ACTION_PF,
-	ACTION_VF,
-	ACTION_VF_ORIGINAL,
-	ACTION_VF_ID,
-	ACTION_METER,
-	ACTION_METER_ID,
-};
-
-/** Size of pattern[] field in struct rte_flow_item_raw. */
-#define ITEM_RAW_PATTERN_SIZE 36
-
-/** Storage size for struct rte_flow_item_raw including pattern. */
-#define ITEM_RAW_SIZE \
-	(offsetof(struct rte_flow_item_raw, pattern) + ITEM_RAW_PATTERN_SIZE)
-
-/** Number of queue[] entries in struct rte_flow_action_rss. */
-#define ACTION_RSS_NUM 32
-
-/** Storage size for struct rte_flow_action_rss including queues. */
-#define ACTION_RSS_SIZE \
-	(offsetof(struct rte_flow_action_rss, queue) + \
-	 sizeof(*((struct rte_flow_action_rss *)0)->queue) * ACTION_RSS_NUM)
-
-/** Maximum number of subsequent tokens and arguments on the stack. */
-#define CTX_STACK_SIZE 16
-
-/** Parser context. */
-struct context {
-	/** Stack of subsequent token lists to process. */
-	const enum index *next[CTX_STACK_SIZE];
-	/** Arguments for stacked tokens. */
-	const void *args[CTX_STACK_SIZE];
-	enum index curr; /**< Current token index. */
-	enum index prev; /**< Index of the last token seen. */
-	int next_num; /**< Number of entries in next[]. */
-	int args_num; /**< Number of entries in args[]. */
-	uint32_t eol:1; /**< EOL has been detected. */
-	uint32_t last:1; /**< No more arguments. */
-	portid_t port; /**< Current port ID (for completions). */
-	uint32_t objdata; /**< Object-specific data. */
-	void *object; /**< Address of current object for relative offsets. */
-	void *objmask; /**< Object a full mask must be written to. */
-};
-
-/** Token argument. */
-struct arg {
-	uint32_t hton:1; /**< Use network byte ordering. */
-	uint32_t sign:1; /**< Value is signed. */
-	uint32_t offset; /**< Relative offset from ctx->object. */
-	uint32_t size; /**< Field size. */
-	const uint8_t *mask; /**< Bit-mask to use instead of offset/size. */
-};
-
-/** Parser token definition. */
-struct token {
-	/** Type displayed during completion (defaults to "TOKEN"). */
-	const char *type;
-	/** Help displayed during completion (defaults to token name). */
-	const char *help;
-	/** Private data used by parser functions. */
-	const void *priv;
-	/**
-	 * Lists of subsequent tokens to push on the stack. Each call to the
-	 * parser consumes the last entry of that stack.
-	 */
-	const enum index *const *next;
-	/** Arguments stack for subsequent tokens that need them. */
-	const struct arg *const *args;
-	/**
-	 * Token-processing callback, returns -1 in case of error, the
-	 * length of the matched string otherwise. If NULL, attempts to
-	 * match the token name.
-	 *
-	 * If buf is not NULL, the result should be stored in it according
-	 * to context. An error is returned if not large enough.
-	 */
-	int (*call)(struct context *ctx, const struct token *token,
-		    const char *str, unsigned int len,
-		    void *buf, unsigned int size);
-	/**
-	 * Callback that provides possible values for this token, used for
-	 * completion. Returns -1 in case of error, the number of possible
-	 * values otherwise. If NULL, the token name is used.
-	 *
-	 * If buf is not NULL, entry index ent is written to buf and the
-	 * full length of the entry is returned (same behavior as
-	 * snprintf()).
-	 */
-	int (*comp)(struct context *ctx, const struct token *token,
-		    unsigned int ent, char *buf, unsigned int size);
-	/** Mandatory token name, no default value. */
-	const char *name;
-};
-
-/** Static initializer for the next field. */
-#define NEXT(...) (const enum index *const []){ __VA_ARGS__, NULL, }
-
-/** Static initializer for a NEXT() entry. */
-#define NEXT_ENTRY(...) (const enum index []){ __VA_ARGS__, ZERO, }
-
-/** Static initializer for the args field. */
-#define ARGS(...) (const struct arg *const []){ __VA_ARGS__, NULL, }
-
-/** Static initializer for ARGS() to target a field. */
-#define ARGS_ENTRY(s, f) \
-	(&(const struct arg){ \
-		.offset = offsetof(s, f), \
-		.size = sizeof(((s *)0)->f), \
-	})
-
-/** Static initializer for ARGS() to target a bit-field. */
-#define ARGS_ENTRY_BF(s, f, b) \
-	(&(const struct arg){ \
-		.size = sizeof(s), \
-		.mask = (const void *)&(const s){ .f = (1 << (b)) - 1 }, \
-	})
-
-/** Static initializer for ARGS() to target an arbitrary bit-mask. */
-#define ARGS_ENTRY_MASK(s, f, m) \
-	(&(const struct arg){ \
-		.offset = offsetof(s, f), \
-		.size = sizeof(((s *)0)->f), \
-		.mask = (const void *)(m), \
-	})
-
-/** Same as ARGS_ENTRY_MASK() using network byte ordering for the value. */
-#define ARGS_ENTRY_MASK_HTON(s, f, m) \
-	(&(const struct arg){ \
-		.hton = 1, \
-		.offset = offsetof(s, f), \
-		.size = sizeof(((s *)0)->f), \
-		.mask = (const void *)(m), \
-	})
-
-/** Static initializer for ARGS() to target a pointer. */
-#define ARGS_ENTRY_PTR(s, f) \
-	(&(const struct arg){ \
-		.size = sizeof(*((s *)0)->f), \
-	})
-
-/** Static initializer for ARGS() with arbitrary size. */
-#define ARGS_ENTRY_USZ(s, f, sz) \
-	(&(const struct arg){ \
-		.offset = offsetof(s, f), \
-		.size = (sz), \
-	})
-
-/** Same as ARGS_ENTRY() using network byte ordering. */
-#define ARGS_ENTRY_HTON(s, f) \
-	(&(const struct arg){ \
-		.hton = 1, \
-		.offset = offsetof(s, f), \
-		.size = sizeof(((s *)0)->f), \
-	})
-
-/** Parser output buffer layout expected by cmd_flow_parsed(). */
-struct buffer {
-	enum index command; /**< Flow command. */
-	portid_t port; /**< Affected port ID. */
-	union {
-		struct {
-			struct rte_flow_attr attr;
-			struct rte_flow_item *pattern;
-			struct rte_flow_action *actions;
-			uint32_t pattern_n;
-			uint32_t actions_n;
-			uint8_t *data;
-		} vc; /**< Validate/create arguments. */
-		struct {
-			uint32_t *rule;
-			uint32_t rule_n;
-		} destroy; /**< Destroy arguments. */
-		struct {
-			uint32_t rule;
-			enum rte_flow_action_type action;
-		} query; /**< Query arguments. */
-		struct {
-			uint32_t *group;
-			uint32_t group_n;
-		} list; /**< List arguments. */
-		struct {
-			int set;
-		} isolate; /**< Isolated mode arguments. */
-	} args; /**< Command arguments. */
-};
-
-/** Private data for pattern items. */
-struct parse_item_priv {
-	enum rte_flow_item_type type; /**< Item type. */
-	uint32_t size; /**< Size of item specification structure. */
-};
-
-#define PRIV_ITEM(t, s) \
-	(&(const struct parse_item_priv){ \
-		.type = RTE_FLOW_ITEM_TYPE_ ## t, \
-		.size = s, \
-	})
-
-/** Private data for actions. */
-struct parse_action_priv {
-	enum rte_flow_action_type type; /**< Action type. */
-	uint32_t size; /**< Size of action configuration structure. */
-};
-
-#define PRIV_ACTION(t, s) \
-	(&(const struct parse_action_priv){ \
-		.type = RTE_FLOW_ACTION_TYPE_ ## t, \
-		.size = s, \
-	})
-
-static const enum index next_vc_attr[] = {
-	GROUP,
-	PRIORITY,
-	INGRESS,
-	EGRESS,
-	PATTERN,
-	ZERO,
-};
-
-static const enum index next_destroy_attr[] = {
-	DESTROY_RULE,
-	END,
-	ZERO,
-};
-
-static const enum index next_list_attr[] = {
-	LIST_GROUP,
-	END,
-	ZERO,
-};
-
-static const enum index item_param[] = {
-	ITEM_PARAM_IS,
-	ITEM_PARAM_SPEC,
-	ITEM_PARAM_LAST,
-	ITEM_PARAM_MASK,
-	ITEM_PARAM_PREFIX,
-	ZERO,
-};
-
-static const enum index next_item[] = {
-	ITEM_END,
-	ITEM_VOID,
-	ITEM_INVERT,
-	ITEM_ANY,
-	ITEM_PF,
-	ITEM_VF,
-	ITEM_PORT,
-	ITEM_RAW,
-	ITEM_ETH,
-	ITEM_VLAN,
-	ITEM_IPV4,
-	ITEM_IPV6,
-	ITEM_ICMP,
-	ITEM_UDP,
-	ITEM_TCP,
-	ITEM_SCTP,
-	ITEM_VXLAN,
-	ITEM_E_TAG,
-	ITEM_NVGRE,
-	ITEM_MPLS,
-	ITEM_GRE,
-	ITEM_FUZZY,
-	ITEM_GTP,
-	ITEM_GTPC,
-	ITEM_GTPU,
-	ZERO,
-};
-
-static const enum index item_fuzzy[] = {
-	ITEM_FUZZY_THRESH,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_any[] = {
-	ITEM_ANY_NUM,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_vf[] = {
-	ITEM_VF_ID,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_port[] = {
-	ITEM_PORT_INDEX,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_raw[] = {
-	ITEM_RAW_RELATIVE,
-	ITEM_RAW_SEARCH,
-	ITEM_RAW_OFFSET,
-	ITEM_RAW_LIMIT,
-	ITEM_RAW_PATTERN,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_eth[] = {
-	ITEM_ETH_DST,
-	ITEM_ETH_SRC,
-	ITEM_ETH_TYPE,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_vlan[] = {
-	ITEM_VLAN_TPID,
-	ITEM_VLAN_TCI,
-	ITEM_VLAN_PCP,
-	ITEM_VLAN_DEI,
-	ITEM_VLAN_VID,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_ipv4[] = {
-	ITEM_IPV4_TOS,
-	ITEM_IPV4_TTL,
-	ITEM_IPV4_PROTO,
-	ITEM_IPV4_SRC,
-	ITEM_IPV4_DST,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_ipv6[] = {
-	ITEM_IPV6_TC,
-	ITEM_IPV6_FLOW,
-	ITEM_IPV6_PROTO,
-	ITEM_IPV6_HOP,
-	ITEM_IPV6_SRC,
-	ITEM_IPV6_DST,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_icmp[] = {
-	ITEM_ICMP_TYPE,
-	ITEM_ICMP_CODE,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_udp[] = {
-	ITEM_UDP_SRC,
-	ITEM_UDP_DST,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_tcp[] = {
-	ITEM_TCP_SRC,
-	ITEM_TCP_DST,
-	ITEM_TCP_FLAGS,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_sctp[] = {
-	ITEM_SCTP_SRC,
-	ITEM_SCTP_DST,
-	ITEM_SCTP_TAG,
-	ITEM_SCTP_CKSUM,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_vxlan[] = {
-	ITEM_VXLAN_VNI,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_e_tag[] = {
-	ITEM_E_TAG_GRP_ECID_B,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_nvgre[] = {
-	ITEM_NVGRE_TNI,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_mpls[] = {
-	ITEM_MPLS_LABEL,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_gre[] = {
-	ITEM_GRE_PROTO,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index item_gtp[] = {
-	ITEM_GTP_TEID,
-	ITEM_NEXT,
-	ZERO,
-};
-
-static const enum index next_action[] = {
-	ACTION_END,
-	ACTION_VOID,
-	ACTION_PASSTHRU,
-	ACTION_MARK,
-	ACTION_FLAG,
-	ACTION_QUEUE,
-	ACTION_DROP,
-	ACTION_COUNT,
-	ACTION_DUP,
-	ACTION_RSS,
-	ACTION_PF,
-	ACTION_VF,
-	ACTION_METER,
-	ZERO,
-};
-
-static const enum index action_mark[] = {
-	ACTION_MARK_ID,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static const enum index action_queue[] = {
-	ACTION_QUEUE_INDEX,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static const enum index action_dup[] = {
-	ACTION_DUP_INDEX,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static const enum index action_rss[] = {
-	ACTION_RSS_QUEUES,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static const enum index action_vf[] = {
-	ACTION_VF_ORIGINAL,
-	ACTION_VF_ID,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static const enum index action_meter[] = {
-	ACTION_METER_ID,
-	ACTION_NEXT,
-	ZERO,
-};
-
-static int parse_init(struct context *, const struct token *,
-		      const char *, unsigned int,
-		      void *, unsigned int);
-static int parse_vc(struct context *, const struct token *,
-		    const char *, unsigned int,
-		    void *, unsigned int);
-static int parse_vc_spec(struct context *, const struct token *,
-			 const char *, unsigned int, void *, unsigned int);
-static int parse_vc_conf(struct context *, const struct token *,
-			 const char *, unsigned int, void *, unsigned int);
-static int parse_vc_action_rss_queue(struct context *, const struct token *,
-				     const char *, unsigned int, void *,
-				     unsigned int);
-static int parse_destroy(struct context *, const struct token *,
-			 const char *, unsigned int,
-			 void *, unsigned int);
-static int parse_flush(struct context *, const struct token *,
-		       const char *, unsigned int,
-		       void *, unsigned int);
-static int parse_query(struct context *, const struct token *,
-		       const char *, unsigned int,
-		       void *, unsigned int);
-static int parse_action(struct context *, const struct token *,
-			const char *, unsigned int,
-			void *, unsigned int);
-static int parse_list(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);
-static int parse_int(struct context *, const struct token *,
-		     const char *, unsigned int,
-		     void *, unsigned int);
-static int parse_prefix(struct context *, const struct token *,
-			const char *, unsigned int,
-			void *, unsigned int);
-static int parse_boolean(struct context *, const struct token *,
-			 const char *, unsigned int,
-			 void *, unsigned int);
-static int parse_string(struct context *, const struct token *,
-			const char *, unsigned int,
-			void *, unsigned int);
-static int parse_mac_addr(struct context *, const struct token *,
-			  const char *, unsigned int,
-			  void *, unsigned int);
-static int parse_ipv4_addr(struct context *, const struct token *,
-			   const char *, unsigned int,
-			   void *, unsigned int);
-static int parse_ipv6_addr(struct context *, const struct token *,
-			   const char *, unsigned int,
-			   void *, unsigned int);
-static int parse_port(struct context *, const struct token *,
-		      const char *, unsigned int,
-		      void *, unsigned int);
-static int comp_none(struct context *, const struct token *,
-		     unsigned int, char *, unsigned int);
-static int comp_boolean(struct context *, const struct token *,
-			unsigned int, char *, unsigned int);
-static int comp_action(struct context *, const struct token *,
-		       unsigned int, char *, unsigned int);
-static int comp_port(struct context *, const struct token *,
-		     unsigned int, char *, unsigned int);
-static int comp_rule_id(struct context *, const struct token *,
-			unsigned int, char *, unsigned int);
-static int comp_vc_action_rss_queue(struct context *, const struct token *,
-				    unsigned int, char *, unsigned int);
-
-/** Token definitions. */
-static const struct token token_list[] = {
-	/* Special tokens. */
-	[ZERO] = {
-		.name = "ZERO",
-		.help = "null entry, abused as the entry point",
-		.next = NEXT(NEXT_ENTRY(FLOW)),
-	},
-	[END] = {
-		.name = "",
-		.type = "RETURN",
-		.help = "command may end here",
-	},
-	/* Common tokens. */
-	[INTEGER] = {
-		.name = "{int}",
-		.type = "INTEGER",
-		.help = "integer value",
-		.call = parse_int,
-		.comp = comp_none,
-	},
-	[UNSIGNED] = {
-		.name = "{unsigned}",
-		.type = "UNSIGNED",
-		.help = "unsigned integer value",
-		.call = parse_int,
-		.comp = comp_none,
-	},
-	[PREFIX] = {
-		.name = "{prefix}",
-		.type = "PREFIX",
-		.help = "prefix length for bit-mask",
-		.call = parse_prefix,
-		.comp = comp_none,
-	},
-	[BOOLEAN] = {
-		.name = "{boolean}",
-		.type = "BOOLEAN",
-		.help = "any boolean value",
-		.call = parse_boolean,
-		.comp = comp_boolean,
-	},
-	[STRING] = {
-		.name = "{string}",
-		.type = "STRING",
-		.help = "fixed string",
-		.call = parse_string,
-		.comp = comp_none,
-	},
-	[MAC_ADDR] = {
-		.name = "{MAC address}",
-		.type = "MAC-48",
-		.help = "standard MAC address notation",
-		.call = parse_mac_addr,
-		.comp = comp_none,
-	},
-	[IPV4_ADDR] = {
-		.name = "{IPv4 address}",
-		.type = "IPV4 ADDRESS",
-		.help = "standard IPv4 address notation",
-		.call = parse_ipv4_addr,
-		.comp = comp_none,
-	},
-	[IPV6_ADDR] = {
-		.name = "{IPv6 address}",
-		.type = "IPV6 ADDRESS",
-		.help = "standard IPv6 address notation",
-		.call = parse_ipv6_addr,
-		.comp = comp_none,
-	},
-	[RULE_ID] = {
-		.name = "{rule id}",
-		.type = "RULE ID",
-		.help = "rule identifier",
-		.call = parse_int,
-		.comp = comp_rule_id,
-	},
-	[PORT_ID] = {
-		.name = "{port_id}",
-		.type = "PORT ID",
-		.help = "port identifier",
-		.call = parse_port,
-		.comp = comp_port,
-	},
-	[GROUP_ID] = {
-		.name = "{group_id}",
-		.type = "GROUP ID",
-		.help = "group identifier",
-		.call = parse_int,
-		.comp = comp_none,
-	},
-	[PRIORITY_LEVEL] = {
-		.name = "{level}",
-		.type = "PRIORITY",
-		.help = "priority level",
-		.call = parse_int,
-		.comp = comp_none,
-	},
-	/* Top-level command. */
-	[FLOW] = {
-		.name = "flow",
-		.type = "{command} {port_id} [{arg} [...]]",
-		.help = "manage ingress/egress flow rules",
-		.next = NEXT(NEXT_ENTRY
-			     (VALIDATE,
-			      CREATE,
-			      DESTROY,
-			      FLUSH,
-			      LIST,
-			      QUERY,
-			      ISOLATE)),
-		.call = parse_init,
-	},
-	/* Sub-level commands. */
-	[VALIDATE] = {
-		.name = "validate",
-		.help = "check whether a flow rule can be created",
-		.next = NEXT(next_vc_attr, NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
-		.call = parse_vc,
-	},
-	[CREATE] = {
-		.name = "create",
-		.help = "create a flow rule",
-		.next = NEXT(next_vc_attr, NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
-		.call = parse_vc,
-	},
-	[DESTROY] = {
-		.name = "destroy",
-		.help = "destroy specific flow rules",
-		.next = NEXT(NEXT_ENTRY(DESTROY_RULE), NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
-		.call = parse_destroy,
-	},
-	[FLUSH] = {
-		.name = "flush",
-		.help = "destroy all flow rules",
-		.next = NEXT(NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
-		.call = parse_flush,
-	},
-	[QUERY] = {
-		.name = "query",
-		.help = "query an existing flow rule",
-		.next = NEXT(NEXT_ENTRY(QUERY_ACTION),
-			     NEXT_ENTRY(RULE_ID),
-			     NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, args.query.action),
-			     ARGS_ENTRY(struct buffer, args.query.rule),
-			     ARGS_ENTRY(struct buffer, port)),
-		.call = parse_query,
-	},
-	[LIST] = {
-		.name = "list",
-		.help = "list existing flow rules",
-		.next = NEXT(next_list_attr, NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
-		.call = parse_list,
-	},
-	[ISOLATE] = {
-		.name = "isolate",
-		.help = "restrict ingress traffic to the defined flow rules",
-		.next = NEXT(NEXT_ENTRY(BOOLEAN),
-			     NEXT_ENTRY(PORT_ID)),
-		.args = ARGS(ARGS_ENTRY(struct buffer, args.isolate.set),
-			     ARGS_ENTRY(struct buffer, port)),
-		.call = parse_isolate,
-	},
-	/* Destroy arguments. */
-	[DESTROY_RULE] = {
-		.name = "rule",
-		.help = "specify a rule identifier",
-		.next = NEXT(next_destroy_attr, NEXT_ENTRY(RULE_ID)),
-		.args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.destroy.rule)),
-		.call = parse_destroy,
-	},
-	/* Query arguments. */
-	[QUERY_ACTION] = {
-		.name = "{action}",
-		.type = "ACTION",
-		.help = "action to query, must be part of the rule",
-		.call = parse_action,
-		.comp = comp_action,
-	},
-	/* List arguments. */
-	[LIST_GROUP] = {
-		.name = "group",
-		.help = "specify a group",
-		.next = NEXT(next_list_attr, NEXT_ENTRY(GROUP_ID)),
-		.args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.list.group)),
-		.call = parse_list,
-	},
-	/* Validate/create attributes. */
-	[GROUP] = {
-		.name = "group",
-		.help = "specify a group",
-		.next = NEXT(next_vc_attr, NEXT_ENTRY(GROUP_ID)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_attr, group)),
-		.call = parse_vc,
-	},
-	[PRIORITY] = {
-		.name = "priority",
-		.help = "specify a priority level",
-		.next = NEXT(next_vc_attr, NEXT_ENTRY(PRIORITY_LEVEL)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_attr, priority)),
-		.call = parse_vc,
-	},
-	[INGRESS] = {
-		.name = "ingress",
-		.help = "affect rule to ingress",
-		.next = NEXT(next_vc_attr),
-		.call = parse_vc,
-	},
-	[EGRESS] = {
-		.name = "egress",
-		.help = "affect rule to egress",
-		.next = NEXT(next_vc_attr),
-		.call = parse_vc,
-	},
-	/* Validate/create pattern. */
-	[PATTERN] = {
-		.name = "pattern",
-		.help = "submit a list of pattern items",
-		.next = NEXT(next_item),
-		.call = parse_vc,
-	},
-	[ITEM_PARAM_IS] = {
-		.name = "is",
-		.help = "match value perfectly (with full bit-mask)",
-		.call = parse_vc_spec,
-	},
-	[ITEM_PARAM_SPEC] = {
-		.name = "spec",
-		.help = "match value according to configured bit-mask",
-		.call = parse_vc_spec,
-	},
-	[ITEM_PARAM_LAST] = {
-		.name = "last",
-		.help = "specify upper bound to establish a range",
-		.call = parse_vc_spec,
-	},
-	[ITEM_PARAM_MASK] = {
-		.name = "mask",
-		.help = "specify bit-mask with relevant bits set to one",
-		.call = parse_vc_spec,
-	},
-	[ITEM_PARAM_PREFIX] = {
-		.name = "prefix",
-		.help = "generate bit-mask from a prefix length",
-		.call = parse_vc_spec,
-	},
-	[ITEM_NEXT] = {
-		.name = "/",
-		.help = "specify next pattern item",
-		.next = NEXT(next_item),
-	},
-	[ITEM_END] = {
-		.name = "end",
-		.help = "end list of pattern items",
-		.priv = PRIV_ITEM(END, 0),
-		.next = NEXT(NEXT_ENTRY(ACTIONS)),
-		.call = parse_vc,
-	},
-	[ITEM_VOID] = {
-		.name = "void",
-		.help = "no-op pattern item",
-		.priv = PRIV_ITEM(VOID, 0),
-		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
-		.call = parse_vc,
-	},
-	[ITEM_INVERT] = {
-		.name = "invert",
-		.help = "perform actions when pattern does not match",
-		.priv = PRIV_ITEM(INVERT, 0),
-		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
-		.call = parse_vc,
-	},
-	[ITEM_ANY] = {
-		.name = "any",
-		.help = "match any protocol for the current layer",
-		.priv = PRIV_ITEM(ANY, sizeof(struct rte_flow_item_any)),
-		.next = NEXT(item_any),
-		.call = parse_vc,
-	},
-	[ITEM_ANY_NUM] = {
-		.name = "num",
-		.help = "number of layers covered",
-		.next = NEXT(item_any, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_any, num)),
-	},
-	[ITEM_PF] = {
-		.name = "pf",
-		.help = "match packets addressed to the physical function",
-		.priv = PRIV_ITEM(PF, 0),
-		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
-		.call = parse_vc,
-	},
-	[ITEM_VF] = {
-		.name = "vf",
-		.help = "match packets addressed to a virtual function ID",
-		.priv = PRIV_ITEM(VF, sizeof(struct rte_flow_item_vf)),
-		.next = NEXT(item_vf),
-		.call = parse_vc,
-	},
-	[ITEM_VF_ID] = {
-		.name = "id",
-		.help = "destination VF ID",
-		.next = NEXT(item_vf, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_vf, id)),
-	},
-	[ITEM_PORT] = {
-		.name = "port",
-		.help = "device-specific physical port index to use",
-		.priv = PRIV_ITEM(PORT, sizeof(struct rte_flow_item_port)),
-		.next = NEXT(item_port),
-		.call = parse_vc,
-	},
-	[ITEM_PORT_INDEX] = {
-		.name = "index",
-		.help = "physical port index",
-		.next = NEXT(item_port, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_port, index)),
-	},
-	[ITEM_RAW] = {
-		.name = "raw",
-		.help = "match an arbitrary byte string",
-		.priv = PRIV_ITEM(RAW, ITEM_RAW_SIZE),
-		.next = NEXT(item_raw),
-		.call = parse_vc,
-	},
-	[ITEM_RAW_RELATIVE] = {
-		.name = "relative",
-		.help = "look for pattern after the previous item",
-		.next = NEXT(item_raw, NEXT_ENTRY(BOOLEAN), item_param),
-		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_item_raw,
-					   relative, 1)),
-	},
-	[ITEM_RAW_SEARCH] = {
-		.name = "search",
-		.help = "search pattern from offset (see also limit)",
-		.next = NEXT(item_raw, NEXT_ENTRY(BOOLEAN), item_param),
-		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_item_raw,
-					   search, 1)),
-	},
-	[ITEM_RAW_OFFSET] = {
-		.name = "offset",
-		.help = "absolute or relative offset for pattern",
-		.next = NEXT(item_raw, NEXT_ENTRY(INTEGER), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, offset)),
-	},
-	[ITEM_RAW_LIMIT] = {
-		.name = "limit",
-		.help = "search area limit for start of pattern",
-		.next = NEXT(item_raw, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, limit)),
-	},
-	[ITEM_RAW_PATTERN] = {
-		.name = "pattern",
-		.help = "byte string to look for",
-		.next = NEXT(item_raw,
-			     NEXT_ENTRY(STRING),
-			     NEXT_ENTRY(ITEM_PARAM_IS,
-					ITEM_PARAM_SPEC,
-					ITEM_PARAM_MASK)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, length),
-			     ARGS_ENTRY_USZ(struct rte_flow_item_raw,
-					    pattern,
-					    ITEM_RAW_PATTERN_SIZE)),
-	},
-	[ITEM_ETH] = {
-		.name = "eth",
-		.help = "match Ethernet header",
-		.priv = PRIV_ITEM(ETH, sizeof(struct rte_flow_item_eth)),
-		.next = NEXT(item_eth),
-		.call = parse_vc,
-	},
-	[ITEM_ETH_DST] = {
-		.name = "dst",
-		.help = "destination MAC",
-		.next = NEXT(item_eth, NEXT_ENTRY(MAC_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, dst)),
-	},
-	[ITEM_ETH_SRC] = {
-		.name = "src",
-		.help = "source MAC",
-		.next = NEXT(item_eth, NEXT_ENTRY(MAC_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, src)),
-	},
-	[ITEM_ETH_TYPE] = {
-		.name = "type",
-		.help = "EtherType",
-		.next = NEXT(item_eth, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, type)),
-	},
-	[ITEM_VLAN] = {
-		.name = "vlan",
-		.help = "match 802.1Q/ad VLAN tag",
-		.priv = PRIV_ITEM(VLAN, sizeof(struct rte_flow_item_vlan)),
-		.next = NEXT(item_vlan),
-		.call = parse_vc,
-	},
-	[ITEM_VLAN_TPID] = {
-		.name = "tpid",
-		.help = "tag protocol identifier",
-		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vlan, tpid)),
-	},
-	[ITEM_VLAN_TCI] = {
-		.name = "tci",
-		.help = "tag control information",
-		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vlan, tci)),
-	},
-	[ITEM_VLAN_PCP] = {
-		.name = "pcp",
-		.help = "priority code point",
-		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
-						  tci, "\xe0\x00")),
-	},
-	[ITEM_VLAN_DEI] = {
-		.name = "dei",
-		.help = "drop eligible indicator",
-		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
-						  tci, "\x10\x00")),
-	},
-	[ITEM_VLAN_VID] = {
-		.name = "vid",
-		.help = "VLAN identifier",
-		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
-						  tci, "\x0f\xff")),
-	},
-	[ITEM_IPV4] = {
-		.name = "ipv4",
-		.help = "match IPv4 header",
-		.priv = PRIV_ITEM(IPV4, sizeof(struct rte_flow_item_ipv4)),
-		.next = NEXT(item_ipv4),
-		.call = parse_vc,
-	},
-	[ITEM_IPV4_TOS] = {
-		.name = "tos",
-		.help = "type of service",
-		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
-					     hdr.type_of_service)),
-	},
-	[ITEM_IPV4_TTL] = {
-		.name = "ttl",
-		.help = "time to live",
-		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
-					     hdr.time_to_live)),
-	},
-	[ITEM_IPV4_PROTO] = {
-		.name = "proto",
-		.help = "next protocol ID",
-		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
-					     hdr.next_proto_id)),
-	},
-	[ITEM_IPV4_SRC] = {
-		.name = "src",
-		.help = "source address",
-		.next = NEXT(item_ipv4, NEXT_ENTRY(IPV4_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
-					     hdr.src_addr)),
-	},
-	[ITEM_IPV4_DST] = {
-		.name = "dst",
-		.help = "destination address",
-		.next = NEXT(item_ipv4, NEXT_ENTRY(IPV4_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
-					     hdr.dst_addr)),
-	},
-	[ITEM_IPV6] = {
-		.name = "ipv6",
-		.help = "match IPv6 header",
-		.priv = PRIV_ITEM(IPV6, sizeof(struct rte_flow_item_ipv6)),
-		.next = NEXT(item_ipv6),
-		.call = parse_vc,
-	},
-	[ITEM_IPV6_TC] = {
-		.name = "tc",
-		.help = "traffic class",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_ipv6,
-						  hdr.vtc_flow,
-						  "\x0f\xf0\x00\x00")),
-	},
-	[ITEM_IPV6_FLOW] = {
-		.name = "flow",
-		.help = "flow label",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_ipv6,
-						  hdr.vtc_flow,
-						  "\x00\x0f\xff\xff")),
-	},
-	[ITEM_IPV6_PROTO] = {
-		.name = "proto",
-		.help = "protocol (next header)",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
-					     hdr.proto)),
-	},
-	[ITEM_IPV6_HOP] = {
-		.name = "hop",
-		.help = "hop limit",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
-					     hdr.hop_limits)),
-	},
-	[ITEM_IPV6_SRC] = {
-		.name = "src",
-		.help = "source address",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(IPV6_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
-					     hdr.src_addr)),
-	},
-	[ITEM_IPV6_DST] = {
-		.name = "dst",
-		.help = "destination address",
-		.next = NEXT(item_ipv6, NEXT_ENTRY(IPV6_ADDR), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
-					     hdr.dst_addr)),
-	},
-	[ITEM_ICMP] = {
-		.name = "icmp",
-		.help = "match ICMP header",
-		.priv = PRIV_ITEM(ICMP, sizeof(struct rte_flow_item_icmp)),
-		.next = NEXT(item_icmp),
-		.call = parse_vc,
-	},
-	[ITEM_ICMP_TYPE] = {
-		.name = "type",
-		.help = "ICMP packet type",
-		.next = NEXT(item_icmp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_icmp,
-					     hdr.icmp_type)),
-	},
-	[ITEM_ICMP_CODE] = {
-		.name = "code",
-		.help = "ICMP packet code",
-		.next = NEXT(item_icmp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_icmp,
-					     hdr.icmp_code)),
-	},
-	[ITEM_UDP] = {
-		.name = "udp",
-		.help = "match UDP header",
-		.priv = PRIV_ITEM(UDP, sizeof(struct rte_flow_item_udp)),
-		.next = NEXT(item_udp),
-		.call = parse_vc,
-	},
-	[ITEM_UDP_SRC] = {
-		.name = "src",
-		.help = "UDP source port",
-		.next = NEXT(item_udp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_udp,
-					     hdr.src_port)),
-	},
-	[ITEM_UDP_DST] = {
-		.name = "dst",
-		.help = "UDP destination port",
-		.next = NEXT(item_udp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_udp,
-					     hdr.dst_port)),
-	},
-	[ITEM_TCP] = {
-		.name = "tcp",
-		.help = "match TCP header",
-		.priv = PRIV_ITEM(TCP, sizeof(struct rte_flow_item_tcp)),
-		.next = NEXT(item_tcp),
-		.call = parse_vc,
-	},
-	[ITEM_TCP_SRC] = {
-		.name = "src",
-		.help = "TCP source port",
-		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
-					     hdr.src_port)),
-	},
-	[ITEM_TCP_DST] = {
-		.name = "dst",
-		.help = "TCP destination port",
-		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
-					     hdr.dst_port)),
-	},
-	[ITEM_TCP_FLAGS] = {
-		.name = "flags",
-		.help = "TCP flags",
-		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
-					     hdr.tcp_flags)),
-	},
-	[ITEM_SCTP] = {
-		.name = "sctp",
-		.help = "match SCTP header",
-		.priv = PRIV_ITEM(SCTP, sizeof(struct rte_flow_item_sctp)),
-		.next = NEXT(item_sctp),
-		.call = parse_vc,
-	},
-	[ITEM_SCTP_SRC] = {
-		.name = "src",
-		.help = "SCTP source port",
-		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
-					     hdr.src_port)),
-	},
-	[ITEM_SCTP_DST] = {
-		.name = "dst",
-		.help = "SCTP destination port",
-		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
-					     hdr.dst_port)),
-	},
-	[ITEM_SCTP_TAG] = {
-		.name = "tag",
-		.help = "validation tag",
-		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
-					     hdr.tag)),
-	},
-	[ITEM_SCTP_CKSUM] = {
-		.name = "cksum",
-		.help = "checksum",
-		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
-					     hdr.cksum)),
-	},
-	[ITEM_VXLAN] = {
-		.name = "vxlan",
-		.help = "match VXLAN header",
-		.priv = PRIV_ITEM(VXLAN, sizeof(struct rte_flow_item_vxlan)),
-		.next = NEXT(item_vxlan),
-		.call = parse_vc,
-	},
-	[ITEM_VXLAN_VNI] = {
-		.name = "vni",
-		.help = "VXLAN identifier",
-		.next = NEXT(item_vxlan, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vxlan, vni)),
-	},
-	[ITEM_E_TAG] = {
-		.name = "e_tag",
-		.help = "match E-Tag header",
-		.priv = PRIV_ITEM(E_TAG, sizeof(struct rte_flow_item_e_tag)),
-		.next = NEXT(item_e_tag),
-		.call = parse_vc,
-	},
-	[ITEM_E_TAG_GRP_ECID_B] = {
-		.name = "grp_ecid_b",
-		.help = "GRP and E-CID base",
-		.next = NEXT(item_e_tag, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_e_tag,
-						  rsvd_grp_ecid_b,
-						  "\x3f\xff")),
-	},
-	[ITEM_NVGRE] = {
-		.name = "nvgre",
-		.help = "match NVGRE header",
-		.priv = PRIV_ITEM(NVGRE, sizeof(struct rte_flow_item_nvgre)),
-		.next = NEXT(item_nvgre),
-		.call = parse_vc,
-	},
-	[ITEM_NVGRE_TNI] = {
-		.name = "tni",
-		.help = "virtual subnet ID",
-		.next = NEXT(item_nvgre, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_nvgre, tni)),
-	},
-	[ITEM_MPLS] = {
-		.name = "mpls",
-		.help = "match MPLS header",
-		.priv = PRIV_ITEM(MPLS, sizeof(struct rte_flow_item_mpls)),
-		.next = NEXT(item_mpls),
-		.call = parse_vc,
-	},
-	[ITEM_MPLS_LABEL] = {
-		.name = "label",
-		.help = "MPLS label",
-		.next = NEXT(item_mpls, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_mpls,
-						  label_tc_s,
-						  "\xff\xff\xf0")),
-	},
-	[ITEM_GRE] = {
-		.name = "gre",
-		.help = "match GRE header",
-		.priv = PRIV_ITEM(GRE, sizeof(struct rte_flow_item_gre)),
-		.next = NEXT(item_gre),
-		.call = parse_vc,
-	},
-	[ITEM_GRE_PROTO] = {
-		.name = "protocol",
-		.help = "GRE protocol type",
-		.next = NEXT(item_gre, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_gre,
-					     protocol)),
-	},
-	[ITEM_FUZZY] = {
-		.name = "fuzzy",
-		.help = "fuzzy pattern match, expect faster than default",
-		.priv = PRIV_ITEM(FUZZY,
-				sizeof(struct rte_flow_item_fuzzy)),
-		.next = NEXT(item_fuzzy),
-		.call = parse_vc,
-	},
-	[ITEM_FUZZY_THRESH] = {
-		.name = "thresh",
-		.help = "match accuracy threshold",
-		.next = NEXT(item_fuzzy, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_fuzzy,
-					thresh)),
-	},
-	[ITEM_GTP] = {
-		.name = "gtp",
-		.help = "match GTP header",
-		.priv = PRIV_ITEM(GTP, sizeof(struct rte_flow_item_gtp)),
-		.next = NEXT(item_gtp),
-		.call = parse_vc,
-	},
-	[ITEM_GTP_TEID] = {
-		.name = "teid",
-		.help = "tunnel endpoint identifier",
-		.next = NEXT(item_gtp, NEXT_ENTRY(UNSIGNED), item_param),
-		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_gtp, teid)),
-	},
-	[ITEM_GTPC] = {
-		.name = "gtpc",
-		.help = "match GTP header",
-		.priv = PRIV_ITEM(GTPC, sizeof(struct rte_flow_item_gtp)),
-		.next = NEXT(item_gtp),
-		.call = parse_vc,
-	},
-	[ITEM_GTPU] = {
-		.name = "gtpu",
-		.help = "match GTP header",
-		.priv = PRIV_ITEM(GTPU, sizeof(struct rte_flow_item_gtp)),
-		.next = NEXT(item_gtp),
-		.call = parse_vc,
-	},
-
-	/* Validate/create actions. */
-	[ACTIONS] = {
-		.name = "actions",
-		.help = "submit a list of associated actions",
-		.next = NEXT(next_action),
-		.call = parse_vc,
-	},
-	[ACTION_NEXT] = {
-		.name = "/",
-		.help = "specify next action",
-		.next = NEXT(next_action),
-	},
-	[ACTION_END] = {
-		.name = "end",
-		.help = "end list of actions",
-		.priv = PRIV_ACTION(END, 0),
-		.call = parse_vc,
-	},
-	[ACTION_VOID] = {
-		.name = "void",
-		.help = "no-op action",
-		.priv = PRIV_ACTION(VOID, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_PASSTHRU] = {
-		.name = "passthru",
-		.help = "let subsequent rule process matched packets",
-		.priv = PRIV_ACTION(PASSTHRU, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_MARK] = {
-		.name = "mark",
-		.help = "attach 32 bit value to packets",
-		.priv = PRIV_ACTION(MARK, sizeof(struct rte_flow_action_mark)),
-		.next = NEXT(action_mark),
-		.call = parse_vc,
-	},
-	[ACTION_MARK_ID] = {
-		.name = "id",
-		.help = "32 bit value to return with packets",
-		.next = NEXT(action_mark, NEXT_ENTRY(UNSIGNED)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_mark, id)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_FLAG] = {
-		.name = "flag",
-		.help = "flag packets",
-		.priv = PRIV_ACTION(FLAG, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_QUEUE] = {
-		.name = "queue",
-		.help = "assign packets to a given queue index",
-		.priv = PRIV_ACTION(QUEUE,
-				    sizeof(struct rte_flow_action_queue)),
-		.next = NEXT(action_queue),
-		.call = parse_vc,
-	},
-	[ACTION_QUEUE_INDEX] = {
-		.name = "index",
-		.help = "queue index to use",
-		.next = NEXT(action_queue, NEXT_ENTRY(UNSIGNED)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_queue, index)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_DROP] = {
-		.name = "drop",
-		.help = "drop packets (note: passthru has priority)",
-		.priv = PRIV_ACTION(DROP, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_COUNT] = {
-		.name = "count",
-		.help = "enable counters for this rule",
-		.priv = PRIV_ACTION(COUNT, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_DUP] = {
-		.name = "dup",
-		.help = "duplicate packets to a given queue index",
-		.priv = PRIV_ACTION(DUP, sizeof(struct rte_flow_action_dup)),
-		.next = NEXT(action_dup),
-		.call = parse_vc,
-	},
-	[ACTION_DUP_INDEX] = {
-		.name = "index",
-		.help = "queue index to duplicate packets to",
-		.next = NEXT(action_dup, NEXT_ENTRY(UNSIGNED)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_dup, index)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_RSS] = {
-		.name = "rss",
-		.help = "spread packets among several queues",
-		.priv = PRIV_ACTION(RSS, ACTION_RSS_SIZE),
-		.next = NEXT(action_rss),
-		.call = parse_vc,
-	},
-	[ACTION_RSS_QUEUES] = {
-		.name = "queues",
-		.help = "queue indices to use",
-		.next = NEXT(action_rss, NEXT_ENTRY(ACTION_RSS_QUEUE)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_RSS_QUEUE] = {
-		.name = "{queue}",
-		.help = "queue index",
-		.call = parse_vc_action_rss_queue,
-		.comp = comp_vc_action_rss_queue,
-	},
-	[ACTION_PF] = {
-		.name = "pf",
-		.help = "redirect packets to physical device function",
-		.priv = PRIV_ACTION(PF, 0),
-		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
-		.call = parse_vc,
-	},
-	[ACTION_VF] = {
-		.name = "vf",
-		.help = "redirect packets to virtual device function",
-		.priv = PRIV_ACTION(VF, sizeof(struct rte_flow_action_vf)),
-		.next = NEXT(action_vf),
-		.call = parse_vc,
-	},
-	[ACTION_VF_ORIGINAL] = {
-		.name = "original",
-		.help = "use original VF ID if possible",
-		.next = NEXT(action_vf, NEXT_ENTRY(BOOLEAN)),
-		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_action_vf,
-					   original, 1)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_VF_ID] = {
-		.name = "id",
-		.help = "VF ID to redirect packets to",
-		.next = NEXT(action_vf, NEXT_ENTRY(UNSIGNED)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_vf, id)),
-		.call = parse_vc_conf,
-	},
-	[ACTION_METER] = {
-		.name = "meter",
-		.help = "meter the directed packets at given id",
-		.priv = PRIV_ACTION(METER,
-				    sizeof(struct rte_flow_action_meter)),
-		.next = NEXT(action_meter),
-		.call = parse_vc,
-	},
-	[ACTION_METER_ID] = {
-		.name = "mtr_id",
-		.help = "meter id to use",
-		.next = NEXT(action_meter, NEXT_ENTRY(UNSIGNED)),
-		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_meter, mtr_id)),
-		.call = parse_vc_conf,
-	},
-};
-
-/** Remove and return last entry from argument stack. */
-static const struct arg *
-pop_args(struct context *ctx)
-{
-	return ctx->args_num ? ctx->args[--ctx->args_num] : NULL;
-}
-
-/** Add entry on top of the argument stack. */
-static int
-push_args(struct context *ctx, const struct arg *arg)
-{
-	if (ctx->args_num == CTX_STACK_SIZE)
-		return -1;
-	ctx->args[ctx->args_num++] = arg;
-	return 0;
-}
-
-/** Spread value into buffer according to bit-mask. */
-static size_t
-arg_entry_bf_fill(void *dst, uintmax_t val, const struct arg *arg)
-{
-	uint32_t i = arg->size;
-	uint32_t end = 0;
-	int sub = 1;
-	int add = 0;
-	size_t len = 0;
-
-	if (!arg->mask)
-		return 0;
-#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
-	if (!arg->hton) {
-		i = 0;
-		end = arg->size;
-		sub = 0;
-		add = 1;
-	}
-#endif
-	while (i != end) {
-		unsigned int shift = 0;
-		uint8_t *buf = (uint8_t *)dst + arg->offset + (i -= sub);
-
-		for (shift = 0; arg->mask[i] >> shift; ++shift) {
-			if (!(arg->mask[i] & (1 << shift)))
-				continue;
-			++len;
-			if (!dst)
-				continue;
-			*buf &= ~(1 << shift);
-			*buf |= (val & 1) << shift;
-			val >>= 1;
-		}
-		i += add;
-	}
-	return len;
-}
-
-/** Compare a string with a partial one of a given length. */
-static int
-strcmp_partial(const char *full, const char *partial, size_t partial_len)
-{
-	int r = strncmp(full, partial, partial_len);
-
-	if (r)
-		return r;
-	if (strlen(full) <= partial_len)
-		return 0;
-	return full[partial_len];
-}
-
-/**
- * Parse a prefix length and generate a bit-mask.
- *
- * Last argument (ctx->args) is retrieved to determine mask size, storage
- * location and whether the result must use network byte ordering.
- */
-static int
-parse_prefix(struct context *ctx, const struct token *token,
-	     const char *str, unsigned int len,
-	     void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	static const uint8_t conv[] = "\x00\x80\xc0\xe0\xf0\xf8\xfc\xfe\xff";
-	char *end;
-	uintmax_t u;
-	unsigned int bytes;
-	unsigned int extra;
-
-	(void)token;
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	errno = 0;
-	u = strtoumax(str, &end, 0);
-	if (errno || (size_t)(end - str) != len)
-		goto error;
-	if (arg->mask) {
-		uintmax_t v = 0;
-
-		extra = arg_entry_bf_fill(NULL, 0, arg);
-		if (u > extra)
-			goto error;
-		if (!ctx->object)
-			return len;
-		extra -= u;
-		while (u--)
-			(v <<= 1, v |= 1);
-		v <<= extra;
-		if (!arg_entry_bf_fill(ctx->object, v, arg) ||
-		    !arg_entry_bf_fill(ctx->objmask, -1, arg))
-			goto error;
-		return len;
-	}
-	bytes = u / 8;
-	extra = u % 8;
-	size = arg->size;
-	if (bytes > size || bytes + !!extra > size)
-		goto error;
-	if (!ctx->object)
-		return len;
-	buf = (uint8_t *)ctx->object + arg->offset;
-#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
-	if (!arg->hton) {
-		memset((uint8_t *)buf + size - bytes, 0xff, bytes);
-		memset(buf, 0x00, size - bytes);
-		if (extra)
-			((uint8_t *)buf)[size - bytes - 1] = conv[extra];
-	} else
-#endif
-	{
-		memset(buf, 0xff, bytes);
-		memset((uint8_t *)buf + bytes, 0x00, size - bytes);
-		if (extra)
-			((uint8_t *)buf)[bytes] = conv[extra];
-	}
-	if (ctx->objmask)
-		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
-	return len;
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/** Default parsing function for token name matching. */
-static int
-parse_default(struct context *ctx, const struct token *token,
-	      const char *str, unsigned int len,
-	      void *buf, unsigned int size)
-{
-	(void)ctx;
-	(void)buf;
-	(void)size;
-	if (strcmp_partial(token->name, str, len))
-		return -1;
-	return len;
-}
-
-/** Parse flow command, initialize output buffer for subsequent tokens. */
-static int
-parse_init(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;
-	/* Make sure buffer is large enough. */
-	if (size < sizeof(*out))
-		return -1;
-	/* Initialize buffer. */
-	memset(out, 0x00, sizeof(*out));
-	memset((uint8_t *)out + sizeof(*out), 0x22, size - sizeof(*out));
-	ctx->objdata = 0;
-	ctx->object = out;
-	ctx->objmask = NULL;
-	return len;
-}
-
-/** Parse tokens for validate/create commands. */
-static int
-parse_vc(struct context *ctx, const struct token *token,
-	 const char *str, unsigned int len,
-	 void *buf, unsigned int size)
-{
-	struct buffer *out = buf;
-	uint8_t *data;
-	uint32_t data_size;
-
-	/* 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 != VALIDATE && ctx->curr != CREATE)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-		out->args.vc.data = (uint8_t *)out + size;
-		return len;
-	}
-	ctx->objdata = 0;
-	ctx->object = &out->args.vc.attr;
-	ctx->objmask = NULL;
-	switch (ctx->curr) {
-	case GROUP:
-	case PRIORITY:
-		return len;
-	case INGRESS:
-		out->args.vc.attr.ingress = 1;
-		return len;
-	case EGRESS:
-		out->args.vc.attr.egress = 1;
-		return len;
-	case PATTERN:
-		out->args.vc.pattern =
-			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
-					       sizeof(double));
-		ctx->object = out->args.vc.pattern;
-		ctx->objmask = NULL;
-		return len;
-	case ACTIONS:
-		out->args.vc.actions =
-			(void *)RTE_ALIGN_CEIL((uintptr_t)
-					       (out->args.vc.pattern +
-						out->args.vc.pattern_n),
-					       sizeof(double));
-		ctx->object = out->args.vc.actions;
-		ctx->objmask = NULL;
-		return len;
-	default:
-		if (!token->priv)
-			return -1;
-		break;
-	}
-	if (!out->args.vc.actions) {
-		const struct parse_item_priv *priv = token->priv;
-		struct rte_flow_item *item =
-			out->args.vc.pattern + out->args.vc.pattern_n;
-
-		data_size = priv->size * 3; /* spec, last, mask */
-		data = (void *)RTE_ALIGN_FLOOR((uintptr_t)
-					       (out->args.vc.data - data_size),
-					       sizeof(double));
-		if ((uint8_t *)item + sizeof(*item) > data)
-			return -1;
-		*item = (struct rte_flow_item){
-			.type = priv->type,
-		};
-		++out->args.vc.pattern_n;
-		ctx->object = item;
-		ctx->objmask = NULL;
-	} else {
-		const struct parse_action_priv *priv = token->priv;
-		struct rte_flow_action *action =
-			out->args.vc.actions + out->args.vc.actions_n;
-
-		data_size = priv->size; /* configuration */
-		data = (void *)RTE_ALIGN_FLOOR((uintptr_t)
-					       (out->args.vc.data - data_size),
-					       sizeof(double));
-		if ((uint8_t *)action + sizeof(*action) > data)
-			return -1;
-		*action = (struct rte_flow_action){
-			.type = priv->type,
-		};
-		++out->args.vc.actions_n;
-		ctx->object = action;
-		ctx->objmask = NULL;
-	}
-	memset(data, 0, data_size);
-	out->args.vc.data = data;
-	ctx->objdata = data_size;
-	return len;
-}
-
-/** Parse pattern item parameter type. */
-static int
-parse_vc_spec(struct context *ctx, const struct token *token,
-	      const char *str, unsigned int len,
-	      void *buf, unsigned int size)
-{
-	struct buffer *out = buf;
-	struct rte_flow_item *item;
-	uint32_t data_size;
-	int index;
-	int objmask = 0;
-
-	(void)size;
-	/* Token name must match. */
-	if (parse_default(ctx, token, str, len, NULL, 0) < 0)
-		return -1;
-	/* Parse parameter types. */
-	switch (ctx->curr) {
-		static const enum index prefix[] = NEXT_ENTRY(PREFIX);
-
-	case ITEM_PARAM_IS:
-		index = 0;
-		objmask = 1;
-		break;
-	case ITEM_PARAM_SPEC:
-		index = 0;
-		break;
-	case ITEM_PARAM_LAST:
-		index = 1;
-		break;
-	case ITEM_PARAM_PREFIX:
-		/* Modify next token to expect a prefix. */
-		if (ctx->next_num < 2)
-			return -1;
-		ctx->next[ctx->next_num - 2] = prefix;
-		/* Fall through. */
-	case ITEM_PARAM_MASK:
-		index = 2;
-		break;
-	default:
-		return -1;
-	}
-	/* Nothing else to do if there is no buffer. */
-	if (!out)
-		return len;
-	if (!out->args.vc.pattern_n)
-		return -1;
-	item = &out->args.vc.pattern[out->args.vc.pattern_n - 1];
-	data_size = ctx->objdata / 3; /* spec, last, mask */
-	/* Point to selected object. */
-	ctx->object = out->args.vc.data + (data_size * index);
-	if (objmask) {
-		ctx->objmask = out->args.vc.data + (data_size * 2); /* mask */
-		item->mask = ctx->objmask;
-	} else
-		ctx->objmask = NULL;
-	/* Update relevant item pointer. */
-	*((const void **[]){ &item->spec, &item->last, &item->mask })[index] =
-		ctx->object;
-	return len;
-}
-
-/** Parse action configuration field. */
-static int
-parse_vc_conf(struct context *ctx, const struct token *token,
-	      const char *str, unsigned int len,
-	      void *buf, unsigned int size)
-{
-	struct buffer *out = buf;
-	struct rte_flow_action *action;
-
-	(void)size;
-	/* 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->args.vc.actions_n)
-		return -1;
-	action = &out->args.vc.actions[out->args.vc.actions_n - 1];
-	/* Point to selected object. */
-	ctx->object = out->args.vc.data;
-	ctx->objmask = NULL;
-	/* Update configuration pointer. */
-	action->conf = ctx->object;
-	return len;
-}
-
-/**
- * Parse queue field for RSS action.
- *
- * Valid tokens are queue indices and the "end" token.
- */
-static int
-parse_vc_action_rss_queue(struct context *ctx, const struct token *token,
-			  const char *str, unsigned int len,
-			  void *buf, unsigned int size)
-{
-	static const enum index next[] = NEXT_ENTRY(ACTION_RSS_QUEUE);
-	int ret;
-	int i;
-
-	(void)token;
-	(void)buf;
-	(void)size;
-	if (ctx->curr != ACTION_RSS_QUEUE)
-		return -1;
-	i = ctx->objdata >> 16;
-	if (!strcmp_partial("end", str, len)) {
-		ctx->objdata &= 0xffff;
-		return len;
-	}
-	if (i >= ACTION_RSS_NUM)
-		return -1;
-	if (push_args(ctx, ARGS_ENTRY(struct rte_flow_action_rss, queue[i])))
-		return -1;
-	ret = parse_int(ctx, token, str, len, NULL, 0);
-	if (ret < 0) {
-		pop_args(ctx);
-		return -1;
-	}
-	++i;
-	ctx->objdata = i << 16 | (ctx->objdata & 0xffff);
-	/* Repeat token. */
-	if (ctx->next_num == RTE_DIM(ctx->next))
-		return -1;
-	ctx->next[ctx->next_num++] = next;
-	if (!ctx->object)
-		return len;
-	((struct rte_flow_action_rss *)ctx->object)->num = i;
-	return len;
-}
-
-/** Parse tokens for destroy command. */
-static int
-parse_destroy(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 != DESTROY)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-		out->args.destroy.rule =
-			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
-					       sizeof(double));
-		return len;
-	}
-	if (((uint8_t *)(out->args.destroy.rule + out->args.destroy.rule_n) +
-	     sizeof(*out->args.destroy.rule)) > (uint8_t *)out + size)
-		return -1;
-	ctx->objdata = 0;
-	ctx->object = out->args.destroy.rule + out->args.destroy.rule_n++;
-	ctx->objmask = NULL;
-	return len;
-}
-
-/** Parse tokens for flush command. */
-static int
-parse_flush(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 != FLUSH)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-	}
-	return len;
-}
-
-/** Parse tokens for query command. */
-static int
-parse_query(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 != QUERY)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-	}
-	return len;
-}
-
-/** Parse action names. */
-static int
-parse_action(struct context *ctx, const struct token *token,
-	     const char *str, unsigned int len,
-	     void *buf, unsigned int size)
-{
-	struct buffer *out = buf;
-	const struct arg *arg = pop_args(ctx);
-	unsigned int i;
-
-	(void)size;
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	/* Parse action name. */
-	for (i = 0; next_action[i]; ++i) {
-		const struct parse_action_priv *priv;
-
-		token = &token_list[next_action[i]];
-		if (strcmp_partial(token->name, str, len))
-			continue;
-		priv = token->priv;
-		if (!priv)
-			goto error;
-		if (out)
-			memcpy((uint8_t *)ctx->object + arg->offset,
-			       &priv->type,
-			       arg->size);
-		return len;
-	}
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/** Parse tokens for list command. */
-static int
-parse_list(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 != LIST)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-		out->args.list.group =
-			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
-					       sizeof(double));
-		return len;
-	}
-	if (((uint8_t *)(out->args.list.group + out->args.list.group_n) +
-	     sizeof(*out->args.list.group)) > (uint8_t *)out + size)
-		return -1;
-	ctx->objdata = 0;
-	ctx->object = out->args.list.group + out->args.list.group_n++;
-	ctx->objmask = NULL;
-	return len;
-}
-
-/** Parse tokens for isolate command. */
-static int
-parse_isolate(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 != ISOLATE)
-			return -1;
-		if (sizeof(*out) > size)
-			return -1;
-		out->command = ctx->curr;
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-	}
-	return len;
-}
-
-/**
- * Parse signed/unsigned integers 8 to 64-bit long.
- *
- * Last argument (ctx->args) is retrieved to determine integer type and
- * storage location.
- */
-static int
-parse_int(struct context *ctx, const struct token *token,
-	  const char *str, unsigned int len,
-	  void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	uintmax_t u;
-	char *end;
-
-	(void)token;
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	errno = 0;
-	u = arg->sign ?
-		(uintmax_t)strtoimax(str, &end, 0) :
-		strtoumax(str, &end, 0);
-	if (errno || (size_t)(end - str) != len)
-		goto error;
-	if (!ctx->object)
-		return len;
-	if (arg->mask) {
-		if (!arg_entry_bf_fill(ctx->object, u, arg) ||
-		    !arg_entry_bf_fill(ctx->objmask, -1, arg))
-			goto error;
-		return len;
-	}
-	buf = (uint8_t *)ctx->object + arg->offset;
-	size = arg->size;
-objmask:
-	switch (size) {
-	case sizeof(uint8_t):
-		*(uint8_t *)buf = u;
-		break;
-	case sizeof(uint16_t):
-		*(uint16_t *)buf = arg->hton ? rte_cpu_to_be_16(u) : u;
-		break;
-	case sizeof(uint8_t [3]):
-#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
-		if (!arg->hton) {
-			((uint8_t *)buf)[0] = u;
-			((uint8_t *)buf)[1] = u >> 8;
-			((uint8_t *)buf)[2] = u >> 16;
-			break;
-		}
-#endif
-		((uint8_t *)buf)[0] = u >> 16;
-		((uint8_t *)buf)[1] = u >> 8;
-		((uint8_t *)buf)[2] = u;
-		break;
-	case sizeof(uint32_t):
-		*(uint32_t *)buf = arg->hton ? rte_cpu_to_be_32(u) : u;
-		break;
-	case sizeof(uint64_t):
-		*(uint64_t *)buf = arg->hton ? rte_cpu_to_be_64(u) : u;
-		break;
-	default:
-		goto error;
-	}
-	if (ctx->objmask && buf != (uint8_t *)ctx->objmask + arg->offset) {
-		u = -1;
-		buf = (uint8_t *)ctx->objmask + arg->offset;
-		goto objmask;
-	}
-	return len;
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/**
- * Parse a string.
- *
- * Two arguments (ctx->args) are retrieved from the stack to store data and
- * its length (in that order).
- */
-static int
-parse_string(struct context *ctx, const struct token *token,
-	     const char *str, unsigned int len,
-	     void *buf, unsigned int size)
-{
-	const struct arg *arg_data = pop_args(ctx);
-	const struct arg *arg_len = pop_args(ctx);
-	char tmp[16]; /* Ought to be enough. */
-	int ret;
-
-	/* Arguments are expected. */
-	if (!arg_data)
-		return -1;
-	if (!arg_len) {
-		push_args(ctx, arg_data);
-		return -1;
-	}
-	size = arg_data->size;
-	/* Bit-mask fill is not supported. */
-	if (arg_data->mask || size < len)
-		goto error;
-	if (!ctx->object)
-		return len;
-	/* Let parse_int() fill length information first. */
-	ret = snprintf(tmp, sizeof(tmp), "%u", len);
-	if (ret < 0)
-		goto error;
-	push_args(ctx, arg_len);
-	ret = parse_int(ctx, token, tmp, ret, NULL, 0);
-	if (ret < 0) {
-		pop_args(ctx);
-		goto error;
-	}
-	buf = (uint8_t *)ctx->object + arg_data->offset;
-	/* Output buffer is not necessarily NUL-terminated. */
-	memcpy(buf, str, len);
-	memset((uint8_t *)buf + len, 0x55, size - len);
-	if (ctx->objmask)
-		memset((uint8_t *)ctx->objmask + arg_data->offset, 0xff, len);
-	return len;
-error:
-	push_args(ctx, arg_len);
-	push_args(ctx, arg_data);
-	return -1;
-}
-
-/**
- * Parse a MAC address.
- *
- * Last argument (ctx->args) is retrieved to determine storage size and
- * location.
- */
-static int
-parse_mac_addr(struct context *ctx, const struct token *token,
-	       const char *str, unsigned int len,
-	       void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	struct ether_addr tmp;
-	int ret;
-
-	(void)token;
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	size = arg->size;
-	/* Bit-mask fill is not supported. */
-	if (arg->mask || size != sizeof(tmp))
-		goto error;
-	/* Only network endian is supported. */
-	if (!arg->hton)
-		goto error;
-	ret = cmdline_parse_etheraddr(NULL, str, &tmp, size);
-	if (ret < 0 || (unsigned int)ret != len)
-		goto error;
-	if (!ctx->object)
-		return len;
-	buf = (uint8_t *)ctx->object + arg->offset;
-	memcpy(buf, &tmp, size);
-	if (ctx->objmask)
-		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
-	return len;
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/**
- * Parse an IPv4 address.
- *
- * Last argument (ctx->args) is retrieved to determine storage size and
- * location.
- */
-static int
-parse_ipv4_addr(struct context *ctx, const struct token *token,
-		const char *str, unsigned int len,
-		void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	char str2[len + 1];
-	struct in_addr tmp;
-	int ret;
-
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	size = arg->size;
-	/* Bit-mask fill is not supported. */
-	if (arg->mask || size != sizeof(tmp))
-		goto error;
-	/* Only network endian is supported. */
-	if (!arg->hton)
-		goto error;
-	memcpy(str2, str, len);
-	str2[len] = '\0';
-	ret = inet_pton(AF_INET, str2, &tmp);
-	if (ret != 1) {
-		/* Attempt integer parsing. */
-		push_args(ctx, arg);
-		return parse_int(ctx, token, str, len, buf, size);
-	}
-	if (!ctx->object)
-		return len;
-	buf = (uint8_t *)ctx->object + arg->offset;
-	memcpy(buf, &tmp, size);
-	if (ctx->objmask)
-		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
-	return len;
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/**
- * Parse an IPv6 address.
- *
- * Last argument (ctx->args) is retrieved to determine storage size and
- * location.
- */
-static int
-parse_ipv6_addr(struct context *ctx, const struct token *token,
-		const char *str, unsigned int len,
-		void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	char str2[len + 1];
-	struct in6_addr tmp;
-	int ret;
-
-	(void)token;
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	size = arg->size;
-	/* Bit-mask fill is not supported. */
-	if (arg->mask || size != sizeof(tmp))
-		goto error;
-	/* Only network endian is supported. */
-	if (!arg->hton)
-		goto error;
-	memcpy(str2, str, len);
-	str2[len] = '\0';
-	ret = inet_pton(AF_INET6, str2, &tmp);
-	if (ret != 1)
-		goto error;
-	if (!ctx->object)
-		return len;
-	buf = (uint8_t *)ctx->object + arg->offset;
-	memcpy(buf, &tmp, size);
-	if (ctx->objmask)
-		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
-	return len;
-error:
-	push_args(ctx, arg);
-	return -1;
-}
-
-/** Boolean values (even indices stand for false). */
-static const char *const boolean_name[] = {
-	"0", "1",
-	"false", "true",
-	"no", "yes",
-	"N", "Y",
-	NULL,
-};
-
-/**
- * Parse a boolean value.
- *
- * Last argument (ctx->args) is retrieved to determine storage size and
- * location.
- */
-static int
-parse_boolean(struct context *ctx, const struct token *token,
-	      const char *str, unsigned int len,
-	      void *buf, unsigned int size)
-{
-	const struct arg *arg = pop_args(ctx);
-	unsigned int i;
-	int ret;
-
-	/* Argument is expected. */
-	if (!arg)
-		return -1;
-	for (i = 0; boolean_name[i]; ++i)
-		if (!strcmp_partial(boolean_name[i], str, len))
-			break;
-	/* Process token as integer. */
-	if (boolean_name[i])
-		str = i & 1 ? "1" : "0";
-	push_args(ctx, arg);
-	ret = parse_int(ctx, token, str, strlen(str), buf, size);
-	return ret > 0 ? (int)len : ret;
-}
-
-/** Parse port and update context. */
-static int
-parse_port(struct context *ctx, const struct token *token,
-	   const char *str, unsigned int len,
-	   void *buf, unsigned int size)
-{
-	struct buffer *out = &(struct buffer){ .port = 0 };
-	int ret;
-
-	if (buf)
-		out = buf;
-	else {
-		ctx->objdata = 0;
-		ctx->object = out;
-		ctx->objmask = NULL;
-		size = sizeof(*out);
-	}
-	ret = parse_int(ctx, token, str, len, out, size);
-	if (ret >= 0)
-		ctx->port = out->port;
-	if (!buf)
-		ctx->object = NULL;
-	return ret;
-}
-
-/** No completion. */
-static int
-comp_none(struct context *ctx, const struct token *token,
-	  unsigned int ent, char *buf, unsigned int size)
-{
-	(void)ctx;
-	(void)token;
-	(void)ent;
-	(void)buf;
-	(void)size;
-	return 0;
-}
-
-/** Complete boolean values. */
-static int
-comp_boolean(struct context *ctx, const struct token *token,
-	     unsigned int ent, char *buf, unsigned int size)
-{
-	unsigned int i;
-
-	(void)ctx;
-	(void)token;
-	for (i = 0; boolean_name[i]; ++i)
-		if (buf && i == ent)
-			return snprintf(buf, size, "%s", boolean_name[i]);
-	if (buf)
-		return -1;
-	return i;
-}
-
-/** Complete action names. */
-static int
-comp_action(struct context *ctx, const struct token *token,
-	    unsigned int ent, char *buf, unsigned int size)
-{
-	unsigned int i;
-
-	(void)ctx;
-	(void)token;
-	for (i = 0; next_action[i]; ++i)
-		if (buf && i == ent)
-			return snprintf(buf, size, "%s",
-					token_list[next_action[i]].name);
-	if (buf)
-		return -1;
-	return i;
-}
-
-/** Complete available ports. */
-static int
-comp_port(struct context *ctx, const struct token *token,
-	  unsigned int ent, char *buf, unsigned int size)
-{
-	unsigned int i = 0;
-	portid_t p;
-
-	(void)ctx;
-	(void)token;
-	RTE_ETH_FOREACH_DEV(p) {
-		if (buf && i == ent)
-			return snprintf(buf, size, "%u", p);
-		++i;
-	}
-	if (buf)
-		return -1;
-	return i;
-}
-
-/** Complete available rule IDs. */
-static int
-comp_rule_id(struct context *ctx, const struct token *token,
-	     unsigned int ent, char *buf, unsigned int size)
-{
-	unsigned int i = 0;
-	struct rte_port *port;
-	struct port_flow *pf;
-
-	(void)token;
-	if (port_id_is_invalid(ctx->port, DISABLED_WARN) ||
-	    ctx->port == (portid_t)RTE_PORT_ALL)
-		return -1;
-	port = &ports[ctx->port];
-	for (pf = port->flow_list; pf != NULL; pf = pf->next) {
-		if (buf && i == ent)
-			return snprintf(buf, size, "%u", pf->id);
-		++i;
-	}
-	if (buf)
-		return -1;
-	return i;
-}
-
-/** Complete queue field for RSS action. */
-static int
-comp_vc_action_rss_queue(struct context *ctx, const struct token *token,
-			 unsigned int ent, char *buf, unsigned int size)
-{
-	static const char *const str[] = { "", "end", NULL };
-	unsigned int i;
-
-	(void)ctx;
-	(void)token;
-	for (i = 0; str[i] != NULL; ++i)
-		if (buf && i == ent)
-			return snprintf(buf, size, "%s", str[i]);
-	if (buf)
-		return -1;
-	return i;
-}
-
-/** Internal context. */
-static struct context cmd_flow_context;
-
-/** Global parser instance (cmdline API). */
-cmdline_parse_inst_t cmd_flow;
-
-/** Initialize context. */
-static void
-cmd_flow_context_init(struct context *ctx)
-{
-	/* A full memset() is not necessary. */
-	ctx->curr = ZERO;
-	ctx->prev = ZERO;
-	ctx->next_num = 0;
-	ctx->args_num = 0;
-	ctx->eol = 0;
-	ctx->last = 0;
-	ctx->port = 0;
-	ctx->objdata = 0;
-	ctx->object = NULL;
-	ctx->objmask = NULL;
-}
-
-/** Parse a token (cmdline API). */
-static int
-cmd_flow_parse(cmdline_parse_token_hdr_t *hdr, const char *src, void *result,
-	       unsigned int size)
-{
-	struct context *ctx = &cmd_flow_context;
-	const struct token *token;
-	const enum index *list;
-	int len;
-	int i;
-
-	(void)hdr;
-	token = &token_list[ctx->curr];
-	/* Check argument length. */
-	ctx->eol = 0;
-	ctx->last = 1;
-	for (len = 0; src[len]; ++len)
-		if (src[len] == '#' || isspace(src[len]))
-			break;
-	if (!len)
-		return -1;
-	/* Last argument and EOL detection. */
-	for (i = len; src[i]; ++i)
-		if (src[i] == '#' || src[i] == '\r' || src[i] == '\n')
-			break;
-		else if (!isspace(src[i])) {
-			ctx->last = 0;
-			break;
-		}
-	for (; src[i]; ++i)
-		if (src[i] == '\r' || src[i] == '\n') {
-			ctx->eol = 1;
-			break;
-		}
-	/* Initialize context if necessary. */
-	if (!ctx->next_num) {
-		if (!token->next)
-			return 0;
-		ctx->next[ctx->next_num++] = token->next[0];
-	}
-	/* Process argument through candidates. */
-	ctx->prev = ctx->curr;
-	list = ctx->next[ctx->next_num - 1];
-	for (i = 0; list[i]; ++i) {
-		const struct token *next = &token_list[list[i]];
-		int tmp;
-
-		ctx->curr = list[i];
-		if (next->call)
-			tmp = next->call(ctx, next, src, len, result, size);
-		else
-			tmp = parse_default(ctx, next, src, len, result, size);
-		if (tmp == -1 || tmp != len)
-			continue;
-		token = next;
-		break;
-	}
-	if (!list[i])
-		return -1;
-	--ctx->next_num;
-	/* Push subsequent tokens if any. */
-	if (token->next)
-		for (i = 0; token->next[i]; ++i) {
-			if (ctx->next_num == RTE_DIM(ctx->next))
-				return -1;
-			ctx->next[ctx->next_num++] = token->next[i];
-		}
-	/* Push arguments if any. */
-	if (token->args)
-		for (i = 0; token->args[i]; ++i) {
-			if (ctx->args_num == RTE_DIM(ctx->args))
-				return -1;
-			ctx->args[ctx->args_num++] = token->args[i];
-		}
-	return len;
-}
-
-/** Return number of completion entries (cmdline API). */
-static int
-cmd_flow_complete_get_nb(cmdline_parse_token_hdr_t *hdr)
-{
-	struct context *ctx = &cmd_flow_context;
-	const struct token *token = &token_list[ctx->curr];
-	const enum index *list;
-	int i;
-
-	(void)hdr;
-	/* Count number of tokens in current list. */
-	if (ctx->next_num)
-		list = ctx->next[ctx->next_num - 1];
-	else
-		list = token->next[0];
-	for (i = 0; list[i]; ++i)
-		;
-	if (!i)
-		return 0;
-	/*
-	 * If there is a single token, use its completion callback, otherwise
-	 * return the number of entries.
-	 */
-	token = &token_list[list[0]];
-	if (i == 1 && token->comp) {
-		/* Save index for cmd_flow_get_help(). */
-		ctx->prev = list[0];
-		return token->comp(ctx, token, 0, NULL, 0);
-	}
-	return i;
-}
-
-/** Return a completion entry (cmdline API). */
-static int
-cmd_flow_complete_get_elt(cmdline_parse_token_hdr_t *hdr, int index,
-			  char *dst, unsigned int size)
-{
-	struct context *ctx = &cmd_flow_context;
-	const struct token *token = &token_list[ctx->curr];
-	const enum index *list;
-	int i;
-
-	(void)hdr;
-	/* Count number of tokens in current list. */
-	if (ctx->next_num)
-		list = ctx->next[ctx->next_num - 1];
-	else
-		list = token->next[0];
-	for (i = 0; list[i]; ++i)
-		;
-	if (!i)
-		return -1;
-	/* If there is a single token, use its completion callback. */
-	token = &token_list[list[0]];
-	if (i == 1 && token->comp) {
-		/* Save index for cmd_flow_get_help(). */
-		ctx->prev = list[0];
-		return token->comp(ctx, token, index, dst, size) < 0 ? -1 : 0;
-	}
-	/* Otherwise make sure the index is valid and use defaults. */
-	if (index >= i)
-		return -1;
-	token = &token_list[list[index]];
-	snprintf(dst, size, "%s", token->name);
-	/* Save index for cmd_flow_get_help(). */
-	ctx->prev = list[index];
-	return 0;
-}
-
-/** Populate help strings for current token (cmdline API). */
-static int
-cmd_flow_get_help(cmdline_parse_token_hdr_t *hdr, char *dst, unsigned int size)
-{
-	struct context *ctx = &cmd_flow_context;
-	const struct token *token = &token_list[ctx->prev];
-
-	(void)hdr;
-	if (!size)
-		return -1;
-	/* Set token type and update global help with details. */
-	snprintf(dst, size, "%s", (token->type ? token->type : "TOKEN"));
-	if (token->help)
-		cmd_flow.help_str = token->help;
-	else
-		cmd_flow.help_str = token->name;
-	return 0;
-}
-
-/** Token definition template (cmdline API). */
-static struct cmdline_token_hdr cmd_flow_token_hdr = {
-	.ops = &(struct cmdline_token_ops){
-		.parse = cmd_flow_parse,
-		.complete_get_nb = cmd_flow_complete_get_nb,
-		.complete_get_elt = cmd_flow_complete_get_elt,
-		.get_help = cmd_flow_get_help,
-	},
-	.offset = 0,
-};
-
-/** Populate the next dynamic token. */
-static void
-cmd_flow_tok(cmdline_parse_token_hdr_t **hdr,
-	     cmdline_parse_token_hdr_t **hdr_inst)
-{
-	struct context *ctx = &cmd_flow_context;
-
-	/* Always reinitialize context before requesting the first token. */
-	if (!(hdr_inst - cmd_flow.tokens))
-		cmd_flow_context_init(ctx);
-	/* Return NULL when no more tokens are expected. */
-	if (!ctx->next_num && ctx->curr) {
-		*hdr = NULL;
-		return;
-	}
-	/* Determine if command should end here. */
-	if (ctx->eol && ctx->last && ctx->next_num) {
-		const enum index *list = ctx->next[ctx->next_num - 1];
-		int i;
-
-		for (i = 0; list[i]; ++i) {
-			if (list[i] != END)
-				continue;
-			*hdr = NULL;
-			return;
-		}
-	}
-	*hdr = &cmd_flow_token_hdr;
-}
-
-/** Dispatch parsed buffer to function calls. */
-static void
-cmd_flow_parsed(const struct buffer *in)
-{
-	switch (in->command) {
-	case VALIDATE:
-		port_flow_validate(in->port, &in->args.vc.attr,
-				   in->args.vc.pattern, in->args.vc.actions);
-		break;
-	case CREATE:
-		port_flow_create(in->port, &in->args.vc.attr,
-				 in->args.vc.pattern, in->args.vc.actions);
-		break;
-	case DESTROY:
-		port_flow_destroy(in->port, in->args.destroy.rule_n,
-				  in->args.destroy.rule);
-		break;
-	case FLUSH:
-		port_flow_flush(in->port);
-		break;
-	case QUERY:
-		port_flow_query(in->port, in->args.query.rule,
-				in->args.query.action);
-		break;
-	case LIST:
-		port_flow_list(in->port, in->args.list.group_n,
-			       in->args.list.group);
-		break;
-	case ISOLATE:
-		port_flow_isolate(in->port, in->args.isolate.set);
-		break;
-	default:
-		break;
-	}
-}
-
-/** Token generator and output processing callback (cmdline API). */
-static void
-cmd_flow_cb(void *arg0, struct cmdline *cl, void *arg2)
-{
-	if (cl == NULL)
-		cmd_flow_tok(arg0, arg2);
-	else
-		cmd_flow_parsed(arg0);
-}
-
-/** Global parser instance (cmdline API). */
-cmdline_parse_inst_t cmd_flow = {
-	.f = cmd_flow_cb,
-	.data = NULL, /**< Unused. */
-	.help_str = NULL, /**< Updated by cmd_flow_get_help(). */
-	.tokens = {
-		NULL,
-	}, /**< Tokens are returned by cmd_flow_tok(). */
-};
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index b2dbd56..246e116 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -66,7 +66,6 @@ 
 #include <rte_ethdev.h>
 #include <rte_string_fns.h>
 #include <rte_cycles.h>
-#include <rte_flow.h>
 #include <rte_errno.h>
 #ifdef RTE_LIBRTE_IXGBE_PMD
 #include <rte_pmd_ixgbe.h>
@@ -78,6 +77,7 @@ 
 #include <rte_pmd_bnxt.h>
 #endif
 #include <rte_gro.h>
+#include <cmdline_flow.h>
 
 #include "testpmd.h"
 
@@ -732,21 +732,6 @@  port_offload_cap_display(portid_t port_id)
 
 }
 
-int
-port_id_is_invalid(portid_t port_id, enum print_warning warning)
-{
-	if (port_id == (portid_t)RTE_PORT_ALL)
-		return 0;
-
-	if (rte_eth_dev_is_valid_port(port_id))
-		return 0;
-
-	if (warning == ENABLED_WARN)
-		printf("Invalid port %d\n", port_id);
-
-	return 1;
-}
-
 static int
 vlan_id_is_invalid(uint16_t vlan_id)
 {
@@ -945,530 +930,6 @@  port_mtu_set(portid_t port_id, uint16_t mtu)
 	printf("Set MTU failed. diag=%d\n", diag);
 }
 
-/* Generic flow management functions. */
-
-/** Generate flow_item[] entry. */
-#define MK_FLOW_ITEM(t, s) \
-	[RTE_FLOW_ITEM_TYPE_ ## t] = { \
-		.name = # t, \
-		.size = s, \
-	}
-
-/** Information about known flow pattern items. */
-static const struct {
-	const char *name;
-	size_t size;
-} flow_item[] = {
-	MK_FLOW_ITEM(END, 0),
-	MK_FLOW_ITEM(VOID, 0),
-	MK_FLOW_ITEM(INVERT, 0),
-	MK_FLOW_ITEM(ANY, sizeof(struct rte_flow_item_any)),
-	MK_FLOW_ITEM(PF, 0),
-	MK_FLOW_ITEM(VF, sizeof(struct rte_flow_item_vf)),
-	MK_FLOW_ITEM(PORT, sizeof(struct rte_flow_item_port)),
-	MK_FLOW_ITEM(RAW, sizeof(struct rte_flow_item_raw)), /* +pattern[] */
-	MK_FLOW_ITEM(ETH, sizeof(struct rte_flow_item_eth)),
-	MK_FLOW_ITEM(VLAN, sizeof(struct rte_flow_item_vlan)),
-	MK_FLOW_ITEM(IPV4, sizeof(struct rte_flow_item_ipv4)),
-	MK_FLOW_ITEM(IPV6, sizeof(struct rte_flow_item_ipv6)),
-	MK_FLOW_ITEM(ICMP, sizeof(struct rte_flow_item_icmp)),
-	MK_FLOW_ITEM(UDP, sizeof(struct rte_flow_item_udp)),
-	MK_FLOW_ITEM(TCP, sizeof(struct rte_flow_item_tcp)),
-	MK_FLOW_ITEM(SCTP, sizeof(struct rte_flow_item_sctp)),
-	MK_FLOW_ITEM(VXLAN, sizeof(struct rte_flow_item_vxlan)),
-	MK_FLOW_ITEM(E_TAG, sizeof(struct rte_flow_item_e_tag)),
-	MK_FLOW_ITEM(NVGRE, sizeof(struct rte_flow_item_nvgre)),
-	MK_FLOW_ITEM(MPLS, sizeof(struct rte_flow_item_mpls)),
-	MK_FLOW_ITEM(GRE, sizeof(struct rte_flow_item_gre)),
-	MK_FLOW_ITEM(FUZZY, sizeof(struct rte_flow_item_fuzzy)),
-	MK_FLOW_ITEM(GTP, sizeof(struct rte_flow_item_gtp)),
-	MK_FLOW_ITEM(GTPC, sizeof(struct rte_flow_item_gtp)),
-	MK_FLOW_ITEM(GTPU, sizeof(struct rte_flow_item_gtp)),
-};
-
-/** Compute storage space needed by item specification. */
-static void
-flow_item_spec_size(const struct rte_flow_item *item,
-		    size_t *size, size_t *pad)
-{
-	if (!item->spec) {
-		*size = 0;
-		goto empty;
-	}
-	switch (item->type) {
-		union {
-			const struct rte_flow_item_raw *raw;
-		} spec;
-
-	case RTE_FLOW_ITEM_TYPE_RAW:
-		spec.raw = item->spec;
-		*size = offsetof(struct rte_flow_item_raw, pattern) +
-			spec.raw->length * sizeof(*spec.raw->pattern);
-		break;
-	default:
-		*size = flow_item[item->type].size;
-		break;
-	}
-empty:
-	*pad = RTE_ALIGN_CEIL(*size, sizeof(double)) - *size;
-}
-
-/** Generate flow_action[] entry. */
-#define MK_FLOW_ACTION(t, s) \
-	[RTE_FLOW_ACTION_TYPE_ ## t] = { \
-		.name = # t, \
-		.size = s, \
-	}
-
-/** Information about known flow actions. */
-static const struct {
-	const char *name;
-	size_t size;
-} flow_action[] = {
-	MK_FLOW_ACTION(END, 0),
-	MK_FLOW_ACTION(VOID, 0),
-	MK_FLOW_ACTION(PASSTHRU, 0),
-	MK_FLOW_ACTION(MARK, sizeof(struct rte_flow_action_mark)),
-	MK_FLOW_ACTION(FLAG, 0),
-	MK_FLOW_ACTION(QUEUE, sizeof(struct rte_flow_action_queue)),
-	MK_FLOW_ACTION(DROP, 0),
-	MK_FLOW_ACTION(COUNT, 0),
-	MK_FLOW_ACTION(DUP, sizeof(struct rte_flow_action_dup)),
-	MK_FLOW_ACTION(RSS, sizeof(struct rte_flow_action_rss)), /* +queue[] */
-	MK_FLOW_ACTION(PF, 0),
-	MK_FLOW_ACTION(VF, sizeof(struct rte_flow_action_vf)),
-};
-
-/** Compute storage space needed by action configuration. */
-static void
-flow_action_conf_size(const struct rte_flow_action *action,
-		      size_t *size, size_t *pad)
-{
-	if (!action->conf) {
-		*size = 0;
-		goto empty;
-	}
-	switch (action->type) {
-		union {
-			const struct rte_flow_action_rss *rss;
-		} conf;
-
-	case RTE_FLOW_ACTION_TYPE_RSS:
-		conf.rss = action->conf;
-		*size = offsetof(struct rte_flow_action_rss, queue) +
-			conf.rss->num * sizeof(*conf.rss->queue);
-		break;
-	default:
-		*size = flow_action[action->type].size;
-		break;
-	}
-empty:
-	*pad = RTE_ALIGN_CEIL(*size, sizeof(double)) - *size;
-}
-
-/** Generate a port_flow entry from attributes/pattern/actions. */
-static struct port_flow *
-port_flow_new(const struct rte_flow_attr *attr,
-	      const struct rte_flow_item *pattern,
-	      const struct rte_flow_action *actions)
-{
-	const struct rte_flow_item *item;
-	const struct rte_flow_action *action;
-	struct port_flow *pf = NULL;
-	size_t tmp;
-	size_t pad;
-	size_t off1 = 0;
-	size_t off2 = 0;
-	int err = ENOTSUP;
-
-store:
-	item = pattern;
-	if (pf)
-		pf->pattern = (void *)&pf->data[off1];
-	do {
-		struct rte_flow_item *dst = NULL;
-
-		if ((unsigned int)item->type >= RTE_DIM(flow_item) ||
-		    !flow_item[item->type].name)
-			goto notsup;
-		if (pf)
-			dst = memcpy(pf->data + off1, item, sizeof(*item));
-		off1 += sizeof(*item);
-		flow_item_spec_size(item, &tmp, &pad);
-		if (item->spec) {
-			if (pf)
-				dst->spec = memcpy(pf->data + off2,
-						   item->spec, tmp);
-			off2 += tmp + pad;
-		}
-		if (item->last) {
-			if (pf)
-				dst->last = memcpy(pf->data + off2,
-						   item->last, tmp);
-			off2 += tmp + pad;
-		}
-		if (item->mask) {
-			if (pf)
-				dst->mask = memcpy(pf->data + off2,
-						   item->mask, tmp);
-			off2 += tmp + pad;
-		}
-		off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
-	} while ((item++)->type != RTE_FLOW_ITEM_TYPE_END);
-	off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
-	action = actions;
-	if (pf)
-		pf->actions = (void *)&pf->data[off1];
-	do {
-		struct rte_flow_action *dst = NULL;
-
-		if ((unsigned int)action->type >= RTE_DIM(flow_action) ||
-		    !flow_action[action->type].name)
-			goto notsup;
-		if (pf)
-			dst = memcpy(pf->data + off1, action, sizeof(*action));
-		off1 += sizeof(*action);
-		flow_action_conf_size(action, &tmp, &pad);
-		if (action->conf) {
-			if (pf)
-				dst->conf = memcpy(pf->data + off2,
-						   action->conf, tmp);
-			off2 += tmp + pad;
-		}
-		off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
-	} while ((action++)->type != RTE_FLOW_ACTION_TYPE_END);
-	if (pf != NULL)
-		return pf;
-	off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
-	tmp = RTE_ALIGN_CEIL(offsetof(struct port_flow, data), sizeof(double));
-	pf = calloc(1, tmp + off1 + off2);
-	if (pf == NULL)
-		err = errno;
-	else {
-		*pf = (const struct port_flow){
-			.size = tmp + off1 + off2,
-			.attr = *attr,
-		};
-		tmp -= offsetof(struct port_flow, data);
-		off2 = tmp + off1;
-		off1 = tmp;
-		goto store;
-	}
-notsup:
-	rte_errno = err;
-	return NULL;
-}
-
-/** Print a message out of a flow error. */
-static int
-port_flow_complain(struct rte_flow_error *error)
-{
-	static const char *const errstrlist[] = {
-		[RTE_FLOW_ERROR_TYPE_NONE] = "no error",
-		[RTE_FLOW_ERROR_TYPE_UNSPECIFIED] = "cause unspecified",
-		[RTE_FLOW_ERROR_TYPE_HANDLE] = "flow rule (handle)",
-		[RTE_FLOW_ERROR_TYPE_ATTR_GROUP] = "group field",
-		[RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY] = "priority field",
-		[RTE_FLOW_ERROR_TYPE_ATTR_INGRESS] = "ingress field",
-		[RTE_FLOW_ERROR_TYPE_ATTR_EGRESS] = "egress field",
-		[RTE_FLOW_ERROR_TYPE_ATTR] = "attributes structure",
-		[RTE_FLOW_ERROR_TYPE_ITEM_NUM] = "pattern length",
-		[RTE_FLOW_ERROR_TYPE_ITEM] = "specific pattern item",
-		[RTE_FLOW_ERROR_TYPE_ACTION_NUM] = "number of actions",
-		[RTE_FLOW_ERROR_TYPE_ACTION] = "specific action",
-	};
-	const char *errstr;
-	char buf[32];
-	int err = rte_errno;
-
-	if ((unsigned int)error->type >= RTE_DIM(errstrlist) ||
-	    !errstrlist[error->type])
-		errstr = "unknown type";
-	else
-		errstr = errstrlist[error->type];
-	printf("Caught error type %d (%s): %s%s\n",
-	       error->type, errstr,
-	       error->cause ? (snprintf(buf, sizeof(buf), "cause: %p, ",
-					error->cause), buf) : "",
-	       error->message ? error->message : "(no stated reason)");
-	return -err;
-}
-
-/** Validate flow rule. */
-int
-port_flow_validate(portid_t port_id,
-		   const struct rte_flow_attr *attr,
-		   const struct rte_flow_item *pattern,
-		   const struct rte_flow_action *actions)
-{
-	struct rte_flow_error error;
-
-	/* Poisoning to make sure PMDs update it in case of error. */
-	memset(&error, 0x11, sizeof(error));
-	if (rte_flow_validate(port_id, attr, pattern, actions, &error))
-		return port_flow_complain(&error);
-	printf("Flow rule validated\n");
-	return 0;
-}
-
-/** Create flow rule. */
-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)
-{
-	struct rte_flow *flow;
-	struct rte_port *port;
-	struct port_flow *pf;
-	uint32_t id;
-	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);
-	if (!pf) {
-		int err = rte_errno;
-
-		printf("Cannot allocate flow: %s\n", rte_strerror(err));
-		rte_flow_destroy(port_id, flow, NULL);
-		return -err;
-	}
-	pf->next = port->flow_list;
-	pf->id = id;
-	pf->flow = flow;
-	port->flow_list = pf;
-	printf("Flow rule #%u created\n", pf->id);
-	return 0;
-}
-
-/** Destroy a number of flow rules. */
-int
-port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule)
-{
-	struct rte_port *port;
-	struct port_flow **tmp;
-	uint32_t c = 0;
-	int ret = 0;
-
-	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
-	    port_id == (portid_t)RTE_PORT_ALL)
-		return -EINVAL;
-	port = &ports[port_id];
-	tmp = &port->flow_list;
-	while (*tmp) {
-		uint32_t i;
-
-		for (i = 0; i != n; ++i) {
-			struct rte_flow_error error;
-			struct port_flow *pf = *tmp;
-
-			if (rule[i] != pf->id)
-				continue;
-			/*
-			 * Poisoning to make sure PMDs update it in case
-			 * of error.
-			 */
-			memset(&error, 0x33, sizeof(error));
-			if (rte_flow_destroy(port_id, pf->flow, &error)) {
-				ret = port_flow_complain(&error);
-				continue;
-			}
-			printf("Flow rule #%u destroyed\n", pf->id);
-			*tmp = pf->next;
-			free(pf);
-			break;
-		}
-		if (i == n)
-			tmp = &(*tmp)->next;
-		++c;
-	}
-	return ret;
-}
-
-/** Remove all flow rules. */
-int
-port_flow_flush(portid_t port_id)
-{
-	struct rte_flow_error error;
-	struct rte_port *port;
-	int ret = 0;
-
-	/* Poisoning to make sure PMDs update it in case of error. */
-	memset(&error, 0x44, sizeof(error));
-	if (rte_flow_flush(port_id, &error)) {
-		ret = port_flow_complain(&error);
-		if (port_id_is_invalid(port_id, DISABLED_WARN) ||
-		    port_id == (portid_t)RTE_PORT_ALL)
-			return ret;
-	}
-	port = &ports[port_id];
-	while (port->flow_list) {
-		struct port_flow *pf = port->flow_list->next;
-
-		free(port->flow_list);
-		port->flow_list = pf;
-	}
-	return ret;
-}
-
-/** Query a flow rule. */
-int
-port_flow_query(portid_t port_id, uint32_t rule,
-		enum rte_flow_action_type action)
-{
-	struct rte_flow_error error;
-	struct rte_port *port;
-	struct port_flow *pf;
-	const char *name;
-	union {
-		struct rte_flow_query_count count;
-	} query;
-
-	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
-	    port_id == (portid_t)RTE_PORT_ALL)
-		return -EINVAL;
-	port = &ports[port_id];
-	for (pf = port->flow_list; pf; pf = pf->next)
-		if (pf->id == rule)
-			break;
-	if (!pf) {
-		printf("Flow rule #%u not found\n", rule);
-		return -ENOENT;
-	}
-	if ((unsigned int)action >= RTE_DIM(flow_action) ||
-	    !flow_action[action].name)
-		name = "unknown";
-	else
-		name = flow_action[action].name;
-	switch (action) {
-	case RTE_FLOW_ACTION_TYPE_COUNT:
-		break;
-	default:
-		printf("Cannot query action type %d (%s)\n", action, name);
-		return -ENOTSUP;
-	}
-	/* Poisoning to make sure PMDs update it in case of error. */
-	memset(&error, 0x55, sizeof(error));
-	memset(&query, 0, sizeof(query));
-	if (rte_flow_query(port_id, pf->flow, action, &query, &error))
-		return port_flow_complain(&error);
-	switch (action) {
-	case RTE_FLOW_ACTION_TYPE_COUNT:
-		printf("%s:\n"
-		       " hits_set: %u\n"
-		       " bytes_set: %u\n"
-		       " hits: %" PRIu64 "\n"
-		       " bytes: %" PRIu64 "\n",
-		       name,
-		       query.count.hits_set,
-		       query.count.bytes_set,
-		       query.count.hits,
-		       query.count.bytes);
-		break;
-	default:
-		printf("Cannot display result for action type %d (%s)\n",
-		       action, name);
-		break;
-	}
-	return 0;
-}
-
-/** List flow rules. */
-void
-port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
-{
-	struct rte_port *port;
-	struct port_flow *pf;
-	struct port_flow *list = NULL;
-	uint32_t i;
-
-	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
-	    port_id == (portid_t)RTE_PORT_ALL)
-		return;
-	port = &ports[port_id];
-	if (!port->flow_list)
-		return;
-	/* Sort flows by group, priority and ID. */
-	for (pf = port->flow_list; pf != NULL; pf = pf->next) {
-		struct port_flow **tmp;
-
-		if (n) {
-			/* Filter out unwanted groups. */
-			for (i = 0; i != n; ++i)
-				if (pf->attr.group == group[i])
-					break;
-			if (i == n)
-				continue;
-		}
-		tmp = &list;
-		while (*tmp &&
-		       (pf->attr.group > (*tmp)->attr.group ||
-			(pf->attr.group == (*tmp)->attr.group &&
-			 pf->attr.priority > (*tmp)->attr.priority) ||
-			(pf->attr.group == (*tmp)->attr.group &&
-			 pf->attr.priority == (*tmp)->attr.priority &&
-			 pf->id > (*tmp)->id)))
-			tmp = &(*tmp)->tmp;
-		pf->tmp = *tmp;
-		*tmp = pf;
-	}
-	printf("ID\tGroup\tPrio\tAttr\tRule\n");
-	for (pf = list; pf != NULL; pf = pf->tmp) {
-		const struct rte_flow_item *item = pf->pattern;
-		const struct rte_flow_action *action = pf->actions;
-
-		printf("%" PRIu32 "\t%" PRIu32 "\t%" PRIu32 "\t%c%c\t",
-		       pf->id,
-		       pf->attr.group,
-		       pf->attr.priority,
-		       pf->attr.ingress ? 'i' : '-',
-		       pf->attr.egress ? 'e' : '-');
-		while (item->type != RTE_FLOW_ITEM_TYPE_END) {
-			if (item->type != RTE_FLOW_ITEM_TYPE_VOID)
-				printf("%s ", flow_item[item->type].name);
-			++item;
-		}
-		printf("=>");
-		while (action->type != RTE_FLOW_ACTION_TYPE_END) {
-			if (action->type != RTE_FLOW_ACTION_TYPE_VOID)
-				printf(" %s", flow_action[action->type].name);
-			++action;
-		}
-		printf("\n");
-	}
-}
-
-/** Restrict ingress traffic to the defined flow rules. */
-int
-port_flow_isolate(portid_t port_id, int set)
-{
-	struct rte_flow_error error;
-
-	/* Poisoning to make sure PMDs update it in case of error. */
-	memset(&error, 0x66, sizeof(error));
-	if (rte_flow_isolate(port_id, set, &error))
-		return port_flow_complain(&error);
-	printf("Ingress traffic on port %u is %s to the defined flow rules\n",
-	       port_id,
-	       set ? "now restricted" : "not restricted anymore");
-	return 0;
-}
-
 /*
  * RX/TX ring descriptors display functions.
  */
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 2a266fd..303a9ec 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -9,6 +9,7 @@ 
 #include <rte_bus_pci.h>
 #include <rte_gro.h>
 #include <rte_gso.h>
+#include <cmdline_flow.h>
 
 #define RTE_PORT_ALL            (~(portid_t)0x0)
 
@@ -51,7 +52,6 @@ 
 #define UMA_NO_CONFIG  0xFF
 
 typedef uint8_t  lcoreid_t;
-typedef uint16_t portid_t;
 typedef uint16_t queueid_t;
 typedef uint16_t streamid_t;
 
@@ -129,90 +129,6 @@  struct fwd_stream {
 /** Offload MACsec in forward engine */
 #define TESTPMD_TX_OFFLOAD_MACSEC            0x0100
 
-/** Descriptor for a single flow. */
-struct port_flow {
-	size_t size; /**< Allocated space including data[]. */
-	struct port_flow *next; /**< Next flow in list. */
-	struct port_flow *tmp; /**< Temporary linking. */
-	uint32_t id; /**< Flow rule ID. */
-	struct rte_flow *flow; /**< Opaque flow object returned by PMD. */
-	struct rte_flow_attr attr; /**< Attributes. */
-	struct rte_flow_item *pattern; /**< Pattern. */
-	struct rte_flow_action *actions; /**< Actions. */
-	uint8_t data[]; /**< Storage for pattern/actions. */
-};
-
-#ifdef TM_MODE
-/**
- * Soft port tm related parameters
- */
-struct softnic_port_tm {
-	uint32_t default_hierarchy_enable; /**< def hierarchy enable flag */
-	uint32_t hierarchy_config;  /**< set to 1 if hierarchy configured */
-
-	uint32_t n_subports_per_port;  /**< Num of subport nodes per port */
-	uint32_t n_pipes_per_subport;  /**< Num of pipe nodes per subport */
-
-	uint64_t tm_pktfield0_slabpos;	/**< Pkt field position for subport */
-	uint64_t tm_pktfield0_slabmask; /**< Pkt field mask for the subport */
-	uint64_t tm_pktfield0_slabshr;
-	uint64_t tm_pktfield1_slabpos; /**< Pkt field position for the pipe */
-	uint64_t tm_pktfield1_slabmask; /**< Pkt field mask for the pipe */
-	uint64_t tm_pktfield1_slabshr;
-	uint64_t tm_pktfield2_slabpos; /**< Pkt field position table index */
-	uint64_t tm_pktfield2_slabmask;	/**< Pkt field mask for tc table idx */
-	uint64_t tm_pktfield2_slabshr;
-	uint64_t tm_tc_table[64];  /**< TC translation table */
-};
-
-/**
- * The data structure associate with softnic port
- */
-struct softnic_port {
-	unsigned int tm_flag;	/**< set to 1 if tm feature is enabled */
-	struct softnic_port_tm tm;	/**< softnic port tm parameters */
-};
-#endif
-
-/**
- * The data structure associated with each port.
- */
-struct rte_port {
-	struct rte_eth_dev_info dev_info;   /**< PCI info + driver name */
-	struct rte_eth_conf     dev_conf;   /**< Port configuration. */
-	struct ether_addr       eth_addr;   /**< Port ethernet address */
-	struct rte_eth_stats    stats;      /**< Last port statistics */
-	uint64_t                tx_dropped; /**< If no descriptor in TX ring */
-	struct fwd_stream       *rx_stream; /**< Port RX stream, if unique */
-	struct fwd_stream       *tx_stream; /**< Port TX stream, if unique */
-	unsigned int            socket_id;  /**< For NUMA support */
-	uint16_t                tx_ol_flags;/**< TX Offload Flags (TESTPMD_TX_OFFLOAD...). */
-	uint16_t                tso_segsz;  /**< Segmentation offload MSS for non-tunneled packets. */
-	uint16_t                tunnel_tso_segsz; /**< Segmentation offload MSS for tunneled pkts. */
-	uint16_t                tx_vlan_id;/**< The tag ID */
-	uint16_t                tx_vlan_id_outer;/**< The outer tag ID */
-	void                    *fwd_ctx;   /**< Forwarding mode context */
-	uint64_t                rx_bad_ip_csum; /**< rx pkts with bad ip checksum  */
-	uint64_t                rx_bad_l4_csum; /**< rx pkts with bad l4 checksum */
-	uint8_t                 tx_queue_stats_mapping_enabled;
-	uint8_t                 rx_queue_stats_mapping_enabled;
-	volatile uint16_t        port_status;    /**< port started or not */
-	uint8_t                 need_reconfig;  /**< need reconfiguring port or not */
-	uint8_t                 need_reconfig_queues; /**< need reconfiguring queues or not */
-	uint8_t                 rss_flag;   /**< enable rss or not */
-	uint8_t                 dcb_flag;   /**< enable dcb */
-	struct rte_eth_rxconf   rx_conf;    /**< rx configuration */
-	struct rte_eth_txconf   tx_conf;    /**< tx configuration */
-	struct ether_addr       *mc_addr_pool; /**< pool of multicast addrs */
-	uint32_t                mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
-	uint8_t                 slave_flag; /**< bonding slave port */
-	struct port_flow        *flow_list; /**< Associated flows. */
-#ifdef TM_MODE
-	unsigned int			softnic_enable;	/**< softnic flag */
-	struct softnic_port     softport;  /**< softnic port params */
-#endif
-};
-
 /**
  * The data structure associated with each forwarding logical core.
  * The logical cores are internally numbered by a core index from 0 to
@@ -693,11 +609,6 @@  int close_ddp_package_file(uint8_t *buf);
 
 void port_queue_region_info_display(portid_t port_id, void *buf);
 
-enum print_warning {
-	ENABLED_WARN = 0,
-	DISABLED_WARN
-};
-int port_id_is_invalid(portid_t port_id, enum print_warning warning);
 int new_socket_id(unsigned int socket_id);
 
 /*
diff --git a/lib/Makefile b/lib/Makefile
index 2cc37d6..f02d9c7 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -16,11 +16,11 @@  DEPDIRS-librte_mbuf := librte_eal librte_mempool
 DIRS-$(CONFIG_RTE_LIBRTE_TIMER) += librte_timer
 DEPDIRS-librte_timer := librte_eal
 DIRS-$(CONFIG_RTE_LIBRTE_CFGFILE) += librte_cfgfile
-DIRS-$(CONFIG_RTE_LIBRTE_CMDLINE) += librte_cmdline
-DEPDIRS-librte_cmdline := librte_eal
 DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
 DEPDIRS-librte_ether := librte_net librte_eal librte_mempool librte_ring
 DEPDIRS-librte_ether += librte_mbuf
+DIRS-$(CONFIG_RTE_LIBRTE_CMDLINE) += librte_cmdline
+DEPDIRS-librte_cmdline := librte_eal librte_ether
 DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
 DEPDIRS-librte_cryptodev := librte_eal librte_mempool librte_ring librte_mbuf
 DEPDIRS-librte_cryptodev += librte_kvargs
diff --git a/lib/librte_cmdline/Makefile b/lib/librte_cmdline/Makefile
index ddae1cf..cf46b22 100644
--- a/lib/librte_cmdline/Makefile
+++ b/lib/librte_cmdline/Makefile
@@ -21,6 +21,7 @@  SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_parse_ipaddr.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_parse_num.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_parse_string.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_rdline.c
+SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_flow.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_vt100.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_socket.c
 SRCS-$(CONFIG_RTE_LIBRTE_CMDLINE) += cmdline_parse_portlist.c
@@ -30,7 +31,7 @@  LDLIBS += -lrte_eal
 
 # install includes
 INCS := cmdline.h cmdline_parse.h cmdline_parse_num.h cmdline_parse_ipaddr.h
-INCS += cmdline_parse_etheraddr.h cmdline_parse_string.h cmdline_rdline.h
+INCS += cmdline_parse_etheraddr.h cmdline_parse_string.h cmdline_rdline.h cmdline_flow.h
 INCS += cmdline_vt100.h cmdline_socket.h cmdline_cirbuf.h cmdline_parse_portlist.h
 SYMLINK-$(CONFIG_RTE_LIBRTE_CMDLINE)-include := $(INCS)
 
diff --git a/lib/librte_cmdline/cmdline.h b/lib/librte_cmdline/cmdline.h
index 65d73b0..41c47cf 100644
--- a/lib/librte_cmdline/cmdline.h
+++ b/lib/librte_cmdline/cmdline.h
@@ -113,4 +113,4 @@  void cmdline_quit(struct cmdline *cl);
 }
 #endif
 
-#endif /* _CMDLINE_SOCKET_H_ */
+#endif /* _CMDLINE_H_ */
diff --git a/lib/librte_cmdline/cmdline_flow.c b/lib/librte_cmdline/cmdline_flow.c
new file mode 100644
index 0000000..b43c10f
--- /dev/null
+++ b/lib/librte_cmdline/cmdline_flow.c
@@ -0,0 +1,1790 @@ 
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright 2016 6WIND S.A.
+ *   Copyright 2016 Mellanox.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of 6WIND S.A. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include "cmdline_flow.h"
+
+int
+port_id_is_invalid(portid_t port_id, enum print_warning warning)
+{
+	if (port_id == (portid_t)RTE_PORT_ALL)
+		return 0;
+
+	if (rte_eth_dev_is_valid_port(port_id))
+		return 0;
+
+	if (warning == ENABLED_WARN)
+		printf("Invalid port %d\n", port_id);
+
+	return 1;
+}
+
+/** Remove and return last entry from argument stack. */
+const struct arg *
+pop_args(struct context *ctx)
+{
+	return ctx->args_num ? ctx->args[--ctx->args_num] : NULL;
+}
+
+/** Add entry on top of the argument stack. */
+int
+push_args(struct context *ctx, const struct arg *arg)
+{
+	if (ctx->args_num == CTX_STACK_SIZE)
+		return -1;
+	ctx->args[ctx->args_num++] = arg;
+	return 0;
+}
+
+/** Spread value into buffer according to bit-mask. */
+size_t
+arg_entry_bf_fill(void *dst, uintmax_t val, const struct arg *arg)
+{
+	uint32_t i = arg->size;
+	uint32_t end = 0;
+	int sub = 1;
+	int add = 0;
+	size_t len = 0;
+
+	if (!arg->mask)
+		return 0;
+#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
+	if (!arg->hton) {
+		i = 0;
+		end = arg->size;
+		sub = 0;
+		add = 1;
+	}
+#endif
+	while (i != end) {
+		unsigned int shift = 0;
+		uint8_t *buf = (uint8_t *)dst + arg->offset + (i -= sub);
+
+		for (shift = 0; arg->mask[i] >> shift; ++shift) {
+			if (!(arg->mask[i] & (1 << shift)))
+				continue;
+			++len;
+			if (!dst)
+				continue;
+			*buf &= ~(1 << shift);
+			*buf |= (val & 1) << shift;
+			val >>= 1;
+		}
+		i += add;
+	}
+	return len;
+}
+
+/** Compare a string with a partial one of a given length. */
+int
+strcmp_partial(const char *full, const char *partial, size_t partial_len)
+{
+	int r = strncmp(full, partial, partial_len);
+
+	if (r)
+		return r;
+	if (strlen(full) <= partial_len)
+		return 0;
+	return full[partial_len];
+}
+
+/**
+ * Parse a prefix length and generate a bit-mask.
+ *
+ * Last argument (ctx->args) is retrieved to determine mask size, storage
+ * location and whether the result must use network byte ordering.
+ */
+int
+parse_prefix(struct context *ctx, const struct token *token,
+	     const char *str, unsigned int len,
+	     void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	const uint8_t conv[] = "\x00\x80\xc0\xe0\xf0\xf8\xfc\xfe\xff";
+	char *end;
+	uintmax_t u;
+	unsigned int bytes;
+	unsigned int extra;
+
+	(void)token;
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	errno = 0;
+	u = strtoumax(str, &end, 0);
+	if (errno || (size_t)(end - str) != len)
+		goto error;
+	if (arg->mask) {
+		uintmax_t v = 0;
+
+		extra = arg_entry_bf_fill(NULL, 0, arg);
+		if (u > extra)
+			goto error;
+		if (!ctx->object)
+			return len;
+		extra -= u;
+		while (u--)
+			(v <<= 1, v |= 1);
+		v <<= extra;
+		if (!arg_entry_bf_fill(ctx->object, v, arg) ||
+		    !arg_entry_bf_fill(ctx->objmask, -1, arg))
+			goto error;
+		return len;
+	}
+	bytes = u / 8;
+	extra = u % 8;
+	size = arg->size;
+	if (bytes > size || bytes + !!extra > size)
+		goto error;
+	if (!ctx->object)
+		return len;
+	buf = (uint8_t *)ctx->object + arg->offset;
+#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
+	if (!arg->hton) {
+		memset((uint8_t *)buf + size - bytes, 0xff, bytes);
+		memset(buf, 0x00, size - bytes);
+		if (extra)
+			((uint8_t *)buf)[size - bytes - 1] = conv[extra];
+	} else
+#endif
+	{
+		memset(buf, 0xff, bytes);
+		memset((uint8_t *)buf + bytes, 0x00, size - bytes);
+		if (extra)
+			((uint8_t *)buf)[bytes] = conv[extra];
+	}
+	if (ctx->objmask)
+		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
+	return len;
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/** Default parsing function for token name matching. */
+int
+parse_default(struct context *ctx, const struct token *token,
+	      const char *str, unsigned int len,
+	      void *buf, unsigned int size)
+{
+	(void)ctx;
+	(void)buf;
+	(void)size;
+	if (strcmp_partial(token->name, str, len))
+		return -1;
+	return len;
+}
+
+/** Parse flow command, initialize output buffer for subsequent tokens. */
+int
+parse_init(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;
+	/* Make sure buffer is large enough. */
+	if (size < sizeof(*out))
+		return -1;
+	/* Initialize buffer. */
+	memset(out, 0x00, sizeof(*out));
+	memset((uint8_t *)out + sizeof(*out), 0x22, size - sizeof(*out));
+	ctx->objdata = 0;
+	ctx->object = out;
+	ctx->objmask = NULL;
+	return len;
+}
+
+/** Parse tokens for validate/create commands. */
+int
+parse_vc(struct context *ctx, const struct token *token,
+	 const char *str, unsigned int len,
+	 void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+	uint8_t *data;
+	uint32_t data_size;
+
+	/* 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 != VALIDATE && ctx->curr != CREATE)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+		out->args.vc.data = (uint8_t *)out + size;
+		return len;
+	}
+	ctx->objdata = 0;
+	ctx->object = &out->args.vc.attr;
+	ctx->objmask = NULL;
+	switch (ctx->curr) {
+	case GROUP:
+	case PRIORITY:
+		return len;
+	case INGRESS:
+		out->args.vc.attr.ingress = 1;
+		return len;
+	case EGRESS:
+		out->args.vc.attr.egress = 1;
+		return len;
+	case PATTERN:
+		out->args.vc.pattern =
+			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+					       sizeof(double));
+		ctx->object = out->args.vc.pattern;
+		ctx->objmask = NULL;
+		return len;
+	case ACTIONS:
+		out->args.vc.actions =
+			(void *)RTE_ALIGN_CEIL((uintptr_t)
+					       (out->args.vc.pattern +
+						out->args.vc.pattern_n),
+					       sizeof(double));
+		ctx->object = out->args.vc.actions;
+		ctx->objmask = NULL;
+		return len;
+	default:
+		if (!token->priv)
+			return -1;
+		break;
+	}
+	if (!out->args.vc.actions) {
+		const struct parse_item_priv *priv = token->priv;
+		struct rte_flow_item *item =
+			out->args.vc.pattern + out->args.vc.pattern_n;
+
+		data_size = priv->size * 3; /* spec, last, mask */
+		data = (void *)RTE_ALIGN_FLOOR((uintptr_t)
+					       (out->args.vc.data - data_size),
+					       sizeof(double));
+		if ((uint8_t *)item + sizeof(*item) > data)
+			return -1;
+		*item = (struct rte_flow_item){
+			.type = priv->type,
+		};
+		++out->args.vc.pattern_n;
+		ctx->object = item;
+		ctx->objmask = NULL;
+	} else {
+		const struct parse_action_priv *priv = token->priv;
+		struct rte_flow_action *action =
+			out->args.vc.actions + out->args.vc.actions_n;
+
+		data_size = priv->size; /* configuration */
+		data = (void *)RTE_ALIGN_FLOOR((uintptr_t)
+					       (out->args.vc.data - data_size),
+					       sizeof(double));
+		if ((uint8_t *)action + sizeof(*action) > data)
+			return -1;
+		*action = (struct rte_flow_action){
+			.type = priv->type,
+		};
+		++out->args.vc.actions_n;
+		ctx->object = action;
+		ctx->objmask = NULL;
+	}
+	memset(data, 0, data_size);
+	out->args.vc.data = data;
+	ctx->objdata = data_size;
+	return len;
+}
+
+/** Parse pattern item parameter type. */
+int
+parse_vc_spec(struct context *ctx, const struct token *token,
+	      const char *str, unsigned int len,
+	      void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+	struct rte_flow_item *item;
+	uint32_t data_size;
+	int index;
+	int objmask = 0;
+
+	(void)size;
+	/* Token name must match. */
+	if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+		return -1;
+	/* Parse parameter types. */
+	switch (ctx->curr) {
+		static const enum index prefix[] = NEXT_ENTRY(PREFIX);
+
+	case ITEM_PARAM_IS:
+		index = 0;
+		objmask = 1;
+		break;
+	case ITEM_PARAM_SPEC:
+		index = 0;
+		break;
+	case ITEM_PARAM_LAST:
+		index = 1;
+		break;
+	case ITEM_PARAM_PREFIX:
+		/* Modify next token to expect a prefix. */
+		if (ctx->next_num < 2)
+			return -1;
+		ctx->next[ctx->next_num - 2] = prefix;
+		/* Fall through. */
+	case ITEM_PARAM_MASK:
+		index = 2;
+		break;
+	default:
+		return -1;
+	}
+	/* Nothing else to do if there is no buffer. */
+	if (!out)
+		return len;
+	if (!out->args.vc.pattern_n)
+		return -1;
+	item = &out->args.vc.pattern[out->args.vc.pattern_n - 1];
+	data_size = ctx->objdata / 3; /* spec, last, mask */
+	/* Point to selected object. */
+	ctx->object = out->args.vc.data + (data_size * index);
+	if (objmask) {
+		ctx->objmask = out->args.vc.data + (data_size * 2); /* mask */
+		item->mask = ctx->objmask;
+	} else
+		ctx->objmask = NULL;
+	/* Update relevant item pointer. */
+	*((const void **[]){ &item->spec, &item->last, &item->mask })[index] =
+		ctx->object;
+	return len;
+}
+
+/** Parse action configuration field. */
+int
+parse_vc_conf(struct context *ctx, const struct token *token,
+	      const char *str, unsigned int len,
+	      void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+	struct rte_flow_action *action;
+
+	(void)size;
+	/* 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->args.vc.actions_n)
+		return -1;
+	action = &out->args.vc.actions[out->args.vc.actions_n - 1];
+	/* Point to selected object. */
+	ctx->object = out->args.vc.data;
+	ctx->objmask = NULL;
+	/* Update configuration pointer. */
+	action->conf = ctx->object;
+	return len;
+}
+
+/**
+ * Parse queue field for RSS action.
+ *
+ * Valid tokens are queue indices and the "end" token.
+ */
+int
+parse_vc_action_rss_queue(struct context *ctx, const struct token *token,
+			  const char *str, unsigned int len,
+			  void *buf, unsigned int size)
+{
+	static const enum index next[] = NEXT_ENTRY(ACTION_RSS_QUEUE);
+	int ret;
+	int i;
+
+	(void)token;
+	(void)buf;
+	(void)size;
+	if (ctx->curr != ACTION_RSS_QUEUE)
+		return -1;
+	i = ctx->objdata >> 16;
+	if (!strcmp_partial("end", str, len)) {
+		ctx->objdata &= 0xffff;
+		return len;
+	}
+	if (i >= ACTION_RSS_NUM)
+		return -1;
+	if (push_args(ctx, ARGS_ENTRY(struct rte_flow_action_rss, queue[i])))
+		return -1;
+	ret = parse_int(ctx, token, str, len, NULL, 0);
+	if (ret < 0) {
+		pop_args(ctx);
+		return -1;
+	}
+	++i;
+	ctx->objdata = i << 16 | (ctx->objdata & 0xffff);
+	/* Repeat token. */
+	if (ctx->next_num == RTE_DIM(ctx->next))
+		return -1;
+	ctx->next[ctx->next_num++] = next;
+	if (!ctx->object)
+		return len;
+	((struct rte_flow_action_rss *)ctx->object)->num = i;
+	return len;
+}
+
+/** Parse tokens for destroy command. */
+int
+parse_destroy(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 != DESTROY)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+		out->args.destroy.rule =
+			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+					       sizeof(double));
+		return len;
+	}
+	if (((uint8_t *)(out->args.destroy.rule + out->args.destroy.rule_n) +
+	     sizeof(*out->args.destroy.rule)) > (uint8_t *)out + size)
+		return -1;
+	ctx->objdata = 0;
+	ctx->object = out->args.destroy.rule + out->args.destroy.rule_n++;
+	ctx->objmask = NULL;
+	return len;
+}
+
+/** Parse tokens for flush command. */
+int
+parse_flush(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 != FLUSH)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+	}
+	return len;
+}
+
+/** Parse tokens for query command. */
+int
+parse_query(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 != QUERY)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+	}
+	return len;
+}
+
+/** Parse action names. */
+int
+parse_action(struct context *ctx, const struct token *token,
+	     const char *str, unsigned int len,
+	     void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+	const struct arg *arg = pop_args(ctx);
+	unsigned int i;
+
+	(void)size;
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	/* Parse action name. */
+	for (i = 0; next_action[i]; ++i) {
+		const struct parse_action_priv *priv;
+
+		token = &token_list[next_action[i]];
+		if (strcmp_partial(token->name, str, len))
+			continue;
+		priv = token->priv;
+		if (!priv)
+			goto error;
+		if (out)
+			memcpy((uint8_t *)ctx->object + arg->offset,
+			       &priv->type,
+			       arg->size);
+		return len;
+	}
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/** Parse tokens for list command. */
+int
+parse_list(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 != LIST)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+		out->args.list.group =
+			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+					       sizeof(double));
+		return len;
+	}
+	if (((uint8_t *)(out->args.list.group + out->args.list.group_n) +
+	     sizeof(*out->args.list.group)) > (uint8_t *)out + size)
+		return -1;
+	ctx->objdata = 0;
+	ctx->object = out->args.list.group + out->args.list.group_n++;
+	ctx->objmask = NULL;
+	return len;
+}
+
+/** Parse tokens for isolate command. */
+int
+parse_isolate(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 != ISOLATE)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+	}
+	return len;
+}
+
+/**
+ * Parse signed/unsigned integers 8 to 64-bit long.
+ *
+ * Last argument (ctx->args) is retrieved to determine integer type and
+ * storage location.
+ */
+int
+parse_int(struct context *ctx, const struct token *token,
+	  const char *str, unsigned int len,
+	  void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	uintmax_t u;
+	char *end;
+
+	(void)token;
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	errno = 0;
+	u = arg->sign ?
+		(uintmax_t)strtoimax(str, &end, 0) :
+		strtoumax(str, &end, 0);
+	if (errno || (size_t)(end - str) != len)
+		goto error;
+	if (!ctx->object)
+		return len;
+	if (arg->mask) {
+		if (!arg_entry_bf_fill(ctx->object, u, arg) ||
+		    !arg_entry_bf_fill(ctx->objmask, -1, arg))
+			goto error;
+		return len;
+	}
+	buf = (uint8_t *)ctx->object + arg->offset;
+	size = arg->size;
+objmask:
+	switch (size) {
+	case sizeof(uint8_t):
+		*(uint8_t *)buf = u;
+		break;
+	case sizeof(uint16_t):
+		*(uint16_t *)buf = arg->hton ? rte_cpu_to_be_16(u) : u;
+		break;
+	case sizeof(uint8_t [3]):
+#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
+		if (!arg->hton) {
+			((uint8_t *)buf)[0] = u;
+			((uint8_t *)buf)[1] = u >> 8;
+			((uint8_t *)buf)[2] = u >> 16;
+			break;
+		}
+#endif
+		((uint8_t *)buf)[0] = u >> 16;
+		((uint8_t *)buf)[1] = u >> 8;
+		((uint8_t *)buf)[2] = u;
+		break;
+	case sizeof(uint32_t):
+		*(uint32_t *)buf = arg->hton ? rte_cpu_to_be_32(u) : u;
+		break;
+	case sizeof(uint64_t):
+		*(uint64_t *)buf = arg->hton ? rte_cpu_to_be_64(u) : u;
+		break;
+	default:
+		goto error;
+	}
+	if (ctx->objmask && buf != (uint8_t *)ctx->objmask + arg->offset) {
+		u = -1;
+		buf = (uint8_t *)ctx->objmask + arg->offset;
+		goto objmask;
+	}
+	return len;
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/**
+ * Parse a string.
+ *
+ * Two arguments (ctx->args) are retrieved from the stack to store data and
+ * its length (in that order).
+ */
+int
+parse_string(struct context *ctx, const struct token *token,
+	     const char *str, unsigned int len,
+	     void *buf, unsigned int size)
+{
+	const struct arg *arg_data = pop_args(ctx);
+	const struct arg *arg_len = pop_args(ctx);
+	char tmp[16]; /* Ought to be enough. */
+	int ret;
+
+	/* Arguments are expected. */
+	if (!arg_data)
+		return -1;
+	if (!arg_len) {
+		push_args(ctx, arg_data);
+		return -1;
+	}
+	size = arg_data->size;
+	/* Bit-mask fill is not supported. */
+	if (arg_data->mask || size < len)
+		goto error;
+	if (!ctx->object)
+		return len;
+	/* Let parse_int() fill length information first. */
+	ret = snprintf(tmp, sizeof(tmp), "%u", len);
+	if (ret < 0)
+		goto error;
+	push_args(ctx, arg_len);
+	ret = parse_int(ctx, token, tmp, ret, NULL, 0);
+	if (ret < 0) {
+		pop_args(ctx);
+		goto error;
+	}
+	buf = (uint8_t *)ctx->object + arg_data->offset;
+	/* Output buffer is not necessarily NUL-terminated. */
+	memcpy(buf, str, len);
+	memset((uint8_t *)buf + len, 0x55, size - len);
+	if (ctx->objmask)
+		memset((uint8_t *)ctx->objmask + arg_data->offset, 0xff, len);
+	return len;
+error:
+	push_args(ctx, arg_len);
+	push_args(ctx, arg_data);
+	return -1;
+}
+
+/**
+ * Parse a MAC address.
+ *
+ * Last argument (ctx->args) is retrieved to determine storage size and
+ * location.
+ */
+int
+parse_mac_addr(struct context *ctx, const struct token *token,
+	       const char *str, unsigned int len,
+	       void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	struct ether_addr tmp;
+	int ret;
+
+	(void)token;
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	size = arg->size;
+	/* Bit-mask fill is not supported. */
+	if (arg->mask || size != sizeof(tmp))
+		goto error;
+	/* Only network endian is supported. */
+	if (!arg->hton)
+		goto error;
+	ret = cmdline_parse_etheraddr(NULL, str, &tmp, size);
+	if (ret < 0 || (unsigned int)ret != len)
+		goto error;
+	if (!ctx->object)
+		return len;
+	buf = (uint8_t *)ctx->object + arg->offset;
+	memcpy(buf, &tmp, size);
+	if (ctx->objmask)
+		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
+	return len;
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/**
+ * Parse an IPv4 address.
+ *
+ * Last argument (ctx->args) is retrieved to determine storage size and
+ * location.
+ */
+int
+parse_ipv4_addr(struct context *ctx, const struct token *token,
+		const char *str, unsigned int len,
+		void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	char str2[len + 1];
+	struct in_addr tmp;
+	int ret;
+
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	size = arg->size;
+	/* Bit-mask fill is not supported. */
+	if (arg->mask || size != sizeof(tmp))
+		goto error;
+	/* Only network endian is supported. */
+	if (!arg->hton)
+		goto error;
+	memcpy(str2, str, len);
+	str2[len] = '\0';
+	ret = inet_pton(AF_INET, str2, &tmp);
+	if (ret != 1) {
+		/* Attempt integer parsing. */
+		push_args(ctx, arg);
+		return parse_int(ctx, token, str, len, buf, size);
+	}
+	if (!ctx->object)
+		return len;
+	buf = (uint8_t *)ctx->object + arg->offset;
+	memcpy(buf, &tmp, size);
+	if (ctx->objmask)
+		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
+	return len;
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/**
+ * Parse an IPv6 address.
+ *
+ * Last argument (ctx->args) is retrieved to determine storage size and
+ * location.
+ */
+int
+parse_ipv6_addr(struct context *ctx, const struct token *token,
+		const char *str, unsigned int len,
+		void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	char str2[len + 1];
+	struct in6_addr tmp;
+	int ret;
+
+	(void)token;
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	size = arg->size;
+	/* Bit-mask fill is not supported. */
+	if (arg->mask || size != sizeof(tmp))
+		goto error;
+	/* Only network endian is supported. */
+	if (!arg->hton)
+		goto error;
+	memcpy(str2, str, len);
+	str2[len] = '\0';
+	ret = inet_pton(AF_INET6, str2, &tmp);
+	if (ret != 1)
+		goto error;
+	if (!ctx->object)
+		return len;
+	buf = (uint8_t *)ctx->object + arg->offset;
+	memcpy(buf, &tmp, size);
+	if (ctx->objmask)
+		memset((uint8_t *)ctx->objmask + arg->offset, 0xff, size);
+	return len;
+error:
+	push_args(ctx, arg);
+	return -1;
+}
+
+/** Boolean values (even indices stand for false). */
+static const char *const boolean_name[] = {
+	"0", "1",
+	"false", "true",
+	"no", "yes",
+	"N", "Y",
+	NULL,
+};
+
+/**
+ * Parse a boolean value.
+ *
+ * Last argument (ctx->args) is retrieved to determine storage size and
+ * location.
+ */
+int
+parse_boolean(struct context *ctx, const struct token *token,
+	      const char *str, unsigned int len,
+	      void *buf, unsigned int size)
+{
+	const struct arg *arg = pop_args(ctx);
+	unsigned int i;
+	int ret;
+
+	/* Argument is expected. */
+	if (!arg)
+		return -1;
+	for (i = 0; boolean_name[i]; ++i)
+		if (!strcmp_partial(boolean_name[i], str, len))
+			break;
+	/* Process token as integer. */
+	if (boolean_name[i])
+		str = i & 1 ? "1" : "0";
+	push_args(ctx, arg);
+	ret = parse_int(ctx, token, str, strlen(str), buf, size);
+	return ret > 0 ? (int)len : ret;
+}
+
+/** Parse port and update context. */
+int
+parse_port(struct context *ctx, const struct token *token,
+	   const char *str, unsigned int len,
+	   void *buf, unsigned int size)
+{
+	struct buffer *out = &(struct buffer){ .port = 0 };
+	int ret;
+
+	if (buf)
+		out = buf;
+	else {
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+		size = sizeof(*out);
+	}
+	ret = parse_int(ctx, token, str, len, out, size);
+	if (ret >= 0)
+		ctx->port = out->port;
+	if (!buf)
+		ctx->object = NULL;
+	return ret;
+}
+
+/** No completion. */
+int
+comp_none(struct context *ctx, const struct token *token,
+	  unsigned int ent, char *buf, unsigned int size)
+{
+	(void)ctx;
+	(void)token;
+	(void)ent;
+	(void)buf;
+	(void)size;
+	return 0;
+}
+
+/** Complete boolean values. */
+int
+comp_boolean(struct context *ctx, const struct token *token,
+	     unsigned int ent, char *buf, unsigned int size)
+{
+	unsigned int i;
+
+	(void)ctx;
+	(void)token;
+	for (i = 0; boolean_name[i]; ++i)
+		if (buf && i == ent)
+			return snprintf(buf, size, "%s", boolean_name[i]);
+	if (buf)
+		return -1;
+	return i;
+}
+
+/** Complete action names. */
+int
+comp_action(struct context *ctx, const struct token *token,
+	    unsigned int ent, char *buf, unsigned int size)
+{
+	unsigned int i;
+
+	(void)ctx;
+	(void)token;
+	for (i = 0; next_action[i]; ++i)
+		if (buf && i == ent)
+			return snprintf(buf, size, "%s",
+					token_list[next_action[i]].name);
+	if (buf)
+		return -1;
+	return i;
+}
+
+/** Complete available ports. */
+int
+comp_port(struct context *ctx, const struct token *token,
+	  unsigned int ent, char *buf, unsigned int size)
+{
+	unsigned int i = 0;
+	portid_t p;
+
+	(void)ctx;
+	(void)token;
+	RTE_ETH_FOREACH_DEV(p) {
+		if (buf && i == ent)
+			return snprintf(buf, size, "%u", p);
+		++i;
+	}
+	if (buf)
+		return -1;
+	return i;
+}
+
+/** Complete available rule IDs. */
+int
+comp_rule_id(struct context *ctx, const struct token *token,
+	     unsigned int ent, char *buf, unsigned int size)
+{
+	unsigned int i = 0;
+	struct rte_port *port;
+	struct port_flow *pf;
+
+	(void)token;
+	if (port_id_is_invalid(ctx->port, DISABLED_WARN) ||
+	    ctx->port == (portid_t)RTE_PORT_ALL)
+		return -1;
+	port = &ports[ctx->port];
+	for (pf = port->flow_list; pf != NULL; pf = pf->next) {
+		if (buf && i == ent)
+			return snprintf(buf, size, "%u", pf->id);
+		++i;
+	}
+	if (buf)
+		return -1;
+	return i;
+}
+
+/** Complete queue field for RSS action. */
+int
+comp_vc_action_rss_queue(struct context *ctx, const struct token *token,
+			 unsigned int ent, char *buf, unsigned int size)
+{
+	const char *const str[] = { "", "end", NULL };
+	unsigned int i;
+
+	(void)ctx;
+	(void)token;
+	for (i = 0; str[i] != NULL; ++i)
+		if (buf && i == ent)
+			return snprintf(buf, size, "%s", str[i]);
+	if (buf)
+		return -1;
+	return i;
+}
+
+/** Internal context. */
+static struct context cmd_flow_context;
+
+/** Global parser instance (cmdline API). */
+cmdline_parse_inst_t cmd_flow;
+
+/** Initialize context. */
+void
+cmd_flow_context_init(struct context *ctx)
+{
+	/* A full memset() is not necessary. */
+	ctx->curr = ZERO;
+	ctx->prev = ZERO;
+	ctx->next_num = 0;
+	ctx->args_num = 0;
+	ctx->eol = 0;
+	ctx->last = 0;
+	ctx->port = 0;
+	ctx->objdata = 0;
+	ctx->object = NULL;
+	ctx->objmask = NULL;
+}
+
+/** Parse a token (cmdline API). */
+int
+cmd_flow_parse(cmdline_parse_token_hdr_t *hdr, const char *src, void *result,
+	       unsigned int size)
+{
+	struct context *ctx = &cmd_flow_context;
+	const struct token *token;
+	const enum index *list;
+	int len;
+	int i;
+
+	(void)hdr;
+	token = &token_list[ctx->curr];
+	/* Check argument length. */
+	ctx->eol = 0;
+	ctx->last = 1;
+	for (len = 0; src[len]; ++len)
+		if (src[len] == '#' || isspace(src[len]))
+			break;
+	if (!len)
+		return -1;
+	/* Last argument and EOL detection. */
+	for (i = len; src[i]; ++i)
+		if (src[i] == '#' || src[i] == '\r' || src[i] == '\n')
+			break;
+		else if (!isspace(src[i])) {
+			ctx->last = 0;
+			break;
+		}
+	for (; src[i]; ++i)
+		if (src[i] == '\r' || src[i] == '\n') {
+			ctx->eol = 1;
+			break;
+		}
+	/* Initialize context if necessary. */
+	if (!ctx->next_num) {
+		if (!token->next)
+			return 0;
+		ctx->next[ctx->next_num++] = token->next[0];
+	}
+	/* Process argument through candidates. */
+	ctx->prev = ctx->curr;
+	list = ctx->next[ctx->next_num - 1];
+	for (i = 0; list[i]; ++i) {
+		const struct token *next = &token_list[list[i]];
+		int tmp;
+
+		ctx->curr = list[i];
+		if (next->call)
+			tmp = next->call(ctx, next, src, len, result, size);
+		else
+			tmp = parse_default(ctx, next, src, len, result, size);
+		if (tmp == -1 || tmp != len)
+			continue;
+		token = next;
+		break;
+	}
+	if (!list[i])
+		return -1;
+	--ctx->next_num;
+	/* Push subsequent tokens if any. */
+	if (token->next)
+		for (i = 0; token->next[i]; ++i) {
+			if (ctx->next_num == RTE_DIM(ctx->next))
+				return -1;
+			ctx->next[ctx->next_num++] = token->next[i];
+		}
+	/* Push arguments if any. */
+	if (token->args)
+		for (i = 0; token->args[i]; ++i) {
+			if (ctx->args_num == RTE_DIM(ctx->args))
+				return -1;
+			ctx->args[ctx->args_num++] = token->args[i];
+		}
+	return len;
+}
+
+/** Return number of completion entries (cmdline API). */
+int
+cmd_flow_complete_get_nb(cmdline_parse_token_hdr_t *hdr)
+{
+	struct context *ctx = &cmd_flow_context;
+	const struct token *token = &token_list[ctx->curr];
+	const enum index *list;
+	int i;
+
+	(void)hdr;
+	/* Count number of tokens in current list. */
+	if (ctx->next_num)
+		list = ctx->next[ctx->next_num - 1];
+	else
+		list = token->next[0];
+	for (i = 0; list[i]; ++i)
+		;
+	if (!i)
+		return 0;
+	/*
+	 * If there is a single token, use its completion callback, otherwise
+	 * return the number of entries.
+	 */
+	token = &token_list[list[0]];
+	if (i == 1 && token->comp) {
+		/* Save index for cmd_flow_get_help(). */
+		ctx->prev = list[0];
+		return token->comp(ctx, token, 0, NULL, 0);
+	}
+	return i;
+}
+
+/** Return a completion entry (cmdline API). */
+int
+cmd_flow_complete_get_elt(cmdline_parse_token_hdr_t *hdr, int index,
+			  char *dst, unsigned int size)
+{
+	struct context *ctx = &cmd_flow_context;
+	const struct token *token = &token_list[ctx->curr];
+	const enum index *list;
+	int i;
+
+	(void)hdr;
+	/* Count number of tokens in current list. */
+	if (ctx->next_num)
+		list = ctx->next[ctx->next_num - 1];
+	else
+		list = token->next[0];
+	for (i = 0; list[i]; ++i)
+		;
+	if (!i)
+		return -1;
+	/* If there is a single token, use its completion callback. */
+	token = &token_list[list[0]];
+	if (i == 1 && token->comp) {
+		/* Save index for cmd_flow_get_help(). */
+		ctx->prev = list[0];
+		return token->comp(ctx, token, index, dst, size) < 0 ? -1 : 0;
+	}
+	/* Otherwise make sure the index is valid and use defaults. */
+	if (index >= i)
+		return -1;
+	token = &token_list[list[index]];
+	snprintf(dst, size, "%s", token->name);
+	/* Save index for cmd_flow_get_help(). */
+	ctx->prev = list[index];
+	return 0;
+}
+
+/** Populate help strings for current token (cmdline API). */
+int
+cmd_flow_get_help(cmdline_parse_token_hdr_t *hdr, char *dst, unsigned int size)
+{
+	struct context *ctx = &cmd_flow_context;
+	const struct token *token = &token_list[ctx->prev];
+
+	(void)hdr;
+	if (!size)
+		return -1;
+	/* Set token type and update global help with details. */
+	snprintf(dst, size, "%s", (token->type ? token->type : "TOKEN"));
+	if (token->help)
+		cmd_flow.help_str = token->help;
+	else
+		cmd_flow.help_str = token->name;
+	return 0;
+}
+
+/** Token definition template (cmdline API). */
+static struct cmdline_token_hdr cmd_flow_token_hdr = {
+	.ops = &(struct cmdline_token_ops){
+		.parse = cmd_flow_parse,
+		.complete_get_nb = cmd_flow_complete_get_nb,
+		.complete_get_elt = cmd_flow_complete_get_elt,
+		.get_help = cmd_flow_get_help,
+	},
+	.offset = 0,
+};
+
+/** Populate the next dynamic token. */
+void
+cmd_flow_tok(cmdline_parse_token_hdr_t **hdr,
+	     cmdline_parse_token_hdr_t **hdr_inst)
+{
+	struct context *ctx = &cmd_flow_context;
+
+	/* Always reinitialize context before requesting the first token. */
+	if (!(hdr_inst - cmd_flow.tokens))
+		cmd_flow_context_init(ctx);
+	/* Return NULL when no more tokens are expected. */
+	if (!ctx->next_num && ctx->curr) {
+		*hdr = NULL;
+		return;
+	}
+	/* Determine if command should end here. */
+	if (ctx->eol && ctx->last && ctx->next_num) {
+		const enum index *list = ctx->next[ctx->next_num - 1];
+		int i;
+
+		for (i = 0; list[i]; ++i) {
+			if (list[i] != END)
+				continue;
+			*hdr = NULL;
+			return;
+		}
+	}
+	*hdr = &cmd_flow_token_hdr;
+}
+
+/** Dispatch parsed buffer to function calls. */
+void
+cmd_flow_parsed(const struct buffer *in)
+{
+	switch (in->command) {
+	case VALIDATE:
+		port_flow_validate(in->port, &in->args.vc.attr,
+				   in->args.vc.pattern, in->args.vc.actions);
+		break;
+	case CREATE:
+		port_flow_create(in->port, &in->args.vc.attr,
+				 in->args.vc.pattern, in->args.vc.actions);
+		break;
+	case DESTROY:
+		port_flow_destroy(in->port, in->args.destroy.rule_n,
+				  in->args.destroy.rule);
+		break;
+	case FLUSH:
+		port_flow_flush(in->port);
+		break;
+	case QUERY:
+		port_flow_query(in->port, in->args.query.rule,
+				in->args.query.action);
+		break;
+	case LIST:
+		port_flow_list(in->port, in->args.list.group_n,
+			       in->args.list.group);
+		break;
+	case ISOLATE:
+		port_flow_isolate(in->port, in->args.isolate.set);
+		break;
+	default:
+		break;
+	}
+}
+
+/** Token generator and output processing callback (cmdline API). */
+void
+cmd_flow_cb(void *arg0, struct cmdline *cl, void *arg2)
+{
+	if (cl == NULL)
+		cmd_flow_tok(arg0, arg2);
+	else
+		cmd_flow_parsed(arg0);
+}
+
+/** Global parser instance (cmdline API). */
+cmdline_parse_inst_t cmd_flow = {
+	.f = cmd_flow_cb,
+	.data = NULL, /**< Unused. */
+	.help_str = NULL, /**< Updated by cmd_flow_get_help(). */
+	.tokens = {
+		NULL,
+	}, /**< Tokens are returned by cmd_flow_tok(). */
+};
+
+/* Generic flow management functions. */
+
+/** Generate a port_flow entry from attributes/pattern/actions. */
+static struct port_flow *
+port_flow_new(const struct rte_flow_attr *attr,
+	      const struct rte_flow_item *pattern,
+	      const struct rte_flow_action *actions)
+{
+	const struct rte_flow_item *item;
+	const struct rte_flow_action *action;
+	struct port_flow *pf = NULL;
+	size_t tmp;
+	size_t pad;
+	size_t off1 = 0;
+	size_t off2 = 0;
+	int err = ENOTSUP;
+
+store:
+	item = pattern;
+	if (pf)
+		pf->pattern = (void *)&pf->data[off1];
+	do {
+		struct rte_flow_item *dst = NULL;
+
+		if ((unsigned int)item->type >= RTE_DIM(flow_item) ||
+		    !flow_item[item->type].name)
+			goto notsup;
+		if (pf)
+			dst = memcpy(pf->data + off1, item, sizeof(*item));
+		off1 += sizeof(*item);
+		flow_item_spec_size(item, &tmp, &pad);
+		if (item->spec) {
+			if (pf)
+				dst->spec = memcpy(pf->data + off2,
+						   item->spec, tmp);
+			off2 += tmp + pad;
+		}
+		if (item->last) {
+			if (pf)
+				dst->last = memcpy(pf->data + off2,
+						   item->last, tmp);
+			off2 += tmp + pad;
+		}
+		if (item->mask) {
+			if (pf)
+				dst->mask = memcpy(pf->data + off2,
+						   item->mask, tmp);
+			off2 += tmp + pad;
+		}
+		off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
+	} while ((item++)->type != RTE_FLOW_ITEM_TYPE_END);
+	off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
+	action = actions;
+	if (pf)
+		pf->actions = (void *)&pf->data[off1];
+	do {
+		struct rte_flow_action *dst = NULL;
+
+		if ((unsigned int)action->type >= RTE_DIM(flow_action) ||
+		    !flow_action[action->type].name)
+			goto notsup;
+		if (pf)
+			dst = memcpy(pf->data + off1, action, sizeof(*action));
+		off1 += sizeof(*action);
+		flow_action_conf_size(action, &tmp, &pad);
+		if (action->conf) {
+			if (pf)
+				dst->conf = memcpy(pf->data + off2,
+						   action->conf, tmp);
+			off2 += tmp + pad;
+		}
+		off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
+	} while ((action++)->type != RTE_FLOW_ACTION_TYPE_END);
+	if (pf != NULL)
+		return pf;
+	off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
+	tmp = RTE_ALIGN_CEIL(offsetof(struct port_flow, data), sizeof(double));
+	pf = calloc(1, tmp + off1 + off2);
+	if (pf == NULL)
+		err = errno;
+	else {
+		*pf = (const struct port_flow){
+			.size = tmp + off1 + off2,
+			.attr = *attr,
+		};
+		tmp -= offsetof(struct port_flow, data);
+		off2 = tmp + off1;
+		off1 = tmp;
+		goto store;
+	}
+notsup:
+	rte_errno = err;
+	return NULL;
+}
+
+/** Print a message out of a flow error. */
+static int
+port_flow_complain(struct rte_flow_error *error)
+{
+	static const char *const errstrlist[] = {
+		[RTE_FLOW_ERROR_TYPE_NONE] = "no error",
+		[RTE_FLOW_ERROR_TYPE_UNSPECIFIED] = "cause unspecified",
+		[RTE_FLOW_ERROR_TYPE_HANDLE] = "flow rule (handle)",
+		[RTE_FLOW_ERROR_TYPE_ATTR_GROUP] = "group field",
+		[RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY] = "priority field",
+		[RTE_FLOW_ERROR_TYPE_ATTR_INGRESS] = "ingress field",
+		[RTE_FLOW_ERROR_TYPE_ATTR_EGRESS] = "egress field",
+		[RTE_FLOW_ERROR_TYPE_ATTR] = "attributes structure",
+		[RTE_FLOW_ERROR_TYPE_ITEM_NUM] = "pattern length",
+		[RTE_FLOW_ERROR_TYPE_ITEM] = "specific pattern item",
+		[RTE_FLOW_ERROR_TYPE_ACTION_NUM] = "number of actions",
+		[RTE_FLOW_ERROR_TYPE_ACTION] = "specific action",
+	};
+	const char *errstr;
+	char buf[32];
+	int err = rte_errno;
+
+	if ((unsigned int)error->type >= RTE_DIM(errstrlist) ||
+	    !errstrlist[error->type])
+		errstr = "unknown type";
+	else
+		errstr = errstrlist[error->type];
+	printf("Caught error type %d (%s): %s%s\n",
+	       error->type, errstr,
+	       error->cause ? (snprintf(buf, sizeof(buf), "cause: %p, ",
+					error->cause), buf) : "",
+	       error->message ? error->message : "(no stated reason)");
+	return -err;
+}
+
+/** Validate flow rule. */
+int
+port_flow_validate(portid_t port_id,
+		   const struct rte_flow_attr *attr,
+		   const struct rte_flow_item *pattern,
+		   const struct rte_flow_action *actions)
+{
+	struct rte_flow_error error;
+
+	/* Poisoning to make sure PMDs update it in case of error. */
+	memset(&error, 0x11, sizeof(error));
+	if (rte_flow_validate(port_id, attr, pattern, actions, &error))
+		return port_flow_complain(&error);
+	printf("Flow rule validated\n");
+	return 0;
+}
+
+/** Create flow rule. */
+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)
+{
+	struct rte_flow *flow;
+	struct rte_port *port;
+	struct port_flow *pf;
+	uint32_t id;
+	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);
+	if (!pf) {
+		int err = rte_errno;
+
+		printf("Cannot allocate flow: %s\n", rte_strerror(err));
+		rte_flow_destroy(port_id, flow, NULL);
+		return -err;
+	}
+	pf->next = port->flow_list;
+	pf->id = id;
+	pf->flow = flow;
+	port->flow_list = pf;
+	printf("Flow rule #%u created\n", pf->id);
+	return 0;
+}
+
+/** Destroy a number of flow rules. */
+int
+port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule)
+{
+	struct rte_port *port;
+	struct port_flow **tmp;
+	uint32_t c = 0;
+	int ret = 0;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+	port = &ports[port_id];
+	tmp = &port->flow_list;
+	while (*tmp) {
+		uint32_t i;
+
+		for (i = 0; i != n; ++i) {
+			struct rte_flow_error error;
+			struct port_flow *pf = *tmp;
+
+			if (rule[i] != pf->id)
+				continue;
+			/*
+			 * Poisoning to make sure PMDs update it in case
+			 * of error.
+			 */
+			memset(&error, 0x33, sizeof(error));
+			if (rte_flow_destroy(port_id, pf->flow, &error)) {
+				ret = port_flow_complain(&error);
+				continue;
+			}
+			printf("Flow rule #%u destroyed\n", pf->id);
+			*tmp = pf->next;
+			free(pf);
+			break;
+		}
+		if (i == n)
+			tmp = &(*tmp)->next;
+		++c;
+	}
+	return ret;
+}
+
+/** Remove all flow rules. */
+int
+port_flow_flush(portid_t port_id)
+{
+	struct rte_flow_error error;
+	struct rte_port *port;
+	int ret = 0;
+
+	/* Poisoning to make sure PMDs update it in case of error. */
+	memset(&error, 0x44, sizeof(error));
+	if (rte_flow_flush(port_id, &error)) {
+		ret = port_flow_complain(&error);
+		if (port_id_is_invalid(port_id, DISABLED_WARN) ||
+		    port_id == (portid_t)RTE_PORT_ALL)
+			return ret;
+	}
+	port = &ports[port_id];
+	while (port->flow_list) {
+		struct port_flow *pf = port->flow_list->next;
+
+		free(port->flow_list);
+		port->flow_list = pf;
+	}
+	return ret;
+}
+
+/** Query a flow rule. */
+int
+port_flow_query(portid_t port_id, uint32_t rule,
+		enum rte_flow_action_type action)
+{
+	struct rte_flow_error error;
+	struct rte_port *port;
+	struct port_flow *pf;
+	const char *name;
+	union {
+		struct rte_flow_query_count count;
+	} query;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+	port = &ports[port_id];
+	for (pf = port->flow_list; pf; pf = pf->next)
+		if (pf->id == rule)
+			break;
+	if (!pf) {
+		printf("Flow rule #%u not found\n", rule);
+		return -ENOENT;
+	}
+	if ((unsigned int)action >= RTE_DIM(flow_action) ||
+	    !flow_action[action].name)
+		name = "unknown";
+	else
+		name = flow_action[action].name;
+	switch (action) {
+	case RTE_FLOW_ACTION_TYPE_COUNT:
+		break;
+	default:
+		printf("Cannot query action type %d (%s)\n", action, name);
+		return -ENOTSUP;
+	}
+	/* Poisoning to make sure PMDs update it in case of error. */
+	memset(&error, 0x55, sizeof(error));
+	memset(&query, 0, sizeof(query));
+	if (rte_flow_query(port_id, pf->flow, action, &query, &error))
+		return port_flow_complain(&error);
+	switch (action) {
+	case RTE_FLOW_ACTION_TYPE_COUNT:
+		printf("%s:\n"
+		       " hits_set: %u\n"
+		       " bytes_set: %u\n"
+		       " hits: %" PRIu64 "\n"
+		       " bytes: %" PRIu64 "\n",
+		       name,
+		       query.count.hits_set,
+		       query.count.bytes_set,
+		       query.count.hits,
+		       query.count.bytes);
+		break;
+	default:
+		printf("Cannot display result for action type %d (%s)\n",
+		       action, name);
+		break;
+	}
+	return 0;
+}
+
+/** List flow rules. */
+void
+port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
+{
+	struct rte_port *port;
+	struct port_flow *pf;
+	struct port_flow *list = NULL;
+	uint32_t i;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return;
+	port = &ports[port_id];
+	if (!port->flow_list)
+		return;
+	/* Sort flows by group, priority and ID. */
+	for (pf = port->flow_list; pf != NULL; pf = pf->next) {
+		struct port_flow **tmp;
+
+		if (n) {
+			/* Filter out unwanted groups. */
+			for (i = 0; i != n; ++i)
+				if (pf->attr.group == group[i])
+					break;
+			if (i == n)
+				continue;
+		}
+		tmp = &list;
+		while (*tmp &&
+		       (pf->attr.group > (*tmp)->attr.group ||
+			(pf->attr.group == (*tmp)->attr.group &&
+			 pf->attr.priority > (*tmp)->attr.priority) ||
+			(pf->attr.group == (*tmp)->attr.group &&
+			 pf->attr.priority == (*tmp)->attr.priority &&
+			 pf->id > (*tmp)->id)))
+			tmp = &(*tmp)->tmp;
+		pf->tmp = *tmp;
+		*tmp = pf;
+	}
+	printf("ID\tGroup\tPrio\tAttr\tRule\n");
+	for (pf = list; pf != NULL; pf = pf->tmp) {
+		const struct rte_flow_item *item = pf->pattern;
+		const struct rte_flow_action *action = pf->actions;
+
+		printf("%" PRIu32 "\t%" PRIu32 "\t%" PRIu32 "\t%c%c\t",
+		       pf->id,
+		       pf->attr.group,
+		       pf->attr.priority,
+		       pf->attr.ingress ? 'i' : '-',
+		       pf->attr.egress ? 'e' : '-');
+		while (item->type != RTE_FLOW_ITEM_TYPE_END) {
+			if (item->type != RTE_FLOW_ITEM_TYPE_VOID)
+				printf("%s ", flow_item[item->type].name);
+			++item;
+		}
+		printf("=>");
+		while (action->type != RTE_FLOW_ACTION_TYPE_END) {
+			if (action->type != RTE_FLOW_ACTION_TYPE_VOID)
+				printf(" %s", flow_action[action->type].name);
+			++action;
+		}
+		printf("\n");
+	}
+}
+
+/** Restrict ingress traffic to the defined flow rules. */
+int
+port_flow_isolate(portid_t port_id, int set)
+{
+	struct rte_flow_error error;
+
+	/* Poisoning to make sure PMDs update it in case of error. */
+	memset(&error, 0x66, sizeof(error));
+	if (rte_flow_isolate(port_id, set, &error))
+		return port_flow_complain(&error);
+	printf("Ingress traffic on port %u is %s to the defined flow rules\n",
+	       port_id,
+	       set ? "now restricted" : "not restricted anymore");
+	return 0;
+}
diff --git a/lib/librte_cmdline/cmdline_flow.h b/lib/librte_cmdline/cmdline_flow.h
new file mode 100644
index 0000000..45219d5
--- /dev/null
+++ b/lib/librte_cmdline/cmdline_flow.h
@@ -0,0 +1,1852 @@ 
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright 2016 6WIND S.A.
+ *   Copyright 2016 Mellanox.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of 6WIND S.A. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _CMDLINE_FLOW_H_
+#define _CMDLINE_FLOW_H_
+
+#define RTE_PORT_ALL (~(portid_t)0x0)
+
+#if defined RTE_LIBRTE_PMD_SOFTNIC && defined RTE_LIBRTE_SCHED
+#define TM_MODE			1
+#else
+#define TM_MODE			0
+#endif
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_flow.h>
+
+#include <cmdline_parse.h>
+#include <cmdline_parse_etheraddr.h>
+
+/**
+ * @file
+ *
+ * Command line Flow API
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Parser token indices. */
+enum index {
+	/* Special tokens. */
+	ZERO = 0,
+	END,
+
+	/* Common tokens. */
+	INTEGER,
+	UNSIGNED,
+	PREFIX,
+	BOOLEAN,
+	STRING,
+	MAC_ADDR,
+	IPV4_ADDR,
+	IPV6_ADDR,
+	RULE_ID,
+	PORT_ID,
+	GROUP_ID,
+	PRIORITY_LEVEL,
+
+	/* Top-level command. */
+	FLOW,
+
+	/* Sub-level commands. */
+	VALIDATE,
+	CREATE,
+	DESTROY,
+	FLUSH,
+	QUERY,
+	LIST,
+	ISOLATE,
+
+	/* Destroy arguments. */
+	DESTROY_RULE,
+
+	/* Query arguments. */
+	QUERY_ACTION,
+
+	/* List arguments. */
+	LIST_GROUP,
+
+	/* Validate/create arguments. */
+	GROUP,
+	PRIORITY,
+	INGRESS,
+	EGRESS,
+
+	/* Validate/create pattern. */
+	PATTERN,
+	ITEM_PARAM_IS,
+	ITEM_PARAM_SPEC,
+	ITEM_PARAM_LAST,
+	ITEM_PARAM_MASK,
+	ITEM_PARAM_PREFIX,
+	ITEM_NEXT,
+	ITEM_END,
+	ITEM_VOID,
+	ITEM_INVERT,
+	ITEM_ANY,
+	ITEM_ANY_NUM,
+	ITEM_PF,
+	ITEM_VF,
+	ITEM_VF_ID,
+	ITEM_PORT,
+	ITEM_PORT_INDEX,
+	ITEM_RAW,
+	ITEM_RAW_RELATIVE,
+	ITEM_RAW_SEARCH,
+	ITEM_RAW_OFFSET,
+	ITEM_RAW_LIMIT,
+	ITEM_RAW_PATTERN,
+	ITEM_ETH,
+	ITEM_ETH_DST,
+	ITEM_ETH_SRC,
+	ITEM_ETH_TYPE,
+	ITEM_VLAN,
+	ITEM_VLAN_TPID,
+	ITEM_VLAN_TCI,
+	ITEM_VLAN_PCP,
+	ITEM_VLAN_DEI,
+	ITEM_VLAN_VID,
+	ITEM_IPV4,
+	ITEM_IPV4_TOS,
+	ITEM_IPV4_TTL,
+	ITEM_IPV4_PROTO,
+	ITEM_IPV4_SRC,
+	ITEM_IPV4_DST,
+	ITEM_IPV6,
+	ITEM_IPV6_TC,
+	ITEM_IPV6_FLOW,
+	ITEM_IPV6_PROTO,
+	ITEM_IPV6_HOP,
+	ITEM_IPV6_SRC,
+	ITEM_IPV6_DST,
+	ITEM_ICMP,
+	ITEM_ICMP_TYPE,
+	ITEM_ICMP_CODE,
+	ITEM_UDP,
+	ITEM_UDP_SRC,
+	ITEM_UDP_DST,
+	ITEM_TCP,
+	ITEM_TCP_SRC,
+	ITEM_TCP_DST,
+	ITEM_TCP_FLAGS,
+	ITEM_SCTP,
+	ITEM_SCTP_SRC,
+	ITEM_SCTP_DST,
+	ITEM_SCTP_TAG,
+	ITEM_SCTP_CKSUM,
+	ITEM_VXLAN,
+	ITEM_VXLAN_VNI,
+	ITEM_E_TAG,
+	ITEM_E_TAG_GRP_ECID_B,
+	ITEM_NVGRE,
+	ITEM_NVGRE_TNI,
+	ITEM_MPLS,
+	ITEM_MPLS_LABEL,
+	ITEM_GRE,
+	ITEM_GRE_PROTO,
+	ITEM_FUZZY,
+	ITEM_FUZZY_THRESH,
+	ITEM_GTP,
+	ITEM_GTP_TEID,
+	ITEM_GTPC,
+	ITEM_GTPU,
+
+	/* Validate/create actions. */
+	ACTIONS,
+	ACTION_NEXT,
+	ACTION_END,
+	ACTION_VOID,
+	ACTION_PASSTHRU,
+	ACTION_MARK,
+	ACTION_MARK_ID,
+	ACTION_FLAG,
+	ACTION_QUEUE,
+	ACTION_QUEUE_INDEX,
+	ACTION_DROP,
+	ACTION_COUNT,
+	ACTION_DUP,
+	ACTION_DUP_INDEX,
+	ACTION_RSS,
+	ACTION_RSS_QUEUES,
+	ACTION_RSS_QUEUE,
+	ACTION_PF,
+	ACTION_VF,
+	ACTION_VF_ORIGINAL,
+	ACTION_VF_ID,
+	ACTION_METER,
+	ACTION_METER_ID,
+};
+
+/** Size of pattern[] field in struct rte_flow_item_raw. */
+#define ITEM_RAW_PATTERN_SIZE 36
+
+/** Storage size for struct rte_flow_item_raw including pattern. */
+#define ITEM_RAW_SIZE \
+	(offsetof(struct rte_flow_item_raw, pattern) + ITEM_RAW_PATTERN_SIZE)
+
+/** Number of queue[] entries in struct rte_flow_action_rss. */
+#define ACTION_RSS_NUM 32
+
+/** Storage size for struct rte_flow_action_rss including queues. */
+#define ACTION_RSS_SIZE \
+	(offsetof(struct rte_flow_action_rss, queue) + \
+	 sizeof(*((struct rte_flow_action_rss *)0)->queue) * ACTION_RSS_NUM)
+
+/** Maximum number of subsequent tokens and arguments on the stack. */
+#define CTX_STACK_SIZE 16
+
+typedef uint16_t portid_t;
+
+/** Descriptor for a single flow. */
+struct port_flow {
+	size_t size; /**< Allocated space including data[]. */
+	struct port_flow *next; /**< Next flow in list. */
+	struct port_flow *tmp; /**< Temporary linking. */
+	uint32_t id; /**< Flow rule ID. */
+	struct rte_flow *flow; /**< Opaque flow object returned by PMD. */
+	struct rte_flow_attr attr; /**< Attributes. */
+	struct rte_flow_item *pattern; /**< Pattern. */
+	struct rte_flow_action *actions; /**< Actions. */
+	uint8_t data[]; /**< Storage for pattern/actions. */
+};
+
+#ifdef TM_MODE
+/**
+ * Soft port tm related parameters
+ */
+struct softnic_port_tm {
+	uint32_t default_hierarchy_enable; /**< def hierarchy enable flag */
+	uint32_t hierarchy_config;  /**< set to 1 if hierarchy configured */
+
+	uint32_t n_subports_per_port;  /**< Num of subport nodes per port */
+	uint32_t n_pipes_per_subport;  /**< Num of pipe nodes per subport */
+
+	uint64_t tm_pktfield0_slabpos;  /**< Pkt field position for subport */
+	uint64_t tm_pktfield0_slabmask; /**< Pkt field mask for the subport */
+	uint64_t tm_pktfield0_slabshr;
+	uint64_t tm_pktfield1_slabpos; /**< Pkt field position for the pipe */
+	uint64_t tm_pktfield1_slabmask; /**< Pkt field mask for the pipe */
+	uint64_t tm_pktfield1_slabshr;
+	uint64_t tm_pktfield2_slabpos; /**< Pkt field position table index */
+	uint64_t tm_pktfield2_slabmask; /**< Pkt field mask for tc table idx */
+	uint64_t tm_pktfield2_slabshr;
+	uint64_t tm_tc_table[64];  /**< TC translation table */
+};
+
+/**
+ * The data structure associate with softnic port
+ */
+struct softnic_port {
+	   unsigned int tm_flag;   /**< set to 1 if tm feature is enabled */
+	   struct softnic_port_tm tm;      /**< softnic port tm parameters */
+};
+#endif
+
+/**
+ * The data structure associated with each port.
+ */
+struct rte_port {
+	struct rte_eth_dev_info dev_info;   /**< PCI info + driver name */
+	struct rte_eth_conf     dev_conf;   /**< Port configuration. */
+	struct ether_addr       eth_addr;   /**< Port ethernet address */
+	struct rte_eth_stats    stats;      /**< Last port statistics */
+	uint64_t                tx_dropped; /**< If no descriptor in TX ring */
+	struct fwd_stream       *rx_stream; /**< Port RX stream, if unique */
+	struct fwd_stream       *tx_stream; /**< Port TX stream, if unique */
+	unsigned int            socket_id;  /**< For NUMA support */
+	uint16_t                tx_ol_flags;/**< TX Offload Flags (TESTPMD_TX_OFFLOAD...). */
+	uint16_t                tso_segsz;  /**< Segmentation offload MSS for non-tunneled packets. */
+	uint16_t                tunnel_tso_segsz; /**< Segmentation offload MSS for tunneled pkts. */
+	uint16_t                tx_vlan_id;/**< The tag ID */
+	uint16_t                tx_vlan_id_outer;/**< The outer tag ID */
+	void                    *fwd_ctx;   /**< Forwarding mode context */
+	uint64_t                rx_bad_ip_csum; /**< rx pkts with bad ip checksum  */
+	uint64_t                rx_bad_l4_csum; /**< rx pkts with bad l4 checksum */
+	uint8_t                 tx_queue_stats_mapping_enabled;
+	uint8_t                 rx_queue_stats_mapping_enabled;
+	volatile uint16_t       port_status;    /**< port started or not */
+	uint8_t                 need_reconfig;  /**< need reconfiguring port or not */
+	uint8_t                 need_reconfig_queues; /**< need reconfiguring queues or not */
+	uint8_t                 rss_flag;   /**< enable rss or not */
+	uint8_t                 dcb_flag;   /**< enable dcb */
+	struct rte_eth_rxconf   rx_conf;    /**< rx configuration */
+	struct rte_eth_txconf   tx_conf;    /**< tx configuration */
+	struct ether_addr       *mc_addr_pool; /**< pool of multicast addrs */
+	uint32_t                mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
+	uint8_t                 slave_flag; /**< bonding slave port */
+	struct port_flow        *flow_list; /**< Associated flows. */
+#ifdef TM_MODE
+	unsigned int            softnic_enable; /**< softnic flag */
+	struct softnic_port     softport;  /**< softnic port params */
+#endif
+};
+
+/** Globally store the involved ports. */
+struct rte_port *ports;
+
+/** Parser context. */
+struct context {
+	/** Stack of subsequent token lists to process. */
+	const enum index *next[CTX_STACK_SIZE];
+	/** Arguments for stacked tokens. */
+	const void *args[CTX_STACK_SIZE];
+	enum index curr; /**< Current token index. */
+	enum index prev; /**< Index of the last token seen. */
+	int next_num; /**< Number of entries in next[]. */
+	int args_num; /**< Number of entries in args[]. */
+	uint32_t eol:1; /**< EOL has been detected. */
+	uint32_t last:1; /**< No more arguments. */
+	portid_t port; /**< Current port ID (for completions). */
+	uint32_t objdata; /**< Object-specific data. */
+	void *object; /**< Address of current object for relative offsets. */
+	void *objmask; /**< Object a full mask must be written to. */
+};
+
+/** Token argument. */
+struct arg {
+	uint32_t hton:1; /**< Use network byte ordering. */
+	uint32_t sign:1; /**< Value is signed. */
+	uint32_t offset; /**< Relative offset from ctx->object. */
+	uint32_t size; /**< Field size. */
+	const uint8_t *mask; /**< Bit-mask to use instead of offset/size. */
+};
+
+/** Parser token definition. */
+struct token {
+	/** Type displayed during completion (defaults to "TOKEN"). */
+	const char *type;
+	/** Help displayed during completion (defaults to token name). */
+	const char *help;
+	/** Private data used by parser functions. */
+	const void *priv;
+	/**
+	 * Lists of subsequent tokens to push on the stack. Each call to the
+	 * parser consumes the last entry of that stack.
+	 */
+	const enum index *const *next;
+	/** Arguments stack for subsequent tokens that need them. */
+	const struct arg *const *args;
+	/**
+	 * Token-processing callback, returns -1 in case of error, the
+	 * length of the matched string otherwise. If NULL, attempts to
+	 * match the token name.
+	 *
+	 * If buf is not NULL, the result should be stored in it according
+	 * to context. An error is returned if not large enough.
+	 */
+	int (*call)(struct context *ctx, const struct token *token,
+			const char *str, unsigned int len,
+			void *buf, unsigned int size);
+	/**
+	 * Callback that provides possible values for this token, used for
+	 * completion. Returns -1 in case of error, the number of possible
+	 * values otherwise. If NULL, the token name is used.
+	 *
+	 * If buf is not NULL, entry index ent is written to buf and the
+	 * full length of the entry is returned (same behavior as
+	 * snprintf()).
+	 */
+	int (*comp)(struct context *ctx, const struct token *token,
+			unsigned int ent, char *buf, unsigned int size);
+	/** Mandatory token name, no default value. */
+	const char *name;
+};
+
+/** Static initializer for the next field. */
+#define NEXT(...) (const enum index *const []){ __VA_ARGS__, NULL, }
+
+/** Static initializer for a NEXT() entry. */
+#define NEXT_ENTRY(...) (const enum index []){ __VA_ARGS__, ZERO, }
+
+/** Static initializer for the args field. */
+#define ARGS(...) (const struct arg *const []){ __VA_ARGS__, NULL, }
+
+/** Static initializer for ARGS() to target a field. */
+#define ARGS_ENTRY(s, f) \
+	(&(const struct arg){ \
+		.offset = offsetof(s, f), \
+		.size = sizeof(((s *)0)->f), \
+	})
+
+/** Static initializer for ARGS() to target a bit-field. */
+#define ARGS_ENTRY_BF(s, f, b) \
+	(&(const struct arg){ \
+		.size = sizeof(s), \
+		.mask = (const void *)&(const s){ .f = (1 << (b)) - 1 }, \
+	})
+
+/** Static initializer for ARGS() to target an arbitrary bit-mask. */
+#define ARGS_ENTRY_MASK(s, f, m) \
+	(&(const struct arg){ \
+		.offset = offsetof(s, f), \
+		.size = sizeof(((s *)0)->f), \
+		.mask = (const void *)(m), \
+	})
+
+/** Same as ARGS_ENTRY_MASK() using network byte ordering for the value. */
+#define ARGS_ENTRY_MASK_HTON(s, f, m) \
+	(&(const struct arg){ \
+		.hton = 1, \
+		.offset = offsetof(s, f), \
+		.size = sizeof(((s *)0)->f), \
+		.mask = (const void *)(m), \
+	})
+
+/** Static initializer for ARGS() to target a pointer. */
+#define ARGS_ENTRY_PTR(s, f) \
+	(&(const struct arg){ \
+		.size = sizeof(*((s *)0)->f), \
+	})
+
+/** Static initializer for ARGS() with arbitrary size. */
+#define ARGS_ENTRY_USZ(s, f, sz) \
+	(&(const struct arg){ \
+		.offset = offsetof(s, f), \
+		.size = (sz), \
+	})
+
+/** Same as ARGS_ENTRY() using network byte ordering. */
+#define ARGS_ENTRY_HTON(s, f) \
+	(&(const struct arg){ \
+		.hton = 1, \
+		.offset = offsetof(s, f), \
+		.size = sizeof(((s *)0)->f), \
+	})
+
+/** Parser output buffer layout expected by cmd_flow_parsed(). */
+struct buffer {
+	enum index command; /**< Flow command. */
+	portid_t port; /**< Affected port ID. */
+	union {
+		struct {
+			struct rte_flow_attr attr;
+			struct rte_flow_item *pattern;
+			struct rte_flow_action *actions;
+			uint32_t pattern_n;
+			uint32_t actions_n;
+			uint8_t *data;
+		} vc; /**< Validate/create arguments. */
+		struct {
+			uint32_t *rule;
+			uint32_t rule_n;
+		} destroy; /**< Destroy arguments. */
+		struct {
+			uint32_t rule;
+			enum rte_flow_action_type action;
+		} query; /**< Query arguments. */
+		struct {
+			uint32_t *group;
+			uint32_t group_n;
+		} list; /**< List arguments. */
+		struct {
+			int set;
+		} isolate; /**< Isolated mode arguments. */
+	} args; /**< Command arguments. */
+};
+
+/** Private data for pattern items. */
+struct parse_item_priv {
+	enum rte_flow_item_type type; /**< Item type. */
+	uint32_t size; /**< Size of item specification structure. */
+};
+
+#define PRIV_ITEM(t, s) \
+	(&(const struct parse_item_priv){ \
+		.type = RTE_FLOW_ITEM_TYPE_ ## t, \
+		.size = s, \
+	})
+
+/** Private data for actions. */
+struct parse_action_priv {
+	enum rte_flow_action_type type; /**< Action type. */
+	uint32_t size; /**< Size of action configuration structure. */
+};
+
+#define PRIV_ACTION(t, s) \
+	(&(const struct parse_action_priv){ \
+		.type = RTE_FLOW_ACTION_TYPE_ ## t, \
+		.size = s, \
+	})
+
+static const enum index next_vc_attr[] = {
+	GROUP,
+	PRIORITY,
+	INGRESS,
+	EGRESS,
+	PATTERN,
+	ZERO,
+};
+
+static const enum index next_destroy_attr[] = {
+	DESTROY_RULE,
+	END,
+	ZERO,
+};
+
+static const enum index next_list_attr[] = {
+	LIST_GROUP,
+	END,
+	ZERO,
+};
+
+static const enum index item_param[] = {
+	ITEM_PARAM_IS,
+	ITEM_PARAM_SPEC,
+	ITEM_PARAM_LAST,
+	ITEM_PARAM_MASK,
+	ITEM_PARAM_PREFIX,
+	ZERO,
+};
+
+static const enum index next_item[] = {
+	ITEM_END,
+	ITEM_VOID,
+	ITEM_INVERT,
+	ITEM_ANY,
+	ITEM_PF,
+	ITEM_VF,
+	ITEM_PORT,
+	ITEM_RAW,
+	ITEM_ETH,
+	ITEM_VLAN,
+	ITEM_IPV4,
+	ITEM_IPV6,
+	ITEM_ICMP,
+	ITEM_UDP,
+	ITEM_TCP,
+	ITEM_SCTP,
+	ITEM_VXLAN,
+	ITEM_E_TAG,
+	ITEM_NVGRE,
+	ITEM_MPLS,
+	ITEM_GRE,
+	ITEM_FUZZY,
+	ITEM_GTP,
+	ITEM_GTPC,
+	ITEM_GTPU,
+	ZERO,
+};
+
+static const enum index item_fuzzy[] = {
+	ITEM_FUZZY_THRESH,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_any[] = {
+	ITEM_ANY_NUM,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_vf[] = {
+	ITEM_VF_ID,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_port[] = {
+	ITEM_PORT_INDEX,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_raw[] = {
+	ITEM_RAW_RELATIVE,
+	ITEM_RAW_SEARCH,
+	ITEM_RAW_OFFSET,
+	ITEM_RAW_LIMIT,
+	ITEM_RAW_PATTERN,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_eth[] = {
+	ITEM_ETH_DST,
+	ITEM_ETH_SRC,
+	ITEM_ETH_TYPE,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_vlan[] = {
+	ITEM_VLAN_TPID,
+	ITEM_VLAN_TCI,
+	ITEM_VLAN_PCP,
+	ITEM_VLAN_DEI,
+	ITEM_VLAN_VID,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_ipv4[] = {
+	ITEM_IPV4_TOS,
+	ITEM_IPV4_TTL,
+	ITEM_IPV4_PROTO,
+	ITEM_IPV4_SRC,
+	ITEM_IPV4_DST,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_ipv6[] = {
+	ITEM_IPV6_TC,
+	ITEM_IPV6_FLOW,
+	ITEM_IPV6_PROTO,
+	ITEM_IPV6_HOP,
+	ITEM_IPV6_SRC,
+	ITEM_IPV6_DST,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_icmp[] = {
+	ITEM_ICMP_TYPE,
+	ITEM_ICMP_CODE,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_udp[] = {
+	ITEM_UDP_SRC,
+	ITEM_UDP_DST,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_tcp[] = {
+	ITEM_TCP_SRC,
+	ITEM_TCP_DST,
+	ITEM_TCP_FLAGS,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_sctp[] = {
+	ITEM_SCTP_SRC,
+	ITEM_SCTP_DST,
+	ITEM_SCTP_TAG,
+	ITEM_SCTP_CKSUM,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_vxlan[] = {
+	ITEM_VXLAN_VNI,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_e_tag[] = {
+	ITEM_E_TAG_GRP_ECID_B,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_nvgre[] = {
+	ITEM_NVGRE_TNI,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_mpls[] = {
+	ITEM_MPLS_LABEL,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_gre[] = {
+	ITEM_GRE_PROTO,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index item_gtp[] = {
+	ITEM_GTP_TEID,
+	ITEM_NEXT,
+	ZERO,
+};
+
+static const enum index next_action[] = {
+	ACTION_END,
+	ACTION_VOID,
+	ACTION_PASSTHRU,
+	ACTION_MARK,
+	ACTION_FLAG,
+	ACTION_QUEUE,
+	ACTION_DROP,
+	ACTION_COUNT,
+	ACTION_DUP,
+	ACTION_RSS,
+	ACTION_PF,
+	ACTION_VF,
+	ACTION_METER,
+	ZERO,
+};
+
+static const enum index action_mark[] = {
+	ACTION_MARK_ID,
+	ACTION_NEXT,
+	ZERO,
+};
+
+static const enum index action_queue[] = {
+	ACTION_QUEUE_INDEX,
+	ACTION_NEXT,
+	ZERO,
+};
+
+static const enum index action_dup[] = {
+	ACTION_DUP_INDEX,
+	ACTION_NEXT,
+	ZERO,
+};
+
+static const enum index action_rss[] = {
+	ACTION_RSS_QUEUES,
+	ACTION_NEXT,
+	ZERO,
+};
+
+static const enum index action_vf[] = {
+	ACTION_VF_ORIGINAL,
+	ACTION_VF_ID,
+	ACTION_NEXT,
+	ZERO,
+};
+
+static const enum index action_meter[] = {
+	ACTION_METER_ID,
+	ACTION_NEXT,
+	ZERO,
+};
+
+/** Warning types. */
+enum print_warning {
+	ENABLED_WARN = 0,
+	DISABLED_WARN
+};
+
+/** Information about known flow pattern items. */
+static const struct {
+	const char *name;
+	size_t size;
+} flow_item[] = {
+	MK_FLOW_ITEM(END, 0),
+	MK_FLOW_ITEM(VOID, 0),
+	MK_FLOW_ITEM(INVERT, 0),
+	MK_FLOW_ITEM(ANY, sizeof(struct rte_flow_item_any)),
+	MK_FLOW_ITEM(PF, 0),
+	MK_FLOW_ITEM(VF, sizeof(struct rte_flow_item_vf)),
+	MK_FLOW_ITEM(PORT, sizeof(struct rte_flow_item_port)),
+	MK_FLOW_ITEM(RAW, sizeof(struct rte_flow_item_raw)), /* +pattern[] */
+	MK_FLOW_ITEM(ETH, sizeof(struct rte_flow_item_eth)),
+	MK_FLOW_ITEM(VLAN, sizeof(struct rte_flow_item_vlan)),
+	MK_FLOW_ITEM(IPV4, sizeof(struct rte_flow_item_ipv4)),
+	MK_FLOW_ITEM(IPV6, sizeof(struct rte_flow_item_ipv6)),
+	MK_FLOW_ITEM(ICMP, sizeof(struct rte_flow_item_icmp)),
+	MK_FLOW_ITEM(UDP, sizeof(struct rte_flow_item_udp)),
+	MK_FLOW_ITEM(TCP, sizeof(struct rte_flow_item_tcp)),
+	MK_FLOW_ITEM(SCTP, sizeof(struct rte_flow_item_sctp)),
+	MK_FLOW_ITEM(VXLAN, sizeof(struct rte_flow_item_vxlan)),
+	MK_FLOW_ITEM(E_TAG, sizeof(struct rte_flow_item_e_tag)),
+	MK_FLOW_ITEM(NVGRE, sizeof(struct rte_flow_item_nvgre)),
+	MK_FLOW_ITEM(MPLS, sizeof(struct rte_flow_item_mpls)),
+	MK_FLOW_ITEM(GRE, sizeof(struct rte_flow_item_gre)),
+	MK_FLOW_ITEM(FUZZY, sizeof(struct rte_flow_item_fuzzy)),
+	MK_FLOW_ITEM(GTP, sizeof(struct rte_flow_item_gtp)),
+	MK_FLOW_ITEM(GTPC, sizeof(struct rte_flow_item_gtp)),
+	MK_FLOW_ITEM(GTPU, sizeof(struct rte_flow_item_gtp)),
+};
+
+/** Information about known flow actions. */
+static const struct {
+	const char *name;
+	size_t size;
+} flow_action[] = {
+	MK_FLOW_ACTION(END, 0),
+	MK_FLOW_ACTION(VOID, 0),
+	MK_FLOW_ACTION(PASSTHRU, 0),
+	MK_FLOW_ACTION(MARK, sizeof(struct rte_flow_action_mark)),
+	MK_FLOW_ACTION(FLAG, 0),
+	MK_FLOW_ACTION(QUEUE, sizeof(struct rte_flow_action_queue)),
+	MK_FLOW_ACTION(DROP, 0),
+	MK_FLOW_ACTION(COUNT, 0),
+	MK_FLOW_ACTION(DUP, sizeof(struct rte_flow_action_dup)),
+	MK_FLOW_ACTION(RSS, sizeof(struct rte_flow_action_rss)), /* +queue[] */
+	MK_FLOW_ACTION(PF, 0),
+	MK_FLOW_ACTION(VF, sizeof(struct rte_flow_action_vf)),
+};
+
+/** Helper functions for parsing. */
+int port_id_is_invalid(portid_t port_id,
+			enum print_warning warning);
+const struct arg *pop_args(struct context *ctx);
+int push_args(struct context *ctx,
+			const struct arg *arg);
+size_t arg_entry_bf_fill(void *dst, uintmax_t val,
+			const struct arg *arg);
+int strcmp_partial(const char *full,
+			const char *partial, size_t partial_len);
+
+/** Parsing functions. */
+int parse_prefix(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_default(struct context *ctx,
+			const struct token *token,
+			const char *str, unsigned int len,
+			void *buf, unsigned int size);
+int parse_init(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_vc(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_vc_spec(struct context *, const struct token *,
+			const char *, unsigned int, void *,
+			unsigned int);
+int parse_vc_conf(struct context *, const struct token *,
+			const char *, unsigned int, void *,
+			unsigned int);
+int parse_vc_action_rss_queue(struct context *,
+			const struct token *,
+			const char *, unsigned int, void *,
+			unsigned int);
+int parse_destroy(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_flush(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_query(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_action(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_list(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_isolate(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_int(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_string(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_mac_addr(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_ipv4_addr(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_ipv6_addr(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_boolean(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+int parse_port(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
+
+/** Completion functions. */
+int comp_none(struct context *, const struct token *,
+			unsigned int, char *, unsigned int);
+int comp_boolean(struct context *, const struct token *,
+			unsigned int, char *, unsigned int);
+int comp_action(struct context *, const struct token *,
+			unsigned int, char *, unsigned int);
+int comp_port(struct context *, const struct token *,
+			unsigned int, char *, unsigned int);
+int comp_rule_id(struct context *, const struct token *,
+			unsigned int, char *, unsigned int);
+int comp_vc_action_rss_queue(struct context *,
+			const struct token *,
+			unsigned int, char *, unsigned int);
+
+/** Flow parsing functions. */
+void cmd_flow_context_init(struct context *ctx);
+int cmd_flow_parse(cmdline_parse_token_hdr_t *hdr,
+			const char *src, void *result,
+			unsigned int size);
+int cmd_flow_complete_get_nb(cmdline_parse_token_hdr_t *hdr);
+int cmd_flow_complete_get_elt(cmdline_parse_token_hdr_t *hdr,
+			int index,
+			char *dst, unsigned int size);
+int cmd_flow_get_help(cmdline_parse_token_hdr_t *hdr,
+			char *dst,
+			unsigned int size);
+void cmd_flow_tok(cmdline_parse_token_hdr_t **hdr,
+			cmdline_parse_token_hdr_t **hdr_inst);
+void cmd_flow_parsed(const struct buffer *in);
+void cmd_flow_cb(void *arg0, struct cmdline *cl, void *arg2);
+
+/** Generic flow management functions. */
+int port_flow_validate(portid_t port_id,
+			const struct rte_flow_attr *attr,
+			const struct rte_flow_item *pattern,
+			const struct rte_flow_action *actions);
+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);
+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_query(portid_t port_id, uint32_t rule,
+		    enum rte_flow_action_type action);
+void port_flow_list(portid_t port_id, uint32_t n,
+			const uint32_t *group);
+int port_flow_isolate(portid_t port_id, int set);
+
+/** Token definitions. */
+static const struct token token_list[] = {
+	/* Special tokens. */
+	[ZERO] = {
+		.name = "ZERO",
+		.help = "null entry, abused as the entry point",
+		.next = NEXT(NEXT_ENTRY(FLOW)),
+	},
+	[END] = {
+		.name = "",
+		.type = "RETURN",
+		.help = "command may end here",
+	},
+	/* Common tokens. */
+	[INTEGER] = {
+		.name = "{int}",
+		.type = "INTEGER",
+		.help = "integer value",
+		.call = parse_int,
+		.comp = comp_none,
+	},
+	[UNSIGNED] = {
+		.name = "{unsigned}",
+		.type = "UNSIGNED",
+		.help = "unsigned integer value",
+		.call = parse_int,
+		.comp = comp_none,
+	},
+	[PREFIX] = {
+		.name = "{prefix}",
+		.type = "PREFIX",
+		.help = "prefix length for bit-mask",
+		.call = parse_prefix,
+		.comp = comp_none,
+	},
+	[BOOLEAN] = {
+		.name = "{boolean}",
+		.type = "BOOLEAN",
+		.help = "any boolean value",
+		.call = parse_boolean,
+		.comp = comp_boolean,
+	},
+	[STRING] = {
+		.name = "{string}",
+		.type = "STRING",
+		.help = "fixed string",
+		.call = parse_string,
+		.comp = comp_none,
+	},
+	[MAC_ADDR] = {
+		.name = "{MAC address}",
+		.type = "MAC-48",
+		.help = "standard MAC address notation",
+		.call = parse_mac_addr,
+		.comp = comp_none,
+	},
+	[IPV4_ADDR] = {
+		.name = "{IPv4 address}",
+		.type = "IPV4 ADDRESS",
+		.help = "standard IPv4 address notation",
+		.call = parse_ipv4_addr,
+		.comp = comp_none,
+	},
+	[IPV6_ADDR] = {
+		.name = "{IPv6 address}",
+		.type = "IPV6 ADDRESS",
+		.help = "standard IPv6 address notation",
+		.call = parse_ipv6_addr,
+		.comp = comp_none,
+	},
+	[RULE_ID] = {
+		.name = "{rule id}",
+		.type = "RULE ID",
+		.help = "rule identifier",
+		.call = parse_int,
+		.comp = comp_rule_id,
+	},
+	[PORT_ID] = {
+		.name = "{port_id}",
+		.type = "PORT ID",
+		.help = "port identifier",
+		.call = parse_port,
+		.comp = comp_port,
+	},
+	[GROUP_ID] = {
+		.name = "{group_id}",
+		.type = "GROUP ID",
+		.help = "group identifier",
+		.call = parse_int,
+		.comp = comp_none,
+	},
+	[PRIORITY_LEVEL] = {
+		.name = "{level}",
+		.type = "PRIORITY",
+		.help = "priority level",
+		.call = parse_int,
+		.comp = comp_none,
+	},
+	/* Top-level command. */
+	[FLOW] = {
+		.name = "flow",
+		.type = "{command} {port_id} [{arg} [...]]",
+		.help = "manage ingress/egress flow rules",
+		.next = NEXT(NEXT_ENTRY
+				 (VALIDATE,
+				  CREATE,
+				  DESTROY,
+				  FLUSH,
+				  LIST,
+				  QUERY,
+				  ISOLATE)),
+		.call = parse_init,
+	},
+	/* Sub-level commands. */
+	[VALIDATE] = {
+		.name = "validate",
+		.help = "check whether a flow rule can be created",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_vc,
+	},
+	[CREATE] = {
+		.name = "create",
+		.help = "create a flow rule",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_vc,
+	},
+	[DESTROY] = {
+		.name = "destroy",
+		.help = "destroy specific flow rules",
+		.next = NEXT(NEXT_ENTRY(DESTROY_RULE), NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_destroy,
+	},
+	[FLUSH] = {
+		.name = "flush",
+		.help = "destroy all flow rules",
+		.next = NEXT(NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_flush,
+	},
+	[QUERY] = {
+		.name = "query",
+		.help = "query an existing flow rule",
+		.next = NEXT(NEXT_ENTRY(QUERY_ACTION),
+				 NEXT_ENTRY(RULE_ID),
+				 NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.query.action),
+				 ARGS_ENTRY(struct buffer, args.query.rule),
+				 ARGS_ENTRY(struct buffer, port)),
+		.call = parse_query,
+	},
+	[LIST] = {
+		.name = "list",
+		.help = "list existing flow rules",
+		.next = NEXT(next_list_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_list,
+	},
+	[ISOLATE] = {
+		.name = "isolate",
+		.help = "restrict ingress traffic to the defined flow rules",
+		.next = NEXT(NEXT_ENTRY(BOOLEAN),
+				 NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, args.isolate.set),
+				 ARGS_ENTRY(struct buffer, port)),
+		.call = parse_isolate,
+	},
+	/* Destroy arguments. */
+	[DESTROY_RULE] = {
+		.name = "rule",
+		.help = "specify a rule identifier",
+		.next = NEXT(next_destroy_attr, NEXT_ENTRY(RULE_ID)),
+		.args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.destroy.rule)),
+		.call = parse_destroy,
+	},
+	/* Query arguments. */
+	[QUERY_ACTION] = {
+		.name = "{action}",
+		.type = "ACTION",
+		.help = "action to query, must be part of the rule",
+		.call = parse_action,
+		.comp = comp_action,
+	},
+	/* List arguments. */
+	[LIST_GROUP] = {
+		.name = "group",
+		.help = "specify a group",
+		.next = NEXT(next_list_attr, NEXT_ENTRY(GROUP_ID)),
+		.args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.list.group)),
+		.call = parse_list,
+	},
+	/* Validate/create attributes. */
+	[GROUP] = {
+		.name = "group",
+		.help = "specify a group",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(GROUP_ID)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_attr, group)),
+		.call = parse_vc,
+	},
+	[PRIORITY] = {
+		.name = "priority",
+		.help = "specify a priority level",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(PRIORITY_LEVEL)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_attr, priority)),
+		.call = parse_vc,
+	},
+	[INGRESS] = {
+		.name = "ingress",
+		.help = "affect rule to ingress",
+		.next = NEXT(next_vc_attr),
+		.call = parse_vc,
+	},
+	[EGRESS] = {
+		.name = "egress",
+		.help = "affect rule to egress",
+		.next = NEXT(next_vc_attr),
+		.call = parse_vc,
+	},
+	/* Validate/create pattern. */
+	[PATTERN] = {
+		.name = "pattern",
+		.help = "submit a list of pattern items",
+		.next = NEXT(next_item),
+		.call = parse_vc,
+	},
+	[ITEM_PARAM_IS] = {
+		.name = "is",
+		.help = "match value perfectly (with full bit-mask)",
+		.call = parse_vc_spec,
+	},
+	[ITEM_PARAM_SPEC] = {
+		.name = "spec",
+		.help = "match value according to configured bit-mask",
+		.call = parse_vc_spec,
+	},
+	[ITEM_PARAM_LAST] = {
+		.name = "last",
+		.help = "specify upper bound to establish a range",
+		.call = parse_vc_spec,
+	},
+	[ITEM_PARAM_MASK] = {
+		.name = "mask",
+		.help = "specify bit-mask with relevant bits set to one",
+		.call = parse_vc_spec,
+	},
+	[ITEM_PARAM_PREFIX] = {
+		.name = "prefix",
+		.help = "generate bit-mask from a prefix length",
+		.call = parse_vc_spec,
+	},
+	[ITEM_NEXT] = {
+		.name = "/",
+		.help = "specify next pattern item",
+		.next = NEXT(next_item),
+	},
+	[ITEM_END] = {
+		.name = "end",
+		.help = "end list of pattern items",
+		.priv = PRIV_ITEM(END, 0),
+		.next = NEXT(NEXT_ENTRY(ACTIONS)),
+		.call = parse_vc,
+	},
+	[ITEM_VOID] = {
+		.name = "void",
+		.help = "no-op pattern item",
+		.priv = PRIV_ITEM(VOID, 0),
+		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
+		.call = parse_vc,
+	},
+	[ITEM_INVERT] = {
+		.name = "invert",
+		.help = "perform actions when pattern does not match",
+		.priv = PRIV_ITEM(INVERT, 0),
+		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
+		.call = parse_vc,
+	},
+	[ITEM_ANY] = {
+		.name = "any",
+		.help = "match any protocol for the current layer",
+		.priv = PRIV_ITEM(ANY, sizeof(struct rte_flow_item_any)),
+		.next = NEXT(item_any),
+		.call = parse_vc,
+	},
+	[ITEM_ANY_NUM] = {
+		.name = "num",
+		.help = "number of layers covered",
+		.next = NEXT(item_any, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_any, num)),
+	},
+	[ITEM_PF] = {
+		.name = "pf",
+		.help = "match packets addressed to the physical function",
+		.priv = PRIV_ITEM(PF, 0),
+		.next = NEXT(NEXT_ENTRY(ITEM_NEXT)),
+		.call = parse_vc,
+	},
+	[ITEM_VF] = {
+		.name = "vf",
+		.help = "match packets addressed to a virtual function ID",
+		.priv = PRIV_ITEM(VF, sizeof(struct rte_flow_item_vf)),
+		.next = NEXT(item_vf),
+		.call = parse_vc,
+	},
+	[ITEM_VF_ID] = {
+		.name = "id",
+		.help = "destination VF ID",
+		.next = NEXT(item_vf, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_vf, id)),
+	},
+	[ITEM_PORT] = {
+		.name = "port",
+		.help = "device-specific physical port index to use",
+		.priv = PRIV_ITEM(PORT, sizeof(struct rte_flow_item_port)),
+		.next = NEXT(item_port),
+		.call = parse_vc,
+	},
+	[ITEM_PORT_INDEX] = {
+		.name = "index",
+		.help = "physical port index",
+		.next = NEXT(item_port, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_port, index)),
+	},
+	[ITEM_RAW] = {
+		.name = "raw",
+		.help = "match an arbitrary byte string",
+		.priv = PRIV_ITEM(RAW, ITEM_RAW_SIZE),
+		.next = NEXT(item_raw),
+		.call = parse_vc,
+	},
+	[ITEM_RAW_RELATIVE] = {
+		.name = "relative",
+		.help = "look for pattern after the previous item",
+		.next = NEXT(item_raw, NEXT_ENTRY(BOOLEAN), item_param),
+		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_item_raw,
+					   relative, 1)),
+	},
+	[ITEM_RAW_SEARCH] = {
+		.name = "search",
+		.help = "search pattern from offset (see also limit)",
+		.next = NEXT(item_raw, NEXT_ENTRY(BOOLEAN), item_param),
+		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_item_raw,
+					   search, 1)),
+	},
+	[ITEM_RAW_OFFSET] = {
+		.name = "offset",
+		.help = "absolute or relative offset for pattern",
+		.next = NEXT(item_raw, NEXT_ENTRY(INTEGER), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, offset)),
+	},
+	[ITEM_RAW_LIMIT] = {
+		.name = "limit",
+		.help = "search area limit for start of pattern",
+		.next = NEXT(item_raw, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, limit)),
+	},
+	[ITEM_RAW_PATTERN] = {
+		.name = "pattern",
+		.help = "byte string to look for",
+		.next = NEXT(item_raw,
+				 NEXT_ENTRY(STRING),
+				 NEXT_ENTRY(ITEM_PARAM_IS,
+					ITEM_PARAM_SPEC,
+					ITEM_PARAM_MASK)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_raw, length),
+				 ARGS_ENTRY_USZ(struct rte_flow_item_raw,
+						pattern,
+						ITEM_RAW_PATTERN_SIZE)),
+	},
+	[ITEM_ETH] = {
+		.name = "eth",
+		.help = "match Ethernet header",
+		.priv = PRIV_ITEM(ETH, sizeof(struct rte_flow_item_eth)),
+		.next = NEXT(item_eth),
+		.call = parse_vc,
+	},
+	[ITEM_ETH_DST] = {
+		.name = "dst",
+		.help = "destination MAC",
+		.next = NEXT(item_eth, NEXT_ENTRY(MAC_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, dst)),
+	},
+	[ITEM_ETH_SRC] = {
+		.name = "src",
+		.help = "source MAC",
+		.next = NEXT(item_eth, NEXT_ENTRY(MAC_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, src)),
+	},
+	[ITEM_ETH_TYPE] = {
+		.name = "type",
+		.help = "EtherType",
+		.next = NEXT(item_eth, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_eth, type)),
+	},
+	[ITEM_VLAN] = {
+		.name = "vlan",
+		.help = "match 802.1Q/ad VLAN tag",
+		.priv = PRIV_ITEM(VLAN, sizeof(struct rte_flow_item_vlan)),
+		.next = NEXT(item_vlan),
+		.call = parse_vc,
+	},
+	[ITEM_VLAN_TPID] = {
+		.name = "tpid",
+		.help = "tag protocol identifier",
+		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vlan, tpid)),
+	},
+	[ITEM_VLAN_TCI] = {
+		.name = "tci",
+		.help = "tag control information",
+		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vlan, tci)),
+	},
+	[ITEM_VLAN_PCP] = {
+		.name = "pcp",
+		.help = "priority code point",
+		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
+						  tci, "\xe0\x00")),
+	},
+	[ITEM_VLAN_DEI] = {
+		.name = "dei",
+		.help = "drop eligible indicator",
+		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
+						  tci, "\x10\x00")),
+	},
+	[ITEM_VLAN_VID] = {
+		.name = "vid",
+		.help = "VLAN identifier",
+		.next = NEXT(item_vlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_vlan,
+						  tci, "\x0f\xff")),
+	},
+	[ITEM_IPV4] = {
+		.name = "ipv4",
+		.help = "match IPv4 header",
+		.priv = PRIV_ITEM(IPV4, sizeof(struct rte_flow_item_ipv4)),
+		.next = NEXT(item_ipv4),
+		.call = parse_vc,
+	},
+	[ITEM_IPV4_TOS] = {
+		.name = "tos",
+		.help = "type of service",
+		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
+						 hdr.type_of_service)),
+	},
+	[ITEM_IPV4_TTL] = {
+		.name = "ttl",
+		.help = "time to live",
+		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
+						 hdr.time_to_live)),
+	},
+	[ITEM_IPV4_PROTO] = {
+		.name = "proto",
+		.help = "next protocol ID",
+		.next = NEXT(item_ipv4, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
+						 hdr.next_proto_id)),
+	},
+	[ITEM_IPV4_SRC] = {
+		.name = "src",
+		.help = "source address",
+		.next = NEXT(item_ipv4, NEXT_ENTRY(IPV4_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
+						 hdr.src_addr)),
+	},
+	[ITEM_IPV4_DST] = {
+		.name = "dst",
+		.help = "destination address",
+		.next = NEXT(item_ipv4, NEXT_ENTRY(IPV4_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv4,
+						 hdr.dst_addr)),
+	},
+	[ITEM_IPV6] = {
+		.name = "ipv6",
+		.help = "match IPv6 header",
+		.priv = PRIV_ITEM(IPV6, sizeof(struct rte_flow_item_ipv6)),
+		.next = NEXT(item_ipv6),
+		.call = parse_vc,
+	},
+	[ITEM_IPV6_TC] = {
+		.name = "tc",
+		.help = "traffic class",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_ipv6,
+						  hdr.vtc_flow,
+						  "\x0f\xf0\x00\x00")),
+	},
+	[ITEM_IPV6_FLOW] = {
+		.name = "flow",
+		.help = "flow label",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_ipv6,
+						  hdr.vtc_flow,
+						  "\x00\x0f\xff\xff")),
+	},
+	[ITEM_IPV6_PROTO] = {
+		.name = "proto",
+		.help = "protocol (next header)",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
+						 hdr.proto)),
+	},
+	[ITEM_IPV6_HOP] = {
+		.name = "hop",
+		.help = "hop limit",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
+						 hdr.hop_limits)),
+	},
+	[ITEM_IPV6_SRC] = {
+		.name = "src",
+		.help = "source address",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(IPV6_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
+						 hdr.src_addr)),
+	},
+	[ITEM_IPV6_DST] = {
+		.name = "dst",
+		.help = "destination address",
+		.next = NEXT(item_ipv6, NEXT_ENTRY(IPV6_ADDR), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_ipv6,
+						 hdr.dst_addr)),
+	},
+	[ITEM_ICMP] = {
+		.name = "icmp",
+		.help = "match ICMP header",
+		.priv = PRIV_ITEM(ICMP, sizeof(struct rte_flow_item_icmp)),
+		.next = NEXT(item_icmp),
+		.call = parse_vc,
+	},
+	[ITEM_ICMP_TYPE] = {
+		.name = "type",
+		.help = "ICMP packet type",
+		.next = NEXT(item_icmp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_icmp,
+						 hdr.icmp_type)),
+	},
+	[ITEM_ICMP_CODE] = {
+		.name = "code",
+		.help = "ICMP packet code",
+		.next = NEXT(item_icmp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_icmp,
+						 hdr.icmp_code)),
+	},
+	[ITEM_UDP] = {
+		.name = "udp",
+		.help = "match UDP header",
+		.priv = PRIV_ITEM(UDP, sizeof(struct rte_flow_item_udp)),
+		.next = NEXT(item_udp),
+		.call = parse_vc,
+	},
+	[ITEM_UDP_SRC] = {
+		.name = "src",
+		.help = "UDP source port",
+		.next = NEXT(item_udp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_udp,
+						 hdr.src_port)),
+	},
+	[ITEM_UDP_DST] = {
+		.name = "dst",
+		.help = "UDP destination port",
+		.next = NEXT(item_udp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_udp,
+						 hdr.dst_port)),
+	},
+	[ITEM_TCP] = {
+		.name = "tcp",
+		.help = "match TCP header",
+		.priv = PRIV_ITEM(TCP, sizeof(struct rte_flow_item_tcp)),
+		.next = NEXT(item_tcp),
+		.call = parse_vc,
+	},
+	[ITEM_TCP_SRC] = {
+		.name = "src",
+		.help = "TCP source port",
+		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
+						 hdr.src_port)),
+	},
+	[ITEM_TCP_DST] = {
+		.name = "dst",
+		.help = "TCP destination port",
+		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
+						 hdr.dst_port)),
+	},
+	[ITEM_TCP_FLAGS] = {
+		.name = "flags",
+		.help = "TCP flags",
+		.next = NEXT(item_tcp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_tcp,
+						 hdr.tcp_flags)),
+	},
+	[ITEM_SCTP] = {
+		.name = "sctp",
+		.help = "match SCTP header",
+		.priv = PRIV_ITEM(SCTP, sizeof(struct rte_flow_item_sctp)),
+		.next = NEXT(item_sctp),
+		.call = parse_vc,
+	},
+	[ITEM_SCTP_SRC] = {
+		.name = "src",
+		.help = "SCTP source port",
+		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
+						 hdr.src_port)),
+	},
+	[ITEM_SCTP_DST] = {
+		.name = "dst",
+		.help = "SCTP destination port",
+		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
+						 hdr.dst_port)),
+	},
+	[ITEM_SCTP_TAG] = {
+		.name = "tag",
+		.help = "validation tag",
+		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
+						 hdr.tag)),
+	},
+	[ITEM_SCTP_CKSUM] = {
+		.name = "cksum",
+		.help = "checksum",
+		.next = NEXT(item_sctp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_sctp,
+						 hdr.cksum)),
+	},
+	[ITEM_VXLAN] = {
+		.name = "vxlan",
+		.help = "match VXLAN header",
+		.priv = PRIV_ITEM(VXLAN, sizeof(struct rte_flow_item_vxlan)),
+		.next = NEXT(item_vxlan),
+		.call = parse_vc,
+	},
+	[ITEM_VXLAN_VNI] = {
+		.name = "vni",
+		.help = "VXLAN identifier",
+		.next = NEXT(item_vxlan, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_vxlan, vni)),
+	},
+	[ITEM_E_TAG] = {
+		.name = "e_tag",
+		.help = "match E-Tag header",
+		.priv = PRIV_ITEM(E_TAG, sizeof(struct rte_flow_item_e_tag)),
+		.next = NEXT(item_e_tag),
+		.call = parse_vc,
+	},
+	[ITEM_E_TAG_GRP_ECID_B] = {
+		.name = "grp_ecid_b",
+		.help = "GRP and E-CID base",
+		.next = NEXT(item_e_tag, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_e_tag,
+						  rsvd_grp_ecid_b,
+						  "\x3f\xff")),
+	},
+	[ITEM_NVGRE] = {
+		.name = "nvgre",
+		.help = "match NVGRE header",
+		.priv = PRIV_ITEM(NVGRE, sizeof(struct rte_flow_item_nvgre)),
+		.next = NEXT(item_nvgre),
+		.call = parse_vc,
+	},
+	[ITEM_NVGRE_TNI] = {
+		.name = "tni",
+		.help = "virtual subnet ID",
+		.next = NEXT(item_nvgre, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_nvgre, tni)),
+	},
+	[ITEM_MPLS] = {
+		.name = "mpls",
+		.help = "match MPLS header",
+		.priv = PRIV_ITEM(MPLS, sizeof(struct rte_flow_item_mpls)),
+		.next = NEXT(item_mpls),
+		.call = parse_vc,
+	},
+	[ITEM_MPLS_LABEL] = {
+		.name = "label",
+		.help = "MPLS label",
+		.next = NEXT(item_mpls, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_MASK_HTON(struct rte_flow_item_mpls,
+						  label_tc_s,
+						  "\xff\xff\xf0")),
+	},
+	[ITEM_GRE] = {
+		.name = "gre",
+		.help = "match GRE header",
+		.priv = PRIV_ITEM(GRE, sizeof(struct rte_flow_item_gre)),
+		.next = NEXT(item_gre),
+		.call = parse_vc,
+	},
+	[ITEM_GRE_PROTO] = {
+		.name = "protocol",
+		.help = "GRE protocol type",
+		.next = NEXT(item_gre, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_gre,
+						 protocol)),
+	},
+	[ITEM_FUZZY] = {
+		.name = "fuzzy",
+		.help = "fuzzy pattern match, expect faster than default",
+		.priv = PRIV_ITEM(FUZZY,
+				sizeof(struct rte_flow_item_fuzzy)),
+		.next = NEXT(item_fuzzy),
+		.call = parse_vc,
+	},
+	[ITEM_FUZZY_THRESH] = {
+		.name = "thresh",
+		.help = "match accuracy threshold",
+		.next = NEXT(item_fuzzy, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_item_fuzzy,
+					thresh)),
+	},
+	[ITEM_GTP] = {
+		.name = "gtp",
+		.help = "match GTP header",
+		.priv = PRIV_ITEM(GTP, sizeof(struct rte_flow_item_gtp)),
+		.next = NEXT(item_gtp),
+		.call = parse_vc,
+	},
+	[ITEM_GTP_TEID] = {
+		.name = "teid",
+		.help = "tunnel endpoint identifier",
+		.next = NEXT(item_gtp, NEXT_ENTRY(UNSIGNED), item_param),
+		.args = ARGS(ARGS_ENTRY_HTON(struct rte_flow_item_gtp, teid)),
+	},
+	[ITEM_GTPC] = {
+		.name = "gtpc",
+		.help = "match GTP header",
+		.priv = PRIV_ITEM(GTPC, sizeof(struct rte_flow_item_gtp)),
+		.next = NEXT(item_gtp),
+		.call = parse_vc,
+	},
+	[ITEM_GTPU] = {
+		.name = "gtpu",
+		.help = "match GTP header",
+		.priv = PRIV_ITEM(GTPU, sizeof(struct rte_flow_item_gtp)),
+		.next = NEXT(item_gtp),
+		.call = parse_vc,
+	},
+
+	/* Validate/create actions. */
+	[ACTIONS] = {
+		.name = "actions",
+		.help = "submit a list of associated actions",
+		.next = NEXT(next_action),
+		.call = parse_vc,
+	},
+	[ACTION_NEXT] = {
+		.name = "/",
+		.help = "specify next action",
+		.next = NEXT(next_action),
+	},
+	[ACTION_END] = {
+		.name = "end",
+		.help = "end list of actions",
+		.priv = PRIV_ACTION(END, 0),
+		.call = parse_vc,
+	},
+	[ACTION_VOID] = {
+		.name = "void",
+		.help = "no-op action",
+		.priv = PRIV_ACTION(VOID, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_PASSTHRU] = {
+		.name = "passthru",
+		.help = "let subsequent rule process matched packets",
+		.priv = PRIV_ACTION(PASSTHRU, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_MARK] = {
+		.name = "mark",
+		.help = "attach 32 bit value to packets",
+		.priv = PRIV_ACTION(MARK, sizeof(struct rte_flow_action_mark)),
+		.next = NEXT(action_mark),
+		.call = parse_vc,
+	},
+	[ACTION_MARK_ID] = {
+		.name = "id",
+		.help = "32 bit value to return with packets",
+		.next = NEXT(action_mark, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_mark, id)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_FLAG] = {
+		.name = "flag",
+		.help = "flag packets",
+		.priv = PRIV_ACTION(FLAG, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_QUEUE] = {
+		.name = "queue",
+		.help = "assign packets to a given queue index",
+		.priv = PRIV_ACTION(QUEUE,
+					sizeof(struct rte_flow_action_queue)),
+		.next = NEXT(action_queue),
+		.call = parse_vc,
+	},
+	[ACTION_QUEUE_INDEX] = {
+		.name = "index",
+		.help = "queue index to use",
+		.next = NEXT(action_queue, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_queue, index)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_DROP] = {
+		.name = "drop",
+		.help = "drop packets (note: passthru has priority)",
+		.priv = PRIV_ACTION(DROP, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_COUNT] = {
+		.name = "count",
+		.help = "enable counters for this rule",
+		.priv = PRIV_ACTION(COUNT, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_DUP] = {
+		.name = "dup",
+		.help = "duplicate packets to a given queue index",
+		.priv = PRIV_ACTION(DUP, sizeof(struct rte_flow_action_dup)),
+		.next = NEXT(action_dup),
+		.call = parse_vc,
+	},
+	[ACTION_DUP_INDEX] = {
+		.name = "index",
+		.help = "queue index to duplicate packets to",
+		.next = NEXT(action_dup, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_dup, index)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_RSS] = {
+		.name = "rss",
+		.help = "spread packets among several queues",
+		.priv = PRIV_ACTION(RSS, ACTION_RSS_SIZE),
+		.next = NEXT(action_rss),
+		.call = parse_vc,
+	},
+	[ACTION_RSS_QUEUES] = {
+		.name = "queues",
+		.help = "queue indices to use",
+		.next = NEXT(action_rss, NEXT_ENTRY(ACTION_RSS_QUEUE)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_RSS_QUEUE] = {
+		.name = "{queue}",
+		.help = "queue index",
+		.call = parse_vc_action_rss_queue,
+		.comp = comp_vc_action_rss_queue,
+	},
+	[ACTION_PF] = {
+		.name = "pf",
+		.help = "redirect packets to physical device function",
+		.priv = PRIV_ACTION(PF, 0),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc,
+	},
+	[ACTION_VF] = {
+		.name = "vf",
+		.help = "redirect packets to virtual device function",
+		.priv = PRIV_ACTION(VF, sizeof(struct rte_flow_action_vf)),
+		.next = NEXT(action_vf),
+		.call = parse_vc,
+	},
+	[ACTION_VF_ORIGINAL] = {
+		.name = "original",
+		.help = "use original VF ID if possible",
+		.next = NEXT(action_vf, NEXT_ENTRY(BOOLEAN)),
+		.args = ARGS(ARGS_ENTRY_BF(struct rte_flow_action_vf,
+					   original, 1)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_VF_ID] = {
+		.name = "id",
+		.help = "VF ID to redirect packets to",
+		.next = NEXT(action_vf, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_vf, id)),
+		.call = parse_vc_conf,
+	},
+	[ACTION_METER] = {
+		.name = "meter",
+		.help = "meter the directed packets at given id",
+		.priv = PRIV_ACTION(METER,
+					sizeof(struct rte_flow_action_meter)),
+		.next = NEXT(action_meter),
+		.call = parse_vc,
+	},
+	[ACTION_METER_ID] = {
+		.name = "mtr_id",
+		.help = "meter id to use",
+		.next = NEXT(action_meter, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct rte_flow_action_meter, mtr_id)),
+		.call = parse_vc_conf,
+	},
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CMDLINE_FLOW_H_ */
\ No newline at end of file
diff --git a/lib/librte_ether/rte_flow.c b/lib/librte_ether/rte_flow.c
index 6659063..3302208 100644
--- a/lib/librte_ether/rte_flow.c
+++ b/lib/librte_ether/rte_flow.c
@@ -51,13 +51,6 @@  struct rte_flow_desc_data {
 	size_t size;
 };
 
-/** Generate flow_item[] entry. */
-#define MK_FLOW_ITEM(t, s) \
-	[RTE_FLOW_ITEM_TYPE_ ## t] = { \
-		.name = # t, \
-		.size = s, \
-	}
-
 /** Information about known flow pattern items. */
 static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 	MK_FLOW_ITEM(END, 0),
@@ -83,13 +76,6 @@  static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 	MK_FLOW_ITEM(NVGRE, sizeof(struct rte_flow_item_nvgre)),
 };
 
-/** Generate flow_action[] entry. */
-#define MK_FLOW_ACTION(t, s) \
-	[RTE_FLOW_ACTION_TYPE_ ## t] = { \
-		.name = # t, \
-		.size = s, \
-	}
-
 /** Information about known flow actions. */
 static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	MK_FLOW_ACTION(END, 0),
@@ -263,7 +249,7 @@  rte_flow_error_set(struct rte_flow_error *error,
 }
 
 /** Compute storage space needed by item specification. */
-static void
+void
 flow_item_spec_size(const struct rte_flow_item *item,
 		    size_t *size, size_t *pad)
 {
@@ -291,7 +277,7 @@  flow_item_spec_size(const struct rte_flow_item *item,
 }
 
 /** Compute storage space needed by action configuration. */
-static void
+void
 flow_action_conf_size(const struct rte_flow_action *action,
 		      size_t *size, size_t *pad)
 {
diff --git a/lib/librte_ether/rte_flow.h b/lib/librte_ether/rte_flow.h
index 47c88ea..a63153e 100644
--- a/lib/librte_ether/rte_flow.h
+++ b/lib/librte_ether/rte_flow.h
@@ -852,6 +852,13 @@  struct rte_flow_item {
 	const void *mask; /**< Bit-mask applied to spec and last. */
 };
 
+/** Generate flow_item[] entry. */
+#define MK_FLOW_ITEM(t, s) \
+	[RTE_FLOW_ITEM_TYPE_ ## t] = { \
+		.name = # t, \
+		.size = s, \
+	}
+
 /**
  * Action types.
  *
@@ -1158,6 +1165,13 @@  struct rte_flow_action {
 	const void *conf; /**< Pointer to action configuration structure. */
 };
 
+/** Generate flow_action[] entry. */
+#define MK_FLOW_ACTION(t, s) \
+	[RTE_FLOW_ACTION_TYPE_ ## t] = { \
+		.name = # t, \
+		.size = s, \
+	}
+
 /**
  * Opaque type returned after successfully creating a flow.
  *
@@ -1166,6 +1180,16 @@  struct rte_flow_action {
  */
 struct rte_flow;
 
+/** Compute storage space needed by item specification. */
+void
+flow_item_spec_size(const struct rte_flow_item *item,
+		    size_t *size, size_t *pad);
+
+/** Compute storage space needed by action configuration. */
+void
+flow_action_conf_size(const struct rte_flow_action *action,
+		    size_t *size, size_t *pad);
+
 /**
  * Verbose error types.
  *