| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2023 Facebook */ |
| #include <test_progs.h> |
| #include <linux/in6.h> |
| #include <sys/socket.h> |
| #include <sched.h> |
| #include <unistd.h> |
| #include "cgroup_helpers.h" |
| #include "testing_helpers.h" |
| #include "cgroup_tcp_skb.skel.h" |
| #include "cgroup_tcp_skb.h" |
| #include "network_helpers.h" |
| |
| #define CGROUP_TCP_SKB_PATH "/test_cgroup_tcp_skb" |
| |
| static int install_filters(int cgroup_fd, |
| struct bpf_link **egress_link, |
| struct bpf_link **ingress_link, |
| struct bpf_program *egress_prog, |
| struct bpf_program *ingress_prog, |
| struct cgroup_tcp_skb *skel) |
| { |
| /* Prepare filters */ |
| skel->bss->g_sock_state = 0; |
| skel->bss->g_unexpected = 0; |
| *egress_link = |
| bpf_program__attach_cgroup(egress_prog, |
| cgroup_fd); |
| if (!ASSERT_OK_PTR(egress_link, "egress_link")) |
| return -1; |
| *ingress_link = |
| bpf_program__attach_cgroup(ingress_prog, |
| cgroup_fd); |
| if (!ASSERT_OK_PTR(ingress_link, "ingress_link")) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void uninstall_filters(struct bpf_link **egress_link, |
| struct bpf_link **ingress_link) |
| { |
| bpf_link__destroy(*egress_link); |
| *egress_link = NULL; |
| bpf_link__destroy(*ingress_link); |
| *ingress_link = NULL; |
| } |
| |
| static int create_client_sock_v6(void) |
| { |
| int fd; |
| |
| fd = socket(AF_INET6, SOCK_STREAM, 0); |
| if (fd < 0) { |
| perror("socket"); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| /* Connect to the server in a cgroup from the outside of the cgroup. */ |
| static int talk_to_cgroup(int *client_fd, int *listen_fd, int *service_fd, |
| struct cgroup_tcp_skb *skel) |
| { |
| int err, cp; |
| char buf[5]; |
| int port; |
| |
| /* Create client & server socket */ |
| err = join_root_cgroup(); |
| if (!ASSERT_OK(err, "join_root_cgroup")) |
| return -1; |
| *client_fd = create_client_sock_v6(); |
| if (!ASSERT_GE(*client_fd, 0, "client_fd")) |
| return -1; |
| err = join_cgroup(CGROUP_TCP_SKB_PATH); |
| if (!ASSERT_OK(err, "join_cgroup")) |
| return -1; |
| *listen_fd = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); |
| if (!ASSERT_GE(*listen_fd, 0, "listen_fd")) |
| return -1; |
| port = get_socket_local_port(*listen_fd); |
| if (!ASSERT_GE(port, 0, "get_socket_local_port")) |
| return -1; |
| skel->bss->g_sock_port = ntohs(port); |
| |
| /* Connect client to server */ |
| err = connect_fd_to_fd(*client_fd, *listen_fd, 0); |
| if (!ASSERT_OK(err, "connect_fd_to_fd")) |
| return -1; |
| *service_fd = accept(*listen_fd, NULL, NULL); |
| if (!ASSERT_GE(*service_fd, 0, "service_fd")) |
| return -1; |
| err = join_root_cgroup(); |
| if (!ASSERT_OK(err, "join_root_cgroup")) |
| return -1; |
| cp = write(*client_fd, "hello", 5); |
| if (!ASSERT_EQ(cp, 5, "write")) |
| return -1; |
| cp = read(*service_fd, buf, 5); |
| if (!ASSERT_EQ(cp, 5, "read")) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* Connect to the server out of a cgroup from inside the cgroup. */ |
| static int talk_to_outside(int *client_fd, int *listen_fd, int *service_fd, |
| struct cgroup_tcp_skb *skel) |
| |
| { |
| int err, cp; |
| char buf[5]; |
| int port; |
| |
| /* Create client & server socket */ |
| err = join_root_cgroup(); |
| if (!ASSERT_OK(err, "join_root_cgroup")) |
| return -1; |
| *listen_fd = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); |
| if (!ASSERT_GE(*listen_fd, 0, "listen_fd")) |
| return -1; |
| err = join_cgroup(CGROUP_TCP_SKB_PATH); |
| if (!ASSERT_OK(err, "join_cgroup")) |
| return -1; |
| *client_fd = create_client_sock_v6(); |
| if (!ASSERT_GE(*client_fd, 0, "client_fd")) |
| return -1; |
| err = join_root_cgroup(); |
| if (!ASSERT_OK(err, "join_root_cgroup")) |
| return -1; |
| port = get_socket_local_port(*listen_fd); |
| if (!ASSERT_GE(port, 0, "get_socket_local_port")) |
| return -1; |
| skel->bss->g_sock_port = ntohs(port); |
| |
| /* Connect client to server */ |
| err = connect_fd_to_fd(*client_fd, *listen_fd, 0); |
| if (!ASSERT_OK(err, "connect_fd_to_fd")) |
| return -1; |
| *service_fd = accept(*listen_fd, NULL, NULL); |
| if (!ASSERT_GE(*service_fd, 0, "service_fd")) |
| return -1; |
| cp = write(*client_fd, "hello", 5); |
| if (!ASSERT_EQ(cp, 5, "write")) |
| return -1; |
| cp = read(*service_fd, buf, 5); |
| if (!ASSERT_EQ(cp, 5, "read")) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int close_connection(int *closing_fd, int *peer_fd, int *listen_fd, |
| struct cgroup_tcp_skb *skel) |
| { |
| __u32 saved_packet_count = 0; |
| int err; |
| int i; |
| |
| /* Wait for ACKs to be sent */ |
| saved_packet_count = skel->bss->g_packet_count; |
| usleep(100000); /* 0.1s */ |
| for (i = 0; |
| skel->bss->g_packet_count != saved_packet_count && i < 10; |
| i++) { |
| saved_packet_count = skel->bss->g_packet_count; |
| usleep(100000); /* 0.1s */ |
| } |
| if (!ASSERT_EQ(skel->bss->g_packet_count, saved_packet_count, |
| "packet_count")) |
| return -1; |
| |
| skel->bss->g_packet_count = 0; |
| saved_packet_count = 0; |
| |
| /* Half shutdown to make sure the closing socket having a chance to |
| * receive a FIN from the peer. |
| */ |
| err = shutdown(*closing_fd, SHUT_WR); |
| if (!ASSERT_OK(err, "shutdown closing_fd")) |
| return -1; |
| |
| /* Wait for FIN and the ACK of the FIN to be observed */ |
| for (i = 0; |
| skel->bss->g_packet_count < saved_packet_count + 2 && i < 10; |
| i++) |
| usleep(100000); /* 0.1s */ |
| if (!ASSERT_GE(skel->bss->g_packet_count, saved_packet_count + 2, |
| "packet_count")) |
| return -1; |
| |
| saved_packet_count = skel->bss->g_packet_count; |
| |
| /* Fully shutdown the connection */ |
| err = close(*peer_fd); |
| if (!ASSERT_OK(err, "close peer_fd")) |
| return -1; |
| *peer_fd = -1; |
| |
| /* Wait for FIN and the ACK of the FIN to be observed */ |
| for (i = 0; |
| skel->bss->g_packet_count < saved_packet_count + 2 && i < 10; |
| i++) |
| usleep(100000); /* 0.1s */ |
| if (!ASSERT_GE(skel->bss->g_packet_count, saved_packet_count + 2, |
| "packet_count")) |
| return -1; |
| |
| err = close(*closing_fd); |
| if (!ASSERT_OK(err, "close closing_fd")) |
| return -1; |
| *closing_fd = -1; |
| |
| close(*listen_fd); |
| *listen_fd = -1; |
| |
| return 0; |
| } |
| |
| /* This test case includes four scenarios: |
| * 1. Connect to the server from outside the cgroup and close the connection |
| * from outside the cgroup. |
| * 2. Connect to the server from outside the cgroup and close the connection |
| * from inside the cgroup. |
| * 3. Connect to the server from inside the cgroup and close the connection |
| * from outside the cgroup. |
| * 4. Connect to the server from inside the cgroup and close the connection |
| * from inside the cgroup. |
| * |
| * The test case is to verify that cgroup_skb/{egress,ingress} filters |
| * receive expected packets including SYN, SYN/ACK, ACK, FIN, and FIN/ACK. |
| */ |
| void test_cgroup_tcp_skb(void) |
| { |
| struct bpf_link *ingress_link = NULL; |
| struct bpf_link *egress_link = NULL; |
| int client_fd = -1, listen_fd = -1; |
| struct cgroup_tcp_skb *skel; |
| int service_fd = -1; |
| int cgroup_fd = -1; |
| int err; |
| |
| skel = cgroup_tcp_skb__open_and_load(); |
| if (!ASSERT_OK(!skel, "skel_open_load")) |
| return; |
| |
| err = setup_cgroup_environment(); |
| if (!ASSERT_OK(err, "setup_cgroup_environment")) |
| goto cleanup; |
| |
| cgroup_fd = create_and_get_cgroup(CGROUP_TCP_SKB_PATH); |
| if (!ASSERT_GE(cgroup_fd, 0, "cgroup_fd")) |
| goto cleanup; |
| |
| /* Scenario 1 */ |
| err = install_filters(cgroup_fd, &egress_link, &ingress_link, |
| skel->progs.server_egress, |
| skel->progs.server_ingress, |
| skel); |
| if (!ASSERT_OK(err, "install_filters")) |
| goto cleanup; |
| |
| err = talk_to_cgroup(&client_fd, &listen_fd, &service_fd, skel); |
| if (!ASSERT_OK(err, "talk_to_cgroup")) |
| goto cleanup; |
| |
| err = close_connection(&client_fd, &service_fd, &listen_fd, skel); |
| if (!ASSERT_OK(err, "close_connection")) |
| goto cleanup; |
| |
| ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected"); |
| ASSERT_EQ(skel->bss->g_sock_state, CLOSED, "g_sock_state"); |
| |
| uninstall_filters(&egress_link, &ingress_link); |
| |
| /* Scenario 2 */ |
| err = install_filters(cgroup_fd, &egress_link, &ingress_link, |
| skel->progs.server_egress_srv, |
| skel->progs.server_ingress_srv, |
| skel); |
| |
| err = talk_to_cgroup(&client_fd, &listen_fd, &service_fd, skel); |
| if (!ASSERT_OK(err, "talk_to_cgroup")) |
| goto cleanup; |
| |
| err = close_connection(&service_fd, &client_fd, &listen_fd, skel); |
| if (!ASSERT_OK(err, "close_connection")) |
| goto cleanup; |
| |
| ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected"); |
| ASSERT_EQ(skel->bss->g_sock_state, TIME_WAIT, "g_sock_state"); |
| |
| uninstall_filters(&egress_link, &ingress_link); |
| |
| /* Scenario 3 */ |
| err = install_filters(cgroup_fd, &egress_link, &ingress_link, |
| skel->progs.client_egress_srv, |
| skel->progs.client_ingress_srv, |
| skel); |
| |
| err = talk_to_outside(&client_fd, &listen_fd, &service_fd, skel); |
| if (!ASSERT_OK(err, "talk_to_outside")) |
| goto cleanup; |
| |
| err = close_connection(&service_fd, &client_fd, &listen_fd, skel); |
| if (!ASSERT_OK(err, "close_connection")) |
| goto cleanup; |
| |
| ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected"); |
| ASSERT_EQ(skel->bss->g_sock_state, CLOSED, "g_sock_state"); |
| |
| uninstall_filters(&egress_link, &ingress_link); |
| |
| /* Scenario 4 */ |
| err = install_filters(cgroup_fd, &egress_link, &ingress_link, |
| skel->progs.client_egress, |
| skel->progs.client_ingress, |
| skel); |
| |
| err = talk_to_outside(&client_fd, &listen_fd, &service_fd, skel); |
| if (!ASSERT_OK(err, "talk_to_outside")) |
| goto cleanup; |
| |
| err = close_connection(&client_fd, &service_fd, &listen_fd, skel); |
| if (!ASSERT_OK(err, "close_connection")) |
| goto cleanup; |
| |
| ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected"); |
| ASSERT_EQ(skel->bss->g_sock_state, TIME_WAIT, "g_sock_state"); |
| |
| uninstall_filters(&egress_link, &ingress_link); |
| |
| cleanup: |
| close(client_fd); |
| close(listen_fd); |
| close(service_fd); |
| close(cgroup_fd); |
| bpf_link__destroy(egress_link); |
| bpf_link__destroy(ingress_link); |
| cleanup_cgroup_environment(); |
| cgroup_tcp_skb__destroy(skel); |
| } |