From 0b7f14c0a9502ac853a1f56b890d8153123aeb7d Mon Sep 17 00:00:00 2001 From: Roberto Scolaro Date: Tue, 26 Mar 2024 10:54:45 +0000 Subject: [PATCH] new(libsinsp_e2e_tests): add forking and net tests Signed-off-by: Roberto Scolaro --- test/libsinsp_e2e/CMakeLists.txt | 5 + test/libsinsp_e2e/forking.cpp | 1113 +++++++++++++++++ test/libsinsp_e2e/ipv6.cpp | 461 +++++++ test/libsinsp_e2e/libsinsp_test_var.h.in | 2 +- test/libsinsp_e2e/resources/CMakeLists.txt | 13 + .../resources/captures/curl_google.scap | Bin 0 -> 22296 bytes .../resources/captures/single_ipv6_conn.scap | Bin 0 -> 23489 bytes .../resources/captures/test_ipv6_client.scap | Bin 0 -> 11154 bytes test/libsinsp_e2e/resources/chname.cpp | 63 + .../resources/forking_main_thread_exit.c | 57 + test/libsinsp_e2e/resources/forking_nested.c | 150 +++ test/libsinsp_e2e/scap_file_reader.h | 89 ++ test/libsinsp_e2e/tcp_client_server.cpp | 456 +++++++ test/libsinsp_e2e/tcp_client_server.h | 476 +++++++ .../tcp_client_server_ipv4_mapped.cpp | 765 +++++++++++ test/libsinsp_e2e/test_helper.cpp | 46 + test/libsinsp_e2e/udp_client_server.cpp | 1099 ++++++++++++++++ test/libsinsp_e2e/utils.h | 85 ++ 18 files changed, 4879 insertions(+), 1 deletion(-) create mode 100644 test/libsinsp_e2e/forking.cpp create mode 100644 test/libsinsp_e2e/ipv6.cpp create mode 100644 test/libsinsp_e2e/resources/captures/curl_google.scap create mode 100644 test/libsinsp_e2e/resources/captures/single_ipv6_conn.scap create mode 100644 test/libsinsp_e2e/resources/captures/test_ipv6_client.scap create mode 100644 test/libsinsp_e2e/resources/chname.cpp create mode 100644 test/libsinsp_e2e/resources/forking_main_thread_exit.c create mode 100644 test/libsinsp_e2e/resources/forking_nested.c create mode 100644 test/libsinsp_e2e/scap_file_reader.h create mode 100644 test/libsinsp_e2e/tcp_client_server.cpp create mode 100644 test/libsinsp_e2e/tcp_client_server.h create mode 100644 test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp create mode 100644 test/libsinsp_e2e/udp_client_server.cpp create mode 100644 test/libsinsp_e2e/utils.h diff --git a/test/libsinsp_e2e/CMakeLists.txt b/test/libsinsp_e2e/CMakeLists.txt index ac4b15c8804..67f3cd67880 100755 --- a/test/libsinsp_e2e/CMakeLists.txt +++ b/test/libsinsp_e2e/CMakeLists.txt @@ -36,14 +36,19 @@ add_executable(libsinsp_e2e_tests container/container_cgroup.cpp container/docker_utils.cpp event_capture.cpp + forking.cpp fs.cpp + ipv6.cpp main.cpp paths.cpp process.cpp subprocess.cpp sys_call_test.cpp + tcp_client_server.cpp + tcp_client_server_ipv4_mapped.cpp threadinfo.cpp thread_state.cpp + udp_client_server.cpp ) if(BUILD_BPF) diff --git a/test/libsinsp_e2e/forking.cpp b/test/libsinsp_e2e/forking.cpp new file mode 100644 index 00000000000..a5286fc0747 --- /dev/null +++ b/test/libsinsp_e2e/forking.cpp @@ -0,0 +1,1113 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "event_capture.h" +#include "sys_call_test.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define FILENAME "test_tmpfile" + +TEST_F(sys_call_test, forking) +{ + // int callnum = 0; + + int ptid; // parent tid + int ctid; // child tid + int gptid; // grandparent tid + int xstatus = 33; // child exit value + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == ptid || evt->get_tid() == ctid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + pid_t childtid; + int status; + childtid = fork(); + + int fd = creat(FILENAME, S_IRWXU); + + if (childtid >= 0) // fork succeeded + { + if (childtid == 0) // fork() returns 0 to the child process + { + ctid = getpid(); + usleep(100); // sleep for 0.1 seconds + close(fd); + _exit(xstatus); // child exits with specific return code + } + else // fork() returns new pid to the parent process + { + ptid = getpid(); + gptid = getppid(); + + close(fd); + + wait(&status); // wait for child to exit, and store its status + // Use WEXITSTATUS to validate status. + } + } + else + { + FAIL(); + } + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) {}; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); +} + +TEST_F(sys_call_test, forking_while_scap_stopped) +{ + int ptid; // parent tid + int ctid; // child tid + int xstatus = 33; // child exit value + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == ptid || evt->get_tid() == ctid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + int status; + + // + // Stop the capture just before the fork so we lose the event. + // + { + std::scoped_lock inspector_handle_lock(inspector_handle); + inspector_handle->stop_capture(); + } + + ctid = fork(); + + int fd = creat(FILENAME, S_IRWXU); + + if (ctid >= 0) // fork succeeded + { + if (ctid == 0) // fork() returns 0 to the child process + { + // + // Restart the capture. + // This is a bit messy because we are in the child + // but it works because the underlying scap's fds + // are duplicated so the ioctl will make its way to + // the parent process as well. + // It's a simple way to make sure the capture is started + // after the child's clone returned. + // + inspector_handle.unsafe_ptr()->start_capture(); + + // + // Wait for 5 seconds to make sure the process will still + // exist when the sinsp will do the lookup to /proc + // + usleep(5000000); + close(fd); + _exit(xstatus); // child exits with specific return code + } + else // fork() returns new pid to the parent process + { + ptid = getpid(); + + close(fd); + + wait(&status); // wait for child to exit, and store its status + // Use WEXITSTATUS to validate status. + } + } + else + { + FAIL(); + } + }; + + // + // OUTPUT VALDATION + // + bool child_exists = false; + bool parent_exists = false; + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + + if (e->get_type() == PPME_SCHEDSWITCH_1_E || e->get_type() == PPME_SCHEDSWITCH_6_E || + e->get_type() == PPME_PROCINFO_E) + { + return; + } + + // + // In both cases, the process should exist + // + if (e->get_tid() == ptid && !parent_exists) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + if (ti) + { + parent_exists = true; + } + + EXPECT_NE((sinsp_threadinfo*)NULL, ti); + } + + if (e->get_tid() == ctid && !child_exists) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + if (ti) + { + child_exists = true; + } + + EXPECT_NE((sinsp_threadinfo*)NULL, ti); + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + EXPECT_TRUE(child_exists); + EXPECT_TRUE(parent_exists); +} + +TEST_F(sys_call_test, forking_process_expired) +{ + int ptid; // parent tid + int ctid; // child tid + int status; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return evt->get_tid() == ptid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + ctid = fork(); + + if (ctid >= 0) // fork succeeded + { + if (ctid == 0) // fork() returns 0 to the child process + { + pause(); + FAIL(); + } + else // fork() returns new pid to the parent process + { + ptid = getpid(); + + // + // Wait 10 seconds. During this time, the process should NOT be removed + // + struct timespec req, rem; + req.tv_sec = 10; + req.tv_nsec = 0; + + syscall(__NR_nanosleep, &req, &rem); + + kill(ctid, SIGUSR1); + wait(&status); + } + } + else + { + FAIL(); + } + }; + + bool sleep_caught = false; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + + if (e->get_tid() == ptid) + { + if (e->get_type() == PPME_SYSCALL_NANOSLEEP_E && !sleep_caught) + { + // + // The child should exist + // + sinsp_threadinfo* ti = param.m_inspector->get_thread_ref(ctid, false, true).get(); + EXPECT_NE((sinsp_threadinfo*)NULL, ti); + } + else if (e->get_type() == PPME_SYSCALL_NANOSLEEP_X && !sleep_caught) + { + // + // The child should exist + // + sinsp_threadinfo* ti = param.m_inspector->get_thread_ref(ctid, false, true).get(); + EXPECT_NE((sinsp_threadinfo*)NULL, ti); + sleep_caught = true; + } + } + else + { + FAIL(); + } + }; + + ASSERT_NO_FATAL_FAILURE({ + event_capture::run(test, + callback, + filter, + event_capture::do_nothing, + event_capture::do_nothing, + event_capture::always_continue, + 131072, + 5 * ONE_SECOND_IN_NS, + ONE_SECOND_IN_NS); + }); + + EXPECT_TRUE(sleep_caught); +} + +/////////////////////////////////////////////////////////////////////////////// +// CLONE VARIANTS +/////////////////////////////////////////////////////////////////////////////// +int ctid; // child tid + +typedef struct +{ + int fd; + int signal; +} clone_params; + +static int clone_callback_1(void* arg) +{ + clone_params* cp; + + cp = (clone_params*)arg; /* Cast arg to true form */ + // getpid() is cached by glibc, usually is invalidated + // correctly in case of fork() or clone() but since we are + // using a weird clone() here something goes wrong with + // recent version of glibc + ctid = syscall(SYS_getpid); + close(cp->fd); + return 0; +} + +TEST_F(sys_call_test, forking_clone_fs) +{ + int callnum = 0; + char bcwd[1024]; + int prfd; + int ptid; // parent tid + int flags = CLONE_FILES | CLONE_FS | CLONE_VM; + int drflags = PPM_CL_CLONE_FILES | PPM_CL_CLONE_FS | PPM_CL_CLONE_VM; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == ptid || evt->get_tid() == ctid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + const int STACK_SIZE = 65536; /* Stack size for cloned child */ + char* stack; /* Start of stack buffer area */ + char* stackTop; /* End of stack buffer area */ + clone_params cp; /* Passed to child function */ + int status; + pid_t pid; + + ptid = getpid(); + + /* Set up an argument structure to be passed to cloned child, and + set some process attributes that will be modified by child */ + + cp.fd = open(FILENAME, O_CREAT | O_WRONLY, S_IRWXU); /* Child will close this fd */ + if (cp.fd == -1) + FAIL(); + prfd = cp.fd; + + cp.signal = SIGTERM; /* Child will change disposition */ + if (signal(cp.signal, SIG_IGN) == SIG_ERR) + FAIL(); + + /* Initialize clone flags using command-line argument (if supplied) */ + + /* Allocate stack for child */ + + stack = (char*)malloc(STACK_SIZE); + if (stack == NULL) + FAIL(); + stackTop = stack + STACK_SIZE; /* Assume stack grows downward */ + + /* Create child; child commences execution in childFunc() */ + + if (clone(clone_callback_1, stackTop, flags, &cp) == -1) + FAIL(); + + /* Parent falls through to here. Wait for child; __WCLONE option is + required for child notifying with signal other than SIGCHLD. */ + + pid = waitpid(-1, &status, __WCLONE); + if (pid == -1) + FAIL(); + + close(cp.fd); + + sleep(1); + free(stack); + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + if (e->get_type() == PPME_SYSCALL_CLONE_20_X) + { + uint64_t res = std::stoll(e->get_param_value_str("res", false)); + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (ti->get_comm() != "libsinsp_e2e_te") + { + return; + } + + if (res == 0) + { + EXPECT_EQ(ctid, ti->m_tid); + } + else + { + EXPECT_EQ(ptid, ti->m_tid); + } + + EXPECT_NE(std::string::npos, e->get_param_value_str("exe").find("libsinsp_e2e_tests")); + EXPECT_EQ("libsinsp_e2e_te", ti->get_comm()); + std::string tmps = getcwd(bcwd, 1024); + EXPECT_EQ(tmps + "/", ti->get_cwd()); + EXPECT_EQ("", e->get_param_value_str("cwd")); + EXPECT_EQ(drflags, std::stol(e->get_param_value_str("flags", false))); + callnum++; + } + else if (e->get_type() == PPME_SYSCALL_CLOSE_E) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (ti->m_tid == ptid || ti->m_tid == ctid) + { + int64_t clfd = std::stoll(e->get_param_value_str("fd", false)); + + if (clfd == prfd) + { + callnum++; + } + } + } + else if (e->get_type() == PPME_SYSCALL_CLOSE_X) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (callnum < 3) + { + return; + } + + int64_t res = std::stoll(e->get_param_value_str("res", false)); + + if (ti->m_tid == ptid) + { + EXPECT_GT(0, res); + } + else if (ti->m_tid == ctid) + { + EXPECT_EQ(0, res); + } + + callnum++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + if (callnum != 6 && callnum != 7) + { + FAIL() << "callnum=" << callnum; + } +} + +TEST_F(sys_call_test, forking_clone_nofs) +{ + int callnum = 0; + char bcwd[1024]; + int prfd; + int ptid; // parent tid + int flags = CLONE_FS | CLONE_VM; + int drflags = PPM_CL_CLONE_FS | PPM_CL_CLONE_VM; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == ptid || evt->get_tid() == ctid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + const int STACK_SIZE = 65536; /* Stack size for cloned child */ + char* stack; /* Start of stack buffer area */ + char* stackTop; /* End of stack buffer area */ + clone_params cp; /* Passed to child function */ + int status; + pid_t pid; + + ptid = getpid(); + + /* Set up an argument structure to be passed to cloned child, and + set some process attributes that will be modified by child */ + + cp.fd = open(FILENAME, O_CREAT | O_WRONLY, S_IRWXU); /* Child will close this fd */ + if (cp.fd == -1) + FAIL(); + prfd = cp.fd; + + cp.signal = SIGTERM; /* Child will change disposition */ + if (signal(cp.signal, SIG_IGN) == SIG_ERR) + FAIL(); + + /* Initialize clone flags using command-line argument (if supplied) */ + + /* Allocate stack for child */ + + stack = (char*)malloc(STACK_SIZE); + if (stack == NULL) + FAIL(); + stackTop = stack + STACK_SIZE; /* Assume stack grows downward */ + + /* Create child; child commences execution in childFunc() */ + + if (clone(clone_callback_1, stackTop, flags, &cp) == -1) + FAIL(); + + /* Parent falls through to here. Wait for child; __WCLONE option is + required for child notifying with signal other than SIGCHLD. */ + + pid = waitpid(-1, &status, __WCLONE); + if (pid == -1) + FAIL(); + + close(cp.fd); + + sleep(1); + free(stack); + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + if (e->get_type() == PPME_SYSCALL_CLONE_20_X) + { + uint64_t res = std::stoull(e->get_param_value_str("res", false)); + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (ti->get_comm() != "libsinsp_e2e_te") + { + return; + } + + if (res == 0) + { + EXPECT_EQ(ctid, ti->m_tid); + } + else + { + EXPECT_EQ(ptid, ti->m_tid); + } + + EXPECT_NE(std::string::npos, e->get_param_value_str("exe").find("libsinsp_e2e_te")); + EXPECT_EQ("libsinsp_e2e_te", ti->get_comm()); + std::string tmps = getcwd(bcwd, 1024); + EXPECT_EQ(tmps + "/", ti->get_cwd()); + EXPECT_EQ("", e->get_param_value_str("cwd")); + EXPECT_EQ(drflags, std::stol(e->get_param_value_str("flags", false))); + callnum++; + } + else if (e->get_type() == PPME_SYSCALL_CLOSE_E) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (ti->m_tid == ptid || ti->m_tid == ctid) + { + int64_t clfd = std::stoll(e->get_param_value_str("fd", false)); + + if (clfd == prfd) + { + callnum++; + } + } + } + else if (e->get_type() == PPME_SYSCALL_CLOSE_X) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (callnum < 3) + { + return; + } + + int64_t res = std::stoll(e->get_param_value_str("res", false)); + + if (ti->m_tid == ptid) + { + EXPECT_EQ(0, res); + } + else if (ti->m_tid == ctid) + { + EXPECT_EQ(0, res); + } + + callnum++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + if (callnum != 6 && callnum != 7) + { + FAIL(); + } +} + +static int clone_callback_2(void* arg) +{ + char bcwd[256]; + + if (chdir("/") != 0) + { + return -1; + } + std::string tmps = getcwd(bcwd, 256); + syscall(SYS_exit); + return -1; +} + +TEST_F(sys_call_test, forking_clone_cwd) +{ + int callnum = 0; + char oriwd[1024]; + char bcwd[256]; + int ptid; // parent tid + int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + int drflags = PPM_CL_CLONE_VM | PPM_CL_CLONE_FS | PPM_CL_CLONE_FILES | PPM_CL_CLONE_SIGHAND | + PPM_CL_CLONE_THREAD; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return evt->get_tid() == ptid; }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + const int STACK_SIZE = 65536; /* Stack size for cloned child */ + char* stack; /* Start of stack buffer area */ + char* stackTop; /* End of stack buffer area */ + clone_params cp; /* Passed to child function */ + + ptid = getpid(); + + ASSERT_TRUE(getcwd(oriwd, 1024) != NULL); + + /* Allocate stack for child */ + + stack = (char*)malloc(STACK_SIZE); + if (stack == NULL) + FAIL(); + stackTop = stack + STACK_SIZE; /* Assume stack grows downward */ + + /* Create child; child commences execution in childFunc() */ + + if (clone(clone_callback_2, stackTop, flags, &cp) == -1) + { + FAIL(); + } + + sleep(1); + + std::string tmps = getcwd(bcwd, 256); + + ASSERT_TRUE(chdir(oriwd) == 0); + + sleep(1); + free(stack); + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + if (e->get_type() == PPME_SYSCALL_CLONE_20_X) + { + uint64_t res = std::stoull(e->get_param_value_str("res", false)); + sinsp_threadinfo* ti = e->get_thread_info(false); + if (ti->get_comm() != "libsinsp_e2e_te") + { + return; + } + + if (res == 0) + { + EXPECT_EQ(ctid, ti->m_tid); + } + else + { + EXPECT_EQ(ptid, ti->m_tid); + } + + EXPECT_NE(std::string::npos, e->get_param_value_str("exe").find("libsinsp_e2e_tests")); + EXPECT_EQ("libsinsp_e2e_te", ti->get_comm()); + EXPECT_EQ(drflags, std::stol(e->get_param_value_str("flags", false))); + callnum++; + } + else if (e->get_type() == PPME_SYSCALL_GETCWD_E) + { + sinsp_threadinfo* ti = e->get_thread_info(false); + + if (ti->m_tid == ptid) + { + if (callnum > 1) + { + EXPECT_EQ(bcwd, ti->get_cwd()); + } + } + else if (ti->m_tid == ctid) + { + EXPECT_EQ("/", ti->get_cwd()); + } + + callnum++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + EXPECT_EQ(3, callnum); +} + +TEST_F(sys_call_test, forking_main_thread_exit) +{ + int evtnum = 0; + int callnum = 0; + int fd; + pid_t cpid; // parent tid + + event_filter_t filter = [&](sinsp_evt* evt) + { + sinsp_threadinfo* ti = evt->get_thread_info(); + if (ti) + { + return ti->m_pid == cpid; + } + else + { + return false; + } + }; + + run_callback_t test = [&](concurrent_object_handle inspector) + { + int status; + + // ptid = getpid(); + + cpid = fork(); + EXPECT_NE(-1, cpid); + if (cpid == 0) + { + execlp(LIBSINSP_TEST_RESOURCES_PATH "/forking_main_thread_exit", + LIBSINSP_TEST_RESOURCES_PATH "/forking_main_thread_exit", + NULL); + perror("execlp"); + FAIL(); + } + else + { + // + // Father, just wait for termination + // + wait(&status); + } + }; + + captured_event_callback_t callback = [&](const callback_param& param) + { + evtnum++; + if (param.m_evt->get_type() == PPME_SYSCALL_OPEN_X) + { + if (param.m_evt->get_param_value_str("name") == "/etc/passwd") + { + EXPECT_EQ("/etc/passwd", param.m_evt->get_param_value_str("fd")); + fd = *(int64_t*)param.m_evt->get_param(0)->m_val; + ++callnum; + } + } + else if (param.m_evt->get_type() == PPME_SYSCALL_OPENAT_2_X) + { + if (param.m_evt->get_param_value_str("name") == "/etc/passwd") + { + EXPECT_EQ("/etc/passwd", param.m_evt->get_param_value_str("fd")); + memcpy(&fd, (int64_t*)param.m_evt->get_param(0)->m_val, sizeof(fd)); + ++callnum; + } + } + else if (param.m_evt->get_type() == PPME_PROCEXIT_1_E && param.m_evt->get_tid() == cpid) + { + ++callnum; + } + else if (param.m_evt->get_type() == PPME_SYSCALL_READ_E) + { + if (memcmp(&fd, param.m_evt->get_param(0)->m_val, sizeof(fd)) == 0) + { + EXPECT_EQ("/etc/passwd", param.m_evt->get_param_value_str("fd")); + ++callnum; + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(3, callnum); +} + +// This test generally does the following: +// - Ensures that a stale process exists +// - Starts another process with the same pid as the stale process, in a pid +// namespace (which counts as "in a container"). +// - Checks to see if the stale process information is used. +// +// To distinguish between the stale process and up-to-date process, use the +// working directory of the process. The stale process sets its working +// directory to "/dev". +// +// Prior to the fix for 664, the stale process would be used and the +// working directory of the second process would (mistakenly) be +// /dev. With the fix, the stale process information is detected and +// removed. +// + +// Create the initial stale process. It chdir()s to "/dev", stops the +// inspector, and returns. +static int stop_sinsp_and_exit(void* arg) +{ + // Get our own, unlocked concurrent inspector handle + concurrent_object_handle inspector_handle = *(concurrent_object_handle*)arg; + + if (chdir("/dev") != 0) + { + return 1; + } + + { + std::scoped_lock inspector_handle_lock(inspector_handle); + inspector_handle->stop_capture(); + } + + // Wait 5 seconds. This ensures that the state for this + // process will be considered stale when the second process + // with the same pid runs. + sleep(5); + + return 0; +} + +// Immediately return. Started by launcher. +static int do_nothing(void* arg) +{ + return 0; +} + +struct stale_clone_ctx +{ + std::mutex m_perform_clone_mtx; + std::condition_variable m_perform_clone; + bool m_clone_ready; + bool m_clone_complete; +}; + +static pid_t clone_helper(int (*func)(void*), + void* arg, + int addl_clone_args = 0, + bool wait_for_complete = true, + char** stackp = NULL); + +// Wait until signaled by the main test thread, start a single +// do_nothing(), signal the main test thread, and exit. +static int launcher(void* arg) +{ + stale_clone_ctx* ctx = (stale_clone_ctx*)arg; + std::unique_lock lk(ctx->m_perform_clone_mtx); + ctx->m_perform_clone.wait(lk, [&] { return ctx->m_clone_ready; }); + + pid_t child = clone_helper(do_nothing, NULL); + EXPECT_NE(child, 0); + + ctx->m_clone_complete = true; + lk.unlock(); + ctx->m_perform_clone.notify_one(); + + if (child == 0) + { + return 1; + } + + return 0; +} + +// Start a new thread using clone(), passing the provided arg. On +// success, returns the process id of the thread that was created. +// On failure, returns 0. Used to start all the other actions. + +static pid_t clone_helper(int (*func)(void*), + void* arg, + int addl_clone_args, + bool wait_for_complete, + char** stackp) +{ + const int STACK_SIZE = 65536; /* Stack size for cloned child */ + char* stack; /* Start of stack buffer area */ + char* stackTop; /* End of stack buffer area */ + int flags = CLONE_VM | CLONE_FILES | SIGCHLD | addl_clone_args; + pid_t pid = 0; + + /* Allocate stack for child */ + stack = (char*)malloc(STACK_SIZE); + if (stack == NULL) + { + return 0; + } + + stackTop = stack + STACK_SIZE; /* Assume stack grows downward */ + + if ((pid = clone(func, stackTop, flags, arg)) == -1) + { + free(stack); + return 0; + } + + if (wait_for_complete) + { + int status; + + if (waitpid(pid, &status, 0) == -1 || status != 0) + { + pid = 0; + } + free(stack); + } + else + { + *stackp = stack; + } + + return pid; +} + +TEST_F(sys_call_test, remove_stale_thread_clone_exit) +{ + std::atomic clones_seen(0); + stale_clone_ctx ctx; + std::atomic recycle_pid(0); + const char* last_pid_filename = "/proc/sys/kernel/ns_last_pid"; + struct stat info; + + ctx.m_clone_ready = false; + ctx.m_clone_complete = false; + + // On some operating systems, + // /proc/sys/kernel/ns_last_pid does not exist. In + // those cases, we print a message and trivially pass + // the test. + + if (stat(last_pid_filename, &info) == -1 && errno == ENOENT) + { + fprintf(stderr, "Doing nothing as %s does not exist\n", last_pid_filename); + return; + } + + // All events matching recycle_pid are selected. Since + // recycle_pid is only set once the first thread exits, this + // effectively captures the actions of the second thread that + // uses the recycled pid. + event_filter_t filter = [&](sinsp_evt* evt) + { + sinsp_threadinfo* tinfo = evt->get_thread_info(); + pid_t rp = recycle_pid.load(); + return (rp != 0 && tinfo && tinfo->m_tid == rp); + }; + + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + pid_t launcher_pid; + char* launcher_stack = NULL; + + // Start a thread that simply waits until signaled, + // and then creates a second do-nothing thread. We'll + // arrange that the host-facing pid is set to a known + // value before this thread creates the second thread. + launcher_pid = clone_helper(launcher, &ctx, CLONE_NEWPID, false, &launcher_stack); + ASSERT_GE(launcher_pid, 0); + + // This is asynchronous so wait to make sure the thread has started. + sleep(1); + + // Start a thread that runs and stops the inspector_handle right + // before exiting. This gives us a pid we can use for the + // second thread. + recycle_pid.store(clone_helper(stop_sinsp_and_exit, &inspector_handle)); + ASSERT_GE(recycle_pid.load(), 0); + + // The first thread has started, turned off the capturing, and + // exited, so start capturing again. + { + std::scoped_lock inspector_handle_lock(inspector_handle); + inspector_handle->start_capture(); + } + + // Arrange that the next thread/process created has + // pid ctx.m_desired pid by writing to + // ns_last_pid. Unfortunately, this has a race + // condition--it's possible that after writing to + // ns_last_pid another different process is started, + // stealing the pid. However, as long as the process + // doesn't have a working directory of "/dev", that + // will be enough to distinguish it from the stale + // process. + + FILE* last_pid_file; + + { + std::lock_guard lk(ctx.m_perform_clone_mtx); + + last_pid_file = fopen(last_pid_filename, "w"); + + ASSERT_NE(last_pid_file, (FILE*)NULL); + + ASSERT_EQ(flock(fileno(last_pid_file), LOCK_EX), 0); + + ASSERT_GT(fprintf(last_pid_file, "%d", recycle_pid.load() - 1), 0); + + fclose(last_pid_file); + + ctx.m_clone_ready = true; + } + + // Signal the launcher thread telling it to start the do_nothing thread. + ctx.m_perform_clone.notify_one(); + + // Wait to be signaled back from the launcher thread that it's done. + { + std::unique_lock lk(ctx.m_perform_clone_mtx); + + ctx.m_perform_clone.wait(lk, [&] { return ctx.m_clone_complete; }); + } + + // The launcher thread should have exited, but just to + // make sure explicitly kill it. + ASSERT_EQ(kill(launcher_pid, SIGTERM), 0); + + free(launcher_stack); + + return; + }; + + // To verify the actions, the filter selects all events + // related to pid recycled_pid. It should see: + // - a clone() representing the second thread using the recycled pid. + // - events with pid=recycled_pid (the do_nothing started by + // create_do_nothings) and cwd= + // + // If any event with pid=recycled_pid has a cwd of + // /dev/, the test fails. + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t etype = e->get_type(); + sinsp_threadinfo* tinfo = e->get_thread_info(); + ASSERT_TRUE((tinfo != NULL)); + + if ((etype == PPME_SYSCALL_CLONE_11_X || etype == PPME_SYSCALL_CLONE_16_X || + etype == PPME_SYSCALL_CLONE_17_X || etype == PPME_SYSCALL_CLONE_20_X) && + e->get_direction() == SCAP_ED_OUT) + { + ++clones_seen; + } + + EXPECT_STRNE(tinfo->get_cwd().c_str(), "/dev/"); + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + // We must have seen one clone related to the recycled + // pid. Otherwise it never actually checked the cwd at all. + EXPECT_EQ(clones_seen.load(), 1u); +} diff --git a/test/libsinsp_e2e/ipv6.cpp b/test/libsinsp_e2e/ipv6.cpp new file mode 100644 index 00000000000..49c03ec8fcf --- /dev/null +++ b/test/libsinsp_e2e/ipv6.cpp @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "scap_file_reader.h" +#include "libsinsp_test_var.h" + +#include + +#include + +#include +#include +#include +#include + +typedef std::function validate_func_t; + +class ipv6_filtercheck_test : public testing::Test +{ +protected: + struct cstring_comp + { + bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } + }; + + typedef std::set cstringset_t; + + virtual void SetUp() {} + + virtual void TearDown() {} + + virtual void read_file(const char* filename, + const char* extra_filter, + std::function evtcb, + bool generate_ip_net_filters = true) + { + m_inspector = file_reader.setup_read_file(); + + m_socket_connected = false; + m_check_local_remote = false; + m_check_is_server = false; + + if (generate_ip_net_filters) + { + gen_ip_net_filters(); + } + + std::string filter = + "evt.type in (socket, connect, recvfrom, sendto, close, accept, connect, bind, read, " + "write, poll) and evt.dir=< and fd.type!=file and fd.type!=unix and fd.type!=file and " + "fd.type!=pipe"; + if (extra_filter) + { + filter += " and "; + filter += extra_filter; + } + + file_reader.run_inspector(filename, filter, evtcb); + } + + void check_ipv6_filterchecks(sinsp_evt* evt) + { + std::string full_output; + std::string full = + "*%evt.num %evt.outputtime %evt.cpu %proc.name (%thread.tid) %evt.dir %evt.type " + "%evt.info"; + sinsp_evt_formatter(m_inspector.get(), full, m_filterlist).tostring(evt, &full_output); + + verify_filtercheck(evt, "*%fd.type", "ipv6", full_output); + verify_filtercheck(evt, "*%fd.typechar", "6", full_output); + verify_filtercheck(evt, "*%fd.sockfamily", "ip", full_output); + + if (m_socket_connected) + { + verify_filtercheck(evt, "*%fd.name", m_conn_names, full_output); + + verify_filtercheck(evt, "*%fd.cip", m_client_ip, full_output); + verify_filtercheck(evt, "*%fd.sip", m_server_ip, full_output); + + verify_filtercheck(evt, "*%fd.cport", m_client_port, full_output); + verify_filtercheck(evt, "*%fd.sport", m_server_ports, full_output); + + ASSERT_TRUE(m_ip_client_filter->run(evt)) + << "fd.ip=" << m_client_ip + << " did not match event. Full event output: " << full_output; + ASSERT_TRUE(m_ip_server_filter->run(evt)) + << "fd.ip=" << m_server_ip + << " did not match event. Full event output: " << full_output; + + ASSERT_TRUE(m_net_client_filter->run(evt)) + << "fd.net=" << m_client_net + << " did not match event. Full event output: " << full_output; + ASSERT_TRUE(m_net_server_filter->run(evt)) + << "fd.net=" << m_server_net + << " did not match event. Full event output: " << full_output; + + ASSERT_TRUE(m_cnet_filter->run(evt)) + << "fd.cnet=" << m_client_net + << " did not match event. Full event output: " << full_output; + ASSERT_TRUE(m_snet_filter->run(evt)) + << "fd.snet=" << m_server_net + << " did not match event. Full event output: " << full_output; + + verify_filtercheck(evt, "*%fd.cproto", m_client_proto, full_output); + verify_filtercheck(evt, "*%fd.sproto", m_server_protos, full_output); + + verify_filtercheck(evt, "*%fd.l4proto", m_l4proto, full_output); + + if (m_check_is_server) + { + verify_filtercheck(evt, "*%fd.is_server", m_is_server, full_output); + } + } + + if (m_check_local_remote) + { + verify_filtercheck(evt, "*%fd.lip", m_client_ip, full_output); + verify_filtercheck(evt, "*%fd.rip", m_server_ip, full_output); + + verify_filtercheck(evt, "*%fd.lport", m_client_port, full_output); + verify_filtercheck(evt, "*%fd.rport", m_server_ports, full_output); + + ASSERT_TRUE(m_lnet_filter->run(evt)) + << "fd.lnet=" << m_client_net + << " did not match event. Full event output: " << full_output; + ASSERT_TRUE(m_rnet_filter->run(evt)) + << "fd.rnet=" << m_server_net + << " did not match event. Full event output: " << full_output; + + verify_filtercheck(evt, "*%fd.lproto", m_client_proto, full_output); + verify_filtercheck(evt, "*%fd.rproto", m_server_protos, full_output); + } + } + + void verify_filtercheck(sinsp_evt* evt, + const char* format, + const char* expectedc, + std::string full_output) + { + cstringset_t expected; + expected.insert(expectedc); + + verify_filtercheck(evt, format, expected, full_output); + } + + void verify_filtercheck(sinsp_evt* evt, + const char* format, + std::string& expecteds, + std::string full_output) + { + cstringset_t expected; + expected.insert(expecteds.c_str()); + + verify_filtercheck(evt, format, expected, full_output); + } + + void verify_filtercheck(sinsp_evt* evt, + const char* cformat, + cstringset_t& expected, + std::string full_output) + { + std::string output; + std::string format = cformat; + + sinsp_evt_formatter(m_inspector.get(), format, m_filterlist).tostring(evt, &output); + + auto it = expected.find(output.c_str()); + + ASSERT_TRUE(it != expected.end()) + << " Result of format " << cformat + << " did not match any expected value. Full event output: " << full_output; + } + + void gen_ip_net_filters() + { + auto inspector = file_reader.setup_read_file(); + sinsp_filter_compiler ip_client(inspector.get(), "fd.ip=" + m_client_ip); + m_ip_client_filter = std::move(ip_client.compile()); + + sinsp_filter_compiler ip_server(inspector.get(), "fd.ip=" + m_server_ip); + m_ip_server_filter = std::move(ip_server.compile()); + + sinsp_filter_compiler net_client(inspector.get(), "fd.net=" + m_client_net); + m_net_client_filter = std::move(net_client.compile()); + + sinsp_filter_compiler net_server(inspector.get(), "fd.net=" + m_server_net); + m_net_server_filter = std::move(net_server.compile()); + + sinsp_filter_compiler cnet(inspector.get(), "fd.cnet=" + m_client_net); + m_cnet_filter = std::move(cnet.compile()); + + sinsp_filter_compiler snet(inspector.get(), "fd.snet=" + m_server_net); + m_snet_filter = std::move(snet.compile()); + + sinsp_filter_compiler lnet(inspector.get(), "fd.lnet=" + m_client_net); + m_lnet_filter = std::move(lnet.compile()); + + sinsp_filter_compiler rnet(inspector.get(), "fd.rnet=" + m_server_net); + m_rnet_filter = std::move(rnet.compile()); + } + + std::string m_client_ip; + std::string m_server_ip; + std::string m_client_port; + cstringset_t m_server_ports; + std::string m_client_net; + std::string m_server_net; + std::string m_client_proto; + cstringset_t m_server_protos; + cstringset_t m_conn_names; + std::string m_l4proto; + std::string m_is_server; + + sinsp_filter_check_list m_filterlist; + std::shared_ptr m_ip_client_filter; + std::shared_ptr m_ip_server_filter; + std::shared_ptr m_net_client_filter; + std::shared_ptr m_net_server_filter; + std::shared_ptr m_cnet_filter; + std::shared_ptr m_snet_filter; + std::shared_ptr m_lnet_filter; + std::shared_ptr m_rnet_filter; + std::shared_ptr m_inspector; + scap_file_reader file_reader; + bool m_socket_connected; + bool m_check_local_remote; + bool m_check_is_server; +}; + +TEST_F(ipv6_filtercheck_test, curl_google_dnsreq) +{ + m_client_ip = "2600:1f18:262c:6542:9aa6:df7a:9a47:d29e"; + m_server_ip = "2001:4860:4860::8888"; + m_client_port = "40251"; + m_server_ports = {"53"}; + m_client_net = "2600:1f18:262c:6542::/64"; + m_server_net = "2001:4860:4860::/64"; + m_client_proto = "40251"; + m_server_protos = {"domain"}; + m_conn_names = {"2600:1f18:262c:6542:9aa6:df7a:9a47:d29e:40251->2001:4860:4860::8888:domain"}; + m_l4proto = "udp"; + m_is_server = "false"; + + read_file(LIBSINSP_TEST_CAPTURES_PATH "/curl_google.scap", + "thread.tid=17498", + [this](sinsp_evt* evt) + { + std::string evname = std::string(evt->get_name()); + + // Once we see a connect or bind, we can assume the + // socket is connected and it's possible to get + // client/server and local/remote information. + if (evname == "connect" || evname == "bind") + { + m_socket_connected = true; + m_check_local_remote = true; + m_check_is_server = true; + } + + check_ipv6_filterchecks(evt); + }); +} + +TEST_F(ipv6_filtercheck_test, curl_google_www) +{ + m_client_ip = "2600:1f18:262c:6542:9aa6:df7a:9a47:d29e"; + m_server_ip = "2607:f8b0:4004:802::2004"; + m_client_port = "37140"; + m_server_ports = {"80"}; + m_client_net = "2600:1f18:262c:6542::/64"; + m_server_net = "2607:f8b0:4004:802::/64"; + m_client_proto = "37140"; + m_server_protos = {"http"}; + m_conn_names = {"2600:1f18:262c:6542:9aa6:df7a:9a47:d29e:37140->2607:f8b0:4004:802::2004:http"}; + m_l4proto = "tcp"; + m_is_server = "false"; + + read_file(LIBSINSP_TEST_CAPTURES_PATH "/curl_google.scap", + "thread.tid=17497", + [this](sinsp_evt* evt) + { + std::string evname = std::string(evt->get_name()); + + // Once we see a connect or bind, we can assume the + // socket is connected and it's possible to get + // client/server and local/remote information. + if (evname == "connect" || evname == "bind") + { + m_socket_connected = true; + m_check_local_remote = true; + m_check_is_server = true; + } + + check_ipv6_filterchecks(evt); + }); +} + +TEST_F(ipv6_filtercheck_test, single_ipv6_conn_client) +{ + m_client_ip = "2001:db8::4"; + m_server_ip = "2001:db8::3"; + m_client_port = "54405"; + + // Some /etc/services map port 1234 to search-agent, so we + // allow both. + m_server_ports = {"1234", "search-agent"}; + + m_client_net = "2001:db8::/64"; + m_server_net = "2001:db8::/64"; + m_client_proto = "54405"; + m_server_protos = {"1234", "search-agent"}; + m_conn_names = {"2001:db8::4:54405->2001:db8::3:1234", + "2001:db8::4:54405->2001:db8::3:search-agent"}; + m_l4proto = "tcp"; + m_is_server = "false"; + + read_file(LIBSINSP_TEST_CAPTURES_PATH "/single_ipv6_conn.scap", + "proc.pid=25888", + [this](sinsp_evt* evt) + { + std::string evname = std::string(evt->get_name()); + + // Once we see a connect, we can assume the + // socket is connected and it's possible to get + // client/server information. However, we can *not* + // get local/remote information as this connection was + // done between two ips on the same local interface. + if (evname == "connect") + { + m_socket_connected = true; + } + + check_ipv6_filterchecks(evt); + }); +} + +TEST_F(ipv6_filtercheck_test, single_ipv6_conn_server) +{ + m_client_ip = "2001:db8::4"; + m_server_ip = "2001:db8::3"; + m_client_port = "54405"; + m_server_ports = {"1234", "search-agent"}; + m_client_net = "2001:db8::/64"; + m_server_net = "2001:db8::/64"; + m_client_proto = "54405"; + m_server_protos = {"1234", "search-agent"}; + m_conn_names = {"2001:db8::4:54405->2001:db8::3:1234", + "2001:db8::4:54405->2001:db8::3:search-agent"}; + m_l4proto = "tcp"; + m_is_server = "server"; + + read_file(LIBSINSP_TEST_CAPTURES_PATH "/single_ipv6_conn.scap", + "proc.pid=25886", + [this](sinsp_evt* evt) + { + std::string evname = std::string(evt->get_name()); + + // Once we see a connect, we can assume the + // socket is connected and it's possible to get + // client/server information. However, we can *not* + // get local/remote information as this connection was + // done between two ips on the same local interface. + if (evname == "connect") + { + m_socket_connected = true; + } + + check_ipv6_filterchecks(evt); + }); +} + +TEST_F(ipv6_filtercheck_test, test_ipv6_client) +{ + // test_ipv6_client.cpp does the following: + // 1. sendto() on an unconnected socket to ::1 + // 2. connect to ::1, port 2345 + // 3. send() on the connected socket (to ::1) + // 4. connect to google dns server, port 53 + // 5. send() on the connected socket (to google dns server) + // 6. sendto() back to ::1, port 2345 + // + // Some /etc/services map port 2345 to dbm, so we allow both. + + // The test verifies that the addresses/ports on the socket + // change properly for the connects/sendtos. + + enum state_t + { + sendto_unconnected, + send_connected, + send_reconnected, + sendto_reconnected, + done + }; + + state_t state = sendto_unconnected; + + read_file( + LIBSINSP_TEST_CAPTURES_PATH "/test_ipv6_client.scap", + "proc.name=test_ipv6_clien", + [&](sinsp_evt* evt) + { + std::string evname = std::string(evt->get_name()); + + std::string full_output; + std::string full = + "*%evt.num %evt.outputtime %evt.cpu %proc.name (%thread.tid) %evt.dir %evt.type " + "%evt.info"; + sinsp_evt_formatter(m_inspector.get(), full, m_filterlist).tostring(evt, &full_output); + + cstringset_t unconnected_names = {"::1:0->::1:2345", "::1:0->::1:dbm"}; + cstringset_t connected_names = {"::1:38255->::1:2345", "::1:38255->::1:dbm"}; + cstringset_t reconnected_names = {"::1:38255->::1:2345", "::1:38255->::1:dbm"}; + + if (evname == "send" || evname == "sendto") + { + switch (state) + { + case sendto_unconnected: + verify_filtercheck(evt, "*%fd.name", unconnected_names, full_output); + state = send_connected; + break; + case send_connected: + verify_filtercheck(evt, "*%fd.name", connected_names, full_output); + state = send_reconnected; + break; + case send_reconnected: + verify_filtercheck(evt, + "*%fd.name", + "::1:38255->2001:4860:4860::8888:domain", + full_output); + state = sendto_reconnected; + break; + case sendto_reconnected: + verify_filtercheck(evt, "*%fd.name", reconnected_names, full_output); + state = done; + break; + case done: + break; + } + } + }, + false); + + ASSERT_TRUE(state == done); +} diff --git a/test/libsinsp_e2e/libsinsp_test_var.h.in b/test/libsinsp_e2e/libsinsp_test_var.h.in index 2f9a83e65ba..c08f7e8df0e 100644 --- a/test/libsinsp_e2e/libsinsp_test_var.h.in +++ b/test/libsinsp_e2e/libsinsp_test_var.h.in @@ -27,7 +27,7 @@ limitations under the License. // Absolute path to the bpf probe .o file #define LIBSINSP_TEST_BPF_PROBE_PATH "${CMAKE_BINARY_DIR}/driver/bpf/probe.o" -#define LIBSINSP_TEST_CAPTURES_PATH "${CMAKE_BINARY_DIR}/test/libsinsp_e2e/captures/" +#define LIBSINSP_TEST_CAPTURES_PATH "${CMAKE_BINARY_DIR}/test/libsinsp_e2e/resources/captures/" #define LIBSINSP_TEST_RESOURCES_PATH "${CMAKE_BINARY_DIR}/test/libsinsp_e2e/resources/" diff --git a/test/libsinsp_e2e/resources/CMakeLists.txt b/test/libsinsp_e2e/resources/CMakeLists.txt index 10cbb57a140..fccdf9eae22 100644 --- a/test/libsinsp_e2e/resources/CMakeLists.txt +++ b/test/libsinsp_e2e/resources/CMakeLists.txt @@ -15,6 +15,19 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) +add_executable(forking_main_thread_exit forking_main_thread_exit.c) +target_link_libraries(forking_main_thread_exit pthread) +add_dependencies(libsinsp_e2e_tests forking_main_thread_exit) + +add_executable(forking_nested forking_nested.c) +target_link_libraries(forking_nested pthread) +add_dependencies(libsinsp_e2e_tests forking_nested) + +add_executable(chname chname.cpp) +target_link_libraries(chname pthread) +add_dependencies(libsinsp_e2e_tests chname) + + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") add_executable(execve execve.c) add_dependencies(libsinsp_e2e_tests execve) diff --git a/test/libsinsp_e2e/resources/captures/curl_google.scap b/test/libsinsp_e2e/resources/captures/curl_google.scap new file mode 100644 index 0000000000000000000000000000000000000000..26c88ba91725dd16bd3252ee6f1bf1c99837ddd1 GIT binary patch literal 22296 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 literal 0 HcmV?d00001 diff --git a/test/libsinsp_e2e/resources/captures/single_ipv6_conn.scap b/test/libsinsp_e2e/resources/captures/single_ipv6_conn.scap new file mode 100644 index 0000000000000000000000000000000000000000..10cce5b5e87ae97036ba497b23985b8fea0edaaf GIT binary patch literal 23489 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 DWNJp* literal 0 HcmV?d00001 diff --git a/test/libsinsp_e2e/resources/captures/test_ipv6_client.scap b/test/libsinsp_e2e/resources/captures/test_ipv6_client.scap new file mode 100644 index 0000000000000000000000000000000000000000..2029ea26802f75e4b25aeaf166ce4274d9f118c3 GIT binary patch literal 11154 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 +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +void run() +{ + while (true) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void changer(char** argv) +{ + char pname[] = "sysdig"; + memcpy((void*)argv[0], pname, sizeof(pname)); + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} + +int main(int argc, char** argv) +{ + char pname[] = "savonarola"; + prctl(PR_SET_NAME, (unsigned long)&pname, 0, 0, 0); + std::vector> threads; + for (int j = 0; j < 20; ++j) + { + threads.push_back(std::make_shared(run)); + } + + auto binded_changer = std::bind(changer, argv); + std::thread changer(binded_changer); + run(); +} diff --git a/test/libsinsp_e2e/resources/forking_main_thread_exit.c b/test/libsinsp_e2e/resources/forking_main_thread_exit.c new file mode 100644 index 00000000000..2193844b5f8 --- /dev/null +++ b/test/libsinsp_e2e/resources/forking_main_thread_exit.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include +#include +#include +#include + +#include +#include + +static int fd; + +void* callback(void* arg) +{ + char buf[1024]; + sleep(1); + if (read(fd, buf, sizeof(buf)) < 0) + { + perror("read"); + } + sleep(10); + return NULL; +} + +// +// This is outside the test files because gtest doesn't like +// pthread_exit() since it triggers an exception to unwind the stack +// +int main() +{ + pthread_t thread; + + fd = open("/etc/passwd", O_RDONLY); + if (fd == -1) + { + perror("open"); + } + + pthread_create(&thread, NULL, callback, NULL); + pthread_exit(NULL); +} diff --git a/test/libsinsp_e2e/resources/forking_nested.c b/test/libsinsp_e2e/resources/forking_nested.c new file mode 100644 index 00000000000..9e18320095c --- /dev/null +++ b/test/libsinsp_e2e/resources/forking_nested.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include +#include +#include +#include + +#include +#include + +void* callback(void* arg) +{ + return NULL; +} + +int main() +{ + int ctid; + int cctid, cctid1, cctid2, cctid3, cctid4, cctid5; + + ctid = fork(); + + if (ctid == 0) + { + // + // CHILD PROCESS + // + printf("*1\n"); + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid = fork(); + + if (cctid == 0) + { + // + // CHILD PROCESS + // + printf("*2\n"); + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid1 = fork(); + + if (cctid1 == 0) + { + // + // CHILD PROCESS + // + printf("*3\n"); + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid2 = fork(); + + if (cctid2 == 0) + { + // + // CHILD PROCESS + // + printf("*4\n"); + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid3 = fork(); + + if (cctid3 == 0) + { + printf("*5\n"); + // + // CHILD PROCESS + // + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid4 = fork(); + + if (cctid4 == 0) + { + printf("*6\n"); + // + // CHILD PROCESS + // + pthread_t thread; + pthread_create(&thread, NULL, callback, NULL); + + usleep(100000); + cctid5 = fork(); + + if (cctid5 == 0) + { + printf("*7\n"); + return 0; + } + else + { + return 0; + } + } + else + { + return 0; + } + } + else + { + return 0; + } + } + else + { + return 0; + } + } + else + { + return 0; + } + } + else + { + return 0; + } + } + else + { + return 0; + } +} diff --git a/test/libsinsp_e2e/scap_file_reader.h b/test/libsinsp_e2e/scap_file_reader.h new file mode 100644 index 00000000000..94bbcd9cf21 --- /dev/null +++ b/test/libsinsp_e2e/scap_file_reader.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#include + +#include +#include +#include + +class scap_file_reader +{ +public: + virtual ~scap_file_reader() { m_inspector = nullptr; } + + virtual std::shared_ptr setup_read_file() + { + if (!m_inspector) + { + m_inspector = std::make_shared(); + m_inspector->set_hostname_and_port_resolution_mode(true); + } + return m_inspector; + } + + virtual void run_inspector(const char* filename, + const std::string filter, + std::function evtcb) + { + m_inspector->open_savefile(filename); + m_inspector->set_filter(filter.c_str()); + + while (true) + { + int32_t res; + sinsp_evt* evt; + + res = m_inspector->next(&evt); + + if (res == SCAP_TIMEOUT) + { + continue; + } + else if (res == SCAP_FILTERED_EVENT) + { + continue; + } + else if (res == SCAP_EOF) + { + break; + } + else if (res != SCAP_SUCCESS) + { + break; + } + + evtcb(evt); + } + + m_inspector->close(); + } + + virtual void read_file_filtered(const char* filename, + const std::string filter, + std::function evtcb) + { + setup_read_file(); + run_inspector(filename, filter, evtcb); + } + +private: + std::shared_ptr m_inspector; +}; diff --git a/test/libsinsp_e2e/tcp_client_server.cpp b/test/libsinsp_e2e/tcp_client_server.cpp new file mode 100644 index 00000000000..914709e56c6 --- /dev/null +++ b/test/libsinsp_e2e/tcp_client_server.cpp @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "utils.h" + +#include "event_capture.h" +#include "subprocess.h" +#include "sys_call_test.h" +#include "tcp_client_server.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +static const std::string default_payload = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM"; +static const std::string http_payload = + "GET / " + "0123456789QWERTYUIOPASDFGHJKLZXCVBNM0123456789QWERTYUIOPASDFGHJKLZXCVBNM0123456789QWERTYUIOPAS" + "DFGHJKLZXCVBNM0123456789QWERTYUIOPASDFGHJKLZXCVBNM0123456789QWERTYUIOPASDFGHJKLZXCVBNM01234567" + "89QWERTYUIOPASDFGHJKLZXCVBNO"; + +void runtest(iotype iot, + const std::string& payload = default_payload, + bool use_shutdown = false, + bool use_accept4 = false, + uint32_t ntransactions = 1, + bool exit_no_close = false, + bool ia32_mode = false) +{ + proc_started_filter client_started_filter; + proc_started_filter server_started_filter; + auto stringify_bool = [](bool v) + { + return v ? "true" : "false"; + }; + unsigned callnum = 0; + std::string helper_exe = LIBSINSP_TEST_PATH "/test_helper"; + if (ia32_mode) + { + helper_exe += "_32"; + } + auto iot_s = std::to_string(iot); + auto ntransactions_s = std::to_string(ntransactions); + subprocess server_proc(helper_exe, + {"tcp_server", + iot_s.c_str(), + "false", + stringify_bool(use_shutdown), + stringify_bool(use_accept4), + ntransactions_s.c_str(), + stringify_bool(exit_no_close)}, false); + int64_t server_pid; + int64_t client_pid; + struct in_addr server_in_addr; + server_in_addr.s_addr = get_server_address(); + char* server_address = inet_ntoa(server_in_addr); + std::string sport; + subprocess test_proc(helper_exe, + {"tcp_client", + server_address, + iot_s.c_str(), + payload, + stringify_bool(false), + ntransactions_s, + stringify_bool(exit_no_close)}, false); + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + if (tinfo && tinfo->m_exe == helper_exe) + { + if (tinfo->m_pid == server_pid) + { + return server_started_filter(evt); + } + else if (tinfo->m_pid == client_pid) + { + return client_started_filter(evt); + } + } + return false; + }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + { + std::scoped_lock inspector_handle_lock(inspector_handle); + inspector_handle->dynamic_snaplen(true); + } + server_proc.start(); + server_proc.wait_for_start(); + server_pid = server_proc.get_pid(); + + test_proc.start(); + test_proc.wait_for_start(); + client_pid = test_proc.get_pid(); + + // We use a random call to tee to signal that we're done + tee(-1, -1, 0, 0); + }; + + std::function log_param = [](const callback_param& param) + { + // cerr << param.m_evt->get_name() << endl; + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + sinsp_evt* evt = param.m_evt; + if (evt->get_type() == PPME_SOCKET_CONNECT_X) + { + std::string tuple = evt->get_param_value_str("tuple"); + + EXPECT_NE((sinsp_fdinfo*)NULL, evt->get_fd_info()); + + if (evt->get_fd_info()->m_type != SCAP_FD_IPV4_SOCK) + { + // + // Skip non-tcp sockets. Python opens unix sockets + // to god knows what. + // + return; + } + + parse_tuple(tuple, src_addr, src_port, dst_addr, dst_port); + + EXPECT_EQ(server_address, src_addr); + if (sport == "") + { + EXPECT_NE("0", src_port); + sport = src_port; + } + else + { + EXPECT_EQ(sport, src_port); + } + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_LISTEN_E) + { + EXPECT_EQ("1", evt->get_param_value_str("backlog")); + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_LISTEN_X) + { + EXPECT_EQ("0", evt->get_param_value_str("res")); + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_ACCEPT4_6_E) + { + EXPECT_EQ("0", evt->get_param_value_str("flags")); + } + else if (evt->get_type() == PPME_SOCKET_ACCEPT_5_X || + evt->get_type() == PPME_SOCKET_ACCEPT4_6_X) + { + parse_tuple(evt->get_param_value_str("tuple"), src_addr, src_port, dst_addr, dst_port); + + EXPECT_EQ(server_address, src_addr); + if (sport == "") + { + EXPECT_NE("0", src_port); + sport = src_port; + } + else + { + EXPECT_EQ(sport, src_port); + } + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + + log_param(param); + callnum++; + } + + if (callnum < 1) + { + return; + } + + // + // 32bit uses send() and recv(), while 64bit always uses sendto() and + // recvfrom() and sets the address to NULL + // + if ((evt->get_type() == PPME_SOCKET_SEND_E || evt->get_type() == PPME_SOCKET_RECV_E || + evt->get_type() == PPME_SOCKET_SENDTO_E || evt->get_type() == PPME_SOCKET_RECVFROM_E || + evt->get_type() == PPME_SYSCALL_READ_E || evt->get_type() == PPME_SYSCALL_WRITE_E || + evt->get_type() == PPME_SYSCALL_READV_E || evt->get_type() == PPME_SYSCALL_WRITEV_E) && + evt->get_fd_info()->m_type == SCAP_FD_IPV4_SOCK) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_E) + { + if (evt->get_param_value_str("tuple") != "") + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + } + + std::string tuple = evt->get_param_value_str("fd"); + tuple = tuple.substr(tuple.find(">")+1); + parse_tuple(tuple, src_addr, src_port, dst_addr, dst_port); + EXPECT_EQ(server_address, src_addr); + EXPECT_EQ(sport, src_port); + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + + log_param(param); + callnum++; + } + else if ((evt->get_type() == PPME_SOCKET_RECV_X || + evt->get_type() == PPME_SOCKET_RECVFROM_X || + evt->get_type() == PPME_SYSCALL_READ_X || + evt->get_type() == PPME_SYSCALL_READV_X || + evt->get_type() == PPME_SYSCALL_WRITEV_X || + evt->get_type() == PPME_SYSCALL_WRITE_X || + evt->get_type() == PPME_SOCKET_SENDTO_X || + evt->get_type() == PPME_SOCKET_SEND_X) && + evt->get_fd_info()->m_type == SCAP_FD_IPV4_SOCK) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_X) + { + if(event_capture::get_engine() != MODERN_BPF_ENGINE) + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + else + { + if(!parse_tuple(evt->get_param_value_str("tuple"), src_addr, src_port, dst_addr, dst_port)) + { + return; + } + EXPECT_EQ(server_address, src_addr); + EXPECT_EQ(server_address, dst_addr); + + if(callnum == 7) + { + EXPECT_EQ(sport, src_port); + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + else if(callnum == 9) + { + EXPECT_EQ(sport, dst_port); + EXPECT_EQ(SERVER_PORT_STR, src_port); + } + } + } + + EXPECT_EQ(payload, evt->get_param_value_str("data")); + + log_param(param); + callnum++; + } + + }; + + // + // OUTPUT VALDATION + // + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_GT(callnum,0); +} + +TEST_F(sys_call_test, tcp_client_server) +{ + runtest(SENDRECEIVE); +} + +TEST_F(sys_call_test, tcp_client_server_read_write) +{ + runtest(READWRITE); +} + +TEST_F(sys_call_test, tcp_client_server_readv_writev) +{ + runtest(READVWRITEV); +} + +TEST_F(sys_call_test, tcp_client_server_shutdown) +{ + runtest(SENDRECEIVE, default_payload, true); +} + +TEST_F(sys_call_test, tcp_client_server_accept4) +{ + runtest(SENDRECEIVE, default_payload, false, true); +} + +TEST_F(sys_call_test, tcp_client_server_multiple) +{ + runtest(SENDRECEIVE, default_payload, false, false, 10); +} + +TEST_F(sys_call_test, tcp_client_server_noclose) +{ + runtest(SENDRECEIVE, default_payload, false, false, 1, true); +} + +TEST_F(sys_call_test, tcp_client_server_http_snaplen) +{ + runtest(SENDRECEIVE, http_payload); +} + +TEST_F(sys_call_test, tcp_client_server_read_write_http_snaplen) +{ + runtest(READWRITE, http_payload); +} + +TEST_F(sys_call_test, tcp_client_server_readv_writev_http_snaplen) +{ + runtest(READVWRITEV, http_payload); +} + +TEST_F(sys_call_test, tcp_client_server_with_connection_before_capturing_starts) +{ + std::thread server_thread; + std::thread client_thread; + tcp_server server(SENDRECEIVE, true); + uint32_t server_ip_address = get_server_address(); + tcp_client client(server_ip_address, SENDRECEIVE, default_payload, true); + + int state = 0; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == server.get_tid() || evt->get_tid() == client.get_tid(); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + server.signal_continue(); + client.signal_continue(); + server_thread.join(); + client_thread.join(); + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* evt = param.m_evt; + if (PPME_SYSCALL_CLOSE_X == evt->get_type() && evt->get_tid() == server.get_tid()) + { + state = 1; + } + }; + + server_thread = std::thread(&tcp_server::run, &server); + client_thread = std::thread(&tcp_client::run, &client); + server.wait_till_ready(); + client.wait_till_ready(); + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_EQ(1, state); +} + + +#ifdef __x86_64__ +TEST_F(sys_call_test32, tcp_client_server) +{ + runtest(SENDRECEIVE, default_payload, false, false, 1, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_read_write) +{ + runtest(READWRITE, default_payload, false, false, 1, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_readv_writev) +{ + runtest(READVWRITEV, default_payload, false, false, 1, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_shutdown) +{ + runtest(SENDRECEIVE, default_payload, true, false, 1, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_accept4) +{ + runtest(SENDRECEIVE, default_payload, false, true, 1, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_multiple) +{ + runtest(SENDRECEIVE, default_payload, false, false, 10, false, true); +} + +TEST_F(sys_call_test32, tcp_client_server_noclose) +{ + runtest(SENDRECEIVE, default_payload, false, false, 1, true, true); +} +#endif diff --git a/test/libsinsp_e2e/tcp_client_server.h b/test/libsinsp_e2e/tcp_client_server.h new file mode 100644 index 00000000000..801b126dd48 --- /dev/null +++ b/test/libsinsp_e2e/tcp_client_server.h @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef HELPER_32 +#include +#endif + +#define SERVER_PORT 3555 +#define SERVER_PORT_STR "3555" +#define FALSE 0 + +typedef enum iotype +{ + READWRITE, + SENDRECEIVE, + READVWRITEV +} iotype; + +class std_event +{ +public: + void set() + { + std::lock_guard lock(m_mutex); + m_is_set = true; + m_cond.notify_one(); + } + void wait() + { + std::unique_lock lock(m_mutex); + if (m_is_set) + { + return; + } + else + { + m_cond.wait(lock, [this]() { return m_is_set; }); + } + } + +private: + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_is_set{false}; +}; + +class tcp_server +{ +public: + tcp_server(iotype iot, + bool wait_for_signal_to_continue = false, + bool use_shutdown = false, + bool use_accept4 = false, + uint32_t ntransactions = 1, + bool exit_no_close = false) + { + m_iot = iot; + m_wait_for_signal_to_continue = wait_for_signal_to_continue; + m_use_shutdown = use_shutdown; + m_use_accept4 = use_accept4; + m_ntransactions = ntransactions; + m_exit_no_close = exit_no_close; + } + + void run() + { + int servSock; + int clntSock; + struct sockaddr_in server_address; + struct sockaddr_in client_address; + unsigned int client_len; + uint32_t j; + int port = (m_exit_no_close) ? SERVER_PORT + 1 : SERVER_PORT; + + m_tid = syscall(SYS_gettid); + + /* Create socket for incoming connections */ + if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror("socket() failed"); + return; + } + + /* Construct local address structure */ + memset(&server_address, 0, sizeof(server_address)); /* Zero out structure */ + server_address.sin_family = AF_INET; /* Internet address family */ + server_address.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */ + server_address.sin_port = htons(port); /* Local port */ + + int yes = 1; + if (setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { +#ifdef FAIL + FAIL() << "setsockopt() failed"; +#endif + } + + /* Bind to the local address */ + if (::bind(servSock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) + { +#ifdef FAIL + FAIL() << "bind() failed"; +#endif + return; + } + /* Mark the socket so it will listen for incoming connections */ + if (listen(servSock, 1) < 0) + { + close(servSock); +#ifdef FAIL + FAIL() << "listen() failed"; +#endif + return; + } + std::cout << "SERVER UP" << std::endl; + do + { + /* Set the size of the in-out parameter */ + client_len = sizeof(client_address); + signal_ready(); + + /* Wait for a client to connect */ + if (m_use_accept4) + { + if ((clntSock = + accept4(servSock, (struct sockaddr*)&client_address, &client_len, 0)) < 0) + { + close(servSock); +#ifdef FAIL + FAIL() << "accept() failed"; +#endif + break; + } + } + else + { + if ((clntSock = accept(servSock, (struct sockaddr*)&client_address, &client_len)) < + 0) + { + close(servSock); +#ifdef FAIL + FAIL() << "accept() failed"; +#endif + break; + } + } + + /* clntSock is connected to a client! */ + wait_for_continue(); + char echoBuffer[1024]; /* Buffer for echo string */ + int recvMsgSize; /* Size of received message */ + for (j = 0; j < m_ntransactions; j++) + { + if (m_iot == SENDRECEIVE) + { + if ((recvMsgSize = recv(clntSock, echoBuffer, sizeof(echoBuffer), 0)) < 0) + { +#ifdef FAIL + FAIL() << "recv() failed"; +#endif + break; + } + + if (send(clntSock, echoBuffer, recvMsgSize, 0) != recvMsgSize) + { +#ifdef FAIL + FAIL() << "send() failed"; +#endif + break; + } + } + else if (m_iot == READWRITE || m_iot == READVWRITEV) + { + if ((recvMsgSize = read(clntSock, echoBuffer, sizeof(echoBuffer))) < 0) + { +#ifdef FAIL + FAIL() << "recv() failed"; +#endif + break; + } + + if (write(clntSock, echoBuffer, recvMsgSize) != recvMsgSize) + { +#ifdef FAIL + FAIL() << "send() failed"; +#endif + break; + } + } + } + + if (m_exit_no_close) + { + return; + } + + if (m_use_shutdown) + { +#ifdef ASSERT_EQ + ASSERT_EQ(0, shutdown(clntSock, SHUT_WR)); +#endif + } + else + { + close(clntSock); /* Close client socket */ + } + break; + } while (0); + + if (m_use_shutdown) + { +#ifdef ASSERT_EQ + ASSERT_EQ(0, shutdown(servSock, SHUT_RDWR)); +#endif + } + else + { + close(servSock); + } + } + + void wait_till_ready() { m_ready.wait(); } + + void signal_continue() { m_continue.set(); } + + int64_t get_tid() { return m_tid; } + +private: + void signal_ready() { m_ready.set(); } + + void wait_for_continue() + { + if (m_wait_for_signal_to_continue) + { + m_continue.wait(); + } + } + + std_event m_ready; + std_event m_continue; + bool m_wait_for_signal_to_continue; + int64_t m_tid; + iotype m_iot; + bool m_use_shutdown; + bool m_use_accept4; + uint32_t m_ntransactions; + bool m_exit_no_close; +}; + +class tcp_client +{ +public: + tcp_client(uint32_t server_ip_address, + iotype iot, + const std::string& payload = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM", + bool on_thread = false, + uint32_t ntransactions = 1, + bool exit_no_close = false) + { + m_server_ip_address = server_ip_address; + m_iot = iot; + m_payload = payload; + m_on_thread = on_thread; + m_ntransactions = ntransactions; + m_exit_no_close = exit_no_close; + } + + void run() + { + int sock; + struct sockaddr_in server_address; + char buffer[m_payload.size() + 1]; + int bytes_received; + uint32_t j; + int port = (m_exit_no_close) ? SERVER_PORT + 1 : SERVER_PORT; + + m_tid = syscall(SYS_gettid); + + /* Create a reliable, stream socket using TCP */ + if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { +#ifdef FAIL + FAIL() << "socket() failed"; +#endif + return; + } + + /* Construct the server address structure */ + memset(&server_address, 0, sizeof(server_address)); /* Zero out structure */ + server_address.sin_family = AF_INET; /* Internet address family */ + server_address.sin_addr.s_addr = m_server_ip_address; /* Server IP address */ + server_address.sin_port = htons(port); /* Server port */ + + /* Establish the connection to the server */ + if (connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) + { +#ifdef FAIL + FAIL() << "connect() failed"; +#endif + return; + } + signal_ready(); + wait_for_continue(); + + for (j = 0; j < m_ntransactions; j++) + { + /* Send the string to the server */ + if (m_iot == SENDRECEIVE) + { + if (send(sock, m_payload.c_str(), m_payload.length(), 0) != + (ssize_t)m_payload.length()) + { + close(sock); +#ifdef FAIL + FAIL() << "send() sent a different number of bytes than expected"; +#endif + return; + } + + if ((bytes_received = recv(sock, buffer, m_payload.length(), 0)) <= 0) + { + close(sock); +#ifdef FAIL + FAIL() << "recv() failed or connection closed prematurely"; +#endif + return; + } + + buffer[bytes_received] = '\0'; /* Terminate the string! */ +#ifdef ASSERT_STREQ + ASSERT_STREQ(m_payload.c_str(), buffer); +#endif + } + else if (m_iot == READWRITE) + { + if (write(sock, m_payload.c_str(), m_payload.length()) != + (ssize_t)m_payload.length()) + { + close(sock); +#ifdef FAIL + FAIL() << "send() sent a different number of bytes than expected"; +#endif + return; + } + + if ((bytes_received = read(sock, buffer, m_payload.length())) <= 0) + { + close(sock); +#ifdef FAIL + FAIL() << "recv() failed or connection closed prematurely"; +#endif + return; + } + + buffer[bytes_received] = '\0'; /* Terminate the string! */ +#ifdef ASSERT_STREQ + ASSERT_STREQ(m_payload.c_str(), buffer); +#endif + } + else if (m_iot == READVWRITEV) + { + int wv_count; + char msg1[m_payload.length() / 3 + 1]; + char msg2[m_payload.length() / 3 + 1]; + char msg3[m_payload.length() / 3 + 1]; + struct iovec wv[3]; + + memcpy(msg1, + m_payload.substr(0, m_payload.length() / 3).c_str(), + m_payload.length() / 3); + memcpy(msg2, + m_payload.substr(m_payload.length() / 3, m_payload.length() * 2 / 3).c_str(), + m_payload.length() / 3); + memcpy(msg3, + m_payload.substr(m_payload.length() * 2 / 3, m_payload.length()).c_str(), + m_payload.length() / 3); + + wv[0].iov_base = msg1; + wv[1].iov_base = msg2; + wv[2].iov_base = msg3; + wv[0].iov_len = m_payload.length() / 3; + wv[1].iov_len = m_payload.length() / 3; + wv[2].iov_len = m_payload.length() / 3; + wv_count = 3; + + if (writev(sock, wv, wv_count) != (ssize_t)m_payload.length()) + { + close(sock); +#ifdef FAIL + FAIL() << "send() sent a different number of bytes than expected"; +#endif + return; + } + + if ((bytes_received = readv(sock, wv, wv_count)) <= 0) + { + close(sock); +#ifdef FAIL + FAIL() << "recv() failed or connection closed prematurely"; +#endif + return; + } + } + } + + if (m_exit_no_close) + { + return; + } + + close(sock); + } + + void wait_till_ready() { m_ready.wait(); } + + void signal_continue() { m_continue.set(); } + + int64_t get_tid() { return m_tid; } + +private: + void signal_ready() { m_ready.set(); } + + void wait_for_continue() + { + if (m_on_thread) + { + m_continue.wait(); + } + } + + uint32_t m_server_ip_address; + iotype m_iot; + std_event m_ready; + std_event m_continue; + int64_t m_tid; + bool m_on_thread; + uint32_t m_ntransactions; + bool m_exit_no_close; + std::string m_payload; +}; diff --git a/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp b/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp new file mode 100644 index 00000000000..4b6aa1f504c --- /dev/null +++ b/test/libsinsp_e2e/tcp_client_server_ipv4_mapped.cpp @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "event_capture.h" +#include "sys_call_test.h" +#include "tcp_client_server.h" +#include "utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define SERVER_PORT 3555 +#define SERVER_PORT_STR "3555" +#define SERVER_PORT_NOCLOSE_STR "3557" +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +#define BUFFER_LENGTH sizeof(PAYLOAD) +#define FALSE 0 + +class tcp_server_ipv4m +{ +public: + tcp_server_ipv4m(iotype iot, + bool wait_for_signal_to_continue = false, + bool use_shutdown = false, + bool use_accept4 = false, + uint32_t ntransactions = 1, + bool exit_no_close = false) + { + m_iot = iot; + m_wait_for_signal_to_continue = wait_for_signal_to_continue; + m_use_shutdown = use_shutdown; + m_use_accept4 = use_accept4; + m_ntransactions = ntransactions; + m_exit_no_close = exit_no_close; + } + + void run() + { + int servSock; + int clntSock; + struct sockaddr_in6 server_address; + struct sockaddr_in6 client_address; + unsigned int client_len; + uint32_t j; + + int port = (m_exit_no_close) ? SERVER_PORT + 2 : SERVER_PORT; + + m_tid = syscall(SYS_gettid); + + /* Create socket for incoming connections */ + if ((servSock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) + { + perror("socket() failed"); + return; + } + + /* Construct local address structure */ + memset(&server_address, 0, sizeof(server_address)); + server_address.sin6_family = AF_INET6; + server_address.sin6_port = htons(port); + server_address.sin6_addr = in6addr_any; + + int yes = 1; + if (setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + FAIL() << "setsockopt() failed"; + } + + /* Bind to the local address */ + if (::bind(servSock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) + { + perror("bind() failed"); + FAIL(); + return; + } + /* Mark the socket so it will listen for incoming connections */ + if (listen(servSock, 1) < 0) + { + close(servSock); + FAIL() << "listen() failed"; + return; + } + do + { + /* Set the size of the in-out parameter */ + client_len = sizeof(client_address); + signal_ready(); + + /* Wait for a client to connect */ + if (m_use_accept4) + { + if ((clntSock = + accept4(servSock, (struct sockaddr*)&client_address, &client_len, 0)) < 0) + { + close(servSock); + FAIL() << "accept() failed"; + break; + } + } + else + { + if ((clntSock = accept(servSock, (struct sockaddr*)&client_address, &client_len)) < + 0) + { + close(servSock); + FAIL() << "accept() failed"; + break; + } + } + + /* clntSock is connected to a client! */ + wait_for_continue(); + char echoBuffer[BUFFER_LENGTH]; /* Buffer for echo string */ + int recvMsgSize; /* Size of received message */ + for (j = 0; j < m_ntransactions; j++) + { + if (m_iot == SENDRECEIVE) + { + if ((recvMsgSize = recv(clntSock, echoBuffer, BUFFER_LENGTH, 0)) < 0) + { + FAIL() << "recv() failed"; + break; + } + + if (send(clntSock, echoBuffer, recvMsgSize, 0) != recvMsgSize) + { + FAIL() << "send() failed"; + break; + } + } + else if (m_iot == READWRITE || m_iot == READVWRITEV) + { + if ((recvMsgSize = read(clntSock, echoBuffer, BUFFER_LENGTH)) < 0) + { + FAIL() << "recv() failed"; + break; + } + + if (write(clntSock, echoBuffer, recvMsgSize) != recvMsgSize) + { + FAIL() << "send() failed"; + break; + } + } + } + + if (m_exit_no_close) + { + return; + } + + if (m_use_shutdown) + { + ASSERT_EQ(0, shutdown(clntSock, SHUT_WR)); + } + else + { + close(clntSock); /* Close client socket */ + } + break; + } while (0); + + if (m_use_shutdown) + { + ASSERT_EQ(0, shutdown(servSock, SHUT_RDWR)); + } + else + { + close(servSock); + } + } + + void wait_till_ready() { m_ready.wait(); } + + void signal_continue() { m_continue.set(); } + + int64_t get_tid() { return m_tid; } + +private: + void signal_ready() { m_ready.set(); } + + void wait_for_continue() + { + if (m_wait_for_signal_to_continue) + { + m_continue.wait(); + } + } + + std_event m_ready; + std_event m_continue; + bool m_wait_for_signal_to_continue; + int64_t m_tid; + iotype m_iot; + bool m_use_shutdown; + bool m_use_accept4; + uint32_t m_ntransactions; + bool m_exit_no_close; +}; + +class tcp_client_ipv4m +{ +public: + tcp_client_ipv4m(uint32_t server_ip_address, + iotype iot, + bool on_thread = false, + uint32_t ntransactions = 1, + bool exit_no_close = false) + { + m_server_ip_address = server_ip_address; + m_iot = iot; + m_on_thread = on_thread; + m_ntransactions = ntransactions; + m_exit_no_close = exit_no_close; + } + + void run() + { + int sock; + struct sockaddr_in server_address; + char buffer[BUFFER_LENGTH]; + int payload_length; + int bytes_received; + uint32_t j; + int port = (m_exit_no_close) ? SERVER_PORT + 2 : SERVER_PORT; + + m_tid = syscall(SYS_gettid); + + /* Create a reliable, stream socket using TCP */ + if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + FAIL() << "socket() failed"; + return; + } + + /* Construct the server address structure */ + memset(&server_address, 0, sizeof(server_address)); /* Zero out structure */ + server_address.sin_family = AF_INET; /* Internet address family */ + server_address.sin_addr.s_addr = m_server_ip_address; /* Server IP address */ + server_address.sin_port = htons(port); /* Server port */ + + /* Establish the connection to the server */ + if (connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) + { + perror("connect() failed"); + FAIL(); + return; + } + signal_ready(); + wait_for_continue(); + payload_length = strlen(PAYLOAD); /* Determine input length */ + + for (j = 0; j < m_ntransactions; j++) + { + /* Send the string to the server */ + if (m_iot == SENDRECEIVE) + { + if (send(sock, PAYLOAD, payload_length, 0) != payload_length) + { + close(sock); + FAIL() << "send() sent a different number of bytes than expected"; + return; + } + + if ((bytes_received = recv(sock, buffer, BUFFER_LENGTH - 1, 0)) <= 0) + { + close(sock); + FAIL() << "recv() failed or connection closed prematurely"; + return; + } + + buffer[bytes_received] = '\0'; /* Terminate the string! */ + ASSERT_STREQ(PAYLOAD, buffer); + } + else if (m_iot == READWRITE) + { + if (write(sock, PAYLOAD, payload_length) != payload_length) + { + close(sock); + FAIL() << "send() sent a different number of bytes than expected"; + return; + } + + if ((bytes_received = read(sock, buffer, BUFFER_LENGTH - 1)) <= 0) + { + close(sock); + FAIL() << "recv() failed or connection closed prematurely"; + return; + } + + buffer[bytes_received] = '\0'; /* Terminate the string! */ + ASSERT_STREQ(PAYLOAD, buffer); + } + else if (m_iot == READVWRITEV) + { + std::string ps(PAYLOAD); + int wv_count; + char msg1[BUFFER_LENGTH / 3 + 1]; + char msg2[BUFFER_LENGTH / 3 + 1]; + char msg3[BUFFER_LENGTH / 3 + 1]; + struct iovec wv[3]; + + memcpy(msg1, ps.substr(0, BUFFER_LENGTH / 3).c_str(), BUFFER_LENGTH / 3); + memcpy(msg2, + ps.substr(BUFFER_LENGTH / 3, BUFFER_LENGTH * 2 / 3).c_str(), + BUFFER_LENGTH / 3); + memcpy(msg3, + ps.substr(BUFFER_LENGTH * 2 / 3, BUFFER_LENGTH).c_str(), + BUFFER_LENGTH / 3); + + wv[0].iov_base = msg1; + wv[1].iov_base = msg2; + wv[2].iov_base = msg3; + wv[0].iov_len = BUFFER_LENGTH / 3; + wv[1].iov_len = BUFFER_LENGTH / 3; + wv[2].iov_len = BUFFER_LENGTH / 3; + wv_count = 3; + + if (writev(sock, wv, wv_count) != payload_length) + { + close(sock); + FAIL() << "send() sent a different number of bytes than expected"; + return; + } + + if ((bytes_received = readv(sock, wv, wv_count)) <= 0) + { + close(sock); + FAIL() << "recv() failed or connection closed prematurely"; + return; + } + } + } + + if (m_exit_no_close) + { + return; + } + + close(sock); + } + + void wait_till_ready() { m_ready.wait(); } + + void signal_continue() { m_continue.set(); } + + int64_t get_tid() { return m_tid; } + +private: + void signal_ready() { m_ready.set(); } + + void wait_for_continue() + { + if (m_on_thread) + { + m_continue.wait(); + } + } + + uint32_t m_server_ip_address; + iotype m_iot; + std_event m_ready; + std_event m_continue; + int64_t m_tid; + bool m_on_thread; + uint32_t m_ntransactions; + bool m_exit_no_close; +}; + +void runtest_ipv4m(iotype iot, + bool use_shutdown = false, + bool use_accept4 = false, + uint32_t ntransactions = 1, + bool exit_no_close = false) +{ + int callnum = 0; + std::thread server_thread; + std::shared_ptr server = std::make_shared(iot, false, use_shutdown, use_accept4, ntransactions, exit_no_close); + + uint32_t server_ip_address = get_server_address(); + + struct in_addr server_in_addr; + server_in_addr.s_addr = get_server_address(); + + char* server_address = inet_ntoa(server_in_addr); + std::string sport; + int state = 0; + int ctid; + int tid = getpid(); + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + return evt->get_tid() == server->get_tid() || evt->get_tid() == tid; + }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + server_thread = std::thread(&tcp_server_ipv4m::run, server); + server->wait_till_ready(); + + tcp_client_ipv4m client(server_ip_address, iot, false, ntransactions, exit_no_close); + + client.run(); + + ctid = client.get_tid(); + sleep(1); + server_thread.join(); + + // We use a random call to tee to signal that we're done + tee(-1, -1, 0, 0); + }; + + std::function log_param = [](const callback_param& param) + { + //std::cerr << param.m_evt->get_name() << std::endl; + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + sinsp_evt* evt = param.m_evt; + if (evt->get_type() == PPME_SOCKET_CONNECT_X) + { + std::string tuple = evt->get_param_value_str("tuple"); + if(!parse_tuple(tuple, src_addr, src_port, dst_addr, dst_port)) + { + return; + } + + EXPECT_NE((sinsp_fdinfo*)NULL, evt->get_fd_info()); + + if (evt->get_fd_info()->m_type != SCAP_FD_IPV4_SOCK) + { + // + // Skip non-tcp sockets. Python opens unix sockets + // to god knows what. + // + return; + } + + EXPECT_EQ(server_address, src_addr); + if (sport == "") + { + EXPECT_NE("0", src_port); + sport = src_port; + } + else + { + EXPECT_EQ(sport, src_port); + } + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_LISTEN_E) + { + EXPECT_EQ("1", evt->get_param_value_str("backlog")); + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_LISTEN_X) + { + EXPECT_EQ("0", evt->get_param_value_str("res")); + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_ACCEPT4_6_E) + { + EXPECT_EQ("0", evt->get_param_value_str("flags")); + } + else if (evt->get_type() == PPME_SOCKET_ACCEPT_5_X || + evt->get_type() == PPME_SOCKET_ACCEPT4_6_X) + { + if(!parse_tuple(evt->get_param_value_str("tuple"), src_addr, src_port, dst_addr, dst_port)) + { + return; + } + + EXPECT_EQ(server_address, src_addr); + if (sport == "") + { + EXPECT_NE("0", src_port); + sport = src_port; + } + else + { + EXPECT_EQ(sport, src_port); + } + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + + log_param(param); + callnum++; + } + + if (callnum < 1) + { + return; + } + + // + // 32bit uses send() and recv(), while 64bit always uses sendto() and + // recvfrom() and sets the address to NULL + // + if (evt->get_type() == PPME_SOCKET_SEND_E || evt->get_type() == PPME_SOCKET_RECV_E || + evt->get_type() == PPME_SOCKET_SENDTO_E || evt->get_type() == PPME_SOCKET_RECVFROM_E || + evt->get_type() == PPME_SYSCALL_READ_E || evt->get_type() == PPME_SYSCALL_WRITE_E || + evt->get_type() == PPME_SYSCALL_READV_E || evt->get_type() == PPME_SYSCALL_WRITEV_E) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_E) + { + if (evt->get_param_value_str("tuple") != "") + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + } + + std::string tuple = evt->get_param_value_str("fd"); + tuple = tuple.substr(tuple.find(">")+1); + if(!parse_tuple(tuple, src_addr, src_port, dst_addr, dst_port)) + { + return; + } + + EXPECT_EQ(server_address, src_addr); + EXPECT_EQ(sport, src_port); + + EXPECT_EQ(server_address, dst_addr); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SOCKET_RECV_X || + evt->get_type() == PPME_SOCKET_RECVFROM_X || + evt->get_type() == PPME_SYSCALL_READ_X) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_X) + { + if(event_capture::get_engine() != MODERN_BPF_ENGINE) + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + else + { + if(!parse_tuple(evt->get_param_value_str("tuple"), src_addr, src_port, dst_addr, dst_port)) + { + return; + } + EXPECT_EQ(server_address, src_addr); + EXPECT_EQ(server_address, dst_addr); + + if(callnum == 7) + { + EXPECT_EQ(sport, src_port); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, dst_port); + } + else + { + EXPECT_EQ(SERVER_PORT_NOCLOSE_STR, dst_port); + } + } + else if(callnum == 9) + { + EXPECT_EQ(sport, dst_port); + if (!exit_no_close) + { + EXPECT_EQ(SERVER_PORT_STR, src_port); + } + else + { + EXPECT_EQ(SERVER_PORT_NOCLOSE_STR, src_port); + } + } + + } + } + + EXPECT_EQ(PAYLOAD, evt->get_param_value_str("data")); + + log_param(param); + callnum++; + } + else if (evt->get_type() == PPME_SYSCALL_READV_X) + { + std::string ds = evt->get_param_value_str("data"); + + EXPECT_EQ(ds, evt->get_param_value_str("data")); + + log_param(param); + callnum++; + } + + if ((PPME_SYSCALL_CLOSE_X == evt->get_type() || + PPME_SOCKET_SHUTDOWN_X == evt->get_type()) && + 0 == state && evt->get_tid() == server->get_tid()) + { + if (exit_no_close) + { + FAIL(); + } + + state = 1; + } + + if (!(use_shutdown || exit_no_close)) + { + if (evt->get_type() == PPME_GENERIC_E) + { + if (std::stoll(evt->get_param_value_str("ID", false)) == PPM_SC_TEE) + { + sinsp_threadinfo* ti = param.m_inspector->get_thread_ref(server->get_tid(), false, true).get(); + ASSERT_NE(ti, nullptr); + ti = param.m_inspector->get_thread_ref(ctid, false, true).get(); + ASSERT_NE(ti, nullptr); + } + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); +} + +TEST_F(sys_call_test, tcp_client_server_ipv4m) +{ + runtest_ipv4m(SENDRECEIVE); +} + +TEST_F(sys_call_test, tcp_client_server_read_write_ipv4m) +{ + runtest_ipv4m(READWRITE); +} + +TEST_F(sys_call_test, tcp_client_server_readv_writev_ipv4m) +{ + runtest_ipv4m(READVWRITEV); +} + +TEST_F(sys_call_test, tcp_client_server_shutdown_ipv4m) +{ + runtest_ipv4m(SENDRECEIVE, true); +} + +TEST_F(sys_call_test, tcp_client_server_accept4_ipv4m) +{ + runtest_ipv4m(SENDRECEIVE, false, true); +} + +TEST_F(sys_call_test, tcp_client_server_multiple_ipv4m) +{ + runtest_ipv4m(SENDRECEIVE, false, false, 10); +} + +TEST_F(sys_call_test, tcp_client_server_noclose_ipv4m) +{ + runtest_ipv4m(SENDRECEIVE, false, false, 1, true); +} + +TEST_F(sys_call_test, tcp_client_server_with_connection_before_capturing_starts_ipv4m) +{ + std::thread server_thread; + std::thread client_thread; + tcp_server_ipv4m server(SENDRECEIVE, true); + uint32_t server_ip_address = get_server_address(); + tcp_client_ipv4m client(server_ip_address, SENDRECEIVE, true); + + int state = 0; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == server.get_tid() || evt->get_tid() == client.get_tid(); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + server.signal_continue(); + client.signal_continue(); + server_thread.join(); + client_thread.join(); + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* evt = param.m_evt; + if (PPME_SYSCALL_CLOSE_X == evt->get_type() && evt->get_tid() == server.get_tid()) + { + state = 1; + } + }; + + server_thread = std::thread(&tcp_server_ipv4m::run, &server); + client_thread = std::thread(&tcp_client_ipv4m::run, &client); + server.wait_till_ready(); + client.wait_till_ready(); + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_EQ(1, state); +} diff --git a/test/libsinsp_e2e/test_helper.cpp b/test/libsinsp_e2e/test_helper.cpp index 76d982da11f..ec41c445abd 100644 --- a/test/libsinsp_e2e/test_helper.cpp +++ b/test/libsinsp_e2e/test_helper.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + #include #include #include @@ -20,6 +38,9 @@ #include #include +#define HELPER_32 +#include "tcp_client_server.h" + #include #include @@ -660,6 +681,31 @@ void cri_container_sleep_lzcat(const vector& args) const unordered_map&)>> func_map = { {"proc_mgmt", proc_mgmt}, {"mmap_test", mmap_test}, + {"tcp_client", + [](const vector& args) + { + auto iot = static_cast(stoi(args.at(1))); + tcp_client client(inet_addr(args.at(0).c_str()), + iot, + args.at(2), + str_to_bool(args.at(3)), + stoi(args.at(4)), + str_to_bool(args.at(5))); + client.run(); + }}, + {"tcp_server", + [](const vector& args) + { + auto iot = static_cast(stoi(args.at(0))); + + tcp_server server(iot, + str_to_bool(args.at(1)), + str_to_bool(args.at(2)), + str_to_bool(args.at(3)), + stoi(args.at(4)), + str_to_bool(args.at(5))); + server.run(); + }}, {"pread_pwrite", pread_pwrite}, {"preadv_pwritev", preadv_pwritev}, {"quotactl_ko", quotactl_ko}, diff --git a/test/libsinsp_e2e/udp_client_server.cpp b/test/libsinsp_e2e/udp_client_server.cpp new file mode 100644 index 00000000000..93b52ec0dff --- /dev/null +++ b/test/libsinsp_e2e/udp_client_server.cpp @@ -0,0 +1,1099 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include "sys_call_test.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; + +#include + +#define SERVER_PORT 3555 +#define SERVER_PORT_STR "3555" +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +#define BUFFER_LENGTH (sizeof(PAYLOAD) - 1) +#define FALSE 0 +#define NTRANSACTIONS 2 + +class udp_server +{ + public: + udp_server(bool use_unix, bool use_sendmsg, bool recvmsg_twobufs, uint32_t port_offset = 0) + { + m_use_unix = use_unix; + m_use_sendmsg = use_sendmsg; + m_recvmsg_twobufs = recvmsg_twobufs; + m_port = SERVER_PORT + port_offset; + m_server_ready = false; + } + + void run() + { + int sd = -1, rc; + char buffer[BUFFER_LENGTH + 10]; + char buffer1[BUFFER_LENGTH - 10]; + struct sockaddr_in serveraddr; + struct sockaddr_in clientaddr; + socklen_t clientaddrlen = sizeof(clientaddr); + int j; + int domain; + + m_tid = syscall(SYS_gettid); + + if (m_use_unix) + { + domain = AF_UNIX; + } + else + { + domain = AF_INET; + } + + do + { + sd = socket(domain, SOCK_DGRAM, 0); + if (sd < 0) + { + perror("socket() failed"); + break; + } + + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = domain; + serveraddr.sin_port = htons(m_port); + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); + + rc = ::bind(sd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); + if (rc < 0) + { + perror("bind() failed"); + break; + } + + { + std::unique_lock lock(m_mutex); + m_server_ready = true; + m_condition_server_ready.notify_one(); + } + + for (j = 0; j < NTRANSACTIONS; j++) + { + if (m_use_sendmsg) + { + struct msghdr msg; + struct iovec iov[2]; + + if (m_recvmsg_twobufs) + { + iov[0].iov_base = buffer1; + iov[0].iov_len = BUFFER_LENGTH - 10; + iov[1].iov_base = buffer; + iov[1].iov_len = BUFFER_LENGTH - 10; + + msg.msg_name = &clientaddr; + msg.msg_namelen = clientaddrlen; + msg.msg_iov = iov; + msg.msg_iovlen = 2; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + // + // Receive the data + // + int res = recvmsg(sd, &msg, 0); + EXPECT_EQ(res, (int)BUFFER_LENGTH); + + // + // Set the send buffer + // + iov[0].iov_len = BUFFER_LENGTH - 10; + iov[1].iov_len = 10; + } + else + { + iov[0].iov_base = buffer; + iov[0].iov_len = BUFFER_LENGTH + 10; + + msg.msg_name = &clientaddr; + msg.msg_namelen = clientaddrlen; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_control = 0; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + // + // Receive the data + // + int res = recvmsg(sd, &msg, 0); + EXPECT_EQ(res, (int)BUFFER_LENGTH); + + // + // Set the send buffer + // + iov[0].iov_len = BUFFER_LENGTH; + } + + // + // Echo the data back to the client + // + if (sendmsg(sd, &msg, 0) == -1) + { + perror("sendmsg() failed"); + break; + } + } + else + { + // + // Receive the data + // + rc = recvfrom(sd, + buffer, + sizeof(buffer), + 0, + (struct sockaddr*)&clientaddr, + &clientaddrlen); + if (rc < 0) + { + perror("recvfrom() failed"); + break; + } + + // + // Echo the data back to the client + // + rc = sendto(sd, + buffer, + sizeof(buffer), + 0, + (struct sockaddr*)&clientaddr, + sizeof(clientaddr)); + if (rc < 0) + { + FAIL(); + perror("sendto() failed"); + break; + } + } + } + } while (FALSE); + + if (sd != -1) + close(sd); + } + + void wait_for_server_ready() + { + { + std::unique_lock lock(m_mutex); + m_condition_server_ready.wait(lock, [this]() { + return m_server_ready; + }); + m_server_ready = false; + } + } + + int64_t get_tid() { return m_tid; } + + private: + std::mutex m_mutex; + std::condition_variable m_condition_server_ready; + bool m_server_ready; + int64_t m_tid; + bool m_use_unix; + bool m_use_sendmsg; + bool m_recvmsg_twobufs; + uint16_t m_port; +}; + +class udp_client +{ + public: + udp_client(uint32_t server_ip_address, + bool use_connect, + uint16_t base_port = SERVER_PORT, + uint32_t num_servers = 1) + : m_use_sendmsg(false), + m_recv(true), + m_payload(PAYLOAD), + m_ignore_errors(false), + m_n_transactions(NTRANSACTIONS) + { + m_use_unix = false; + m_server_ip_address = server_ip_address; + m_use_connect = use_connect; + for (uint32_t idx = 0; idx < num_servers; idx++) + { + m_server_ports.push_back(base_port + idx); + } + } + + void run() + { + int sd; + int domain; + + if (m_use_unix) + { + domain = AF_UNIX; + } + else + { + domain = AF_INET; + } + + sd = socket(domain, SOCK_DGRAM, 0); + if (sd < 0) + { + FAIL(); + } + + for (auto port : m_server_ports) + { + run_using_port(sd, domain, port); + } + + if (sd != -1) + { + close(sd); + } + } + + void run_using_port(int sd, int domain, uint16_t port) + { + int rc; + int j; + struct sockaddr_in serveraddr; + socklen_t serveraddrlen = sizeof(serveraddr); + + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = domain; + serveraddr.sin_port = htons(port); + serveraddr.sin_addr.s_addr = m_server_ip_address; + + if (m_use_connect) + { + if (connect(sd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0 && + !m_ignore_errors) + { + close(sd); + FAIL() << "connect() failed"; + } + } + + for (j = 0; j < m_n_transactions; j++) + { + if (!m_use_sendmsg) + { + if (m_use_connect) + { + rc = sendto(sd, m_payload.data(), m_payload.size(), 0, NULL, 0); + } + else + { + rc = sendto(sd, + m_payload.data(), + m_payload.size(), + 0, + (struct sockaddr*)&serveraddr, + sizeof(serveraddr)); + } + } + else + { + struct msghdr msg = {0}; + if (m_use_connect) + { + msg.msg_name = NULL; + } + else + { + msg.msg_name = (void*)&serveraddr; + msg.msg_namelen = sizeof(serveraddr); + } + struct iovec iov; + iov.iov_base = (void*)m_payload.data(); + iov.iov_len = m_payload.size(); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + rc = sendmsg(sd, &msg, MSG_DONTWAIT); + } + if (rc < 0 && !m_ignore_errors) + { + close(sd); + FAIL(); + } + + // + // Use the recvfrom() function to receive the data back from the + // server. + // + if (m_recv) + { + char* buffer = (char*)malloc(m_payload.size()); + rc = recvfrom(sd, + buffer, + m_payload.size(), + 0, + (struct sockaddr*)&serveraddr, + &serveraddrlen); + free(buffer); + if (rc < 0 && !m_ignore_errors) + { + close(sd); + FAIL(); + } + } + } + } + + bool m_use_sendmsg; + bool m_recv; + std::string m_payload; + bool m_use_connect; + bool m_ignore_errors; + int m_n_transactions; + + private: + bool m_use_unix; + uint32_t m_server_ip_address; + std::vector m_server_ports; +}; + +class udp_servers_and_client +{ + public: + udp_servers_and_client(bool use_unix, + bool use_sendmsg, + bool recvmsg_twobufs, + bool use_connect, + uint32_t num_servers) + { + m_server_ip_address = get_server_address(); + struct in_addr server_in_addr; + server_in_addr.s_addr = m_server_ip_address; + m_server_address = inet_ntoa(server_in_addr); + m_use_connect = use_connect; + + for (uint32_t idx = 0; idx < num_servers; idx++) + { + m_server_ports.insert(SERVER_PORT + idx); + m_servers.emplace_back( + std::make_shared(use_unix, use_sendmsg, recvmsg_twobufs, idx)); + } + } + + uint32_t server_ip_address() { return m_server_ip_address; } + + std::string& server_address() { return m_server_address; } + + bool is_server_tid(int64_t tid) + { + for (auto& srv : m_servers) + { + if (tid == srv->get_tid()) + { + return true; + } + } + + return false; + } + + std::vector>& get_servers() { return m_servers; } + + bool is_server_port(std::string& portstr) + { + uint16_t port = std::stoi(portstr); + + return (port >= SERVER_PORT && port < SERVER_PORT + m_servers.size()); + } + + bool filter(sinsp_evt* evt) { return is_server_tid(evt->get_tid()); } + + std::string server_port_yaml() + { + std::stringstream out; + for (auto port : m_server_ports) + { + out << " - " << port << "\n"; + } + return out.str(); + } + + void start() + { + for (uint32_t idx = 0; idx < m_servers.size(); idx++) + { + m_threads.emplace_back(std::thread(&udp_server::run, m_servers[idx])); + m_servers[idx]->wait_for_server_ready(); + } + + udp_client client(m_server_ip_address, m_use_connect, SERVER_PORT, m_servers.size()); + client.run(); + + for (auto& thread : m_threads) + { + thread.join(); + } + } + + private: + uint32_t m_server_ip_address; + std::string m_server_address; + std::vector m_threads; + std::vector> m_servers; + std::set m_server_ports; + bool m_use_connect; +}; + +inline void parse_tuple(const std::string& tuple, + std::string& src_addr, + std::string& src_port, + std::string& dst_addr, + std::string& dst_port) +{ + std::string token; + std::stringstream ss(tuple); + std::vector tst; + while (std::getline(ss, token, '>')) { + tst.push_back(token); + } + + std::string srcstr = tst[0].substr(0, tst[0].size() - 1); + std::string dststr = tst[1]; + + ss.clear(); + ss.str(srcstr); + std::vector sst; + while (std::getline(ss, token, ':')) { + sst.push_back(token); + } + + EXPECT_EQ(2, (int)sst.size()); + src_addr = sst[0]; + src_port = sst[1]; + + ss.clear(); + ss.str(dststr); + std::vector dst; + while (std::getline(ss, token, ':')) { + dst.push_back(token); + } + EXPECT_EQ(2, (int)dst.size()); + dst_addr = dst[0]; + dst_port = dst[1]; + +} + +TEST_F(sys_call_test, udp_client_server) +{ + int32_t state = 0; + bool use_unix = false, use_sendmsg = false, recvmsg_twobufs = false, use_connect = false; + uint32_t num_servers = 1; + udp_servers_and_client udps(use_unix, use_sendmsg, recvmsg_twobufs, use_connect, num_servers); + int64_t fd_server_socket = 0; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return udps.filter(evt) || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) { udps.start(); }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + if (type == PPME_SOCKET_RECVFROM_E) + { + memcpy(&fd_server_socket, e->get_param(0)->m_val, sizeof(fd_server_socket)); + } + switch (state) + { + case 0: + EXPECT_NE(PPME_SOCKET_SENDTO_X, type); + EXPECT_NE(PPME_SOCKET_RECVFROM_X, type); + + if (type == PPME_SOCKET_SENDTO_E) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, + src_port, dst_addr, dst_port); + EXPECT_EQ("0.0.0.0", src_addr); + + EXPECT_EQ(udps.server_address(), dst_addr); + EXPECT_TRUE(udps.is_server_port(dst_port)); + + state++; + } + break; + case 1: + if (type == PPME_SOCKET_RECVFROM_X) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, + src_port, dst_addr, dst_port); + + EXPECT_EQ(udps.server_address(), src_addr); + EXPECT_NE("0", src_port); + EXPECT_EQ("0.0.0.0", dst_addr); + EXPECT_TRUE(udps.is_server_port(dst_port)); + + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + sinsp_fdinfo* fdinfo = e->get_thread_info(false)->get_fd(fd_server_socket); + ASSERT_TRUE(fdinfo); + EXPECT_EQ(udps.server_ip_address(), fdinfo->m_sockinfo.m_ipv4info.m_fields.m_sip); + + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + + state++; + } + break; + case 2: + EXPECT_NE(PPME_SOCKET_SENDTO_X, type); + EXPECT_NE(PPME_SOCKET_RECVFROM_X, type); + + if (type == PPME_SOCKET_SENDTO_E) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, + src_port, dst_addr, dst_port); + + EXPECT_EQ("0.0.0.0", src_addr); + EXPECT_TRUE(udps.is_server_port(src_port)); + EXPECT_EQ(udps.server_address(), dst_addr); + EXPECT_NE("0", dst_port); + + state++; + } + break; + case 3: + if (type == PPME_SOCKET_RECVFROM_X) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, + src_port, dst_addr, dst_port); + + EXPECT_EQ(udps.server_address(), src_addr); + EXPECT_TRUE(udps.is_server_port(src_port)); + + EXPECT_EQ("0.0.0.0", dst_addr); + EXPECT_NE("0", dst_port); + + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + sinsp_fdinfo* fdinfo = e->get_thread_info(false)->get_fd(fd_server_socket); + ASSERT_TRUE(fdinfo); + EXPECT_EQ(udps.server_ip_address(), fdinfo->m_sockinfo.m_ipv4info.m_fields.m_sip); + + state = 4; + } + break; + case 4: + break; + default: + FAIL(); + break; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); +} + +TEST_F(sys_call_test, udp_client_server_with_connect_by_client) +{ + bool use_unix = false, use_sendmsg = false, recvmsg_twobufs = false, use_connect = true; + uint32_t num_servers = 1; + udp_servers_and_client udps(use_unix, use_sendmsg, recvmsg_twobufs, use_connect, num_servers); + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + int callnum = 0; + std::string client_port; + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return udps.filter(evt) || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) { udps.start(); }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + if (PPME_SOCKET_CONNECT_X == type) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, + src_port, dst_addr, dst_port); + + EXPECT_EQ(udps.server_address(), src_addr); + + EXPECT_EQ(udps.server_address(), dst_addr); + EXPECT_TRUE(udps.is_server_port(dst_port)); + + callnum++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + ASSERT_EQ(1, callnum); +} + +TEST_F(sys_call_test, udp_client_server_sendmsg) +{ + bool use_unix = false, use_sendmsg = true, recvmsg_twobufs = false, use_connect = false; + uint32_t num_servers = 1; + udp_servers_and_client udps(use_unix, use_sendmsg, recvmsg_twobufs, use_connect, num_servers); + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return udps.filter(evt) || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) { udps.start(); }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + if (type == PPME_SOCKET_RECVMSG_X) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, src_port, + dst_addr, dst_port); + + EXPECT_EQ(udps.server_address(), src_addr); + EXPECT_NE("0", src_port); + EXPECT_EQ("0.0.0.0", dst_addr); + EXPECT_TRUE(udps.is_server_port(dst_port)); + + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + + EXPECT_EQ(udps.server_ip_address(), + e->get_fd_info()->m_sockinfo.m_ipv4info.m_fields.m_sip); + } + else if (type == PPME_SOCKET_SENDMSG_E) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, src_port, + dst_addr, dst_port); + + EXPECT_EQ("0.0.0.0", src_addr); + EXPECT_TRUE(udps.is_server_port(src_port)); + EXPECT_EQ(udps.server_address(), dst_addr); + EXPECT_NE("0", dst_port); + + EXPECT_EQ((int)BUFFER_LENGTH, std::stoi(e->get_param_value_str("size"))); + } + else if (type == PPME_SOCKET_SENDMSG_X) + { + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); +} + +TEST_F(sys_call_test, udp_client_server_sendmsg_2buf) +{ + bool use_unix = false, use_sendmsg = true, recvmsg_twobufs = true, use_connect = false; + uint32_t num_servers = 1; + udp_servers_and_client udps(use_unix, use_sendmsg, recvmsg_twobufs, use_connect, num_servers); + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return udps.filter(evt) || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) { udps.start(); }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + std::string src_addr; + std::string src_port; + std::string dst_addr; + std::string dst_port; + + if (type == PPME_SOCKET_RECVMSG_X) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, src_port, + dst_addr, dst_port); + + EXPECT_EQ(udps.server_address(), src_addr); + EXPECT_NE("0", src_port); + EXPECT_EQ("0.0.0.0", dst_addr); + EXPECT_TRUE(udps.is_server_port(dst_port)); + + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + + EXPECT_EQ(udps.server_ip_address(), + e->get_fd_info()->m_sockinfo.m_ipv4info.m_fields.m_sip); + } + else if (type == PPME_SOCKET_SENDMSG_E) + { + parse_tuple(e->get_param_value_str("tuple"), src_addr, src_port, + dst_addr, dst_port); + + EXPECT_EQ("0.0.0.0", src_addr); + EXPECT_TRUE(udps.is_server_port(src_port)); + + EXPECT_EQ(udps.server_address(), dst_addr); + EXPECT_NE("0", dst_port); + EXPECT_EQ((int)BUFFER_LENGTH, std::stoi(e->get_param_value_str("size"))); + } + else if (type == PPME_SOCKET_SENDMSG_X) + { + EXPECT_EQ(PAYLOAD, e->get_param_value_str("data")); + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); +} + +static void run_fd_name_changed_test(bool use_sendmsg, + bool recvmsg_twobufs, + bool use_connect, + event_filter_t m_tid_filter, + uint32_t expected_name_changed_evts) +{ + bool use_unix = false; + uint32_t num_servers = 2; + udp_servers_and_client udps(use_unix, use_sendmsg, recvmsg_twobufs, use_connect, num_servers); + + unique_ptr fd_name_changed; + + uint32_t num_name_changed_evts = 0; + + // INIT FILTER + before_open_t before_open = [&](sinsp* inspector) + { + sinsp_filter_compiler compiler(inspector, "fd.name_changed=true"); + fd_name_changed = std::move(compiler.compile()); + }; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return udps.filter(evt) || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) { udps.start(); }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + if (fd_name_changed->run(e)) + { + num_name_changed_evts++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter, before_open); }); + + ASSERT_EQ(num_name_changed_evts, expected_name_changed_evts); +} + +TEST_F(sys_call_test, udp_client_server_fd_name_changed) +{ + bool use_sendmsg = false, recvmsg_twobufs = false, use_connect = false; + + // This test only needs to count events. We want to + // see 7 events, representing the following: + // - The servers bind()ing their sockets to their server ports. + // - the udp client sending to the first server. + // - the first udp server receiving from the udp client + // - the udp client receiving the echoed response from the first udp server. + // This results in an event, even though this fd has already + // been used between the server and client, because this + // recvfrom sets the client side port as a result of + // the recvfrom(). + // - the udp client sending to the second server + // - the second udp server receiving from the udp client + // + // Events that do *not* trigger name_changed are: + // - the first/second udp server sending the echoed response to the udp client. This is because + // it's using + // the same client/server address + port as when it received the packet from the udp client. + // - the udp client receiving the second echo back from the second server. This is because + // the client side port was already set from the communication with the first server. + + run_fd_name_changed_test(use_sendmsg, recvmsg_twobufs, use_connect, m_tid_filter, 7); +} + +TEST_F(sys_call_test, udp_client_server_connect_fd_name_changed) +{ + bool use_sendmsg = false, recvmsg_twobufs = false, use_connect = true; + + // When the client uses connect, there is one fewer name + // changed event, as there is no name change when the client + // receives the echoed response from the server. + + run_fd_name_changed_test(use_sendmsg, recvmsg_twobufs, use_connect, m_tid_filter, 6); +} + +TEST_F(sys_call_test, udp_client_server_sendmsg_fd_name_changed) +{ + bool use_sendmsg = true, recvmsg_twobufs = false, use_connect = false; + + run_fd_name_changed_test(use_sendmsg, recvmsg_twobufs, use_connect, m_tid_filter, 7); +} + +TEST_F(sys_call_test, udp_client_server_multiple_connect_name_changed) +{ + unique_ptr fd_name_changed; + uint32_t num_name_changed_evts = 0; + + // INIT FILTER + before_open_t before_open = [&](sinsp* inspector) + { + sinsp_filter_compiler compiler(inspector, "fd.name_changed=true"); + fd_name_changed = std::move(compiler.compile()); + }; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) { return m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) + { + FAIL(); + } + + std::list ports = {8172, 8193, 8193, 8172, 8171}; + + for (auto& port : ports) + { + struct sockaddr_in serveraddr; + + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(port); + serveraddr.sin_addr.s_addr = get_server_address(); + + if (connect(sd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) + { + close(sd); + FAIL() << "connect() failed"; + } + } + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + if (fd_name_changed->run(e)) + { + num_name_changed_evts++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter, before_open); }); + + // Every connect should result in a name changed event other than the duplicate port. + ASSERT_EQ(num_name_changed_evts, 4u); +} + +TEST_F(sys_call_test, udp_client_server_sendmsg_2buf_fd_name_changed) +{ + bool use_sendmsg = true, recvmsg_twobufs = true, use_connect = false; + + run_fd_name_changed_test(use_sendmsg, recvmsg_twobufs, use_connect, m_tid_filter, 7); +} + +TEST_F(sys_call_test, statsd_client_snaplen) +{ + // Test if the driver correctly increase snaplen for statsd traffic + std::string payload = + "soluta.necessitatibus.voluptatem.consequuntur.dignissimos.repudiandae.nostrum.lorem.ipsum:" + "18|c"; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + return m_tid_filter(evt) && (evt->get_type() == PPME_SOCKET_SENDMSG_X || + evt->get_type() == PPME_SOCKET_SENDTO_X); + }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + // sendto with addr + udp_client client(0x0100007F, false, 8125); + client.m_payload = payload; + client.m_ignore_errors = true; + client.m_recv = false; + client.m_n_transactions = 1; + client.run(); + + // sendto without addr (connect) + client.m_use_connect = true; + client.run(); + + // sendmsg with addr + client.m_use_connect = false; + client.m_use_sendmsg = true; + client.run(); + + // sendmsg without addr + client.m_use_connect = true; + client.run(); + }; + + // + // OUTPUT VALDATION + // + int n = 0; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + ++n; + EXPECT_EQ(payload, e->get_param_value_str("data")) + << "Failure on " << e->get_name() << " n=" << n; + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(4, n); +} + +TEST_F(sys_call_test, statsd_client_no_snaplen) +{ + // Test if the driver correctly increase snaplen for statsd traffic + std::string payload = + "soluta.necessitatibus.voluptatem.consequuntur.dignissimos.repudiandae.nostrum.lorem.ipsum:" + "18|c"; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + return m_tid_filter(evt) && (evt->get_type() == PPME_SOCKET_SENDMSG_X || + evt->get_type() == PPME_SOCKET_SENDTO_X); + }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + // sendto with addr + // Different port + udp_client client(0x0100007F, false, 8126); + client.m_payload = payload; + client.m_ignore_errors = true; + client.m_recv = false; + client.m_n_transactions = 1; + client.run(); + + // sendto without addr (connect) + client.m_use_connect = true; + client.run(); + + // sendmsg with addr + client.m_use_connect = false; + client.m_use_sendmsg = true; + client.run(); + + // sendmsg without addr + client.m_use_connect = true; + client.run(); + }; + + // + // OUTPUT VALDATION + // + int n = 0; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + ++n; + EXPECT_EQ(payload.substr(0, 80), e->get_param_value_str("data")) + << "Failure on " << e->get_name() << " n=" << n; + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(4, n); +} diff --git a/test/libsinsp_e2e/utils.h b/test/libsinsp_e2e/utils.h new file mode 100644 index 00000000000..0514a0eab1a --- /dev/null +++ b/test/libsinsp_e2e/utils.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#include +#include +#include + +#include + +inline bool parse_tuple(const std::string& tuple, + std::string& src_addr, + std::string& src_port, + std::string& dst_addr, + std::string& dst_port) +{ + std::string token; + std::stringstream ss(tuple); + std::vector tst; + std::string srcstr; + std::string dststr; + + if(tuple.find("->") == std::string::npos) + { + return false; + } + + while (std::getline(ss, token, '>')) { + tst.push_back(token); + } + + srcstr = tst[0].substr(0, tst[0].size() - 1); + dststr = tst[1]; + + ss.clear(); + ss.str(srcstr); + std::vector sst; + while (std::getline(ss, token, ':')) { + sst.push_back(token); + } + + EXPECT_EQ(2, (int)sst.size()); + src_addr = sst[0]; + src_port = sst[1]; + + ss.clear(); + ss.str(dststr); + std::vector dst; + while (std::getline(ss, token, ':')) { + dst.push_back(token); + } + EXPECT_EQ(2, (int)dst.size()); + dst_addr = dst[0]; + dst_port = dst[1]; + + return true; +} + +class nsenter +{ +public: + nsenter(int pid, const std::string& type); + virtual ~nsenter(); + +private: + int open_ns_fd(int pid, const std::string& type); + static std::unordered_map m_home_ns; + std::string m_type; +};