Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Add __noinline variant of cls_redirect selftest

As one of the most complicated and close-to-real-world programs, cls_redirect
is a good candidate to exercise libbpf's logic of handling bpf2bpf calls. So
add variant with using explicit __noinline for majority of functions except
few most basic ones. If those few functions are inlined, verifier starts to
complain about program instruction limit of 1mln instructions being exceeded,
most probably due to instruction overhead of doing a sub-program call.
Convert user-space part of selftest to have to sub-tests: with and without
inlining.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Cc: Lorenz Bauer <lmb@cloudflare.com>
Link: https://lore.kernel.org/bpf/20200903203542.15944-15-andriin@fb.com

authored by

Andrii Nakryiko and committed by
Alexei Starovoitov
ee333df5 baaf680e

+114 -63
+57 -15
tools/testing/selftests/bpf/prog_tests/cls_redirect.c
··· 12 12 13 13 #include "progs/test_cls_redirect.h" 14 14 #include "test_cls_redirect.skel.h" 15 + #include "test_cls_redirect_subprogs.skel.h" 15 16 16 17 #define ENCAP_IP INADDR_LOOPBACK 17 18 #define ENCAP_PORT (1234) 19 + 20 + static int duration = 0; 18 21 19 22 struct addr_port { 20 23 in_port_t port; ··· 364 361 close(fds[i]); 365 362 } 366 363 367 - void test_cls_redirect(void) 364 + static void test_cls_redirect_common(struct bpf_program *prog) 368 365 { 369 - struct test_cls_redirect *skel = NULL; 370 366 struct bpf_prog_test_run_attr tattr = {}; 371 367 int families[] = { AF_INET, AF_INET6 }; 372 368 struct sockaddr_storage ss; 373 369 struct sockaddr *addr; 374 370 socklen_t slen; 375 371 int i, j, err; 376 - 377 372 int servers[__NR_KIND][ARRAY_SIZE(families)] = {}; 378 373 int conns[__NR_KIND][ARRAY_SIZE(families)] = {}; 379 374 struct tuple tuples[__NR_KIND][ARRAY_SIZE(families)]; 380 - 381 - skel = test_cls_redirect__open(); 382 - if (CHECK_FAIL(!skel)) 383 - return; 384 - 385 - skel->rodata->ENCAPSULATION_IP = htonl(ENCAP_IP); 386 - skel->rodata->ENCAPSULATION_PORT = htons(ENCAP_PORT); 387 - 388 - if (CHECK_FAIL(test_cls_redirect__load(skel))) 389 - goto cleanup; 390 375 391 376 addr = (struct sockaddr *)&ss; 392 377 for (i = 0; i < ARRAY_SIZE(families); i++) { ··· 393 402 goto cleanup; 394 403 } 395 404 396 - tattr.prog_fd = bpf_program__fd(skel->progs.cls_redirect); 405 + tattr.prog_fd = bpf_program__fd(prog); 397 406 for (i = 0; i < ARRAY_SIZE(tests); i++) { 398 407 struct test_cfg *test = &tests[i]; 399 408 ··· 441 450 } 442 451 443 452 cleanup: 444 - test_cls_redirect__destroy(skel); 445 453 close_fds((int *)servers, sizeof(servers) / sizeof(servers[0][0])); 446 454 close_fds((int *)conns, sizeof(conns) / sizeof(conns[0][0])); 455 + } 456 + 457 + static void test_cls_redirect_inlined(void) 458 + { 459 + struct test_cls_redirect *skel; 460 + int err; 461 + 462 + skel = test_cls_redirect__open(); 463 + if (CHECK(!skel, "skel_open", "failed\n")) 464 + return; 465 + 466 + skel->rodata->ENCAPSULATION_IP = htonl(ENCAP_IP); 467 + skel->rodata->ENCAPSULATION_PORT = htons(ENCAP_PORT); 468 + 469 + err = test_cls_redirect__load(skel); 470 + if (CHECK(err, "skel_load", "failed: %d\n", err)) 471 + goto cleanup; 472 + 473 + test_cls_redirect_common(skel->progs.cls_redirect); 474 + 475 + cleanup: 476 + test_cls_redirect__destroy(skel); 477 + } 478 + 479 + static void test_cls_redirect_subprogs(void) 480 + { 481 + struct test_cls_redirect_subprogs *skel; 482 + int err; 483 + 484 + skel = test_cls_redirect_subprogs__open(); 485 + if (CHECK(!skel, "skel_open", "failed\n")) 486 + return; 487 + 488 + skel->rodata->ENCAPSULATION_IP = htonl(ENCAP_IP); 489 + skel->rodata->ENCAPSULATION_PORT = htons(ENCAP_PORT); 490 + 491 + err = test_cls_redirect_subprogs__load(skel); 492 + if (CHECK(err, "skel_load", "failed: %d\n", err)) 493 + goto cleanup; 494 + 495 + test_cls_redirect_common(skel->progs.cls_redirect); 496 + 497 + cleanup: 498 + test_cls_redirect_subprogs__destroy(skel); 499 + } 500 + 501 + void test_cls_redirect(void) 502 + { 503 + if (test__start_subtest("cls_redirect_inlined")) 504 + test_cls_redirect_inlined(); 505 + if (test__start_subtest("cls_redirect_subprogs")) 506 + test_cls_redirect_subprogs(); 447 507 }
+55 -48
tools/testing/selftests/bpf/progs/test_cls_redirect.c
··· 22 22 23 23 #include "test_cls_redirect.h" 24 24 25 + #ifdef SUBPROGS 26 + #define INLINING __noinline 27 + #else 28 + #define INLINING __always_inline 29 + #endif 30 + 25 31 #define offsetofend(TYPE, MEMBER) \ 26 32 (offsetof(TYPE, MEMBER) + sizeof((((TYPE *)0)->MEMBER))) 27 33 ··· 131 125 uint8_t *const tail; 132 126 } buf_t; 133 127 134 - static size_t buf_off(const buf_t *buf) 128 + static __always_inline size_t buf_off(const buf_t *buf) 135 129 { 136 130 /* Clang seems to optimize constructs like 137 131 * a - b + c ··· 151 145 return off; 152 146 } 153 147 154 - static bool buf_copy(buf_t *buf, void *dst, size_t len) 148 + static __always_inline bool buf_copy(buf_t *buf, void *dst, size_t len) 155 149 { 156 150 if (bpf_skb_load_bytes(buf->skb, buf_off(buf), dst, len)) { 157 151 return false; ··· 161 155 return true; 162 156 } 163 157 164 - static bool buf_skip(buf_t *buf, const size_t len) 158 + static __always_inline bool buf_skip(buf_t *buf, const size_t len) 165 159 { 166 160 /* Check whether off + len is valid in the non-linear part. */ 167 161 if (buf_off(buf) + len > buf->skb->len) { ··· 179 173 * If scratch is not NULL, the function will attempt to load non-linear 180 174 * data via bpf_skb_load_bytes. On success, scratch is returned. 181 175 */ 182 - static void *buf_assign(buf_t *buf, const size_t len, void *scratch) 176 + static __always_inline void *buf_assign(buf_t *buf, const size_t len, void *scratch) 183 177 { 184 178 if (buf->head + len > buf->tail) { 185 179 if (scratch == NULL) { ··· 194 188 return ptr; 195 189 } 196 190 197 - static bool pkt_skip_ipv4_options(buf_t *buf, const struct iphdr *ipv4) 191 + static INLINING bool pkt_skip_ipv4_options(buf_t *buf, const struct iphdr *ipv4) 198 192 { 199 193 if (ipv4->ihl <= 5) { 200 194 return true; ··· 203 197 return buf_skip(buf, (ipv4->ihl - 5) * 4); 204 198 } 205 199 206 - static bool ipv4_is_fragment(const struct iphdr *ip) 200 + static INLINING bool ipv4_is_fragment(const struct iphdr *ip) 207 201 { 208 202 uint16_t frag_off = ip->frag_off & bpf_htons(IP_OFFSET_MASK); 209 203 return (ip->frag_off & bpf_htons(IP_MF)) != 0 || frag_off > 0; 210 204 } 211 205 212 - static struct iphdr *pkt_parse_ipv4(buf_t *pkt, struct iphdr *scratch) 206 + static __always_inline struct iphdr *pkt_parse_ipv4(buf_t *pkt, struct iphdr *scratch) 213 207 { 214 208 struct iphdr *ipv4 = buf_assign(pkt, sizeof(*ipv4), scratch); 215 209 if (ipv4 == NULL) { ··· 228 222 } 229 223 230 224 /* Parse the L4 ports from a packet, assuming a layout like TCP or UDP. */ 231 - static bool pkt_parse_icmp_l4_ports(buf_t *pkt, flow_ports_t *ports) 225 + static INLINING bool pkt_parse_icmp_l4_ports(buf_t *pkt, flow_ports_t *ports) 232 226 { 233 227 if (!buf_copy(pkt, ports, sizeof(*ports))) { 234 228 return false; ··· 243 237 return true; 244 238 } 245 239 246 - static uint16_t pkt_checksum_fold(uint32_t csum) 240 + static INLINING uint16_t pkt_checksum_fold(uint32_t csum) 247 241 { 248 242 /* The highest reasonable value for an IPv4 header 249 243 * checksum requires two folds, so we just do that always. ··· 253 247 return (uint16_t)~csum; 254 248 } 255 249 256 - static void pkt_ipv4_checksum(struct iphdr *iph) 250 + static INLINING void pkt_ipv4_checksum(struct iphdr *iph) 257 251 { 258 252 iph->check = 0; 259 253 ··· 274 268 iph->check = pkt_checksum_fold(acc); 275 269 } 276 270 277 - static bool pkt_skip_ipv6_extension_headers(buf_t *pkt, 278 - const struct ipv6hdr *ipv6, 279 - uint8_t *upper_proto, 280 - bool *is_fragment) 271 + static INLINING 272 + bool pkt_skip_ipv6_extension_headers(buf_t *pkt, 273 + const struct ipv6hdr *ipv6, 274 + uint8_t *upper_proto, 275 + bool *is_fragment) 281 276 { 282 277 /* We understand five extension headers. 283 278 * https://tools.ietf.org/html/rfc8200#section-4.1 states that all ··· 343 336 * scratch is allocated on the stack. However, this usage should be safe since 344 337 * it's the callers stack after all. 345 338 */ 346 - static inline __attribute__((__always_inline__)) struct ipv6hdr * 339 + static __always_inline struct ipv6hdr * 347 340 pkt_parse_ipv6(buf_t *pkt, struct ipv6hdr *scratch, uint8_t *proto, 348 341 bool *is_fragment) 349 342 { ··· 361 354 362 355 /* Global metrics, per CPU 363 356 */ 364 - struct bpf_map_def metrics_map SEC("maps") = { 365 - .type = BPF_MAP_TYPE_PERCPU_ARRAY, 366 - .key_size = sizeof(unsigned int), 367 - .value_size = sizeof(metrics_t), 368 - .max_entries = 1, 369 - }; 357 + struct { 358 + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 359 + __uint(max_entries, 1); 360 + __type(key, unsigned int); 361 + __type(value, metrics_t); 362 + } metrics_map SEC(".maps"); 370 363 371 - static metrics_t *get_global_metrics(void) 364 + static INLINING metrics_t *get_global_metrics(void) 372 365 { 373 366 uint64_t key = 0; 374 367 return bpf_map_lookup_elem(&metrics_map, &key); 375 368 } 376 369 377 - static ret_t accept_locally(struct __sk_buff *skb, encap_headers_t *encap) 370 + static INLINING ret_t accept_locally(struct __sk_buff *skb, encap_headers_t *encap) 378 371 { 379 372 const int payload_off = 380 373 sizeof(*encap) + ··· 395 388 return bpf_redirect(skb->ifindex, BPF_F_INGRESS); 396 389 } 397 390 398 - static ret_t forward_with_gre(struct __sk_buff *skb, encap_headers_t *encap, 399 - struct in_addr *next_hop, metrics_t *metrics) 391 + static INLINING ret_t forward_with_gre(struct __sk_buff *skb, encap_headers_t *encap, 392 + struct in_addr *next_hop, metrics_t *metrics) 400 393 { 401 394 metrics->forwarded_packets_total_gre++; 402 395 ··· 516 509 return bpf_redirect(skb->ifindex, 0); 517 510 } 518 511 519 - static ret_t forward_to_next_hop(struct __sk_buff *skb, encap_headers_t *encap, 520 - struct in_addr *next_hop, metrics_t *metrics) 512 + static INLINING ret_t forward_to_next_hop(struct __sk_buff *skb, encap_headers_t *encap, 513 + struct in_addr *next_hop, metrics_t *metrics) 521 514 { 522 515 /* swap L2 addresses */ 523 516 /* This assumes that packets are received from a router. ··· 553 546 return bpf_redirect(skb->ifindex, 0); 554 547 } 555 548 556 - static ret_t skip_next_hops(buf_t *pkt, int n) 549 + static INLINING ret_t skip_next_hops(buf_t *pkt, int n) 557 550 { 558 551 switch (n) { 559 552 case 1: ··· 573 566 * pkt is positioned just after the variable length GLB header 574 567 * iff the call is successful. 575 568 */ 576 - static ret_t get_next_hop(buf_t *pkt, encap_headers_t *encap, 577 - struct in_addr *next_hop) 569 + static INLINING ret_t get_next_hop(buf_t *pkt, encap_headers_t *encap, 570 + struct in_addr *next_hop) 578 571 { 579 572 if (encap->unigue.next_hop > encap->unigue.hop_count) { 580 573 return TC_ACT_SHOT; ··· 608 601 * return value, and calling code works while still being "generic" to 609 602 * IPv4 and IPv6. 610 603 */ 611 - static uint64_t fill_tuple(struct bpf_sock_tuple *tuple, void *iph, 612 - uint64_t iphlen, uint16_t sport, uint16_t dport) 604 + static INLINING uint64_t fill_tuple(struct bpf_sock_tuple *tuple, void *iph, 605 + uint64_t iphlen, uint16_t sport, uint16_t dport) 613 606 { 614 607 switch (iphlen) { 615 608 case sizeof(struct iphdr): { ··· 637 630 } 638 631 } 639 632 640 - static verdict_t classify_tcp(struct __sk_buff *skb, 641 - struct bpf_sock_tuple *tuple, uint64_t tuplen, 642 - void *iph, struct tcphdr *tcp) 633 + static INLINING verdict_t classify_tcp(struct __sk_buff *skb, 634 + struct bpf_sock_tuple *tuple, uint64_t tuplen, 635 + void *iph, struct tcphdr *tcp) 643 636 { 644 637 struct bpf_sock *sk = 645 638 bpf_skc_lookup_tcp(skb, tuple, tuplen, BPF_F_CURRENT_NETNS, 0); ··· 670 663 return UNKNOWN; 671 664 } 672 665 673 - static verdict_t classify_udp(struct __sk_buff *skb, 674 - struct bpf_sock_tuple *tuple, uint64_t tuplen) 666 + static INLINING verdict_t classify_udp(struct __sk_buff *skb, 667 + struct bpf_sock_tuple *tuple, uint64_t tuplen) 675 668 { 676 669 struct bpf_sock *sk = 677 670 bpf_sk_lookup_udp(skb, tuple, tuplen, BPF_F_CURRENT_NETNS, 0); ··· 688 681 return UNKNOWN; 689 682 } 690 683 691 - static verdict_t classify_icmp(struct __sk_buff *skb, uint8_t proto, 692 - struct bpf_sock_tuple *tuple, uint64_t tuplen, 693 - metrics_t *metrics) 684 + static INLINING verdict_t classify_icmp(struct __sk_buff *skb, uint8_t proto, 685 + struct bpf_sock_tuple *tuple, uint64_t tuplen, 686 + metrics_t *metrics) 694 687 { 695 688 switch (proto) { 696 689 case IPPROTO_TCP: ··· 705 698 } 706 699 } 707 700 708 - static verdict_t process_icmpv4(buf_t *pkt, metrics_t *metrics) 701 + static INLINING verdict_t process_icmpv4(buf_t *pkt, metrics_t *metrics) 709 702 { 710 703 struct icmphdr icmp; 711 704 if (!buf_copy(pkt, &icmp, sizeof(icmp))) { ··· 752 745 sizeof(tuple.ipv4), metrics); 753 746 } 754 747 755 - static verdict_t process_icmpv6(buf_t *pkt, metrics_t *metrics) 748 + static INLINING verdict_t process_icmpv6(buf_t *pkt, metrics_t *metrics) 756 749 { 757 750 struct icmp6hdr icmp6; 758 751 if (!buf_copy(pkt, &icmp6, sizeof(icmp6))) { ··· 804 797 metrics); 805 798 } 806 799 807 - static verdict_t process_tcp(buf_t *pkt, void *iph, uint64_t iphlen, 808 - metrics_t *metrics) 800 + static INLINING verdict_t process_tcp(buf_t *pkt, void *iph, uint64_t iphlen, 801 + metrics_t *metrics) 809 802 { 810 803 metrics->l4_protocol_packets_total_tcp++; 811 804 ··· 826 819 return classify_tcp(pkt->skb, &tuple, tuplen, iph, tcp); 827 820 } 828 821 829 - static verdict_t process_udp(buf_t *pkt, void *iph, uint64_t iphlen, 830 - metrics_t *metrics) 822 + static INLINING verdict_t process_udp(buf_t *pkt, void *iph, uint64_t iphlen, 823 + metrics_t *metrics) 831 824 { 832 825 metrics->l4_protocol_packets_total_udp++; 833 826 ··· 844 837 return classify_udp(pkt->skb, &tuple, tuplen); 845 838 } 846 839 847 - static verdict_t process_ipv4(buf_t *pkt, metrics_t *metrics) 840 + static INLINING verdict_t process_ipv4(buf_t *pkt, metrics_t *metrics) 848 841 { 849 842 metrics->l3_protocol_packets_total_ipv4++; 850 843 ··· 881 874 } 882 875 } 883 876 884 - static verdict_t process_ipv6(buf_t *pkt, metrics_t *metrics) 877 + static INLINING verdict_t process_ipv6(buf_t *pkt, metrics_t *metrics) 885 878 { 886 879 metrics->l3_protocol_packets_total_ipv6++; 887 880
+2
tools/testing/selftests/bpf/progs/test_cls_redirect_subprogs.c
··· 1 + #define SUBPROGS 2 + #include "test_cls_redirect.c"