| // SPDX-License-Identifier: GPL-2.0 |
| |
| #define _GNU_SOURCE |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <bpf/bpf.h> |
| #include <linux/bpf.h> |
| #include <bpf/libbpf.h> |
| #include <arpa/inet.h> |
| #include <asm/byteorder.h> |
| #include <netinet/udp.h> |
| #include <poll.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| #include "test_progs.h" |
| #include "network_helpers.h" |
| #include "bpf_util.h" |
| #include "bpf_flow.skel.h" |
| |
| #define CFG_PORT_INNER 8000 |
| #define CFG_PORT_GUE 6080 |
| #define SUBTEST_NAME_MAX_LEN 32 |
| #define TEST_NAME_MAX_LEN (32 + SUBTEST_NAME_MAX_LEN) |
| #define MAX_SOURCE_PORTS 3 |
| #define TEST_PACKETS_COUNT 10 |
| #define TEST_PACKET_LEN 100 |
| #define TEST_PACKET_PATTERN 'a' |
| #define TEST_IPV4 "192.168.0.1/32" |
| #define TEST_IPV6 "100::a/128" |
| #define TEST_TUNNEL_REMOTE "127.0.0.2" |
| #define TEST_TUNNEL_LOCAL "127.0.0.1" |
| |
| #define INIT_ADDR4(addr4, port) \ |
| { \ |
| .sin_family = AF_INET, \ |
| .sin_port = __constant_htons(port), \ |
| .sin_addr.s_addr = __constant_htonl(addr4), \ |
| } |
| |
| #define INIT_ADDR6(addr6, port) \ |
| { \ |
| .sin6_family = AF_INET6, \ |
| .sin6_port = __constant_htons(port), \ |
| .sin6_addr = addr6, \ |
| } |
| #define TEST_IN4_SRC_ADDR_DEFAULT INIT_ADDR4(INADDR_LOOPBACK + 2, 0) |
| #define TEST_IN4_DST_ADDR_DEFAULT INIT_ADDR4(INADDR_LOOPBACK, CFG_PORT_INNER) |
| #define TEST_OUT4_SRC_ADDR_DEFAULT INIT_ADDR4(INADDR_LOOPBACK + 1, 0) |
| #define TEST_OUT4_DST_ADDR_DEFAULT INIT_ADDR4(INADDR_LOOPBACK, 0) |
| |
| #define TEST_IN6_SRC_ADDR_DEFAULT INIT_ADDR6(IN6ADDR_LOOPBACK_INIT, 0) |
| #define TEST_IN6_DST_ADDR_DEFAULT \ |
| INIT_ADDR6(IN6ADDR_LOOPBACK_INIT, CFG_PORT_INNER) |
| #define TEST_OUT6_SRC_ADDR_DEFAULT INIT_ADDR6(IN6ADDR_LOOPBACK_INIT, 0) |
| #define TEST_OUT6_DST_ADDR_DEFAULT INIT_ADDR6(IN6ADDR_LOOPBACK_INIT, 0) |
| |
| #define TEST_IN4_SRC_ADDR_DISSECT_CONTINUE INIT_ADDR4(INADDR_LOOPBACK + 126, 0) |
| #define TEST_IN4_SRC_ADDR_IPIP INIT_ADDR4((in_addr_t)0x01010101, 0) |
| #define TEST_IN4_DST_ADDR_IPIP INIT_ADDR4((in_addr_t)0xC0A80001, CFG_PORT_INNER) |
| |
| struct grehdr { |
| uint16_t unused; |
| uint16_t protocol; |
| } __packed; |
| |
| struct guehdr { |
| union { |
| struct { |
| #if defined(__LITTLE_ENDIAN_BITFIELD) |
| __u8 hlen : 5, control : 1, version : 2; |
| #elif defined(__BIG_ENDIAN_BITFIELD) |
| __u8 version : 2, control : 1, hlen : 5; |
| #else |
| #error "Please fix <asm/byteorder.h>" |
| #endif |
| __u8 proto_ctype; |
| __be16 flags; |
| }; |
| __be32 word; |
| }; |
| }; |
| |
| static char buf[ETH_DATA_LEN]; |
| |
| struct test_configuration { |
| char name[SUBTEST_NAME_MAX_LEN]; |
| int (*test_setup)(void); |
| void (*test_teardown)(void); |
| int source_ports[MAX_SOURCE_PORTS]; |
| int cfg_l3_inner; |
| struct sockaddr_in in_saddr4; |
| struct sockaddr_in in_daddr4; |
| struct sockaddr_in6 in_saddr6; |
| struct sockaddr_in6 in_daddr6; |
| int cfg_l3_outer; |
| struct sockaddr_in out_saddr4; |
| struct sockaddr_in out_daddr4; |
| struct sockaddr_in6 out_saddr6; |
| struct sockaddr_in6 out_daddr6; |
| int cfg_encap_proto; |
| uint8_t cfg_dsfield_inner; |
| uint8_t cfg_dsfield_outer; |
| int cfg_l3_extra; |
| struct sockaddr_in extra_saddr4; |
| struct sockaddr_in extra_daddr4; |
| struct sockaddr_in6 extra_saddr6; |
| struct sockaddr_in6 extra_daddr6; |
| }; |
| |
| static unsigned long util_gettime(void) |
| { |
| struct timeval tv; |
| |
| gettimeofday(&tv, NULL); |
| return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); |
| } |
| |
| static void build_ipv4_header(void *header, uint8_t proto, uint32_t src, |
| uint32_t dst, int payload_len, uint8_t tos) |
| { |
| struct iphdr *iph = header; |
| |
| iph->ihl = 5; |
| iph->version = 4; |
| iph->tos = tos; |
| iph->ttl = 8; |
| iph->tot_len = htons(sizeof(*iph) + payload_len); |
| iph->id = htons(1337); |
| iph->protocol = proto; |
| iph->saddr = src; |
| iph->daddr = dst; |
| iph->check = build_ip_csum((void *)iph); |
| } |
| |
| static void ipv6_set_dsfield(struct ipv6hdr *ip6h, uint8_t dsfield) |
| { |
| uint16_t val, *ptr = (uint16_t *)ip6h; |
| |
| val = ntohs(*ptr); |
| val &= 0xF00F; |
| val |= ((uint16_t)dsfield) << 4; |
| *ptr = htons(val); |
| } |
| |
| static void build_ipv6_header(void *header, uint8_t proto, |
| const struct sockaddr_in6 *src, |
| const struct sockaddr_in6 *dst, int payload_len, |
| uint8_t dsfield) |
| { |
| struct ipv6hdr *ip6h = header; |
| |
| ip6h->version = 6; |
| ip6h->payload_len = htons(payload_len); |
| ip6h->nexthdr = proto; |
| ip6h->hop_limit = 8; |
| ipv6_set_dsfield(ip6h, dsfield); |
| |
| memcpy(&ip6h->saddr, &src->sin6_addr, sizeof(ip6h->saddr)); |
| memcpy(&ip6h->daddr, &dst->sin6_addr, sizeof(ip6h->daddr)); |
| } |
| |
| static void build_udp_header(void *header, int payload_len, uint16_t sport, |
| uint16_t dport, int family) |
| { |
| struct udphdr *udph = header; |
| int len = sizeof(*udph) + payload_len; |
| |
| udph->source = htons(sport); |
| udph->dest = htons(dport); |
| udph->len = htons(len); |
| udph->check = 0; |
| if (family == AF_INET) |
| udph->check = build_udp_v4_csum(header - sizeof(struct iphdr), |
| udph); |
| else |
| udph->check = build_udp_v6_csum(header - sizeof(struct ipv6hdr), |
| udph); |
| } |
| |
| static void build_gue_header(void *header, uint8_t proto) |
| { |
| struct guehdr *gueh = header; |
| |
| gueh->proto_ctype = proto; |
| } |
| |
| static void build_gre_header(void *header, uint16_t proto) |
| { |
| struct grehdr *greh = header; |
| |
| greh->protocol = htons(proto); |
| } |
| |
| static int l3_length(int family) |
| { |
| if (family == AF_INET) |
| return sizeof(struct iphdr); |
| else |
| return sizeof(struct ipv6hdr); |
| } |
| |
| static int build_packet(const struct test_configuration *test, uint16_t sport) |
| { |
| int ol3_len = 0, ol4_len = 0, il3_len = 0, il4_len = 0; |
| int el3_len = 0, packet_len; |
| |
| memset(buf, 0, ETH_DATA_LEN); |
| |
| if (test->cfg_l3_extra) |
| el3_len = l3_length(test->cfg_l3_extra); |
| |
| /* calculate header offsets */ |
| if (test->cfg_encap_proto) { |
| ol3_len = l3_length(test->cfg_l3_outer); |
| |
| if (test->cfg_encap_proto == IPPROTO_GRE) |
| ol4_len = sizeof(struct grehdr); |
| else if (test->cfg_encap_proto == IPPROTO_UDP) |
| ol4_len = sizeof(struct udphdr) + sizeof(struct guehdr); |
| } |
| |
| il3_len = l3_length(test->cfg_l3_inner); |
| il4_len = sizeof(struct udphdr); |
| |
| packet_len = el3_len + ol3_len + ol4_len + il3_len + il4_len + |
| TEST_PACKET_LEN; |
| if (!ASSERT_LE(packet_len, sizeof(buf), "check packet size")) |
| return -1; |
| |
| /* |
| * Fill packet from inside out, to calculate correct checksums. |
| * But create ip before udp headers, as udp uses ip for pseudo-sum. |
| */ |
| memset(buf + el3_len + ol3_len + ol4_len + il3_len + il4_len, |
| TEST_PACKET_PATTERN, TEST_PACKET_LEN); |
| |
| /* add zero byte for udp csum padding */ |
| buf[el3_len + ol3_len + ol4_len + il3_len + il4_len + TEST_PACKET_LEN] = |
| 0; |
| |
| switch (test->cfg_l3_inner) { |
| case PF_INET: |
| build_ipv4_header(buf + el3_len + ol3_len + ol4_len, |
| IPPROTO_UDP, test->in_saddr4.sin_addr.s_addr, |
| test->in_daddr4.sin_addr.s_addr, |
| il4_len + TEST_PACKET_LEN, |
| test->cfg_dsfield_inner); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf + el3_len + ol3_len + ol4_len, |
| IPPROTO_UDP, &test->in_saddr6, |
| &test->in_daddr6, il4_len + TEST_PACKET_LEN, |
| test->cfg_dsfield_inner); |
| break; |
| } |
| |
| build_udp_header(buf + el3_len + ol3_len + ol4_len + il3_len, |
| TEST_PACKET_LEN, sport, CFG_PORT_INNER, |
| test->cfg_l3_inner); |
| |
| if (!test->cfg_encap_proto) |
| return il3_len + il4_len + TEST_PACKET_LEN; |
| |
| switch (test->cfg_l3_outer) { |
| case PF_INET: |
| build_ipv4_header(buf + el3_len, test->cfg_encap_proto, |
| test->out_saddr4.sin_addr.s_addr, |
| test->out_daddr4.sin_addr.s_addr, |
| ol4_len + il3_len + il4_len + TEST_PACKET_LEN, |
| test->cfg_dsfield_outer); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf + el3_len, test->cfg_encap_proto, |
| &test->out_saddr6, &test->out_daddr6, |
| ol4_len + il3_len + il4_len + TEST_PACKET_LEN, |
| test->cfg_dsfield_outer); |
| break; |
| } |
| |
| switch (test->cfg_encap_proto) { |
| case IPPROTO_UDP: |
| build_gue_header(buf + el3_len + ol3_len + ol4_len - |
| sizeof(struct guehdr), |
| test->cfg_l3_inner == PF_INET ? IPPROTO_IPIP : |
| IPPROTO_IPV6); |
| build_udp_header(buf + el3_len + ol3_len, |
| sizeof(struct guehdr) + il3_len + il4_len + |
| TEST_PACKET_LEN, |
| sport, CFG_PORT_GUE, test->cfg_l3_outer); |
| break; |
| case IPPROTO_GRE: |
| build_gre_header(buf + el3_len + ol3_len, |
| test->cfg_l3_inner == PF_INET ? ETH_P_IP : |
| ETH_P_IPV6); |
| break; |
| } |
| |
| switch (test->cfg_l3_extra) { |
| case PF_INET: |
| build_ipv4_header(buf, |
| test->cfg_l3_outer == PF_INET ? IPPROTO_IPIP : |
| IPPROTO_IPV6, |
| test->extra_saddr4.sin_addr.s_addr, |
| test->extra_daddr4.sin_addr.s_addr, |
| ol3_len + ol4_len + il3_len + il4_len + |
| TEST_PACKET_LEN, |
| 0); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf, |
| test->cfg_l3_outer == PF_INET ? IPPROTO_IPIP : |
| IPPROTO_IPV6, |
| &test->extra_saddr6, &test->extra_daddr6, |
| ol3_len + ol4_len + il3_len + il4_len + |
| TEST_PACKET_LEN, |
| 0); |
| break; |
| } |
| |
| return el3_len + ol3_len + ol4_len + il3_len + il4_len + |
| TEST_PACKET_LEN; |
| } |
| |
| /* sender transmits encapsulated over RAW or unencap'd over UDP */ |
| static int setup_tx(const struct test_configuration *test) |
| { |
| int family, fd, ret; |
| |
| if (test->cfg_l3_extra) |
| family = test->cfg_l3_extra; |
| else if (test->cfg_l3_outer) |
| family = test->cfg_l3_outer; |
| else |
| family = test->cfg_l3_inner; |
| |
| fd = socket(family, SOCK_RAW, IPPROTO_RAW); |
| if (!ASSERT_OK_FD(fd, "setup tx socket")) |
| return fd; |
| |
| if (test->cfg_l3_extra) { |
| if (test->cfg_l3_extra == PF_INET) |
| ret = connect(fd, (void *)&test->extra_daddr4, |
| sizeof(test->extra_daddr4)); |
| else |
| ret = connect(fd, (void *)&test->extra_daddr6, |
| sizeof(test->extra_daddr6)); |
| if (!ASSERT_OK(ret, "connect")) { |
| close(fd); |
| return ret; |
| } |
| } else if (test->cfg_l3_outer) { |
| /* connect to destination if not encapsulated */ |
| if (test->cfg_l3_outer == PF_INET) |
| ret = connect(fd, (void *)&test->out_daddr4, |
| sizeof(test->out_daddr4)); |
| else |
| ret = connect(fd, (void *)&test->out_daddr6, |
| sizeof(test->out_daddr6)); |
| if (!ASSERT_OK(ret, "connect")) { |
| close(fd); |
| return ret; |
| } |
| } else { |
| /* otherwise using loopback */ |
| if (test->cfg_l3_inner == PF_INET) |
| ret = connect(fd, (void *)&test->in_daddr4, |
| sizeof(test->in_daddr4)); |
| else |
| ret = connect(fd, (void *)&test->in_daddr6, |
| sizeof(test->in_daddr6)); |
| if (!ASSERT_OK(ret, "connect")) { |
| close(fd); |
| return ret; |
| } |
| } |
| |
| return fd; |
| } |
| |
| /* receiver reads unencapsulated UDP */ |
| static int setup_rx(const struct test_configuration *test) |
| { |
| int fd, ret; |
| |
| fd = socket(test->cfg_l3_inner, SOCK_DGRAM, 0); |
| if (!ASSERT_OK_FD(fd, "socket rx")) |
| return fd; |
| |
| if (test->cfg_l3_inner == PF_INET) |
| ret = bind(fd, (void *)&test->in_daddr4, |
| sizeof(test->in_daddr4)); |
| else |
| ret = bind(fd, (void *)&test->in_daddr6, |
| sizeof(test->in_daddr6)); |
| if (!ASSERT_OK(ret, "bind rx")) { |
| close(fd); |
| return ret; |
| } |
| |
| return fd; |
| } |
| |
| static int do_tx(int fd, const char *pkt, int len) |
| { |
| int ret; |
| |
| ret = write(fd, pkt, len); |
| return ret != len; |
| } |
| |
| static int do_poll(int fd, short events, int timeout) |
| { |
| struct pollfd pfd; |
| int ret; |
| |
| pfd.fd = fd; |
| pfd.events = events; |
| |
| ret = poll(&pfd, 1, timeout); |
| return ret; |
| } |
| |
| static int do_rx(int fd) |
| { |
| char rbuf; |
| int ret, num = 0; |
| |
| while (1) { |
| ret = recv(fd, &rbuf, 1, MSG_DONTWAIT); |
| if (ret == -1 && errno == EAGAIN) |
| break; |
| if (ret < 0) |
| return -1; |
| if (!ASSERT_EQ(rbuf, TEST_PACKET_PATTERN, "check pkt pattern")) |
| return -1; |
| num++; |
| } |
| |
| return num; |
| } |
| |
| static int run_test(const struct test_configuration *test, |
| int source_port_index) |
| { |
| int fdt = -1, fdr = -1, len, tx = 0, rx = 0, err; |
| unsigned long tstop, tcur; |
| |
| fdr = setup_rx(test); |
| fdt = setup_tx(test); |
| if (!ASSERT_OK_FD(fdr, "setup rx") || !ASSERT_OK_FD(fdt, "setup tx")) { |
| err = -1; |
| goto out_close_sockets; |
| } |
| |
| len = build_packet(test, |
| (uint16_t)test->source_ports[source_port_index]); |
| if (!ASSERT_GT(len, 0, "build test packet")) |
| return -1; |
| |
| tcur = util_gettime(); |
| tstop = tcur; |
| |
| while (tx < TEST_PACKETS_COUNT) { |
| if (!ASSERT_OK(do_tx(fdt, buf, len), "do_tx")) |
| break; |
| tx++; |
| err = do_rx(fdr); |
| if (!ASSERT_GE(err, 0, "do_rx")) |
| break; |
| rx += err; |
| } |
| |
| /* read straggler packets, if any */ |
| if (rx < tx) { |
| tstop = util_gettime() + 100; |
| while (rx < tx) { |
| tcur = util_gettime(); |
| if (tcur >= tstop) |
| break; |
| |
| err = do_poll(fdr, POLLIN, tstop - tcur); |
| if (err < 0) |
| break; |
| err = do_rx(fdr); |
| if (err >= 0) |
| rx += err; |
| } |
| } |
| |
| out_close_sockets: |
| close(fdt); |
| close(fdr); |
| return rx; |
| } |
| |
| static int attach_and_configure_program(struct bpf_flow *skel) |
| { |
| struct bpf_map *prog_array = skel->maps.jmp_table; |
| int main_prog_fd, sub_prog_fd, map_fd, i, err; |
| struct bpf_program *prog; |
| char prog_name[32]; |
| |
| main_prog_fd = bpf_program__fd(skel->progs._dissect); |
| if (main_prog_fd < 0) |
| return main_prog_fd; |
| |
| err = bpf_prog_attach(main_prog_fd, 0, BPF_FLOW_DISSECTOR, 0); |
| if (err) |
| return err; |
| |
| map_fd = bpf_map__fd(prog_array); |
| if (map_fd < 0) |
| return map_fd; |
| |
| for (i = 0; i < bpf_map__max_entries(prog_array); i++) { |
| snprintf(prog_name, sizeof(prog_name), "flow_dissector_%d", i); |
| |
| prog = bpf_object__find_program_by_name(skel->obj, prog_name); |
| if (!prog) |
| return -1; |
| |
| sub_prog_fd = bpf_program__fd(prog); |
| if (sub_prog_fd < 0) |
| return -1; |
| |
| err = bpf_map_update_elem(map_fd, &i, &sub_prog_fd, BPF_ANY); |
| if (err) |
| return -1; |
| } |
| |
| return main_prog_fd; |
| } |
| |
| static void detach_program(struct bpf_flow *skel, int prog_fd) |
| { |
| bpf_prog_detach2(prog_fd, 0, BPF_FLOW_DISSECTOR); |
| } |
| |
| static int set_port_drop(int pf, bool multi_port) |
| { |
| char dst_port[16]; |
| |
| snprintf(dst_port, sizeof(dst_port), "%d", CFG_PORT_INNER); |
| |
| SYS(fail, "tc qdisc add dev lo ingress"); |
| SYS(fail_delete_qdisc, "tc filter add %s %s %s %s %s %s %s %s %s %s %s %s", |
| "dev lo", |
| "parent FFFF:", |
| "protocol", pf == PF_INET6 ? "ipv6" : "ip", |
| "pref 1337", |
| "flower", |
| "ip_proto udp", |
| "src_port", multi_port ? "8-10" : "9", |
| "dst_port", dst_port, |
| "action drop"); |
| return 0; |
| |
| fail_delete_qdisc: |
| SYS_NOFAIL("tc qdisc del dev lo ingress"); |
| fail: |
| return 1; |
| } |
| |
| static void remove_filter(void) |
| { |
| SYS_NOFAIL("tc filter del dev lo ingress"); |
| SYS_NOFAIL("tc qdisc del dev lo ingress"); |
| } |
| |
| static int ipv4_setup(void) |
| { |
| return set_port_drop(PF_INET, false); |
| } |
| |
| static int ipv6_setup(void) |
| { |
| return set_port_drop(PF_INET6, false); |
| } |
| |
| static int port_range_setup(void) |
| { |
| return set_port_drop(PF_INET, true); |
| } |
| |
| static int set_addresses(void) |
| { |
| SYS(out, "ip -4 addr add %s dev lo", TEST_IPV4); |
| SYS(out_remove_ipv4, "ip -6 addr add %s dev lo", TEST_IPV6); |
| return 0; |
| out_remove_ipv4: |
| SYS_NOFAIL("ip -4 addr del %s dev lo", TEST_IPV4); |
| out: |
| return -1; |
| } |
| |
| static void unset_addresses(void) |
| { |
| SYS_NOFAIL("ip -4 addr del %s dev lo", TEST_IPV4); |
| SYS_NOFAIL("ip -6 addr del %s dev lo", TEST_IPV6); |
| } |
| |
| static int ipip_setup(void) |
| { |
| if (!ASSERT_OK(set_addresses(), "configure addresses")) |
| return -1; |
| if (!ASSERT_OK(set_port_drop(PF_INET, false), "set filter")) |
| goto out_unset_addresses; |
| SYS(out_remove_filter, |
| "ip link add ipip_test type ipip remote %s local %s dev lo", |
| TEST_TUNNEL_REMOTE, TEST_TUNNEL_LOCAL); |
| SYS(out_clean_netif, "ip link set ipip_test up"); |
| return 0; |
| |
| out_clean_netif: |
| SYS_NOFAIL("ip link del ipip_test"); |
| out_remove_filter: |
| remove_filter(); |
| out_unset_addresses: |
| unset_addresses(); |
| return -1; |
| } |
| |
| static void ipip_shutdown(void) |
| { |
| SYS_NOFAIL("ip link del ipip_test"); |
| remove_filter(); |
| unset_addresses(); |
| } |
| |
| static int gre_setup(void) |
| { |
| if (!ASSERT_OK(set_addresses(), "configure addresses")) |
| return -1; |
| if (!ASSERT_OK(set_port_drop(PF_INET, false), "set filter")) |
| goto out_unset_addresses; |
| SYS(out_remove_filter, |
| "ip link add gre_test type gre remote %s local %s dev lo", |
| TEST_TUNNEL_REMOTE, TEST_TUNNEL_LOCAL); |
| SYS(out_clean_netif, "ip link set gre_test up"); |
| return 0; |
| |
| out_clean_netif: |
| SYS_NOFAIL("ip link del ipip_test"); |
| out_remove_filter: |
| remove_filter(); |
| out_unset_addresses: |
| unset_addresses(); |
| return -1; |
| } |
| |
| static void gre_shutdown(void) |
| { |
| SYS_NOFAIL("ip link del gre_test"); |
| remove_filter(); |
| unset_addresses(); |
| } |
| |
| static const struct test_configuration tests_input[] = { |
| { |
| .name = "ipv4", |
| .test_setup = ipv4_setup, |
| .test_teardown = remove_filter, |
| .source_ports = { 8, 9, 10 }, |
| .cfg_l3_inner = PF_INET, |
| .in_saddr4 = TEST_IN4_SRC_ADDR_DEFAULT, |
| .in_daddr4 = TEST_IN4_DST_ADDR_DEFAULT |
| }, |
| { |
| .name = "ipv4_continue_dissect", |
| .test_setup = ipv4_setup, |
| .test_teardown = remove_filter, |
| .source_ports = { 8, 9, 10 }, |
| .cfg_l3_inner = PF_INET, |
| .in_saddr4 = TEST_IN4_SRC_ADDR_DISSECT_CONTINUE, |
| .in_daddr4 = TEST_IN4_DST_ADDR_DEFAULT }, |
| { |
| .name = "ipip", |
| .test_setup = ipip_setup, |
| .test_teardown = ipip_shutdown, |
| .source_ports = { 8, 9, 10 }, |
| .cfg_l3_inner = PF_INET, |
| .in_saddr4 = TEST_IN4_SRC_ADDR_IPIP, |
| .in_daddr4 = TEST_IN4_DST_ADDR_IPIP, |
| .out_saddr4 = TEST_OUT4_SRC_ADDR_DEFAULT, |
| .out_daddr4 = TEST_OUT4_DST_ADDR_DEFAULT, |
| .cfg_l3_outer = PF_INET, |
| .cfg_encap_proto = IPPROTO_IPIP, |
| |
| }, |
| { |
| .name = "gre", |
| .test_setup = gre_setup, |
| .test_teardown = gre_shutdown, |
| .source_ports = { 8, 9, 10 }, |
| .cfg_l3_inner = PF_INET, |
| .in_saddr4 = TEST_IN4_SRC_ADDR_IPIP, |
| .in_daddr4 = TEST_IN4_DST_ADDR_IPIP, |
| .out_saddr4 = TEST_OUT4_SRC_ADDR_DEFAULT, |
| .out_daddr4 = TEST_OUT4_DST_ADDR_DEFAULT, |
| .cfg_l3_outer = PF_INET, |
| .cfg_encap_proto = IPPROTO_GRE, |
| }, |
| { |
| .name = "port_range", |
| .test_setup = port_range_setup, |
| .test_teardown = remove_filter, |
| .source_ports = { 7, 9, 11 }, |
| .cfg_l3_inner = PF_INET, |
| .in_saddr4 = TEST_IN4_SRC_ADDR_DEFAULT, |
| .in_daddr4 = TEST_IN4_DST_ADDR_DEFAULT }, |
| { |
| .name = "ipv6", |
| .test_setup = ipv6_setup, |
| .test_teardown = remove_filter, |
| .source_ports = { 8, 9, 10 }, |
| .cfg_l3_inner = PF_INET6, |
| .in_saddr6 = TEST_IN6_SRC_ADDR_DEFAULT, |
| .in_daddr6 = TEST_IN6_DST_ADDR_DEFAULT |
| }, |
| }; |
| |
| struct test_ctx { |
| struct bpf_flow *skel; |
| struct netns_obj *ns; |
| int prog_fd; |
| }; |
| |
| static int test_global_init(struct test_ctx *ctx) |
| { |
| int err; |
| |
| ctx->skel = bpf_flow__open_and_load(); |
| if (!ASSERT_OK_PTR(ctx->skel, "open and load flow_dissector")) |
| return -1; |
| |
| ctx->ns = netns_new("flow_dissector_classification", true); |
| if (!ASSERT_OK_PTR(ctx->ns, "switch ns")) |
| goto out_destroy_skel; |
| |
| err = write_sysctl("/proc/sys/net/ipv4/conf/default/rp_filter", "0"); |
| err |= write_sysctl("/proc/sys/net/ipv4/conf/all/rp_filter", "0"); |
| err |= write_sysctl("/proc/sys/net/ipv4/conf/lo/rp_filter", "0"); |
| if (!ASSERT_OK(err, "configure net tunables")) |
| goto out_clean_ns; |
| |
| ctx->prog_fd = attach_and_configure_program(ctx->skel); |
| if (!ASSERT_OK_FD(ctx->prog_fd, "attach and configure program")) |
| goto out_clean_ns; |
| return 0; |
| out_clean_ns: |
| netns_free(ctx->ns); |
| out_destroy_skel: |
| bpf_flow__destroy(ctx->skel); |
| return -1; |
| } |
| |
| static void test_global_shutdown(struct test_ctx *ctx) |
| { |
| detach_program(ctx->skel, ctx->prog_fd); |
| netns_free(ctx->ns); |
| bpf_flow__destroy(ctx->skel); |
| } |
| |
| void test_flow_dissector_classification(void) |
| { |
| struct test_ctx ctx; |
| const struct test_configuration *test; |
| int i; |
| |
| if (test_global_init(&ctx)) |
| return; |
| |
| for (i = 0; i < ARRAY_SIZE(tests_input); i++) { |
| if (!test__start_subtest(tests_input[i].name)) |
| continue; |
| test = &tests_input[i]; |
| /* All tests are expected to have one rx-ok port first, |
| * then a non-working rx port, and finally a rx-ok port |
| */ |
| if (test->test_setup && |
| !ASSERT_OK(test->test_setup(), "init filter")) |
| continue; |
| |
| ASSERT_EQ(run_test(test, 0), TEST_PACKETS_COUNT, |
| "test first port"); |
| ASSERT_EQ(run_test(test, 1), 0, "test second port"); |
| ASSERT_EQ(run_test(test, 2), TEST_PACKETS_COUNT, |
| "test third port"); |
| if (test->test_teardown) |
| test->test_teardown(); |
| } |
| test_global_shutdown(&ctx); |
| } |