diff mbox series

[bpf-next,v2] selftests/bpf: convert test_xdp_features.sh to test_progs

Message ID 20240910-convert_xdp_tests-v2-1-a46367c9d038@bootlin.com (mailing list archive)
State New
Headers show
Series [bpf-next,v2] selftests/bpf: convert test_xdp_features.sh to test_progs | expand

Commit Message

Alexis Lothoré (eBPF Foundation) Sept. 10, 2024, 6:10 p.m. UTC
test_xdp_features.sh is a shell script allowing to test that xdp features
advertised by an interface are indeed delivered. The test works by starting
two instance of the same program, both attaching specific xdp programs to
each side of a veth link, and then make those programs manage packets and
collect stats to check whether tested XDP feature is indeed delivered or
not. However this test is not integrated in test_progs framework and so can
not run automatically in CI.

Rewrite test_xdp_features to integrate it in test_progs so it can run
automatically in CI. The main changes brought by the rewrite are the
following:
- instead of running to separated processes (each one managing either the
  tester veth or the DUT vet), run a single process
- slightly change testing direction (v0 is the tester in local namespace,
  v1 is the Device Under Test in remote namespace)
- group all tests previously managed by test_xdp_features as subtests (one
  per tested XDP feature). As a consequence, run only once some steps
  instead of once per subtest (eg: starting/stopping the udp server). On
  the contrary, make sure that each subtest properly cleans up its state
  (ie detach xdp programs, reset test stats, etc)
- since there is now a single process, get rid of the "control" tcp channel
  used to configure DUT. Configuring the DUT now only consists in switching
  to DUT network namespace and run the relevant commands
- since there is no more control channel, get rid of TLVs, keep only the
  CMD_ECHO packet type, and set it as a magic
- simplify network setup: use only ipv6 instead of both ipv4 and ipv6,
  force static neighbours instead of waiting for autoconfiguration, do not
  force gro (fetch xdp features only once xdp programs are loaded instead)

The existing XDP programs are reused, with some minor changes:
- tester and dut stats maps are converted to global variables for easier
  usage
- programs do not use TLV struct anymore but the magic replacing the echo
  command
- avoid to accidentally make tests pass: drop packets instead of forwarding
  them to userspace when they do not match the expected payload
- make sure to perform host <-> network endianness conversion on constants
  rather than packet parts

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v2:
- fix endianness management in userspace packet parsing (call htonl on
  constant rather than packet part)

The xdp_features rewrite has been tested in a x86_64 qemu environment on my
machine and in CI. In my environment, the test takes a bit less than 2s to
execute.

  # ./test_progs -a xdp_features
  #561/1   xdp_features/XDP_PASS:OK
  #561/2   xdp_features/XDP_DROP:OK
  #561/3   xdp_features/XDP_ABORTED:OK
  #561/4   xdp_features/XDP_TX:OK
  #561/5   xdp_features/XDP_REDIRECT:OK
  #561/6   xdp_features/XDP_NDO_XMIT:OK
  #561     xdp_features:OK
  Summary: 1/6 PASSED, 0 SKIPPED, 0 FAILED
---
 tools/testing/selftests/bpf/.gitignore             |   1 -
 tools/testing/selftests/bpf/Makefile               |  10 +-
 .../selftests/bpf/prog_tests/xdp_features.c        | 446 +++++++++++++
 tools/testing/selftests/bpf/progs/xdp_features.c   |  49 +-
 tools/testing/selftests/bpf/test_xdp_features.sh   | 107 ---
 tools/testing/selftests/bpf/xdp_features.c         | 718 ---------------------
 tools/testing/selftests/bpf/xdp_features.h         |  17 +-
 7 files changed, 462 insertions(+), 886 deletions(-)


---
base-commit: b9a1776000bbc3b794a398e1ef8bec3dd6ed57ab
change-id: 20240730-convert_xdp_tests-ccd66bfe33db

Best regards,

Comments

Simon Horman Sept. 11, 2024, 2:18 p.m. UTC | #1
On Tue, Sep 10, 2024 at 08:10:35PM +0200, Alexis Lothoré (eBPF Foundation) wrote:
> test_xdp_features.sh is a shell script allowing to test that xdp features
> advertised by an interface are indeed delivered. The test works by starting
> two instance of the same program, both attaching specific xdp programs to
> each side of a veth link, and then make those programs manage packets and
> collect stats to check whether tested XDP feature is indeed delivered or
> not. However this test is not integrated in test_progs framework and so can
> not run automatically in CI.
> 
> Rewrite test_xdp_features to integrate it in test_progs so it can run
> automatically in CI. The main changes brought by the rewrite are the
> following:
> - instead of running to separated processes (each one managing either the
>   tester veth or the DUT vet), run a single process
> - slightly change testing direction (v0 is the tester in local namespace,
>   v1 is the Device Under Test in remote namespace)
> - group all tests previously managed by test_xdp_features as subtests (one
>   per tested XDP feature). As a consequence, run only once some steps
>   instead of once per subtest (eg: starting/stopping the udp server). On
>   the contrary, make sure that each subtest properly cleans up its state
>   (ie detach xdp programs, reset test stats, etc)
> - since there is now a single process, get rid of the "control" tcp channel
>   used to configure DUT. Configuring the DUT now only consists in switching
>   to DUT network namespace and run the relevant commands
> - since there is no more control channel, get rid of TLVs, keep only the
>   CMD_ECHO packet type, and set it as a magic
> - simplify network setup: use only ipv6 instead of both ipv4 and ipv6,
>   force static neighbours instead of waiting for autoconfiguration, do not
>   force gro (fetch xdp features only once xdp programs are loaded instead)
> 
> The existing XDP programs are reused, with some minor changes:
> - tester and dut stats maps are converted to global variables for easier
>   usage
> - programs do not use TLV struct anymore but the magic replacing the echo
>   command
> - avoid to accidentally make tests pass: drop packets instead of forwarding
>   them to userspace when they do not match the expected payload
> - make sure to perform host <-> network endianness conversion on constants
>   rather than packet parts
> 
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

...

> diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_features.c b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
> new file mode 100644
> index 000000000000..bcb36a2d2767
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
> @@ -0,0 +1,446 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/**
> + * Test XDP features
> + *
> + * Sets up a veth pair, and for each xdp feature under test:
> + * - asks the tested interface its xdp capabilities through bpf_xdp_query
> + * - attach and run some specific programs on both interfaces to check if
> + *   announced capability is respected
> + */

Hi Alexis,

This is neither a full review nor an issue that needs to block progress.
But, FWIIW, the comment above is not a Kernel doc, yet starts with '/**'.
I suggest that it should start with '/*' instead.

...
Alexis Lothoré (eBPF Foundation) Sept. 12, 2024, 8:17 p.m. UTC | #2
Hi Simon,

On 9/11/24 16:18, Simon Horman wrote:

[...]

>> diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_features.c b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
>> new file mode 100644
>> index 000000000000..bcb36a2d2767
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
>> @@ -0,0 +1,446 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +/**
>> + * Test XDP features
>> + *
>> + * Sets up a veth pair, and for each xdp feature under test:
>> + * - asks the tested interface its xdp capabilities through bpf_xdp_query
>> + * - attach and run some specific programs on both interfaces to check if
>> + *   announced capability is respected
>> + */
> 
> Hi Alexis,
> 
> This is neither a full review nor an issue that needs to block progress.
> But, FWIIW, the comment above is not a Kernel doc, yet starts with '/**'.
> I suggest that it should start with '/*' instead.

ACK. I'll wait for more comments on the series, and add the fix to the
corresponding revision, if any.

Thanks,

Alexis
Simon Horman Sept. 13, 2024, 7:26 a.m. UTC | #3
On Thu, Sep 12, 2024 at 10:17:13PM +0200, Alexis Lothoré wrote:
> Hi Simon,
> 
> On 9/11/24 16:18, Simon Horman wrote:
> 
> [...]
> 
> >> diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_features.c b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
> >> new file mode 100644
> >> index 000000000000..bcb36a2d2767
> >> --- /dev/null
> >> +++ b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
> >> @@ -0,0 +1,446 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +
> >> +/**
> >> + * Test XDP features
> >> + *
> >> + * Sets up a veth pair, and for each xdp feature under test:
> >> + * - asks the tested interface its xdp capabilities through bpf_xdp_query
> >> + * - attach and run some specific programs on both interfaces to check if
> >> + *   announced capability is respected
> >> + */
> > 
> > Hi Alexis,
> > 
> > This is neither a full review nor an issue that needs to block progress.
> > But, FWIIW, the comment above is not a Kernel doc, yet starts with '/**'.
> > I suggest that it should start with '/*' instead.
> 
> ACK. I'll wait for more comments on the series, and add the fix to the
> corresponding revision, if any.

