Skip to content

Commit 3b03791

Browse files
mfijalkoAlexei Starovoitov
authored and
Alexei Starovoitov
committed
selftests/bpf: Add tailcall_bpf2bpf tests
Add four tests to tailcalls selftest explicitly named "tailcall_bpf2bpf_X" as their purpose is to validate that combination of tailcalls with bpf2bpf calls are working properly. These tests also validate LD_ABS from subprograms. Signed-off-by: Maciej Fijalkowski <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 09b28d7 commit 3b03791

File tree

5 files changed

+533
-0
lines changed

5 files changed

+533
-0
lines changed

tools/testing/selftests/bpf/prog_tests/tailcalls.c

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// SPDX-License-Identifier: GPL-2.0
22
#include <test_progs.h>
3+
#include <network_helpers.h>
34

45
/* test_tailcall_1 checks basic functionality by patching multiple locations
56
* in a single program for a single tail call slot with nop->jmp, jmp->nop
@@ -472,6 +473,329 @@ static void test_tailcall_5(void)
472473
bpf_object__close(obj);
473474
}
474475

476+
/* test_tailcall_bpf2bpf_1 purpose is to make sure that tailcalls are working
477+
* correctly in correlation with BPF subprograms
478+
*/
479+
static void test_tailcall_bpf2bpf_1(void)
480+
{
481+
int err, map_fd, prog_fd, main_fd, i;
482+
struct bpf_map *prog_array;
483+
struct bpf_program *prog;
484+
struct bpf_object *obj;
485+
__u32 retval, duration;
486+
char prog_name[32];
487+
488+
err = bpf_prog_load("tailcall_bpf2bpf1.o", BPF_PROG_TYPE_SCHED_CLS,
489+
&obj, &prog_fd);
490+
if (CHECK_FAIL(err))
491+
return;
492+
493+
prog = bpf_object__find_program_by_title(obj, "classifier");
494+
if (CHECK_FAIL(!prog))
495+
goto out;
496+
497+
main_fd = bpf_program__fd(prog);
498+
if (CHECK_FAIL(main_fd < 0))
499+
goto out;
500+
501+
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
502+
if (CHECK_FAIL(!prog_array))
503+
goto out;
504+
505+
map_fd = bpf_map__fd(prog_array);
506+
if (CHECK_FAIL(map_fd < 0))
507+
goto out;
508+
509+
/* nop -> jmp */
510+
for (i = 0; i < bpf_map__def(prog_array)->max_entries; i++) {
511+
snprintf(prog_name, sizeof(prog_name), "classifier/%i", i);
512+
513+
prog = bpf_object__find_program_by_title(obj, prog_name);
514+
if (CHECK_FAIL(!prog))
515+
goto out;
516+
517+
prog_fd = bpf_program__fd(prog);
518+
if (CHECK_FAIL(prog_fd < 0))
519+
goto out;
520+
521+
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
522+
if (CHECK_FAIL(err))
523+
goto out;
524+
}
525+
526+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
527+
0, &retval, &duration);
528+
CHECK(err || retval != 1, "tailcall",
529+
"err %d errno %d retval %d\n", err, errno, retval);
530+
531+
/* jmp -> nop, call subprog that will do tailcall */
532+
i = 1;
533+
err = bpf_map_delete_elem(map_fd, &i);
534+
if (CHECK_FAIL(err))
535+
goto out;
536+
537+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
538+
0, &retval, &duration);
539+
CHECK(err || retval != 0, "tailcall", "err %d errno %d retval %d\n",
540+
err, errno, retval);
541+
542+
/* make sure that subprog can access ctx and entry prog that
543+
* called this subprog can properly return
544+
*/
545+
i = 0;
546+
err = bpf_map_delete_elem(map_fd, &i);
547+
if (CHECK_FAIL(err))
548+
goto out;
549+
550+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
551+
0, &retval, &duration);
552+
CHECK(err || retval != sizeof(pkt_v4) * 2,
553+
"tailcall", "err %d errno %d retval %d\n",
554+
err, errno, retval);
555+
out:
556+
bpf_object__close(obj);
557+
}
558+
559+
/* test_tailcall_bpf2bpf_2 checks that the count value of the tail call limit
560+
* enforcement matches with expectations when tailcall is preceded with
561+
* bpf2bpf call.
562+
*/
563+
static void test_tailcall_bpf2bpf_2(void)
564+
{
565+
int err, map_fd, prog_fd, main_fd, data_fd, i, val;
566+
struct bpf_map *prog_array, *data_map;
567+
struct bpf_program *prog;
568+
struct bpf_object *obj;
569+
__u32 retval, duration;
570+
char buff[128] = {};
571+
572+
err = bpf_prog_load("tailcall_bpf2bpf2.o", BPF_PROG_TYPE_SCHED_CLS,
573+
&obj, &prog_fd);
574+
if (CHECK_FAIL(err))
575+
return;
576+
577+
prog = bpf_object__find_program_by_title(obj, "classifier");
578+
if (CHECK_FAIL(!prog))
579+
goto out;
580+
581+
main_fd = bpf_program__fd(prog);
582+
if (CHECK_FAIL(main_fd < 0))
583+
goto out;
584+
585+
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
586+
if (CHECK_FAIL(!prog_array))
587+
goto out;
588+
589+
map_fd = bpf_map__fd(prog_array);
590+
if (CHECK_FAIL(map_fd < 0))
591+
goto out;
592+
593+
prog = bpf_object__find_program_by_title(obj, "classifier/0");
594+
if (CHECK_FAIL(!prog))
595+
goto out;
596+
597+
prog_fd = bpf_program__fd(prog);
598+
if (CHECK_FAIL(prog_fd < 0))
599+
goto out;
600+
601+
i = 0;
602+
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
603+
if (CHECK_FAIL(err))
604+
goto out;
605+
606+
err = bpf_prog_test_run(main_fd, 1, buff, sizeof(buff), 0,
607+
&duration, &retval, NULL);
608+
CHECK(err || retval != 1, "tailcall", "err %d errno %d retval %d\n",
609+
err, errno, retval);
610+
611+
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
612+
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
613+
return;
614+
615+
data_fd = bpf_map__fd(data_map);
616+
if (CHECK_FAIL(map_fd < 0))
617+
return;
618+
619+
i = 0;
620+
err = bpf_map_lookup_elem(data_fd, &i, &val);
621+
CHECK(err || val != 33, "tailcall count", "err %d errno %d count %d\n",
622+
err, errno, val);
623+
624+
i = 0;
625+
err = bpf_map_delete_elem(map_fd, &i);
626+
if (CHECK_FAIL(err))
627+
goto out;
628+
629+
err = bpf_prog_test_run(main_fd, 1, buff, sizeof(buff), 0,
630+
&duration, &retval, NULL);
631+
CHECK(err || retval != 0, "tailcall", "err %d errno %d retval %d\n",
632+
err, errno, retval);
633+
out:
634+
bpf_object__close(obj);
635+
}
636+
637+
/* test_tailcall_bpf2bpf_3 checks that non-trivial amount of stack (up to
638+
* 256 bytes) can be used within bpf subprograms that have the tailcalls
639+
* in them
640+
*/
641+
static void test_tailcall_bpf2bpf_3(void)
642+
{
643+
int err, map_fd, prog_fd, main_fd, i;
644+
struct bpf_map *prog_array;
645+
struct bpf_program *prog;
646+
struct bpf_object *obj;
647+
__u32 retval, duration;
648+
char prog_name[32];
649+
650+
err = bpf_prog_load("tailcall_bpf2bpf3.o", BPF_PROG_TYPE_SCHED_CLS,
651+
&obj, &prog_fd);
652+
if (CHECK_FAIL(err))
653+
return;
654+
655+
prog = bpf_object__find_program_by_title(obj, "classifier");
656+
if (CHECK_FAIL(!prog))
657+
goto out;
658+
659+
main_fd = bpf_program__fd(prog);
660+
if (CHECK_FAIL(main_fd < 0))
661+
goto out;
662+
663+
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
664+
if (CHECK_FAIL(!prog_array))
665+
goto out;
666+
667+
map_fd = bpf_map__fd(prog_array);
668+
if (CHECK_FAIL(map_fd < 0))
669+
goto out;
670+
671+
for (i = 0; i < bpf_map__def(prog_array)->max_entries; i++) {
672+
snprintf(prog_name, sizeof(prog_name), "classifier/%i", i);
673+
674+
prog = bpf_object__find_program_by_title(obj, prog_name);
675+
if (CHECK_FAIL(!prog))
676+
goto out;
677+
678+
prog_fd = bpf_program__fd(prog);
679+
if (CHECK_FAIL(prog_fd < 0))
680+
goto out;
681+
682+
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
683+
if (CHECK_FAIL(err))
684+
goto out;
685+
}
686+
687+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
688+
&duration, &retval, NULL);
689+
CHECK(err || retval != sizeof(pkt_v4) * 3,
690+
"tailcall", "err %d errno %d retval %d\n",
691+
err, errno, retval);
692+
693+
i = 1;
694+
err = bpf_map_delete_elem(map_fd, &i);
695+
if (CHECK_FAIL(err))
696+
goto out;
697+
698+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
699+
&duration, &retval, NULL);
700+
CHECK(err || retval != sizeof(pkt_v4),
701+
"tailcall", "err %d errno %d retval %d\n",
702+
err, errno, retval);
703+
704+
i = 0;
705+
err = bpf_map_delete_elem(map_fd, &i);
706+
if (CHECK_FAIL(err))
707+
goto out;
708+
709+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
710+
&duration, &retval, NULL);
711+
CHECK(err || retval != sizeof(pkt_v4) * 2,
712+
"tailcall", "err %d errno %d retval %d\n",
713+
err, errno, retval);
714+
out:
715+
bpf_object__close(obj);
716+
}
717+
718+
/* test_tailcall_bpf2bpf_4 checks that tailcall counter is correctly preserved
719+
* across tailcalls combined with bpf2bpf calls. for making sure that tailcall
720+
* counter behaves correctly, bpf program will go through following flow:
721+
*
722+
* entry -> entry_subprog -> tailcall0 -> bpf_func0 -> subprog0 ->
723+
* -> tailcall1 -> bpf_func1 -> subprog1 -> tailcall2 -> bpf_func2 ->
724+
* subprog2 [here bump global counter] --------^
725+
*
726+
* We go through first two tailcalls and start counting from the subprog2 where
727+
* the loop begins. At the end of the test make sure that the global counter is
728+
* equal to 31, because tailcall counter includes the first two tailcalls
729+
* whereas global counter is incremented only on loop presented on flow above.
730+
*/
731+
static void test_tailcall_bpf2bpf_4(void)
732+
{
733+
int err, map_fd, prog_fd, main_fd, data_fd, i, val;
734+
struct bpf_map *prog_array, *data_map;
735+
struct bpf_program *prog;
736+
struct bpf_object *obj;
737+
__u32 retval, duration;
738+
char prog_name[32];
739+
740+
err = bpf_prog_load("tailcall_bpf2bpf4.o", BPF_PROG_TYPE_SCHED_CLS,
741+
&obj, &prog_fd);
742+
if (CHECK_FAIL(err))
743+
return;
744+
745+
prog = bpf_object__find_program_by_title(obj, "classifier");
746+
if (CHECK_FAIL(!prog))
747+
goto out;
748+
749+
main_fd = bpf_program__fd(prog);
750+
if (CHECK_FAIL(main_fd < 0))
751+
goto out;
752+
753+
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
754+
if (CHECK_FAIL(!prog_array))
755+
goto out;
756+
757+
map_fd = bpf_map__fd(prog_array);
758+
if (CHECK_FAIL(map_fd < 0))
759+
goto out;
760+
761+
for (i = 0; i < bpf_map__def(prog_array)->max_entries; i++) {
762+
snprintf(prog_name, sizeof(prog_name), "classifier/%i", i);
763+
764+
prog = bpf_object__find_program_by_title(obj, prog_name);
765+
if (CHECK_FAIL(!prog))
766+
goto out;
767+
768+
prog_fd = bpf_program__fd(prog);
769+
if (CHECK_FAIL(prog_fd < 0))
770+
goto out;
771+
772+
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
773+
if (CHECK_FAIL(err))
774+
goto out;
775+
}
776+
777+
err = bpf_prog_test_run(main_fd, 1, &pkt_v4, sizeof(pkt_v4), 0,
778+
&duration, &retval, NULL);
779+
CHECK(err || retval != sizeof(pkt_v4) * 3, "tailcall", "err %d errno %d retval %d\n",
780+
err, errno, retval);
781+
782+
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
783+
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
784+
return;
785+
786+
data_fd = bpf_map__fd(data_map);
787+
if (CHECK_FAIL(map_fd < 0))
788+
return;
789+
790+
i = 0;
791+
err = bpf_map_lookup_elem(data_fd, &i, &val);
792+
CHECK(err || val != 31, "tailcall count", "err %d errno %d count %d\n",
793+
err, errno, val);
794+
795+
out:
796+
bpf_object__close(obj);
797+
}
798+
475799
void test_tailcalls(void)
476800
{
477801
if (test__start_subtest("tailcall_1"))
@@ -484,4 +808,12 @@ void test_tailcalls(void)
484808
test_tailcall_4();
485809
if (test__start_subtest("tailcall_5"))
486810
test_tailcall_5();
811+
if (test__start_subtest("tailcall_bpf2bpf_1"))
812+
test_tailcall_bpf2bpf_1();
813+
if (test__start_subtest("tailcall_bpf2bpf_2"))
814+
test_tailcall_bpf2bpf_2();
815+
if (test__start_subtest("tailcall_bpf2bpf_3"))
816+
test_tailcall_bpf2bpf_3();
817+
if (test__start_subtest("tailcall_bpf2bpf_4"))
818+
test_tailcall_bpf2bpf_4();
487819
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include <linux/bpf.h>
3+
#include <bpf/bpf_helpers.h>
4+
5+
struct {
6+
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
7+
__uint(max_entries, 2);
8+
__uint(key_size, sizeof(__u32));
9+
__uint(value_size, sizeof(__u32));
10+
} jmp_table SEC(".maps");
11+
12+
#define TAIL_FUNC(x) \
13+
SEC("classifier/" #x) \
14+
int bpf_func_##x(struct __sk_buff *skb) \
15+
{ \
16+
return x; \
17+
}
18+
TAIL_FUNC(0)
19+
TAIL_FUNC(1)
20+
21+
static __noinline
22+
int subprog_tail(struct __sk_buff *skb)
23+
{
24+
bpf_tail_call(skb, &jmp_table, 0);
25+
26+
return skb->len * 2;
27+
}
28+
29+
SEC("classifier")
30+
int entry(struct __sk_buff *skb)
31+
{
32+
bpf_tail_call(skb, &jmp_table, 1);
33+
34+
return subprog_tail(skb);
35+
}
36+
37+
char __license[] SEC("license") = "GPL";
38+
int _version SEC("version") = 1;

0 commit comments

Comments
 (0)