diff mbox series

[bpf-next,V9,7/7] bpf/selftests: tests using bpf_check_mtu BPF-helper

Message ID 160822601093.3481451.9135115478358953965.stgit@firesoul (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series bpf: New approach for BPF MTU handling | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for bpf-next
netdev/subject_prefix success Link
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/kdoc success Errors and warnings before: 2 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch fail CHECK: Please don't use multiple blank lines ERROR: code indent should use tabs where possible ERROR: do not initialise globals to 0 ERROR: do not use assignment in if condition WARNING: Comparisons should place the constant on the right side of the test WARNING: Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: please, no spaces at the start of a line
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/header_inline success Link
netdev/stable success Stable not CCed

Commit Message

Jesper Dangaard Brouer Dec. 17, 2020, 5:26 p.m. UTC
Adding selftest for BPF-helper bpf_check_mtu(). Making sure
it can be used from both XDP and TC.

Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com>
---
 tools/testing/selftests/bpf/prog_tests/check_mtu.c |  204 ++++++++++++++++++++
 tools/testing/selftests/bpf/progs/test_check_mtu.c |  196 +++++++++++++++++++
 2 files changed, 400 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/check_mtu.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_check_mtu.c

Comments

Jesper Dangaard Brouer Dec. 18, 2020, 10:53 a.m. UTC | #1
On Thu, 17 Dec 2020 18:26:50 +0100
Jesper Dangaard Brouer <brouer@redhat.com> wrote:

> Adding selftest for BPF-helper bpf_check_mtu(). Making sure
> it can be used from both XDP and TC.
> 
> Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com>
> ---
>  tools/testing/selftests/bpf/prog_tests/check_mtu.c |  204 ++++++++++++++++++++
>  tools/testing/selftests/bpf/progs/test_check_mtu.c |  196 +++++++++++++++++++
>  2 files changed, 400 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/check_mtu.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_check_mtu.c

Will send V10 as I have an error in this selftests

> diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
> new file mode 100644
> index 000000000000..b5d0c3a9abe8
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
[...]
> +static void test_check_mtu_run_xdp(struct test_check_mtu *skel,
> +				   struct bpf_program *prog,
> +				   __u32 mtu_expect)
> +{
> +	const char *prog_name = bpf_program__name(prog);
> +	int retval_expect = XDP_PASS;
> +	__u32 mtu_result = 0;
> +	char buf[256];
> +	int err;
> +
> +	struct bpf_prog_test_run_attr tattr = {
> +		.repeat = 1,
> +		.data_in = &pkt_v4,
> +		.data_size_in = sizeof(pkt_v4),
> +		.data_out = buf,
> +		.data_size_out = sizeof(buf),
> +		.prog_fd = bpf_program__fd(prog),
> +	};
> +
> +	memset(buf, 0, sizeof(buf));
> +
> +	err = bpf_prog_test_run_xattr(&tattr);
> +	CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
                               ^^^^^^^^^^^
You/I cannot use the check "errno != 0" here, as something else could
have set it earlier.


> +		   "prog_name:%s (err %d errno %d retval %d)\n",
> +		   prog_name, err, errno, tattr.retval);
> +
> +        CHECK(tattr.retval != retval_expect, "retval",
> +	      "progname:%s unexpected retval=%d expected=%d\n",
> +	      prog_name, tattr.retval, retval_expect);
> +
> +	/* Extract MTU that BPF-prog got */
> +	mtu_result = skel->bss->global_bpf_mtu_xdp;
> +	CHECK(mtu_result != mtu_expect, "MTU-compare-user",
> +	      "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);
> +}


> +static void test_check_mtu_run_tc(struct test_check_mtu *skel,
> +				  struct bpf_program *prog,
> +				  __u32 mtu_expect)
> +{
[...]
> +	err = bpf_prog_test_run_xattr(&tattr);
> +	CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
> +		   "prog_name:%s (err %d errno %d retval %d)\n",
> +		   prog_name, err, errno, tattr.retval);

Same issue here.
Andrii Nakryiko Dec. 18, 2020, 8:13 p.m. UTC | #2
On Thu, Dec 17, 2020 at 9:30 AM Jesper Dangaard Brouer
<brouer@redhat.com> wrote:
>
> Adding selftest for BPF-helper bpf_check_mtu(). Making sure
> it can be used from both XDP and TC.
>
> Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com>
> ---
>  tools/testing/selftests/bpf/prog_tests/check_mtu.c |  204 ++++++++++++++++++++
>  tools/testing/selftests/bpf/progs/test_check_mtu.c |  196 +++++++++++++++++++
>  2 files changed, 400 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/check_mtu.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_check_mtu.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
> new file mode 100644
> index 000000000000..b5d0c3a9abe8
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
> @@ -0,0 +1,204 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2020 Jesper Dangaard Brouer */
> +
> +#include <linux/if_link.h> /* before test_progs.h, avoid bpf_util.h redefines */
> +
> +#include <test_progs.h>
> +#include "test_check_mtu.skel.h"
> +#include <network_helpers.h>
> +
> +#include <stdlib.h>
> +#include <inttypes.h>
> +
> +#define IFINDEX_LO 1
> +
> +static __u32 duration; /* Hint: needed for CHECK macro */
> +
> +static int read_mtu_device_lo(void)
> +{
> +       const char *filename = "/sys/devices/virtual/net/lo/mtu";
> +       char buf[11] = {};
> +       int value;
> +       int fd;
> +
> +       fd = open(filename, 0, O_RDONLY);
> +       if (fd == -1)
> +               return -1;
> +
> +       if (read(fd, buf, sizeof(buf)) == -1)

close fd missing here?

> +               return -2;
> +       close(fd);
> +
> +       value = strtoimax(buf, NULL, 10);
> +       if (errno == ERANGE)
> +               return -3;
> +
> +       return value;
> +}
> +
> +static void test_check_mtu_xdp_attach(struct bpf_program *prog)
> +{
> +       int err = 0;
> +       int fd;
> +
> +       fd = bpf_program__fd(prog);
> +       err = bpf_set_link_xdp_fd(IFINDEX_LO, fd, XDP_FLAGS_SKB_MODE);
> +       if (CHECK(err, "XDP-attach", "failed"))
> +               return;
> +
> +       bpf_set_link_xdp_fd(IFINDEX_LO, -1, 0);

can you please use bpf_link-based bpf_program__attach_xdp() which will
provide auto-cleanup in case of crash?

also check that it succeeded?

> +}
> +
> +static void test_check_mtu_run_xdp(struct test_check_mtu *skel,
> +                                  struct bpf_program *prog,
> +                                  __u32 mtu_expect)
> +{
> +       const char *prog_name = bpf_program__name(prog);
> +       int retval_expect = XDP_PASS;
> +       __u32 mtu_result = 0;
> +       char buf[256];
> +       int err;
> +
> +       struct bpf_prog_test_run_attr tattr = {
> +               .repeat = 1,
> +               .data_in = &pkt_v4,
> +               .data_size_in = sizeof(pkt_v4),
> +               .data_out = buf,
> +               .data_size_out = sizeof(buf),
> +               .prog_fd = bpf_program__fd(prog),
> +       };

nit: it's a variable declaration, so keep it all in one block. There
is also opts-based variant, which might be good to use here instead.

> +
> +       memset(buf, 0, sizeof(buf));

char buf[256] = {}; would make this unnecessary


> +
> +       err = bpf_prog_test_run_xattr(&tattr);
> +       CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
> +                  "prog_name:%s (err %d errno %d retval %d)\n",
> +                  prog_name, err, errno, tattr.retval);
> +
> +        CHECK(tattr.retval != retval_expect, "retval",

whitespaces are off?

> +             "progname:%s unexpected retval=%d expected=%d\n",
> +             prog_name, tattr.retval, retval_expect);
> +
> +       /* Extract MTU that BPF-prog got */
> +       mtu_result = skel->bss->global_bpf_mtu_xdp;
> +       CHECK(mtu_result != mtu_expect, "MTU-compare-user",
> +             "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);

There is nicer ASSERT_EQ() macro for such cases:

ASSERT_EQ(mtu_result, mtu_expect, "MTU-compare-user"); it will format
sensible error message automatically

> +}
> +

[...]

> +       char buf[256];
> +       int err;
> +
> +       struct bpf_prog_test_run_attr tattr = {
> +               .repeat = 1,
> +               .data_in = &pkt_v4,
> +               .data_size_in = sizeof(pkt_v4),
> +               .data_out = buf,
> +               .data_size_out = sizeof(buf),
> +               .prog_fd = bpf_program__fd(prog),
> +       };
> +
> +       memset(buf, 0, sizeof(buf));
> +

same as above

> +       err = bpf_prog_test_run_xattr(&tattr);
> +       CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
> +                  "prog_name:%s (err %d errno %d retval %d)\n",
> +                  prog_name, err, errno, tattr.retval);
> +
> +        CHECK(tattr.retval != retval_expect, "retval",

same :)