Thanks, much appreciated.
Martin KaFai Lau Sept. 13, 2024, 10:22 p.m. UTC | #4
On 9/10/24 11:10 AM, Alexis Lothoré (eBPF Foundation) wrote:
> test_xdp_features.sh is a shell script allowing to test that xdp features
> advertised by an interface are indeed delivered. The test works by starting
> two instance of the same program, both attaching specific xdp programs to
> each side of a veth link, and then make those programs manage packets and
> collect stats to check whether tested XDP feature is indeed delivered or
> not. However this test is not integrated in test_progs framework and so can
> not run automatically in CI.
> 
> Rewrite test_xdp_features to integrate it in test_progs so it can run
> automatically in CI. The main changes brought by the rewrite are the
> following:
> - instead of running to separated processes (each one managing either the
>    tester veth or the DUT vet), run a single process
> - slightly change testing direction (v0 is the tester in local namespace,
>    v1 is the Device Under Test in remote namespace)
> - group all tests previously managed by test_xdp_features as subtests (one
>    per tested XDP feature). As a consequence, run only once some steps
>    instead of once per subtest (eg: starting/stopping the udp server). On
>    the contrary, make sure that each subtest properly cleans up its state
>    (ie detach xdp programs, reset test stats, etc)
> - since there is now a single process, get rid of the "control" tcp channel
>    used to configure DUT. Configuring the DUT now only consists in switching
>    to DUT network namespace and run the relevant commands
> - since there is no more control channel, get rid of TLVs, keep only the
>    CMD_ECHO packet type, and set it as a magic
> - simplify network setup: use only ipv6 instead of both ipv4 and ipv6,
>    force static neighbours instead of waiting for autoconfiguration, do not
>    force gro (fetch xdp features only once xdp programs are loaded instead)
> 
> The existing XDP programs are reused, with some minor changes:
> - tester and dut stats maps are converted to global variables for easier
>    usage
> - programs do not use TLV struct anymore but the magic replacing the echo
>    command
> - avoid to accidentally make tests pass: drop packets instead of forwarding
>    them to userspace when they do not match the expected payload
> - make sure to perform host <-> network endianness conversion on constants
>    rather than packet parts
> 
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> Changes in v2:
> - fix endianness management in userspace packet parsing (call htonl on
>    constant rather than packet part)
> 
> The xdp_features rewrite has been tested in a x86_64 qemu environment on my
> machine and in CI. In my environment, the test takes a bit less than 2s to
> execute.
> 
>    # ./test_progs -a xdp_features
>    #561/1   xdp_features/XDP_PASS:OK
>    #561/2   xdp_features/XDP_DROP:OK
>    #561/3   xdp_features/XDP_ABORTED:OK
>    #561/4   xdp_features/XDP_TX:OK
>    #561/5   xdp_features/XDP_REDIRECT:OK
>    #561/6   xdp_features/XDP_NDO_XMIT:OK
>    #561     xdp_features:OK
>    Summary: 1/6 PASSED, 0 SKIPPED, 0 FAILED
> ---
>   tools/testing/selftests/bpf/.gitignore             |   1 -
>   tools/testing/selftests/bpf/Makefile               |  10 +-
>   .../selftests/bpf/prog_tests/xdp_features.c        | 446 +++++++++++++
>   tools/testing/selftests/bpf/progs/xdp_features.c   |  49 +-
>   tools/testing/selftests/bpf/test_xdp_features.sh   | 107 ---
>   tools/testing/selftests/bpf/xdp_features.c         | 718 ---------------------

 From the initial commit message of xdp_features.c, its primary usage is to test 
a physical network device that supports a certain XDP features.

iiuc, test_xdp_features.sh only uses the veth and veth will also be the only 
device tested after moving to prog_tests/xdp_features.c? It is a reasonable 
addition to test_progs for an end-to-end xdp test by using veth. However, 
test_progs will not be able to test the physical network device.

Lorenzo, is the xdp_features.c still used for device testing?
Lorenzo Bianconi Sept. 14, 2024, 9:25 a.m. UTC | #5
On Sep 13, Martin KaFai Lau wrote:
> On 9/10/24 11:10 AM, Alexis Lothoré (eBPF Foundation) wrote:
> > test_xdp_features.sh is a shell script allowing to test that xdp features
> > advertised by an interface are indeed delivered. The test works by starting
> > two instance of the same program, both attaching specific xdp programs to
> > each side of a veth link, and then make those programs manage packets and
> > collect stats to check whether tested XDP feature is indeed delivered or
> > not. However this test is not integrated in test_progs framework and so can
> > not run automatically in CI.
> > 
> > Rewrite test_xdp_features to integrate it in test_progs so it can run
> > automatically in CI. The main changes brought by the rewrite are the
> > following:
> > - instead of running to separated processes (each one managing either the
> >    tester veth or the DUT vet), run a single process
> > - slightly change testing direction (v0 is the tester in local namespace,
> >    v1 is the Device Under Test in remote namespace)
> > - group all tests previously managed by test_xdp_features as subtests (one
> >    per tested XDP feature). As a consequence, run only once some steps
> >    instead of once per subtest (eg: starting/stopping the udp server). On
> >    the contrary, make sure that each subtest properly cleans up its state
> >    (ie detach xdp programs, reset test stats, etc)
> > - since there is now a single process, get rid of the "control" tcp channel
> >    used to configure DUT. Configuring the DUT now only consists in switching
> >    to DUT network namespace and run the relevant commands
> > - since there is no more control channel, get rid of TLVs, keep only the
> >    CMD_ECHO packet type, and set it as a magic
> > - simplify network setup: use only ipv6 instead of both ipv4 and ipv6,
> >    force static neighbours instead of waiting for autoconfiguration, do not
> >    force gro (fetch xdp features only once xdp programs are loaded instead)
> > 
> > The existing XDP programs are reused, with some minor changes:
> > - tester and dut stats maps are converted to global variables for easier
> >    usage
> > - programs do not use TLV struct anymore but the magic replacing the echo
> >    command
> > - avoid to accidentally make tests pass: drop packets instead of forwarding
> >    them to userspace when they do not match the expected payload
> > - make sure to perform host <-> network endianness conversion on constants
> >    rather than packet parts
> > 
> > Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> > ---
> > Changes in v2:
> > - fix endianness management in userspace packet parsing (call htonl on
> >    constant rather than packet part)
> > 
> > The xdp_features rewrite has been tested in a x86_64 qemu environment on my
> > machine and in CI. In my environment, the test takes a bit less than 2s to
> > execute.
> > 
> >    # ./test_progs -a xdp_features
> >    #561/1   xdp_features/XDP_PASS:OK
> >    #561/2   xdp_features/XDP_DROP:OK
> >    #561/3   xdp_features/XDP_ABORTED:OK
> >    #561/4   xdp_features/XDP_TX:OK
> >    #561/5   xdp_features/XDP_REDIRECT:OK
> >    #561/6   xdp_features/XDP_NDO_XMIT:OK
> >    #561     xdp_features:OK
> >    Summary: 1/6 PASSED, 0 SKIPPED, 0 FAILED
> > ---
> >   tools/testing/selftests/bpf/.gitignore             |   1 -
> >   tools/testing/selftests/bpf/Makefile               |  10 +-
> >   .../selftests/bpf/prog_tests/xdp_features.c        | 446 +++++++++++++
> >   tools/testing/selftests/bpf/progs/xdp_features.c   |  49 +-
> >   tools/testing/selftests/bpf/test_xdp_features.sh   | 107 ---
> >   tools/testing/selftests/bpf/xdp_features.c         | 718 ---------------------

Hi Alexis.

sorry for the late reply.

> 
> From the initial commit message of xdp_features.c, its primary usage is to
> test a physical network device that supports a certain XDP features.
> 
> iiuc, test_xdp_features.sh only uses the veth and veth will also be the only
> device tested after moving to prog_tests/xdp_features.c? It is a reasonable
> addition to test_progs for an end-to-end xdp test by using veth. However,
> test_progs will not be able to test the physical network device.
> 
> Lorenzo, is the xdp_features.c still used for device testing?
> 

correct, xdp_features.c is intended to test the real xdp features supported by
the NIC under test (DUT), not just the advertised ones (iirc that was a
requisite to add xdp kernel feature support). For this reason we need two
separated processes running on the tester device and on the DUT (they are
usually two different devices). test_xdp_features.sh was just a simple test
script used to develop xdp_features.c.
What about extending xdp_features.c to integrate it in the CI?

Regards,
Lorenzo
Jakub Kicinski Sept. 14, 2024, 1:38 p.m. UTC | #6
On Sat, 14 Sep 2024 11:25:47 +0200 Lorenzo Bianconi wrote:
> On Sep 13, Martin KaFai Lau wrote:
> > test a physical network device that supports a certain XDP features.
> > 
> > iiuc, test_xdp_features.sh only uses the veth and veth will also be the only
> > device tested after moving to prog_tests/xdp_features.c? It is a reasonable
> > addition to test_progs for an end-to-end xdp test by using veth. However,
> > test_progs will not be able to test the physical network device.
> > 
> > Lorenzo, is the xdp_features.c still used for device testing?
> 
> correct, xdp_features.c is intended to test the real xdp features supported by
> the NIC under test (DUT), not just the advertised ones (iirc that was a
> requisite to add xdp kernel feature support). For this reason we need two
> separated processes running on the tester device and on the DUT (they are
> usually two different devices). test_xdp_features.sh was just a simple test
> script used to develop xdp_features.c.
> What about extending xdp_features.c to integrate it in the CI?

No preference but just to raise awareness - drivers/net's NetDrvEpEnv
class provides the setup for running tests with an endpoint.
XDP tests intended for HW would fit there pretty well.
Alexis Lothoré (eBPF Foundation) Sept. 22, 2024, 10:04 a.m. UTC | #7
Hello all, sorry for the slow feedback, I have been off last week.

On 9/14/24 15:38, Jakub Kicinski wrote:
> On Sat, 14 Sep 2024 11:25:47 +0200 Lorenzo Bianconi wrote:
>> On Sep 13, Martin KaFai Lau wrote:
>>> test a physical network device that supports a certain XDP features.
>>>
>>> iiuc, test_xdp_features.sh only uses the veth and veth will also be the only
>>> device tested after moving to prog_tests/xdp_features.c? It is a reasonable
>>> addition to test_progs for an end-to-end xdp test by using veth. However,
>>> test_progs will not be able to test the physical network device.
>>>
>>> Lorenzo, is the xdp_features.c still used for device testing?
>>
>> correct, xdp_features.c is intended to test the real xdp features supported by
>> the NIC under test (DUT), not just the advertised ones (iirc that was a
>> requisite to add xdp kernel feature support). For this reason we need two
>> separated processes running on the tester device and on the DUT (they are
>> usually two different devices). test_xdp_features.sh was just a simple test
>> script used to develop xdp_features.c.
>> What about extending xdp_features.c to integrate it in the CI?

So IIUC Lorenzo's answer, we _need_ to keep the possibility to run this test on
real hardware, and so we _need_ to still be able to run two processes, possibly
on two different machines. If that's so, indeed my rework breaks this. I have
then multiple questions/doubts before being able to rework this:
- the main goal of this rework is to be able to automatically run this test in
CI, and the resulting constraint is that it must integrate in a standalone,
already-existing c program (test_progs). I guess I can preserve the standalone
xdp_features program as it is, and make test_progs just start  it twice (on two
different veths). It would involve the following changes:
  - keep a dedicated build step for this small, standalone xdp_features.c, and
add a "controller" part in test_progs (instead of fully migrating xdp_features
program into test_progs, which  is what the current series revision does)
  - simply make the controller part create the testing network in CI, fork/start
the xdp_features program on both veths, and check return codes.
My main concern is about the possible flakiness of this whole setup (multiple
processes and tcp/udp channels involved), but if keeping the standalone version
is really needed, I can give a try. Does it sound reasonable ?
- one part of my overall goal is to clean up the tools/testing/selftests/bpf
directory from anything that is not tested automatically. What should we do with
the wrapping shell script (test_xdp_features.sh) ? Since test_progs will
automate the test with veths, I guess it is still ok to just remove it ?

> No preference but just to raise awareness - drivers/net's NetDrvEpEnv
> class provides the setup for running tests with an endpoint.
> XDP tests intended for HW would fit there pretty well.

Thanks for the hint. If we want to keep some tooling for real hw xdp features
testing, maybe we could add a small part in tools/testing/selftests/drivers/net
and make it use this NetDrvEpEnv ? Or it is a bigger hint that the whole test
about xdp features could be moved there (and then tested by net kselftests
rather than by ebpf ci specifically) ? @Lorenzo and eBPF tests maintainers, any
opinion ?

Thanks,

Alexis
Martin KaFai Lau Sept. 25, 2024, 1:37 a.m. UTC | #8
On 9/22/24 12:04 PM, Alexis Lothoré wrote:
> Hello all, sorry for the slow feedback, I have been off last week.
> 
> On 9/14/24 15:38, Jakub Kicinski wrote:
>> On Sat, 14 Sep 2024 11:25:47 +0200 Lorenzo Bianconi wrote:
>>> On Sep 13, Martin KaFai Lau wrote:
>>>> test a physical network device that supports a certain XDP features.
>>>>
>>>> iiuc, test_xdp_features.sh only uses the veth and veth will also be the only
>>>> device tested after moving to prog_tests/xdp_features.c? It is a reasonable
>>>> addition to test_progs for an end-to-end xdp test by using veth. However,
>>>> test_progs will not be able to test the physical network device.
>>>>
>>>> Lorenzo, is the xdp_features.c still used for device testing?
>>>
>>> correct, xdp_features.c is intended to test the real xdp features supported by
>>> the NIC under test (DUT), not just the advertised ones (iirc that was a
>>> requisite to add xdp kernel feature support). For this reason we need two
>>> separated processes running on the tester device and on the DUT (they are
>>> usually two different devices). test_xdp_features.sh was just a simple test
>>> script used to develop xdp_features.c.
>>> What about extending xdp_features.c to integrate it in the CI?
> 
> So IIUC Lorenzo's answer, we _need_ to keep the possibility to run this test on
> real hardware, and so we _need_ to still be able to run two processes, possibly
> on two different machines. If that's so, indeed my rework breaks this. I have
> then multiple questions/doubts before being able to rework this:
> - the main goal of this rework is to be able to automatically run this test in
> CI, and the resulting constraint is that it must integrate in a standalone,
> already-existing c program (test_progs). I guess I can preserve the standalone
> xdp_features program as it is, and make test_progs just start  it twice (on two
> different veths). It would involve the following changes:
>    - keep a dedicated build step for this small, standalone xdp_features.c, and
> add a "controller" part in test_progs (instead of fully migrating xdp_features
> program into test_progs, which  is what the current series revision does)
>    - simply make the controller part create the testing network in CI, fork/start
> the xdp_features program on both veths, and check return codes.
> My main concern is about the possible flakiness of this whole setup (multiple

The test could be simpler if it does not need to run in two separate machines.

Also, there are bpf_prog_test_run_opts() style tests that provide a device 
agnostic way to test the xdp helpers/features which should have covered most of 
the cases exercised in progs/xdp_features.c?

I am not sure which case in xdp_features.c does not have existing coverage in 
test_progs. From a quick look, it seems only BPF_MAP_TYPE_CPUMAP is missing 
(please check)? If that is the case, it may be more straight forward to add this 
one test case to the test_progs. Check if it can borrow a similar setup from 
prog_tests/test_xdp_veth.c, and then leave xdp_features.* as-is.

There are other .sh tests that could better use the test_progs migration. In 
particular the ones without existing test coverage. For non XDP related, 
test_tcp_check_syncookie.sh, test_flow_dissector.sh, and test_tc_edt.sh should 
be the good ones.

For XDP, test_xdp_meta.sh should be useful also. You may also want to check the 
test_xdp_redirect_*.sh.

> processes and tcp/udp channels involved), but if keeping the standalone version
> is really needed, I can give a try. Does it sound reasonable ?
> - one part of my overall goal is to clean up the tools/testing/selftests/bpf
> directory from anything that is not tested automatically. What should we do with
> the wrapping shell script (test_xdp_features.sh) ? Since test_progs will
> automate the test with veths, I guess it is still ok to just remove it ?
> 
>> No preference but just to raise awareness - drivers/net's NetDrvEpEnv
>> class provides the setup for running tests with an endpoint.
>> XDP tests intended for HW would fit there pretty well.
> 
> Thanks for the hint. If we want to keep some tooling for real hw xdp features
> testing, maybe we could add a small part in tools/testing/selftests/drivers/net
> and make it use this NetDrvEpEnv ? Or it is a bigger hint that the whole test
> about xdp features could be moved there (and then tested by net kselftests
> rather than by ebpf ci specifically) ? @Lorenzo and eBPF tests maintainers, any
> opinion ?
> 
> Thanks,
> 
> Alexis
>
Martin KaFai Lau Sept. 25, 2024, 8:01 p.m. UTC | #9
On 9/25/24 3:37 AM, Martin KaFai Lau wrote:
> I am not sure which case in xdp_features.c does not have existing coverage in 
> test_progs. From a quick look, it seems only BPF_MAP_TYPE_CPUMAP is missing 
> (please check)?

Re: CPUMAP, I noticed there is a xdp_(cpu)map_attach.c test but it only does 
attach test. May be something similar can be done like 
https://lore.kernel.org/bpf/20240911-devel-koalo-fix-ingress-ifindex-v4-2-5c643ae10258@linutronix.de/ 
to exercise the xdp prog that does cpumap redirect.
Alexis Lothoré (eBPF Foundation) Sept. 26, 2024, 10:14 a.m. UTC | #10
Hi Martin,

On 9/25/24 22:01, Martin KaFai Lau wrote:
> On 9/25/24 3:37 AM, Martin KaFai Lau wrote:
>> I am not sure which case in xdp_features.c does not have existing coverage in
>> test_progs. From a quick look, it seems only BPF_MAP_TYPE_CPUMAP is missing
>> (please check)?
> 
> Re: CPUMAP, I noticed there is a xdp_(cpu)map_attach.c test but it only does
> attach test. May be something similar can be done like
> https://lore.kernel.org/bpf/20240911-devel-koalo-fix-ingress-ifindex-v4-2-5c643ae10258@linutronix.de/ to exercise the xdp prog that does cpumap redirect.

Thanks for the guidance, I was not aware of the possibility to perform those
tests with BPF_PROG_RUN.
Indeed, the basic return values from XDP programs seem to be covered with this
method in other tests under prog_tests. For BPF_MAP_TYPE_CPUMAP, I can check and
try to apply the same logic as the series in progress you have pointed.

Once done, test_xdp_features.sh could be let as-is. Let me know if we should
stillmove it elsewhere instead of keeping it in selftests
(tools/bpf/xdp_features/ maybe ?)

And noted for the most relevant tests to convert after that, thanks :)

Alexis
Martin KaFai Lau Oct. 4, 2024, 4:44 a.m. UTC | #11
On 9/24/24 6:37 PM, Martin KaFai Lau wrote:
> There are other .sh tests that could better use the test_progs migration. In 
> particular the ones without existing test coverage. For non XDP related, 
> test_tcp_check_syncookie.sh, test_flow_dissector.sh, and test_tc_edt.sh should 
> be the good ones.

I just took a closer look at the test_tc_edt.* for another reason. It seems 
doing some bandwidth test which may not be a good fit (e.g. too flaky) for 
test_progs. I would leave it to the bottom of the todo list for now.

> 
> For XDP, test_xdp_meta.sh should be useful also. You may also want to check the 
> test_xdp_redirect_*.sh.
> 
>> processes and tcp/udp channels involved), but if keeping the standalone version
>> is really needed, I can give a try. Does it sound reasonable ?
>> - one part of my overall goal is to clean up the tools/testing/selftests/bpf
>> directory from anything that is not tested automatically. What should we do with
>> the wrapping shell script (test_xdp_features.sh) ? Since test_progs will
>> automate the test with veths, I guess it is still ok to just remove it ?
>>
>>> No preference but just to raise awareness - drivers/net's NetDrvEpEnv
>>> class provides the setup for running tests with an endpoint.
>>> XDP tests intended for HW would fit there pretty well.
>>
>> Thanks for the hint. If we want to keep some tooling for real hw xdp features
>> testing, maybe we could add a small part in tools/testing/selftests/drivers/net
>> and make it use this NetDrvEpEnv ? Or it is a bigger hint that the whole test
>> about xdp features could be moved there (and then tested by net kselftests
>> rather than by ebpf ci specifically) ? @Lorenzo and eBPF tests maintainers, any
>> opinion ?
>>
>> Thanks,
>>
>> Alexis
>>
> 
>
Alexis Lothoré (eBPF Foundation) Oct. 4, 2024, 6:23 a.m. UTC | #12
On 10/4/24 06:44, Martin KaFai Lau wrote:
> On 9/24/24 6:37 PM, Martin KaFai Lau wrote:
>> There are other .sh tests that could better use the test_progs migration. In
>> particular the ones without existing test coverage. For non XDP related,
>> test_tcp_check_syncookie.sh, test_flow_dissector.sh, and test_tc_edt.sh should
>> be the good ones.
> 
> I just took a closer look at the test_tc_edt.* for another reason. It seems
> doing some bandwidth test which may not be a good fit (e.g. too flaky) for
> test_progs. I would leave it to the bottom of the todo list for now.