> +             "progname:%s unexpected retval=%d expected=%d\n",
> +             prog_name, tattr.retval, retval_expect);
> +
> +       /* Extract MTU that BPF-prog got */
> +       mtu_result = skel->bss->global_bpf_mtu_tc;
> +       CHECK(mtu_result != mtu_expect, "MTU-compare-user",
> +             "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);
> +}
> +
> +

[...]

> +
> +void test_check_mtu(void)
> +{
> +       struct test_check_mtu *skel;
> +       __u32 mtu_lo;
> +
> +       skel = test_check_mtu__open_and_load();
> +       if (CHECK(!skel, "open and load skel", "failed"))
> +               return; /* Exit if e.g. helper unknown to kernel */
> +
> +       if (test__start_subtest("bpf_check_mtu XDP-attach"))
> +               test_check_mtu_xdp_attach(skel->progs.xdp_use_helper_basic);
> +
> +       test_check_mtu__destroy(skel);

here it's not clear why you instantiate skeleton outside of
test_check_mtu_xdp_attach() subtest. Can you please move it in? It
will keep this failure local to that specific subtest, not the entire
test. And is just cleaner, of course.

> +
> +       mtu_lo = read_mtu_device_lo();
> +       if (CHECK(mtu_lo < 0, "reading MTU value", "failed (err:%d)", mtu_lo))

ASSERT_OK() could be used here

> +               return;
> +
> +       if (test__start_subtest("bpf_check_mtu XDP-run"))
> +               test_check_mtu_xdp(mtu_lo, 0);
> +
> +       if (test__start_subtest("bpf_check_mtu XDP-run ifindex-lookup"))
> +               test_check_mtu_xdp(mtu_lo, IFINDEX_LO);
> +
> +       if (test__start_subtest("bpf_check_mtu TC-run"))
> +               test_check_mtu_tc(mtu_lo, 0);
> +
> +       if (test__start_subtest("bpf_check_mtu TC-run ifindex-lookup"))
> +               test_check_mtu_tc(mtu_lo, IFINDEX_LO);
> +}