ACK, thanks
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index e6533b3400de..93bf35213042 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -48,4 +48,3 @@  xskxceiver
 xdp_redirect_multi
 xdp_synproxy
 xdp_hw_metadata
-xdp_features
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 04716a5e43f1..db4a802c3e06 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -145,8 +145,7 @@  TEST_PROGS := test_kmod.sh \
 	test_bpftool.sh \
 	test_bpftool_metadata.sh \
 	test_doc_build.sh \
-	test_xsk.sh \
-	test_xdp_features.sh
+	test_xsk.sh
 
 TEST_PROGS_EXTENDED := with_addr.sh \
 	with_tunnels.sh ima_setup.sh verify_sig_setup.sh \
@@ -157,7 +156,7 @@  TEST_GEN_PROGS_EXTENDED = \
 	flow_dissector_load test_flow_dissector test_tcp_check_syncookie_user \
 	test_lirc_mode2_user xdping test_cpp runqslower bench bpf_testmod.ko \
 	xskxceiver xdp_redirect_multi xdp_synproxy veristat xdp_hw_metadata \
-	xdp_features bpf_test_no_cfi.ko
+	bpf_test_no_cfi.ko
 
 TEST_GEN_FILES += liburandom_read.so urandom_read sign-file uprobe_multi
 
@@ -519,7 +518,6 @@  test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton
 test_usdt.skel.h-deps := test_usdt.bpf.o test_usdt_multispec.bpf.o
 xsk_xdp_progs.skel.h-deps := xsk_xdp_progs.bpf.o
 xdp_hw_metadata.skel.h-deps := xdp_hw_metadata.bpf.o
-xdp_features.skel.h-deps := xdp_features.bpf.o
 
 LINKED_BPF_OBJS := $(foreach skel,$(LINKED_SKELS),$($(skel)-deps))
 LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.c,$(LINKED_BPF_OBJS))
@@ -787,10 +785,6 @@  $(OUTPUT)/xdp_hw_metadata: xdp_hw_metadata.c $(OUTPUT)/network_helpers.o $(OUTPU
 	$(call msg,BINARY,,$@)
 	$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
 
-$(OUTPUT)/xdp_features: xdp_features.c $(OUTPUT)/network_helpers.o $(OUTPUT)/xdp_features.skel.h | $(OUTPUT)
-	$(call msg,BINARY,,$@)
-	$(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
-
 # Make sure we are able to include and link libbpf against c++.
 $(OUTPUT)/test_cpp: test_cpp.cpp $(OUTPUT)/test_core_extern.skel.h $(BPFOBJ)
 	$(call msg,CXX,,$@)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_features.c b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
new file mode 100644
index 000000000000..bcb36a2d2767
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_features.c
@@ -0,0 +1,446 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/**
+ * Test XDP features
+ *
+ * Sets up a veth pair, and for each xdp feature under test:
+ * - asks the tested interface its xdp capabilities through bpf_xdp_query
+ * - attach and run some specific programs on both interfaces to check if
+ *   announced capability is respected
+ */
+#include <pthread.h>
+#include <linux/if_link.h>
+#include <linux/netdev.h>
+#include <linux/if_link.h>
+#include <sys/socket.h>
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "xdp_features.skel.h"
+#include "xdp_features.h"
+
+#define TESTER_VETH	"v0"
+#define TESTER_MAC	"00:11:22:33:44:55"
+#define TESTER_VETH_IPV6	"2001:db8::1"
+#define DUT_NS	"xdp_features_ns"
+#define DUT_VETH	"v1"
+#define DUT_MAC	"aa:bb:cc:dd:ee:ff"
+#define DUT_VETH_IPV6	"2001:db8::11"
+#define IP6_MASK	64
+#define LOOP_DELAY_US	10000
+#define TEST_NAME_MAX_LEN	32
+#define TEST_PACKET_COUNT	10
+
+struct test_data {
+	struct xdp_features *skel;
+	pthread_t dut_echo_thread;
+	int echo_server_sock;
+	int tester_ifindex;
+	int dut_ifindex;
+	struct sockaddr_storage tester_addr;
+	struct sockaddr_storage dut_addr;
+	bool quit_dut_echo_thread;
+};
+
+static void *run_dut_echo_thread(void *arg)
+{
+	struct test_data *t = (struct test_data *)arg;
+	__u32 magic;
+
+	while (!t->quit_dut_echo_thread) {
+		struct sockaddr_storage addr;
+		socklen_t addrlen;
+		size_t n;
+
+		n = recvfrom(t->echo_server_sock, &magic, sizeof(magic),
+			     MSG_WAITALL, (struct sockaddr *)&addr, &addrlen);
+		if (n != sizeof(magic)) {
+			usleep(LOOP_DELAY_US);
+			continue;
+		}
+
+		if (magic != htonl(CMD_ECHO))
+			continue;
+
+		/* Answer echo command with the very same message */
+		sendto(t->echo_server_sock, &magic, sizeof(magic),
+		       MSG_NOSIGNAL | MSG_CONFIRM, (struct sockaddr *)&addr,
+		       addrlen);
+	}
+	pthread_exit(NULL);
+}
+
+static int dut_start_echo_server(struct test_data *t)
+{
+	struct nstoken *token;
+	int err = 0, flags;
+
+	token = open_netns(DUT_NS);
+	if (!ASSERT_OK_PTR(token, "open dut ns"))
+		return -EINVAL;
+
+	t->echo_server_sock =
+		start_server(AF_INET6, SOCK_DGRAM, NULL, DUT_ECHO_PORT, 0);
+	if (!ASSERT_OK_FD(t->echo_server_sock, "start dut echo server")) {
+		err = t->echo_server_sock;
+		goto restore_ns;
+	}
+
+	flags = fcntl(t->echo_server_sock, F_GETFL, 0);
+	err = fcntl(t->echo_server_sock, F_SETFL, flags | O_NONBLOCK);
+	if (!ASSERT_OK(err, "set non-blocking socket"))
+		goto close_server;
+
+	err = pthread_create(&t->dut_echo_thread, NULL, run_dut_echo_thread, t);
+	if (!ASSERT_OK(err, "start dut echo thread"))
+		goto close_server;
+
+	close_netns(token);
+	return 0;
+
+close_server:
+	close(t->echo_server_sock);
+restore_ns:
+	close_netns(token);
+	return err;
+}
+
+static void dut_stop_echo_server(struct test_data *t)
+{
+	struct nstoken *token;
+
+	token = open_netns(DUT_NS);
+	if (!ASSERT_OK_PTR(token, "open dut ns"))
+		return;
+
+	t->quit_dut_echo_thread = true;
+	pthread_join(t->dut_echo_thread, NULL);
+
+	close(t->echo_server_sock);
+	close_netns(token);
+}
+
+static int dut_attach_xdp_prog(struct test_data *t, int flags,
+			       enum netdev_xdp_act drv_feature,
+			       enum xdp_action action)
+{
+	struct bpf_program *prog;
+	unsigned int key = 0;
+	int err, fd = 0;
+
+	if (drv_feature == NETDEV_XDP_ACT_NDO_XMIT) {
+		struct bpf_devmap_val entry = { .ifindex = t->dut_ifindex };
+
+		err = bpf_map__update_elem(t->skel->maps.dev_map, &key,
+					   sizeof(key), &entry, sizeof(entry),
+					   0);
+		if (!ASSERT_OK(err, "update dev map"))
+			return err;
+
+		fd = bpf_program__fd(t->skel->progs.xdp_do_redirect_cpumap);
+		action = XDP_REDIRECT;
+	}
+
+	switch (action) {
+	case XDP_TX:
+		prog = t->skel->progs.xdp_do_tx;
+		break;
+	case XDP_DROP:
+		prog = t->skel->progs.xdp_do_drop;
+		break;
+	case XDP_ABORTED:
+		prog = t->skel->progs.xdp_do_aborted;
+		break;
+	case XDP_PASS:
+		prog = t->skel->progs.xdp_do_pass;
+		break;
+	case XDP_REDIRECT: {
+		struct bpf_cpumap_val entry = {
+			.qsize = 4096,
+			.bpf_prog.fd = fd,
+		};
+
+		err = bpf_map__update_elem(t->skel->maps.cpu_map, &key,
+					   sizeof(key), &entry, sizeof(entry),
+					   0);
+		if (!ASSERT_OK(err, "update cpu map"))
+			return err;
+
+		prog = t->skel->progs.xdp_do_redirect;
+		break;
+	}
+	default:
+		return -ENOTSUP;
+	}
+
+	err = bpf_xdp_attach(t->dut_ifindex, bpf_program__fd(prog), flags,
+			     NULL);
+	ASSERT_OK(err, "attach xdp prog to dut");
+	return err;
+}
+
+static int dut_start_test(struct test_data *t, enum netdev_xdp_act drv_feature,
+			  enum xdp_action action)
+{
+	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
+	struct nstoken *token = open_netns(DUT_NS);
+	int err;
+
+	if (!ASSERT_OK_PTR(token, "open dut ns"))
+		return -EINVAL;
+
+	err = dut_attach_xdp_prog(t, flags, drv_feature, action);
+	ASSERT_OK(err, "attach xdp program to dut");
+	close_netns(token);
+
+	return err;
+}
+
+static void dut_stop_test(struct test_data *t)
+{
+	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
+	struct nstoken *token = open_netns(DUT_NS);
+
+	if (!ASSERT_OK_PTR(token, "open dut ns"))
+		return;
+
+	bpf_xdp_detach(t->dut_ifindex, flags, NULL);
+	close_netns(token);
+}
+
+static int dut_get_xdp_features(struct test_data *t, __u64 *xdp_features)
+{
+	struct nstoken *token = open_netns(DUT_NS);
+	int err;
+
+	if (!ASSERT_OK_PTR(token, "open dut ns"))
+		return -EINVAL;
+
+	LIBBPF_OPTS(bpf_xdp_query_opts, opts);
+	err = bpf_xdp_query(t->dut_ifindex, XDP_FLAGS_DRV_MODE, &opts);
+	close_netns(token);
+
+	if (ASSERT_OK(err, "get dut interface xdp features"))
+		*xdp_features = opts.feature_flags;
+
+	return err;
+}
+
+static int send_echo_msg(struct test_data *t)
+{
+	__u32 magic = htonl(CMD_ECHO);
+	int sockfd, n;
+
+	sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
+	if (!ASSERT_OK_FD(sockfd, "open tester socket"))
+		return sockfd;
+
+	n = sendto(sockfd, &magic, sizeof(magic), MSG_NOSIGNAL | MSG_CONFIRM,
+		   (struct sockaddr *)&t->dut_addr,
+		   sizeof(struct sockaddr_storage));
+	close(sockfd);
+
+	return n == sizeof(magic) ? 0 : -EINVAL;
+}
+
+static bool tester_collect_detected_cap(struct test_data *t,
+					enum netdev_xdp_act drv_feature,
+					enum xdp_action action)
+{
+	if (!t->skel->bss->dut_stats)
+		return false;
+
+	if (drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
+		return t->skel->bss->tester_stats > 0;
+
+	switch (action) {
+	case XDP_PASS:
+	case XDP_TX:
+	case XDP_REDIRECT:
+		return t->skel->bss->tester_stats > 0;
+	case XDP_DROP:
+	case XDP_ABORTED:
+		return t->skel->bss->tester_stats == 0;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static void reset_test_stats(struct test_data *t,
+			     struct sockaddr_storage *tester_addr,
+			     struct sockaddr_storage *dut_addr)
+{
+	t->skel->bss->tester_stats = 0;
+	t->skel->bss->dut_stats = 0;
+}
+
+static int setup_network(struct test_data *t)
+{
+	struct nstoken *token;
+	int err;
+
+	err = make_sockaddr(AF_INET6, DUT_VETH_IPV6, DUT_ECHO_PORT,
+			    &t->dut_addr, NULL);
+	if (!ASSERT_OK(err, "dut data addr"))
+		return -1;
+
+	err = make_sockaddr(AF_INET6, TESTER_VETH_IPV6, 0, &t->tester_addr,
+			    NULL);
+	if (!ASSERT_OK(err, "tester addr"))
+		return -1;
+
+	/* Create interfaces and testing namespace */
+	SYS(fail, "ip netns add %s", DUT_NS);
+	SYS(cleanup_ns,
+	    "ip link add %s address %s type veth peer name %s netns %s address %s",
+	    TESTER_VETH, TESTER_MAC, DUT_VETH, DUT_NS, DUT_MAC);
+
+	/* Configure tester side in local namespace */
+	SYS(cleanup_interfaces, "ip a add %s/%d nodad dev %s", TESTER_VETH_IPV6,
+	    IP6_MASK, TESTER_VETH);
+	SYS(cleanup_interfaces, "ip link set %s up", TESTER_VETH);
+	SYS(cleanup_interfaces,
+	    "ethtool -K %s tx-checksumming off > /dev/null 2>&1", TESTER_VETH);
+	SYS(cleanup_interfaces, "ip neigh add %s dev %s lladdr %s",
+	    DUT_VETH_IPV6, TESTER_VETH, DUT_MAC);
+	t->tester_ifindex = if_nametoindex(TESTER_VETH);
+	if (!ASSERT_NEQ(t->tester_ifindex, 0,
+			"get tester veth interface index"))
+		goto cleanup_interfaces;
+
+	/* Configure dut side in remote namespace */
+	token = open_netns(DUT_NS);
+	if (!ASSERT_OK_PTR(token, "switch to dut ns"))
+		goto cleanup_interfaces;
+	SYS(restore_ns, "ip link set %s up", DUT_VETH);
+	SYS(restore_ns, "ip a add %s/%d nodad dev %s", DUT_VETH_IPV6, IP6_MASK,
+	    DUT_VETH);
+	SYS(restore_ns, "ethtool -K %s tx-checksumming off > /dev/null 2>&1",
+	    DUT_VETH);
+	SYS(restore_ns, "ip neigh add %s dev %s lladdr %s", TESTER_VETH_IPV6,
+	    DUT_VETH, TESTER_MAC);
+	t->dut_ifindex = if_nametoindex(DUT_VETH);
+	if (!ASSERT_NEQ(t->dut_ifindex, 0, "get dut veth interface index"))
+		goto restore_ns;
+	close_netns(token);
+
+	return 0;
+
+restore_ns:
+	close_netns(token);
+cleanup_interfaces:
+	SYS_NOFAIL("ip link del %s", TESTER_VETH);
+cleanup_ns:
+	SYS_NOFAIL("ip netns del %s", DUT_NS);
+fail:
+	return 1;
+}
+
+static void cleanup_network(void)
+{
+	SYS_NOFAIL("ip netns del %s", DUT_NS);
+	SYS_NOFAIL("ip link del %s", TESTER_VETH);
+}
+
+static int tester_run(char *name, struct test_data *t,
+		      enum netdev_xdp_act drv_feature, enum xdp_action action)
+{
+	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
+	unsigned long long advertised_feature;
+
+	char test_name[TEST_NAME_MAX_LEN];
+	struct bpf_program *prog;
+	int i, err = -EINVAL;
+	bool detected_cap;
+
+	if (drv_feature == NETDEV_XDP_ACT_NDO_XMIT || action == XDP_TX)
+		prog = t->skel->progs.xdp_tester_check_tx;
+	else
+		prog = t->skel->progs.xdp_tester_check_rx;
+
+	err = bpf_xdp_attach(t->tester_ifindex, bpf_program__fd(prog), flags,
+			     NULL);
+	if (!ASSERT_OK(err, "attach xdp program to tester"))
+		goto out;
+
+	err = dut_start_test(t, drv_feature, action);
+	if (!ASSERT_OK(err, "send CMD_START to DUT"))
+		goto out;
+
+	err = dut_get_xdp_features(t, &advertised_feature);
+	if (!ASSERT_OK(err, "get tester XDP capabilities"))
+		goto out;
+
+	for (i = 0; i < TEST_PACKET_COUNT; i++) {
+		err = send_echo_msg(t);
+		if (!ASSERT_OK(err, "send echo message"))
+			goto out;
+
+		usleep(LOOP_DELAY_US);
+	}
+	dut_stop_test(t);
+
+	detected_cap = tester_collect_detected_cap(t, drv_feature, action);
+
+	snprintf(test_name, TEST_NAME_MAX_LEN, "%s advertised capabilities",
+		 name);
+	ASSERT_EQ(advertised_feature & drv_feature, drv_feature, test_name);
+	snprintf(test_name, TEST_NAME_MAX_LEN, "%s detected capabilities",
+		 name);
+	ASSERT_TRUE(detected_cap, test_name);
+out:
+	reset_test_stats(t, &t->tester_addr, &t->dut_addr);
+	bpf_xdp_detach(t->tester_ifindex, flags, NULL);
+	return err < 0 ? err : 0;
+}
+
+void serial_test_xdp_features(void)
+{
+	struct test_data t = { 0 };
+
+	if (!ASSERT_OK(setup_network(&t), "setup network"))
+		return;
+
+	t.skel = xdp_features__open();
+	if (!ASSERT_OK_PTR(t.skel, "open skel"))
+		goto cleanup_network;
+	t.skel->rodata->tester_addr =
+		((struct sockaddr_in6 *)&t.tester_addr)->sin6_addr;
+	t.skel->rodata->dut_addr =
+		((struct sockaddr_in6 *)&t.dut_addr)->sin6_addr;
+	if (!ASSERT_OK(xdp_features__load(t.skel), "load progs"))
+		goto cleanup_progs;
+	if (!ASSERT_OK(xdp_features__attach(t.skel), "attach progs"))
+		goto cleanup_progs;
+
+	if (!ASSERT_OK(dut_start_echo_server(&t), "start DUT main thread"))
+		goto cleanup_progs;
+
+	if (test__start_subtest("XDP_PASS"))
+		tester_run("XDP_PASS", &t, NETDEV_XDP_ACT_BASIC, XDP_PASS);
+
+	if (test__start_subtest("XDP_DROP"))
+		tester_run("XDP_DROP", &t, NETDEV_XDP_ACT_BASIC, XDP_DROP);
+
+	if (test__start_subtest("XDP_ABORTED"))
+		tester_run("XDP_ABORTED", &t, NETDEV_XDP_ACT_BASIC,
+			   XDP_ABORTED);
+
+	if (test__start_subtest("XDP_TX"))
+		tester_run("XDP_TX", &t, NETDEV_XDP_ACT_BASIC, XDP_TX);
+
+	if (test__start_subtest("XDP_REDIRECT"))
+		tester_run("XDP_REDIRECT", &t, NETDEV_XDP_ACT_REDIRECT,
+			   XDP_REDIRECT);
+
+	if (test__start_subtest("XDP_NDO_XMIT"))
+		tester_run("XDP_NDO_XMIT", &t, NETDEV_XDP_ACT_NDO_XMIT, 0);
+
+	dut_stop_echo_server(&t);
+
+cleanup_progs:
+	xdp_features__destroy(t.skel);
+cleanup_network:
+	cleanup_network();
+}
diff --git a/tools/testing/selftests/bpf/progs/xdp_features.c b/tools/testing/selftests/bpf/progs/xdp_features.c
index 67424084a38a..acfadd711921 100644
--- a/tools/testing/selftests/bpf/progs/xdp_features.c
+++ b/tools/testing/selftests/bpf/progs/xdp_features.c
@@ -30,19 +30,9 @@  struct xdp_cpumap_stats {
 	unsigned int drop;
 };
 
-struct {
-	__uint(type, BPF_MAP_TYPE_ARRAY);
-	__type(key, __u32);
-	__type(value, __u32);
-	__uint(max_entries, 1);
-} stats SEC(".maps");
+__u32 tester_stats;
+__u32 dut_stats;
 
-struct {
-	__uint(type, BPF_MAP_TYPE_ARRAY);
-	__type(key, __u32);
-	__type(value, __u32);
-	__uint(max_entries, 1);
-} dut_stats SEC(".maps");
 
 struct {
 	__uint(type, BPF_MAP_TYPE_CPUMAP);
@@ -67,7 +57,7 @@  xdp_process_echo_packet(struct xdp_md *xdp, bool dut)
 	void *data_end = (void *)(long)xdp->data_end;
 	void *data = (void *)(long)xdp->data;
 	struct ethhdr *eh = data;
-	struct tlv_hdr *tlv;
+	__u32 *magic;
 	struct udphdr *uh;
 	__be16 port;
 
@@ -124,28 +114,23 @@  xdp_process_echo_packet(struct xdp_md *xdp, bool dut)
 	if (port != bpf_htons(DUT_ECHO_PORT))
 		return -EINVAL;
 
-	tlv = (struct tlv_hdr *)(uh + 1);
-	if (tlv + 1 > data_end)
+	magic = (__u32 *)(uh + 1);
+	if (magic + 1 > data_end)
 		return -EINVAL;
 
-	return bpf_htons(tlv->type) == CMD_ECHO ? 0 : -EINVAL;
+	return *magic == bpf_htonl(CMD_ECHO) ? 0 : -EINVAL;
 }
 
 static __always_inline int
 xdp_update_stats(struct xdp_md *xdp, bool tx, bool dut)
 {
-	__u32 *val, key = 0;
-
 	if (xdp_process_echo_packet(xdp, tx))
 		return -EINVAL;
 
 	if (dut)
-		val = bpf_map_lookup_elem(&dut_stats, &key);
+		__sync_add_and_fetch(&dut_stats, 1);
 	else
-		val = bpf_map_lookup_elem(&stats, &key);
-
-	if (val)
-		__sync_add_and_fetch(val, 1);
+		__sync_add_and_fetch(&tester_stats, 1);
 
 	return 0;
 }
@@ -204,7 +189,7 @@  int xdp_do_tx(struct xdp_md *xdp)
 	__u8 tmp_mac[ETH_ALEN];
 
 	if (xdp_update_stats(xdp, true, true))
-		return XDP_PASS;
+		return XDP_DROP;
 
 	__builtin_memcpy(tmp_mac, eh->h_source, ETH_ALEN);
 	__builtin_memcpy(eh->h_source, eh->h_dest, ETH_ALEN);
@@ -217,7 +202,7 @@  SEC("xdp")
 int xdp_do_redirect(struct xdp_md *xdp)
 {
 	if (xdp_process_echo_packet(xdp, true))
-		return XDP_PASS;
+		return XDP_DROP;
 
 	return bpf_redirect_map(&cpu_map, 0, 0);
 }
@@ -226,11 +211,7 @@  SEC("tp_btf/xdp_exception")
 int BPF_PROG(xdp_exception, const struct net_device *dev,
 	     const struct bpf_prog *xdp, __u32 act)
 {
-	__u32 *val, key = 0;
-
-	val = bpf_map_lookup_elem(&dut_stats, &key);
-	if (val)
-		__sync_add_and_fetch(val, 1);
+	__sync_add_and_fetch(&dut_stats, 1);
 
 	return 0;
 }
@@ -239,11 +220,7 @@  SEC("tp_btf/xdp_cpumap_kthread")
 int BPF_PROG(tp_xdp_cpumap_kthread, int map_id, unsigned int processed,
 	     unsigned int drops, int sched, struct xdp_cpumap_stats *xdp_stats)
 {
-	__u32 *val, key = 0;
-
-	val = bpf_map_lookup_elem(&dut_stats, &key);
-	if (val)
-		__sync_add_and_fetch(val, 1);
+	__sync_add_and_fetch(&dut_stats, 1);
 
 	return 0;
 }
@@ -256,7 +233,7 @@  int xdp_do_redirect_cpumap(struct xdp_md *xdp)
 	__u8 tmp_mac[ETH_ALEN];
 
 	if (xdp_process_echo_packet(xdp, true))
-		return XDP_PASS;
+		return XDP_DROP;
 
 	__builtin_memcpy(tmp_mac, eh->h_source, ETH_ALEN);
 	__builtin_memcpy(eh->h_source, eh->h_dest, ETH_ALEN);
diff --git a/tools/testing/selftests/bpf/test_xdp_features.sh b/tools/testing/selftests/bpf/test_xdp_features.sh
deleted file mode 100755
index 0aa71c4455c0..000000000000
--- a/tools/testing/selftests/bpf/test_xdp_features.sh
+++ /dev/null
@@ -1,107 +0,0 @@ 
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-
-readonly NS="ns1-$(mktemp -u XXXXXX)"
-readonly V0_IP4=10.10.0.11
-readonly V1_IP4=10.10.0.1
-readonly V0_IP6=2001:db8::11
-readonly V1_IP6=2001:db8::1
-
-ret=1
-
-setup() {
-	{
-		ip netns add ${NS}
-
-		ip link add v1 type veth peer name v0 netns ${NS}
-
-		ip link set v1 up
-		ip addr add $V1_IP4/24 dev v1
-		ip addr add $V1_IP6/64 nodad dev v1
-		ip -n ${NS} link set dev v0 up
-		ip -n ${NS} addr add $V0_IP4/24 dev v0
-		ip -n ${NS} addr add $V0_IP6/64 nodad dev v0
-
-		# Enable XDP mode and disable checksum offload
-		ethtool -K v1 gro on
-		ethtool -K v1 tx-checksumming off
-		ip netns exec ${NS} ethtool -K v0 gro on
-		ip netns exec ${NS} ethtool -K v0 tx-checksumming off
-	} > /dev/null 2>&1
-}
-
-cleanup() {
-	ip link del v1 2> /dev/null
-	ip netns del ${NS} 2> /dev/null
-	[ "$(pidof xdp_features)" = "" ] || kill $(pidof xdp_features) 2> /dev/null
-}
-
-wait_for_dut_server() {
-	while sleep 1; do
-		ss -tlp | grep -q xdp_features
-		[ $? -eq 0 ] && break
-	done
-}
-
-test_xdp_features() {
-	setup
-
-	## XDP_PASS
-	./xdp_features -f XDP_PASS -D $V1_IP6 -T $V0_IP6 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_PASS \
-					   -D $V1_IP6 -C $V1_IP6 \
-					   -T $V0_IP6 v0
-	[ $? -ne 0 ] && exit
-
-	## XDP_DROP
-	./xdp_features -f XDP_DROP -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_DROP \
-					   -D ::ffff:$V1_IP4 \
-					   -C ::ffff:$V1_IP4 \
-					   -T ::ffff:$V0_IP4 v0
-	[ $? -ne 0 ] && exit
-
-	## XDP_ABORTED
-	./xdp_features -f XDP_ABORTED -D $V1_IP6 -T $V0_IP6 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_ABORTED \
-					   -D $V1_IP6 -C $V1_IP6 \
-					   -T $V0_IP6 v0
-	[ $? -ne 0 ] && exit
-
-	## XDP_TX
-	./xdp_features -f XDP_TX -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_TX \
-					   -D ::ffff:$V1_IP4 \
-					   -C ::ffff:$V1_IP4 \
-					   -T ::ffff:$V0_IP4 v0
-	[ $? -ne 0 ] && exit
-
-	## XDP_REDIRECT
-	./xdp_features -f XDP_REDIRECT -D $V1_IP6 -T $V0_IP6 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_REDIRECT \
-					   -D $V1_IP6 -C $V1_IP6 \
-					   -T $V0_IP6 v0
-	[ $? -ne 0 ] && exit
-
-	## XDP_NDO_XMIT
-	./xdp_features -f XDP_NDO_XMIT -D ::ffff:$V1_IP4 -T ::ffff:$V0_IP4 v1 &
-	wait_for_dut_server
-	ip netns exec ${NS} ./xdp_features -t -f XDP_NDO_XMIT \
-					   -D ::ffff:$V1_IP4 \
-					   -C ::ffff:$V1_IP4 \
-					   -T ::ffff:$V0_IP4 v0
-	ret=$?
-	cleanup
-}
-
-set -e
-trap cleanup 2 3 6 9
-
-test_xdp_features
-
-exit $ret
diff --git a/tools/testing/selftests/bpf/xdp_features.c b/tools/testing/selftests/bpf/xdp_features.c
deleted file mode 100644
index 595c79141cf3..000000000000
--- a/tools/testing/selftests/bpf/xdp_features.c
+++ /dev/null
@@ -1,718 +0,0 @@ 
-// SPDX-License-Identifier: GPL-2.0
-#include <uapi/linux/bpf.h>
-#include <uapi/linux/netdev.h>
-#include <linux/if_link.h>
-#include <signal.h>
-#include <argp.h>
-#include <net/if.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <unistd.h>
-#include <arpa/inet.h>
-#include <bpf/bpf.h>
-#include <bpf/libbpf.h>
-#include <pthread.h>
-
-#include <network_helpers.h>
-
-#include "xdp_features.skel.h"
-#include "xdp_features.h"
-
-#define RED(str)	"\033[0;31m" str "\033[0m"
-#define GREEN(str)	"\033[0;32m" str "\033[0m"
-#define YELLOW(str)	"\033[0;33m" str "\033[0m"
-
-static struct env {
-	bool verbosity;
-	char ifname[IF_NAMESIZE];
-	int ifindex;
-	bool is_tester;
-	struct {
-		enum netdev_xdp_act drv_feature;
-		enum xdp_action action;
-	} feature;
-	struct sockaddr_storage dut_ctrl_addr;
-	struct sockaddr_storage dut_addr;
-	struct sockaddr_storage tester_addr;
-} env;
-
-#define BUFSIZE		128
-
-void test__fail(void) { /* for network_helpers.c */ }
-
-static int libbpf_print_fn(enum libbpf_print_level level,
-			   const char *format, va_list args)
-{
-	if (level == LIBBPF_DEBUG && !env.verbosity)
-		return 0;
-	return vfprintf(stderr, format, args);
-}
-
-static volatile bool exiting;
-
-static void sig_handler(int sig)
-{
-	exiting = true;
-}
-
-const char *argp_program_version = "xdp-features 0.0";
-const char argp_program_doc[] =
-"XDP features detection application.\n"
-"\n"
-"XDP features application checks the XDP advertised features match detected ones.\n"
-"\n"
-"USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n"
-"\n"
-"dut-data-ip, tester-data-ip, dut-ctrl-ip: IPv6 or IPv4-mapped-IPv6 addresses;\n"
-"\n"
-"XDP features\n:"
-"- XDP_PASS\n"
-"- XDP_DROP\n"
-"- XDP_ABORTED\n"
-"- XDP_REDIRECT\n"
-"- XDP_NDO_XMIT\n"
-"- XDP_TX\n";
-
-static const struct argp_option opts[] = {
-	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
-	{ "tester", 't', NULL, 0, "Tester mode" },
-	{ "feature", 'f', "XDP-FEATURE", 0, "XDP feature to test" },
-	{ "dut_data_ip", 'D', "DUT-DATA-IP", 0, "DUT IP data channel" },
-	{ "dut_ctrl_ip", 'C', "DUT-CTRL-IP", 0, "DUT IP control channel" },
-	{ "tester_data_ip", 'T', "TESTER-DATA-IP", 0, "Tester IP data channel" },
-	{},
-};
-
-static int get_xdp_feature(const char *arg)
-{
-	if (!strcmp(arg, "XDP_PASS")) {
-		env.feature.action = XDP_PASS;
-		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
-	} else if (!strcmp(arg, "XDP_DROP")) {
-		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
-		env.feature.action = XDP_DROP;
-	} else if (!strcmp(arg, "XDP_ABORTED")) {
-		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
-		env.feature.action = XDP_ABORTED;
-	} else if (!strcmp(arg, "XDP_TX")) {
-		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
-		env.feature.action = XDP_TX;
-	} else if (!strcmp(arg, "XDP_REDIRECT")) {
-		env.feature.drv_feature = NETDEV_XDP_ACT_REDIRECT;
-		env.feature.action = XDP_REDIRECT;
-	} else if (!strcmp(arg, "XDP_NDO_XMIT")) {
-		env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
-	} else {
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-static char *get_xdp_feature_str(void)
-{
-	switch (env.feature.action) {
-	case XDP_PASS:
-		return YELLOW("XDP_PASS");
-	case XDP_DROP:
-		return YELLOW("XDP_DROP");
-	case XDP_ABORTED:
-		return YELLOW("XDP_ABORTED");
-	case XDP_TX:
-		return YELLOW("XDP_TX");
-	case XDP_REDIRECT:
-		return YELLOW("XDP_REDIRECT");
-	default:
-		break;
-	}
-
-	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
-		return YELLOW("XDP_NDO_XMIT");
-
-	return "";
-}
-
-static error_t parse_arg(int key, char *arg, struct argp_state *state)
-{
-	switch (key) {
-	case 'v':
-		env.verbosity = true;
-		break;
-	case 't':
-		env.is_tester = true;
-		break;
-	case 'f':
-		if (get_xdp_feature(arg) < 0) {
-			fprintf(stderr, "Invalid xdp feature: %s\n", arg);
-			argp_usage(state);
-			return ARGP_ERR_UNKNOWN;
-		}
-		break;
-	case 'D':
-		if (make_sockaddr(AF_INET6, arg, DUT_ECHO_PORT,
-				  &env.dut_addr, NULL)) {
-			fprintf(stderr,
-				"Invalid address assigned to the Device Under Test: %s\n",
-				arg);
-			return ARGP_ERR_UNKNOWN;
-		}
-		break;
-	case 'C':
-		if (make_sockaddr(AF_INET6, arg, DUT_CTRL_PORT,
-				  &env.dut_ctrl_addr, NULL)) {
-			fprintf(stderr,
-				"Invalid address assigned to the Device Under Test: %s\n",
-				arg);
-			return ARGP_ERR_UNKNOWN;
-		}
-		break;
-	case 'T':
-		if (make_sockaddr(AF_INET6, arg, 0, &env.tester_addr, NULL)) {
-			fprintf(stderr,
-				"Invalid address assigned to the Tester device: %s\n",
-				arg);
-			return ARGP_ERR_UNKNOWN;
-		}
-		break;
-	case ARGP_KEY_ARG:
-		errno = 0;
-		if (strlen(arg) >= IF_NAMESIZE) {
-			fprintf(stderr, "Invalid device name: %s\n", arg);
-			argp_usage(state);
-			return ARGP_ERR_UNKNOWN;
-		}
-
-		env.ifindex = if_nametoindex(arg);
-		if (!env.ifindex)
-			env.ifindex = strtoul(arg, NULL, 0);
-		if (!env.ifindex || !if_indextoname(env.ifindex, env.ifname)) {
-			fprintf(stderr,
-				"Bad interface index or name (%d): %s\n",
-				errno, strerror(errno));
-			argp_usage(state);
-			return ARGP_ERR_UNKNOWN;
-		}
-		break;
-	default:
-		return ARGP_ERR_UNKNOWN;
-	}
-
-	return 0;
-}
-
-static const struct argp argp = {
-	.options = opts,
-	.parser = parse_arg,
-	.doc = argp_program_doc,
-};
-
-static void set_env_default(void)
-{
-	env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
-	env.feature.action = -EINVAL;
-	env.ifindex = -ENODEV;
-	strcpy(env.ifname, "unknown");
-	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_CTRL_PORT,
-		      &env.dut_ctrl_addr, NULL);
-	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_ECHO_PORT,
-		      &env.dut_addr, NULL);
-	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", 0, &env.tester_addr, NULL);
-}
-
-static void *dut_echo_thread(void *arg)
-{
-	unsigned char buf[sizeof(struct tlv_hdr)];
-	int sockfd = *(int *)arg;
-
-	while (!exiting) {
-		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
-		struct sockaddr_storage addr;
-		socklen_t addrlen;
-		size_t n;
-
-		n = recvfrom(sockfd, buf, sizeof(buf), MSG_WAITALL,
-			     (struct sockaddr *)&addr, &addrlen);
-		if (n != ntohs(tlv->len))
-			continue;
-
-		if (ntohs(tlv->type) != CMD_ECHO)
-			continue;
-
-		sendto(sockfd, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM,
-		       (struct sockaddr *)&addr, addrlen);
-	}
-
-	pthread_exit((void *)0);
-	close(sockfd);
-
-	return NULL;
-}
-
-static int dut_run_echo_thread(pthread_t *t, int *sockfd)
-{
-	int err;
-
-	sockfd = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
-					DUT_ECHO_PORT, 0, 1);
-	if (!sockfd) {
-		fprintf(stderr,
-			"Failed creating data UDP socket on device %s\n",
-			env.ifname);
-		return -errno;
-	}
-
-	/* start echo channel */
-	err = pthread_create(t, NULL, dut_echo_thread, sockfd);
-	if (err) {
-		fprintf(stderr,
-			"Failed creating data UDP thread on device %s: %s\n",
-			env.ifname, strerror(-err));
-		free_fds(sockfd, 1);
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
-{
-	enum xdp_action action = env.feature.action;
-	struct bpf_program *prog;
-	unsigned int key = 0;
-	int err, fd = 0;
-
-	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) {
-		struct bpf_devmap_val entry = {
-			.ifindex = env.ifindex,
-		};
-
-		err = bpf_map__update_elem(skel->maps.dev_map,
-					   &key, sizeof(key),
-					   &entry, sizeof(entry), 0);
-		if (err < 0)
-			return err;
-
-		fd = bpf_program__fd(skel->progs.xdp_do_redirect_cpumap);
-		action = XDP_REDIRECT;
-	}
-
-	switch (action) {
-	case XDP_TX:
-		prog = skel->progs.xdp_do_tx;
-		break;
-	case XDP_DROP:
-		prog = skel->progs.xdp_do_drop;
-		break;
-	case XDP_ABORTED:
-		prog = skel->progs.xdp_do_aborted;
-		break;
-	case XDP_PASS:
-		prog = skel->progs.xdp_do_pass;
-		break;
-	case XDP_REDIRECT: {
-		struct bpf_cpumap_val entry = {
-			.qsize = 2048,
-			.bpf_prog.fd = fd,
-		};
-
-		err = bpf_map__update_elem(skel->maps.cpu_map,
-					   &key, sizeof(key),
-					   &entry, sizeof(entry), 0);
-		if (err < 0)
-			return err;
-
-		prog = skel->progs.xdp_do_redirect;
-		break;
-	}
-	default:
-		return -EINVAL;
-	}
-
-	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
-	if (err)
-		fprintf(stderr, "Failed attaching XDP program to device %s\n",
-			env.ifname);
-	return err;
-}
-
-static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
-		    size_t val_size)
-{
-	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
-	size_t len;
-
-	len = recv(sockfd, buf, bufsize, 0);
-	if (len != ntohs(tlv->len) || len < sizeof(*tlv))
-		return -EINVAL;
-
-	if (val) {
-		len -= sizeof(*tlv);
-		if (len > val_size)
-			return -ENOMEM;
-
-		memcpy(val, tlv->data, len);
-	}
-
-	return 0;
-}
-
-static int dut_run(struct xdp_features *skel)
-{
-	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
-	int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
-	struct sockaddr_storage ctrl_addr;
-	pthread_t dut_thread = 0;
-	socklen_t addrlen;
-
-	sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
-					DUT_CTRL_PORT, 0, 1);
-	if (!sockfd) {
-		fprintf(stderr,
-			"Failed creating control socket on device %s\n", env.ifname);
-		return -errno;
-	}
-
-	ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen);
-	if (ctrl_sockfd < 0) {
-		fprintf(stderr,
-			"Failed accepting connections on device %s control socket\n",
-			env.ifname);
-		free_fds(sockfd, 1);
-		return -errno;
-	}
-
-	/* CTRL loop */
-	while (!exiting) {
-		unsigned char buf[BUFSIZE] = {};
-		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
-
-		err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
-		if (err)
-			continue;
-
-		switch (ntohs(tlv->type)) {
-		case CMD_START: {
-			if (state == CMD_START)
-				continue;
-
-			state = CMD_START;
-			/* Load the XDP program on the DUT */
-			err = dut_attach_xdp_prog(skel, flags);
-			if (err)
-				goto out;
-
-			err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
-			if (err < 0)
-				goto out;
-
-			tlv->type = htons(CMD_ACK);
-			tlv->len = htons(sizeof(*tlv));
-			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
-			if (err < 0)
-				goto end_thread;
-			break;
-		}
-		case CMD_STOP:
-			if (state != CMD_START)
-				break;
-
-			state = CMD_STOP;
-
-			exiting = true;
-			bpf_xdp_detach(env.ifindex, flags, NULL);
-
-			tlv->type = htons(CMD_ACK);
-			tlv->len = htons(sizeof(*tlv));
-			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
-			goto end_thread;
-		case CMD_GET_XDP_CAP: {
-			LIBBPF_OPTS(bpf_xdp_query_opts, opts);
-			unsigned long long val;
-			size_t n;
-
-			err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE,
-					    &opts);
-			if (err) {
-				fprintf(stderr,
-					"Failed querying XDP cap for device %s\n",
-					env.ifname);
-				goto end_thread;
-			}
-
-			tlv->type = htons(CMD_ACK);
-			n = sizeof(*tlv) + sizeof(opts.feature_flags);
-			tlv->len = htons(n);
-
-			val = htobe64(opts.feature_flags);
-			memcpy(tlv->data, &val, sizeof(val));
-
-			err = send(ctrl_sockfd, buf, n, 0);
-			if (err < 0)
-				goto end_thread;
-			break;
-		}
-		case CMD_GET_STATS: {
-			unsigned int key = 0, val;
-			size_t n;
-
-			err = bpf_map__lookup_elem(skel->maps.dut_stats,
-						   &key, sizeof(key),
-						   &val, sizeof(val), 0);
-			if (err) {
-				fprintf(stderr,
-					"bpf_map_lookup_elem failed (%d)\n", err);
-				goto end_thread;
-			}
-
-			tlv->type = htons(CMD_ACK);
-			n = sizeof(*tlv) + sizeof(val);
-			tlv->len = htons(n);
-
-			val = htonl(val);
-			memcpy(tlv->data, &val, sizeof(val));
-
-			err = send(ctrl_sockfd, buf, n, 0);
-			if (err < 0)
-				goto end_thread;
-			break;
-		}
-		default:
-			break;
-		}
-	}
-
-end_thread:
-	pthread_join(dut_thread, NULL);
-out:
-	bpf_xdp_detach(env.ifindex, flags, NULL);
-	close(ctrl_sockfd);
-	free_fds(sockfd, 1);
-
-	return err;
-}
-
-static bool tester_collect_detected_cap(struct xdp_features *skel,
-					unsigned int dut_stats)
-{
-	unsigned int err, key = 0, val;
-
-	if (!dut_stats)
-		return false;
-
-	err = bpf_map__lookup_elem(skel->maps.stats, &key, sizeof(key),
-				   &val, sizeof(val), 0);
-	if (err) {
-		fprintf(stderr, "bpf_map_lookup_elem failed (%d)\n", err);
-		return false;
-	}
-
-	switch (env.feature.action) {
-	case XDP_PASS:
-	case XDP_TX:
-	case XDP_REDIRECT:
-		return val > 0;
-	case XDP_DROP:
-	case XDP_ABORTED:
-		return val == 0;
-	default:
-		break;
-	}
-
-	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
-		return val > 0;
-
-	return false;
-}
-
-static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val,
-			     size_t val_size)
-{
-	unsigned char buf[BUFSIZE] = {};
-	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
-	int err;
-
-	tlv->type = htons(cmd);
-	tlv->len = htons(sizeof(*tlv));
-
-	err = send(sockfd, buf, sizeof(*tlv), 0);
-	if (err < 0)
-		return err;
-
-	err = recv_msg(sockfd, buf, BUFSIZE, val, val_size);
-	if (err < 0)
-		return err;
-
-	return ntohs(tlv->type) == CMD_ACK ? 0 : -EINVAL;
-}
-
-static int send_echo_msg(void)
-{
-	unsigned char buf[sizeof(struct tlv_hdr)];
-	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
-	int sockfd, n;
-
-	sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
-	if (sockfd < 0) {
-		fprintf(stderr,
-			"Failed creating data UDP socket on device %s\n",
-			env.ifname);
-		return -errno;
-	}
-
-	tlv->type = htons(CMD_ECHO);
-	tlv->len = htons(sizeof(*tlv));
-
-	n = sendto(sockfd, buf, sizeof(*tlv), MSG_NOSIGNAL | MSG_CONFIRM,
-		   (struct sockaddr *)&env.dut_addr, sizeof(env.dut_addr));
-	close(sockfd);
-
-	return n == ntohs(tlv->len) ? 0 : -EINVAL;
-}
-
-static int tester_run(struct xdp_features *skel)
-{
-	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
-	unsigned long long advertised_feature;
-	struct bpf_program *prog;
-	unsigned int stats;
-	int i, err, sockfd;
-	bool detected_cap;
-
-	sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-	if (sockfd < 0) {
-		fprintf(stderr,
-			"Failed creating tester service control socket\n");
-		return -errno;
-	}
-
-	if (settimeo(sockfd, 1000) < 0)
-		return -EINVAL;
-
-	err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
-		      sizeof(env.dut_ctrl_addr));
-	if (err) {
-		fprintf(stderr,
-			"Failed connecting to the Device Under Test control socket\n");
-		return -errno;
-	}
-
-	err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
-				sizeof(advertised_feature));
-	if (err < 0) {
-		close(sockfd);
-		return err;
-	}
-
-	advertised_feature = be64toh(advertised_feature);
-
-	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT ||
-	    env.feature.action == XDP_TX)
-		prog = skel->progs.xdp_tester_check_tx;
-	else
-		prog = skel->progs.xdp_tester_check_rx;
-
-	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
-	if (err) {
-		fprintf(stderr, "Failed attaching XDP program to device %s\n",
-			env.ifname);
-		goto out;
-	}
-
-	err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);
-	if (err)
-		goto out;
-
-	for (i = 0; i < 10 && !exiting; i++) {
-		err = send_echo_msg();
-		if (err < 0)
-			goto out;
-
-		sleep(1);
-	}
-
-	err = send_and_recv_msg(sockfd, CMD_GET_STATS, &stats, sizeof(stats));
-	if (err)
-		goto out;
-
-	/* stop the test */
-	err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 0);
-	/* send a new echo message to wake echo thread of the dut */
-	send_echo_msg();
-
-	detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
-
-	fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
-		detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
-		env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
-							     : RED("NOT ADVERTISED"));
-out:
-	bpf_xdp_detach(env.ifindex, flags, NULL);
-	close(sockfd);
-	return err < 0 ? err : 0;
-}
-
-int main(int argc, char **argv)
-{
-	struct xdp_features *skel;
-	int err;
-
-	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
-	libbpf_set_print(libbpf_print_fn);
-
-	signal(SIGINT, sig_handler);
-	signal(SIGTERM, sig_handler);
-
-	set_env_default();
-
-	/* Parse command line arguments */
-	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
-	if (err)
-		return err;
-
-	if (env.ifindex < 0) {
-		fprintf(stderr, "Invalid device name %s\n", env.ifname);
-		return -ENODEV;
-	}
-
-	/* Load and verify BPF application */
-	skel = xdp_features__open();
-	if (!skel) {
-		fprintf(stderr, "Failed to open and load BPF skeleton\n");
-		return -EINVAL;
-	}
-
-	skel->rodata->tester_addr =
-		((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr;
-	skel->rodata->dut_addr =
-		((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr;
-
-	/* Load & verify BPF programs */
-	err = xdp_features__load(skel);
-	if (err) {
-		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
-		goto cleanup;
-	}
-
-	err = xdp_features__attach(skel);
-	if (err) {
-		fprintf(stderr, "Failed to attach BPF skeleton\n");
-		goto cleanup;
-	}
-
-	if (env.is_tester) {
-		/* Tester */
-		fprintf(stdout, "Starting tester service on device %s\n",
-			env.ifname);
-		err = tester_run(skel);
-	} else {
-		/* DUT */
-		fprintf(stdout, "Starting test on device %s\n", env.ifname);
-		err = dut_run(skel);
-	}
-
-cleanup:
-	xdp_features__destroy(skel);
-
-	return err < 0 ? -err : 0;
-}
diff --git a/tools/testing/selftests/bpf/xdp_features.h b/tools/testing/selftests/bpf/xdp_features.h
index 2670c541713b..2fa7a2e156c7 100644
--- a/tools/testing/selftests/bpf/xdp_features.h
+++ b/tools/testing/selftests/bpf/xdp_features.h
@@ -1,20 +1,5 @@ 
 /* SPDX-License-Identifier: GPL-2.0 */
 
-/* test commands */
-enum test_commands {
-	CMD_STOP,		/* CMD */
-	CMD_START,		/* CMD */
-	CMD_ECHO,		/* CMD */
-	CMD_ACK,		/* CMD + data */
-	CMD_GET_XDP_CAP,	/* CMD */
-	CMD_GET_STATS,		/* CMD */
-};
+#define CMD_ECHO	0x4543484F /* 4 bytes magic */
 
-#define DUT_CTRL_PORT	12345
 #define DUT_ECHO_PORT	12346
-
-struct tlv_hdr {
-	__be16 type;
-	__be16 len;
-	__u8 data[];
-};