[...]

> +
> +       global_bpf_mtu_tc = mtu_len;
> +       return retval;
> +}
> +
> +SEC("classifier")

nice use of the same SEC()'tion BPF programs!


> +int tc_minus_delta(struct __sk_buff *ctx)
> +{
> +       int retval = BPF_OK; /* Expected retval on successful test */
> +       __u32 ifindex = GLOBAL_USER_IFINDEX;
> +       __u32 skb_len = ctx->len;
> +       __u32 mtu_len = 0;
> +       int delta;
> +
> +       /* Boarderline test case: Minus delta exceeding packet length allowed */
> +       delta = -((skb_len - ETH_HLEN) + 1);
> +
> +       /* Minus length (adjusted via delta) still pass MTU check, other helpers
> +        * are responsible for catching this, when doing actual size adjust
> +        */
> +       if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))
> +               retval = BPF_DROP;
> +
> +       global_bpf_mtu_xdp = mtu_len;
> +       return retval;
> +}
>
>
Jesper Dangaard Brouer Dec. 22, 2020, 3:30 p.m. UTC | #3
On Fri, 18 Dec 2020 12:13:45 -0800
Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote:

> On Thu, Dec 17, 2020 at 9:30 AM Jesper Dangaard Brouer
> <brouer@redhat.com> wrote:
> >
> > Adding selftest for BPF-helper bpf_check_mtu(). Making sure
> > it can be used from both XDP and TC.
> >
> > Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com>
> > ---
> >  tools/testing/selftests/bpf/prog_tests/check_mtu.c |  204 ++++++++++++++++++++
> >  tools/testing/selftests/bpf/progs/test_check_mtu.c |  196 +++++++++++++++++++
> >  2 files changed, 400 insertions(+)
> >  create mode 100644 tools/testing/selftests/bpf/prog_tests/check_mtu.c
> >  create mode 100644 tools/testing/selftests/bpf/progs/test_check_mtu.c
> >
> > diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
> > new file mode 100644
> > index 000000000000..b5d0c3a9abe8
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
> > @@ -0,0 +1,204 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright (c) 2020 Jesper Dangaard Brouer */
> > +
> > +#include <linux/if_link.h> /* before test_progs.h, avoid bpf_util.h redefines */
> > +
> > +#include <test_progs.h>
> > +#include "test_check_mtu.skel.h"
> > +#include <network_helpers.h>
> > +
> > +#include <stdlib.h>
> > +#include <inttypes.h>
> > +
> > +#define IFINDEX_LO 1
> > +
> > +static __u32 duration; /* Hint: needed for CHECK macro */
> > +
> > +static int read_mtu_device_lo(void)
> > +{
> > +       const char *filename = "/sys/devices/virtual/net/lo/mtu";

I will change this to: /sys/class/net/lo/mtu

> > +       char buf[11] = {};
> > +       int value;
> > +       int fd;
> > +
> > +       fd = open(filename, 0, O_RDONLY);
> > +       if (fd == -1)
> > +               return -1;
> > +
> > +       if (read(fd, buf, sizeof(buf)) == -1)  
> 
> close fd missing here?

ack, fixed.

> > +               return -2;
> > +       close(fd);
> > +
> > +       value = strtoimax(buf, NULL, 10);
> > +       if (errno == ERANGE)
> > +               return -3;
> > +
> > +       return value;
> > +}
> > +
> > +static void test_check_mtu_xdp_attach(struct bpf_program *prog)
> > +{
> > +       int err = 0;
> > +       int fd;
> > +
> > +       fd = bpf_program__fd(prog);
> > +       err = bpf_set_link_xdp_fd(IFINDEX_LO, fd, XDP_FLAGS_SKB_MODE);
> > +       if (CHECK(err, "XDP-attach", "failed"))
> > +               return;
> > +
> > +       bpf_set_link_xdp_fd(IFINDEX_LO, -1, 0);  
> 
> can you please use bpf_link-based bpf_program__attach_xdp() which will
> provide auto-cleanup in case of crash?

Sure, that will be good for me to learn.

> also check that it succeeded?
> 
> > +}
> > +
> > +static void test_check_mtu_run_xdp(struct test_check_mtu *skel,
> > +                                  struct bpf_program *prog,
> > +                                  __u32 mtu_expect)
> > +{
> > +       const char *prog_name = bpf_program__name(prog);
> > +       int retval_expect = XDP_PASS;
> > +       __u32 mtu_result = 0;
> > +       char buf[256];
> > +       int err;
> > +
> > +       struct bpf_prog_test_run_attr tattr = {
> > +               .repeat = 1,
> > +               .data_in = &pkt_v4,
> > +               .data_size_in = sizeof(pkt_v4),
> > +               .data_out = buf,
> > +               .data_size_out = sizeof(buf),
> > +               .prog_fd = bpf_program__fd(prog),
> > +       };  
> 
> nit: it's a variable declaration, so keep it all in one block. There
> is also opts-based variant, which might be good to use here instead.
> 
> > +
> > +       memset(buf, 0, sizeof(buf));  
> 
> char buf[256] = {}; would make this unnecessary

ok.

> 
> > +
> > +       err = bpf_prog_test_run_xattr(&tattr);
> > +       CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
> > +                  "prog_name:%s (err %d errno %d retval %d)\n",
> > +                  prog_name, err, errno, tattr.retval);
> > +
> > +        CHECK(tattr.retval != retval_expect, "retval",  
> 
> whitespaces are off?

Yes, I noticed with scripts/checkpatch.pl.  And there are a couple
more, that I've already fixed.


> > +             "progname:%s unexpected retval=%d expected=%d\n",
> > +             prog_name, tattr.retval, retval_expect);
> > +
> > +       /* Extract MTU that BPF-prog got */
> > +       mtu_result = skel->bss->global_bpf_mtu_xdp;
> > +       CHECK(mtu_result != mtu_expect, "MTU-compare-user",
> > +             "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);  
> 
> There is nicer ASSERT_EQ() macro for such cases:
> 
> ASSERT_EQ(mtu_result, mtu_expect, "MTU-compare-user"); it will format
> sensible error message automatically

Nice simplification :-)

> 
> > +}
> > +  
> 
> [...]

[... same ...] 

> [...]
> 
> > +
> > +void test_check_mtu(void)
> > +{
> > +       struct test_check_mtu *skel;
> > +       __u32 mtu_lo;
> > +
> > +       skel = test_check_mtu__open_and_load();
> > +       if (CHECK(!skel, "open and load skel", "failed"))
> > +               return; /* Exit if e.g. helper unknown to kernel */
> > +
> > +       if (test__start_subtest("bpf_check_mtu XDP-attach"))
> > +               test_check_mtu_xdp_attach(skel->progs.xdp_use_helper_basic);
> > +
> > +       test_check_mtu__destroy(skel);  
> 
> here it's not clear why you instantiate skeleton outside of
> test_check_mtu_xdp_attach() subtest. Can you please move it in? It
> will keep this failure local to that specific subtest, not the entire
> test. And is just cleaner, of course.

Sure will "move it in".  The intent was to fail the entire test if this
failed, but it is more clean to "move it in".

> > +
> > +       mtu_lo = read_mtu_device_lo();
> > +       if (CHECK(mtu_lo < 0, "reading MTU value", "failed (err:%d)", mtu_lo))  
> 
> ASSERT_OK() could be used here
> 
> > +               return;
> > +
> > +       if (test__start_subtest("bpf_check_mtu XDP-run"))
> > +               test_check_mtu_xdp(mtu_lo, 0);
> > +
> > +       if (test__start_subtest("bpf_check_mtu XDP-run ifindex-lookup"))
> > +               test_check_mtu_xdp(mtu_lo, IFINDEX_LO);
> > +
> > +       if (test__start_subtest("bpf_check_mtu TC-run"))
> > +               test_check_mtu_tc(mtu_lo, 0);
> > +
> > +       if (test__start_subtest("bpf_check_mtu TC-run ifindex-lookup"))
> > +               test_check_mtu_tc(mtu_lo, IFINDEX_LO);
> > +}  
> 
> [...]
> 
> > +
> > +       global_bpf_mtu_tc = mtu_len;
> > +       return retval;
> > +}
> > +
> > +SEC("classifier")  
> 
> nice use of the same SEC()'tion BPF programs!
> 
> 
> > +int tc_minus_delta(struct __sk_buff *ctx)
> > +{
> > +       int retval = BPF_OK; /* Expected retval on successful test */
> > +       __u32 ifindex = GLOBAL_USER_IFINDEX;
> > +       __u32 skb_len = ctx->len;
> > +       __u32 mtu_len = 0;
> > +       int delta;
> > +
> > +       /* Boarderline test case: Minus delta exceeding packet length allowed */
> > +       delta = -((skb_len - ETH_HLEN) + 1);
> > +
> > +       /* Minus length (adjusted via delta) still pass MTU check, other helpers
> > +        * are responsible for catching this, when doing actual size adjust
> > +        */
> > +       if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))
> > +               retval = BPF_DROP;
> > +
> > +       global_bpf_mtu_xdp = mtu_len;
> > +       return retval;
> > +}
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
new file mode 100644
index 000000000000..b5d0c3a9abe8
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c
@@ -0,0 +1,204 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Jesper Dangaard Brouer */
+
+#include <linux/if_link.h> /* before test_progs.h, avoid bpf_util.h redefines */
+
+#include <test_progs.h>
+#include "test_check_mtu.skel.h"
+#include <network_helpers.h>
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#define IFINDEX_LO 1
+
+static __u32 duration; /* Hint: needed for CHECK macro */
+
+static int read_mtu_device_lo(void)
+{
+	const char *filename = "/sys/devices/virtual/net/lo/mtu";
+	char buf[11] = {};
+	int value;
+	int fd;
+
+	fd = open(filename, 0, O_RDONLY);
+	if (fd == -1)
+		return -1;
+
+	if (read(fd, buf, sizeof(buf)) == -1)
+		return -2;
+	close(fd);
+
+	value = strtoimax(buf, NULL, 10);
+	if (errno == ERANGE)
+		return -3;
+
+	return value;
+}
+
+static void test_check_mtu_xdp_attach(struct bpf_program *prog)
+{
+	int err = 0;
+	int fd;
+
+	fd = bpf_program__fd(prog);
+	err = bpf_set_link_xdp_fd(IFINDEX_LO, fd, XDP_FLAGS_SKB_MODE);
+	if (CHECK(err, "XDP-attach", "failed"))
+		return;
+
+	bpf_set_link_xdp_fd(IFINDEX_LO, -1, 0);
+}
+
+static void test_check_mtu_run_xdp(struct test_check_mtu *skel,
+				   struct bpf_program *prog,
+				   __u32 mtu_expect)
+{
+	const char *prog_name = bpf_program__name(prog);
+	int retval_expect = XDP_PASS;
+	__u32 mtu_result = 0;
+	char buf[256];
+	int err;
+
+	struct bpf_prog_test_run_attr tattr = {
+		.repeat = 1,
+		.data_in = &pkt_v4,
+		.data_size_in = sizeof(pkt_v4),
+		.data_out = buf,
+		.data_size_out = sizeof(buf),
+		.prog_fd = bpf_program__fd(prog),
+	};
+
+	memset(buf, 0, sizeof(buf));
+
+	err = bpf_prog_test_run_xattr(&tattr);
+	CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
+		   "prog_name:%s (err %d errno %d retval %d)\n",
+		   prog_name, err, errno, tattr.retval);
+
+        CHECK(tattr.retval != retval_expect, "retval",
+	      "progname:%s unexpected retval=%d expected=%d\n",
+	      prog_name, tattr.retval, retval_expect);
+
+	/* Extract MTU that BPF-prog got */
+	mtu_result = skel->bss->global_bpf_mtu_xdp;
+	CHECK(mtu_result != mtu_expect, "MTU-compare-user",
+	      "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);
+}
+
+static void test_check_mtu_xdp(__u32 mtu, __u32 ifindex)
+{
+	struct test_check_mtu *skel;
+	int err;
+
+	skel = test_check_mtu__open();
+	if (CHECK(!skel, "skel_open", "failed"))
+		return;
+
+	/* Update "constants" in BPF-prog *BEFORE* libbpf load */
+	skel->rodata->GLOBAL_USER_MTU = mtu;
+	skel->rodata->GLOBAL_USER_IFINDEX = ifindex;
+
+	err = test_check_mtu__load(skel);
+	if (CHECK(err, "skel_load", "failed: %d\n", err))
+		goto cleanup;
+
+	test_check_mtu_run_xdp(skel, skel->progs.xdp_use_helper, mtu);
+	test_check_mtu_run_xdp(skel, skel->progs.xdp_exceed_mtu, mtu);
+	test_check_mtu_run_xdp(skel, skel->progs.xdp_minus_delta, mtu);
+
+cleanup:
+	test_check_mtu__destroy(skel);
+}
+
+static void test_check_mtu_run_tc(struct test_check_mtu *skel,
+				  struct bpf_program *prog,
+				  __u32 mtu_expect)
+{
+	const char *prog_name = bpf_program__name(prog);
+	int retval_expect = BPF_OK;
+	__u32 mtu_result = 0;
+	char buf[256];
+	int err;
+
+	struct bpf_prog_test_run_attr tattr = {
+		.repeat = 1,
+		.data_in = &pkt_v4,
+		.data_size_in = sizeof(pkt_v4),
+		.data_out = buf,
+		.data_size_out = sizeof(buf),
+		.prog_fd = bpf_program__fd(prog),
+	};
+
+	memset(buf, 0, sizeof(buf));
+
+	err = bpf_prog_test_run_xattr(&tattr);
+	CHECK_ATTR(err != 0 || errno != 0, "bpf_prog_test_run",
+		   "prog_name:%s (err %d errno %d retval %d)\n",
+		   prog_name, err, errno, tattr.retval);
+
+        CHECK(tattr.retval != retval_expect, "retval",
+	      "progname:%s unexpected retval=%d expected=%d\n",
+	      prog_name, tattr.retval, retval_expect);
+
+	/* Extract MTU that BPF-prog got */
+	mtu_result = skel->bss->global_bpf_mtu_tc;
+	CHECK(mtu_result != mtu_expect, "MTU-compare-user",
+	      "failed (MTU user:%d bpf:%d)", mtu_expect, mtu_result);
+}
+
+
+static void test_check_mtu_tc(__u32 mtu, __u32 ifindex)
+{
+	struct test_check_mtu *skel;
+	int err;
+
+	skel = test_check_mtu__open();
+	if (CHECK(!skel, "skel_open", "failed"))
+		return;
+
+	/* Update "constants" in BPF-prog *BEFORE* libbpf load */
+	skel->rodata->GLOBAL_USER_MTU = mtu;
+	skel->rodata->GLOBAL_USER_IFINDEX = ifindex;
+
+	err = test_check_mtu__load(skel);
+	if (CHECK(err, "skel_load", "failed: %d\n", err))
+		goto cleanup;
+
+	test_check_mtu_run_tc(skel, skel->progs.tc_use_helper, mtu);
+	test_check_mtu_run_tc(skel, skel->progs.tc_exceed_mtu, mtu);
+	test_check_mtu_run_tc(skel, skel->progs.tc_exceed_mtu_da, mtu);
+	test_check_mtu_run_tc(skel, skel->progs.tc_minus_delta, mtu);
+cleanup:
+	test_check_mtu__destroy(skel);
+}
+
+void test_check_mtu(void)
+{
+	struct test_check_mtu *skel;
+	__u32 mtu_lo;
+
+	skel = test_check_mtu__open_and_load();
+	if (CHECK(!skel, "open and load skel", "failed"))
+		return; /* Exit if e.g. helper unknown to kernel */
+
+	if (test__start_subtest("bpf_check_mtu XDP-attach"))
+		test_check_mtu_xdp_attach(skel->progs.xdp_use_helper_basic);
+
+	test_check_mtu__destroy(skel);
+
+	mtu_lo = read_mtu_device_lo();
+	if (CHECK(mtu_lo < 0, "reading MTU value", "failed (err:%d)", mtu_lo))
+		return;
+
+	if (test__start_subtest("bpf_check_mtu XDP-run"))
+		test_check_mtu_xdp(mtu_lo, 0);
+
+	if (test__start_subtest("bpf_check_mtu XDP-run ifindex-lookup"))
+		test_check_mtu_xdp(mtu_lo, IFINDEX_LO);
+
+	if (test__start_subtest("bpf_check_mtu TC-run"))
+		test_check_mtu_tc(mtu_lo, 0);
+
+	if (test__start_subtest("bpf_check_mtu TC-run ifindex-lookup"))
+		test_check_mtu_tc(mtu_lo, IFINDEX_LO);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_check_mtu.c b/tools/testing/selftests/bpf/progs/test_check_mtu.c
new file mode 100644
index 000000000000..91a026b8d458
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_check_mtu.c
@@ -0,0 +1,196 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Jesper Dangaard Brouer */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <linux/if_ether.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+char _license[] SEC("license") = "GPL";
+
+/* Userspace will update with MTU it can see on device */
+static volatile const int GLOBAL_USER_MTU;
+static volatile const __u32 GLOBAL_USER_IFINDEX;
+
+/* BPF-prog will update these with MTU values it can see */
+__u32 global_bpf_mtu_xdp = 0;
+__u32 global_bpf_mtu_tc  = 0;
+
+SEC("xdp")
+int xdp_use_helper_basic(struct xdp_md *ctx)
+{
+	__u32 mtu_len = 0;
+
+	if (bpf_check_mtu(ctx, 0, &mtu_len, 0, 0))
+		return XDP_ABORTED;
+
+	return XDP_PASS;
+}
+
+SEC("xdp")
+int xdp_use_helper(struct xdp_md *ctx)
+{
+	int retval = XDP_PASS; /* Expected retval on successful test */
+	__u32 mtu_len = 0;
+	__u32 ifindex = 0;
+	int delta = 0;
+
+	/* When ifindex is zero, save net_device lookup and use ctx netdev */
+	if (GLOBAL_USER_IFINDEX > 0)
+		ifindex = GLOBAL_USER_IFINDEX;
+
+	if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0)) {
+		/* mtu_len is also valid when check fail */
+		retval = XDP_ABORTED;
+		goto out;
+	}
+
+	if (GLOBAL_USER_MTU != mtu_len)
+		retval = XDP_DROP;
+
+out:
+	global_bpf_mtu_xdp = mtu_len;
+	return retval;
+}
+
+SEC("xdp")
+int xdp_exceed_mtu(struct xdp_md *ctx)
+{
+	void *data_end = (void *)(long)ctx->data_end;
+	void *data = (void *)(long)ctx->data;
+	__u32 ifindex = GLOBAL_USER_IFINDEX;
+	__u32 data_len = data_end - data;
+	int retval = XDP_ABORTED; /* Fail */
+	__u32 mtu_len = 0;
+
+	int delta;
+	int err;
+
+	/* Exceed MTU with 1 via delta adjust */
+	delta = GLOBAL_USER_MTU - (data_len - ETH_HLEN) + 1;
+
+	if ((err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))) {
+		retval = XDP_PASS; /* Success in exceeding MTU check */
+		if (err != BPF_MTU_CHK_RET_FRAG_NEEDED)
+			retval = XDP_DROP;
+	}
+
+	global_bpf_mtu_xdp = mtu_len;
+	return retval;
+}
+
+SEC("xdp")
+int xdp_minus_delta(struct xdp_md *ctx)
+{
+	int retval = XDP_PASS; /* Expected retval on successful test */
+	void *data_end = (void *)(long)ctx->data_end;
+	void *data = (void *)(long)ctx->data;
+	__u32 ifindex = GLOBAL_USER_IFINDEX;
+	__u32 data_len = data_end - data;
+	__u32 mtu_len = 0;
+	int delta;
+
+	/* Boarderline test case: Minus delta exceeding packet length allowed */
+	delta = -((data_len - ETH_HLEN) + 1);
+
+	/* Minus length (adjusted via delta) still pass MTU check, other helpers
+	 * are responsible for catching this, when doing actual size adjust
+	 */
+	if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))
+		retval = XDP_ABORTED;
+
+	global_bpf_mtu_xdp = mtu_len;
+	return retval;
+}
+
+SEC("classifier")
+int tc_use_helper(struct __sk_buff *ctx)
+{
+	int retval = BPF_OK; /* Expected retval on successful test */
+	__u32 mtu_len = 0;
+	int delta = 0;
+
+	if (bpf_check_mtu(ctx, 0, &mtu_len, delta, 0)) {
+		retval = BPF_DROP;
+		goto out;
+	}
+
+	if (GLOBAL_USER_MTU != mtu_len)
+		retval = BPF_REDIRECT;
+out:
+	global_bpf_mtu_tc = mtu_len;
+	return retval;
+}
+
+SEC("classifier")
+int tc_exceed_mtu(struct __sk_buff *ctx)
+{
+	__u32 ifindex = GLOBAL_USER_IFINDEX;
+	int retval = BPF_DROP; /* Fail */
+	__u32 skb_len = ctx->len;
+	__u32 mtu_len = 0;
+	int delta;
+	int err;
+
+	/* Exceed MTU with 1 via delta adjust */
+	delta = GLOBAL_USER_MTU - (skb_len - ETH_HLEN) + 1;
+
+	if ((err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))) {
+		retval = BPF_OK; /* Success in exceeding MTU check */
+		if (err != BPF_MTU_CHK_RET_FRAG_NEEDED)
+			retval = BPF_DROP;
+	}
+
+	global_bpf_mtu_tc = mtu_len;
+	return retval;
+}
+
+SEC("classifier")
+int tc_exceed_mtu_da(struct __sk_buff *ctx)
+{
+	/* SKB Direct-Access variant */
+	void *data_end = (void *)(long)ctx->data_end;
+	void *data = (void *)(long)ctx->data;
+	__u32 ifindex = GLOBAL_USER_IFINDEX;
+	__u32 data_len = data_end - data;
+	int retval = BPF_DROP; /* Fail */
+	__u32 mtu_len = 0;
+	int delta;
+	int err;
+
+	/* Exceed MTU with 1 via delta adjust */
+	delta = GLOBAL_USER_MTU - (data_len - ETH_HLEN) + 1;
+
+	if ((err = bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))) {
+		retval = BPF_OK; /* Success in exceeding MTU check */
+		if (err != BPF_MTU_CHK_RET_FRAG_NEEDED)
+			retval = BPF_DROP;
+	}
+
+	global_bpf_mtu_tc = mtu_len;
+	return retval;
+}
+
+SEC("classifier")
+int tc_minus_delta(struct __sk_buff *ctx)
+{
+	int retval = BPF_OK; /* Expected retval on successful test */
+	__u32 ifindex = GLOBAL_USER_IFINDEX;
+	__u32 skb_len = ctx->len;
+	__u32 mtu_len = 0;
+	int delta;
+
+	/* Boarderline test case: Minus delta exceeding packet length allowed */
+	delta = -((skb_len - ETH_HLEN) + 1);
+
+	/* Minus length (adjusted via delta) still pass MTU check, other helpers
+	 * are responsible for catching this, when doing actual size adjust
+	 */
+	if (bpf_check_mtu(ctx, ifindex, &mtu_len, delta, 0))
+		retval = BPF_DROP;
+
+	global_bpf_mtu_xdp = mtu_len;
+	return retval;
+}