diff mbox series

[bpf-next,1/4] selftests/bpf: Add traffic monitor functions.

Message ID 20240713055552.2482367-2-thinker.li@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series monitor network traffic for flaky test cases | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 8 this patch: 8
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 11 maintainers not CCed: kpsingh@kernel.org shuah@kernel.org haoluo@google.com daniel@iogearbox.net john.fastabend@gmail.com jolsa@kernel.org linux-kselftest@vger.kernel.org yonghong.song@linux.dev mykolal@fb.com eddyz87@gmail.com sdf@fomichev.me
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning WARNING: line length of 86 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 96 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 2 this patch: 2
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-28 fail Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-29 fail Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test
bpf/vmtest-bpf-next-VM_Test-32 fail Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-33 fail Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / test
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 fail Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 fail Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 fail Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 fail Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 fail Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 fail Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc

Commit Message

Kui-Feng Lee July 13, 2024, 5:55 a.m. UTC
Add functions that run tcpdump in the background, report the traffic log
captured by tcpdump, and stop tcpdump. They are supposed to be used for
debugging flaky network test cases. A monitored test case should call
traffic_monitor_start() to start a tcpdump process in the background for a
given namespace, call traffic_monitor_report() to print the log from
tcpdump, and call traffic_monitor_stop() to shutdown the tcpdump process.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 tools/testing/selftests/bpf/network_helpers.c | 244 ++++++++++++++++++
 tools/testing/selftests/bpf/network_helpers.h |   5 +
 2 files changed, 249 insertions(+)

Comments

Kui-Feng Lee July 14, 2024, 12:18 a.m. UTC | #1
On 7/12/24 22:55, Kui-Feng Lee wrote:
> Add functions that run tcpdump in the background, report the traffic log
> captured by tcpdump, and stop tcpdump. They are supposed to be used for
> debugging flaky network test cases. A monitored test case should call
> traffic_monitor_start() to start a tcpdump process in the background for a
> given namespace, call traffic_monitor_report() to print the log from
> tcpdump, and call traffic_monitor_stop() to shutdown the tcpdump process.
> 
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
>   tools/testing/selftests/bpf/network_helpers.c | 244 ++++++++++++++++++
>   tools/testing/selftests/bpf/network_helpers.h |   5 +
>   2 files changed, 249 insertions(+)
> 
> diff --git a/tools/testing/selftests/bpf/network_helpers.c b/tools/testing/selftests/bpf/network_helpers.c
> index 44c2c8fa542a..cf0e03f3b95c 100644
> --- a/tools/testing/selftests/bpf/network_helpers.c
> +++ b/tools/testing/selftests/bpf/network_helpers.c
> @@ -12,6 +12,8 @@
>   #include <sys/mount.h>
>   #include <sys/stat.h>
>   #include <sys/un.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
>   
>   #include <linux/err.h>
>   #include <linux/in.h>
> @@ -575,6 +577,248 @@ int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
>   	return 0;
>   }
>   
> +struct tmonitor_ctx {
> +	pid_t pid;
> +	const char *netns;
> +	char log_name[PATH_MAX];
> +};
> +
> +/* Make sure that tcpdump has handled all previous packets.
> + *
> + * Send one or more UDP packets to the loopback interface. The packet
> + * contains a mark string. The mark is used to check if tcpdump has handled
> + * the packet. The function waits for tcpdump to print a message for the
> + * packet containing the mark (by checking the payload length and the
> + * destination). This is not a perfect solution, but it should be enough
> + * for testing purposes.
> + *
> + * log_name is the file name where tcpdump writes its output.
> + * mark is the string that is sent in the UDP packet.
> + * repeat specifies if the function should send multiple packets.
> + *
> + * Device "lo" should be up in the namespace for this to work.  This
> + * function should be called in the same network namespace as a
> + * tmonitor_ctx created for in order to create a socket for sending mark
> + * packets.
> + */
> +static int traffic_monitor_sync(const char *log_name, const char *mark,
> +				bool repeat)
> +{
> +	const int max_loop = 1000; /* 10s */
> +	char mark_pkt_pattern[64];
> +	struct sockaddr_in addr;
> +	int sock, log_fd, rd_pos = 0;
> +	int pattern_size;
> +	struct stat st;
> +	char buf[4096];
> +	int send_cnt = repeat ? max_loop : 1;
> +	bool found;
> +	int i, n;
> +
> +	sock = socket(AF_INET, SOCK_DGRAM, 0);
> +	if (sock < 0) {
> +		log_err("Failed to create socket");
> +		return -1;
> +	}
> +
> +	/* Check only the destination and the payload length */
> +	pattern_size = snprintf(mark_pkt_pattern, sizeof(mark_pkt_pattern),
> +				" > 127.0.0.241.4321: UDP, length %ld",
> +				strlen(mark));
> +
> +	addr.sin_family = AF_INET;
> +	addr.sin_addr.s_addr = inet_addr("127.0.0.241");
> +	addr.sin_port = htons(4321);
> +
> +	/* Wait for the log file to be created */
> +	for (i = 0; i < max_loop; i++) {
> +		log_fd = open(log_name, O_RDONLY);
> +		if (log_fd >= 0) {
> +			fstat(log_fd, &st);
> +			rd_pos = st.st_size;
> +			break;
> +		}
> +		usleep(10000);
> +	}
> +	/* Wait for the mark packet */
> +	for (found = false; i < max_loop && !found; i++) {
> +		if (send_cnt-- > 0) {
> +			/* Send an UDP packet */
> +			if (sendto(sock, mark, strlen(mark), 0,
> +				   (struct sockaddr *)&addr,
> +				   sizeof(addr)) != strlen(mark))
> +				log_err("Failed to sendto");
> +		}
> +
> +		usleep(10000);
> +		fstat(log_fd, &st);
> +		/* Check the content of the log file */
> +		while (rd_pos + pattern_size <= st.st_size) {
> +			lseek(log_fd, rd_pos, SEEK_SET);
> +			n = read(log_fd, buf, sizeof(buf) - 1);
> +			if (n < pattern_size)
> +				break;
> +			buf[n] = 0;
> +			if (strstr(buf, mark_pkt_pattern)) {
> +				found = true;
> +				break;
> +			}
> +			rd_pos += n - pattern_size + 1;
> +		}
> +	}
> +
> +	close(log_fd);
> +	close(sock);
> +
> +	if (!found) {
> +		log_err("Waited too long for synchronizing traffic monitor");
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Start a tcpdump process to monitor traffic.
> + *
> + * netns specifies what network namespace you want to monitor. It will
> + * monitor the current namespace if netns is NULL.
> + */
> +struct tmonitor_ctx *traffic_monitor_start(const char *netns)
> +{
> +	struct tmonitor_ctx *ctx = NULL;
> +	struct nstoken *nstoken = NULL;
> +	char log_name[PATH_MAX];
> +	int status, log_fd;
> +	pid_t pid;
> +
> +	if (netns) {
> +		nstoken = open_netns(netns);
> +		if (!nstoken)
> +			return NULL;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_err("Failed to fork");
> +		goto error;
> +	}
> +
> +	if (pid == 0) {
> +		/* Child */
> +		pid = getpid();
> +		snprintf(log_name, sizeof(log_name), "/tmp/tmon_tcpdump_%d.log", pid);
> +		log_fd = open(log_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
> +		dup2(log_fd, STDOUT_FILENO);
> +		dup2(log_fd, STDERR_FILENO);
> +		if (log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO)
> +			close(log_fd);
> +
> +		/* -n don't convert addresses to hostnames.
> +		 *
> +		 * --immediate-mode handle captured packets immediately.
> +		 *
> +		 * -l print messages with line buffer. With this option,
> +		 * the output will be written at the end of each line
> +		 * rather than when the output buffer is full. This is
> +		 * needed to sync with tcpdump efficiently.
> +		 */
> +		execlp("tcpdump", "tcpdump", "-i", "any", "-n", "--immediate-mode", "-l", NULL);
> +		log_err("Failed to exec tcpdump");
> +		exit(1);
> +	}
> +
> +	ctx = malloc(sizeof(*ctx));
> +	if (!ctx) {
> +		log_err("Failed to malloc ctx");
> +		goto error;
> +	}
> +
> +	ctx->pid = pid;
> +	ctx->netns = netns;
> +	snprintf(ctx->log_name, sizeof(ctx->log_name), "/tmp/tmon_tcpdump_%d.log", pid);
> +
> +	/* Wait for tcpdump to be ready */
> +	if (traffic_monitor_sync(ctx->log_name, "hello", true)) {
> +		status = 0;
> +		if (waitpid(pid, &status, WNOHANG) >= 0 &&
> +		    !WIFEXITED(status) && !WIFSIGNALED(status))
> +			log_err("Wait too long for tcpdump");
> +		else
> +			log_err("Fail to start tcpdump");
> +		goto error;
> +	}
> +
> +	close_netns(nstoken);
> +
> +	return ctx;
> +
> +error:
> +	close_netns(nstoken);
> +	if (pid > 0) {
> +		kill(pid, SIGTERM);
> +		waitpid(pid, NULL, 0);
> +		snprintf(log_name, sizeof(log_name), "/tmp/tmon_tcpdump_%d.log", pid);
> +		unlink(log_name);
> +	}
> +	free(ctx);
> +
> +	return NULL;
> +}
> +
> +void traffic_monitor_stop(struct tmonitor_ctx *ctx)
> +{
> +	if (!ctx)
> +		return;
> +	kill(ctx->pid, SIGTERM);
> +	/* Wait the tcpdump process in case that the log file is created
> +	 * after this line.
> +	 */
> +	waitpid(ctx->pid, NULL, 0);
> +	unlink(ctx->log_name);
> +	free(ctx);
> +}
> +
> +/* Report the traffic monitored by tcpdump.
> + *
> + * The function reads the log file created by tcpdump and writes the
> + * content to stderr.
> + */
> +void traffic_monitor_report(struct tmonitor_ctx *ctx)
> +{
> +	struct nstoken *nstoken = NULL;
> +	char buf[4096];
> +	int log_fd, n;

log_fd should be initialized. I will fix it in the next version.

> +
> +	if (!ctx)
> +		return;
> +
> +	/* Make sure all previous packets have been handled by
> +	 * tcpdump.
> +	 */
> +	if (ctx->netns) {
> +		nstoken = open_netns(ctx->netns);
> +		if (!nstoken) {
> +			log_err("Failed to open netns: %s", ctx->netns);
> +			goto out;
> +		}
> +	}
> +	traffic_monitor_sync(ctx->log_name, "sync for report", false);
> +	close_netns(nstoken);
> +
> +	/* Read the log file and write to stderr */
> +	log_fd = open(ctx->log_name, O_RDONLY);
> +	if (log_fd < 0) {
> +		log_err("Failed to open log file");
> +		return;
> +	}
> +
> +	while ((n = read(log_fd, buf, sizeof(buf))) > 0)
> +		fwrite(buf, n, 1, stderr);
> +
> +out:
> +	close(log_fd);
> +}
> +
>   struct send_recv_arg {
>   	int		fd;
>   	uint32_t	bytes;
> diff --git a/tools/testing/selftests/bpf/network_helpers.h b/tools/testing/selftests/bpf/network_helpers.h
> index 9ea36524b9db..d757e495fb39 100644
> --- a/tools/testing/selftests/bpf/network_helpers.h
> +++ b/tools/testing/selftests/bpf/network_helpers.h
> @@ -72,6 +72,11 @@ int get_socket_local_port(int sock_fd);
>   int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
>   int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
>   
> +struct tmonitor_ctx;
> +struct tmonitor_ctx *traffic_monitor_start(const char *netns);
> +void traffic_monitor_stop(struct tmonitor_ctx *ctx);
> +void traffic_monitor_report(struct tmonitor_ctx *ctx);
> +
>   struct nstoken;
>   /**
>    * open_netns() - Switch to specified network namespace by name.
kernel test robot July 14, 2024, 3:27 p.m. UTC | #2
Hi Kui-Feng,

kernel test robot noticed the following build warnings:

[auto build test WARNING on bpf-next/master]

url:    https://github.com/intel-lab-lkp/linux/commits/Kui-Feng-Lee/selftests-bpf-Add-traffic-monitor-functions/20240713-140129
base:   https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link:    https://lore.kernel.org/r/20240713055552.2482367-2-thinker.li%40gmail.com
patch subject: [PATCH bpf-next 1/4] selftests/bpf: Add traffic monitor functions.
:::::: branch date: 9 hours ago
:::::: commit date: 9 hours ago
compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/r/202407132213.2rer2R4r-lkp@intel.com/

includecheck warnings: (new ones prefixed by >>)
>> tools/testing/selftests/bpf/network_helpers.c: sys/stat.h is included more than once.

vim +13 tools/testing/selftests/bpf/network_helpers.c

    10	
    11	#include <arpa/inet.h>
    12	#include <sys/mount.h>
  > 13	#include <sys/stat.h>
    14	#include <sys/un.h>
    15	#include <sys/types.h>
  > 16	#include <sys/stat.h>
    17
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/network_helpers.c b/tools/testing/selftests/bpf/network_helpers.c
index 44c2c8fa542a..cf0e03f3b95c 100644
--- a/tools/testing/selftests/bpf/network_helpers.c
+++ b/tools/testing/selftests/bpf/network_helpers.c
@@ -12,6 +12,8 @@ 
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/un.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #include <linux/err.h>
 #include <linux/in.h>
@@ -575,6 +577,248 @@  int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param)
 	return 0;
 }
 
+struct tmonitor_ctx {
+	pid_t pid;
+	const char *netns;
+	char log_name[PATH_MAX];
+};
+
+/* Make sure that tcpdump has handled all previous packets.
+ *
+ * Send one or more UDP packets to the loopback interface. The packet
+ * contains a mark string. The mark is used to check if tcpdump has handled
+ * the packet. The function waits for tcpdump to print a message for the
+ * packet containing the mark (by checking the payload length and the
+ * destination). This is not a perfect solution, but it should be enough
+ * for testing purposes.
+ *
+ * log_name is the file name where tcpdump writes its output.
+ * mark is the string that is sent in the UDP packet.
+ * repeat specifies if the function should send multiple packets.
+ *
+ * Device "lo" should be up in the namespace for this to work.  This
+ * function should be called in the same network namespace as a
+ * tmonitor_ctx created for in order to create a socket for sending mark
+ * packets.
+ */
+static int traffic_monitor_sync(const char *log_name, const char *mark,
+				bool repeat)
+{
+	const int max_loop = 1000; /* 10s */
+	char mark_pkt_pattern[64];
+	struct sockaddr_in addr;
+	int sock, log_fd, rd_pos = 0;
+	int pattern_size;
+	struct stat st;
+	char buf[4096];
+	int send_cnt = repeat ? max_loop : 1;
+	bool found;
+	int i, n;
+
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock < 0) {
+		log_err("Failed to create socket");
+		return -1;
+	}
+
+	/* Check only the destination and the payload length */
+	pattern_size = snprintf(mark_pkt_pattern, sizeof(mark_pkt_pattern),
+				" > 127.0.0.241.4321: UDP, length %ld",
+				strlen(mark));
+
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = inet_addr("127.0.0.241");
+	addr.sin_port = htons(4321);
+
+	/* Wait for the log file to be created */
+	for (i = 0; i < max_loop; i++) {
+		log_fd = open(log_name, O_RDONLY);
+		if (log_fd >= 0) {
+			fstat(log_fd, &st);
+			rd_pos = st.st_size;
+			break;
+		}
+		usleep(10000);
+	}
+	/* Wait for the mark packet */
+	for (found = false; i < max_loop && !found; i++) {
+		if (send_cnt-- > 0) {
+			/* Send an UDP packet */
+			if (sendto(sock, mark, strlen(mark), 0,
+				   (struct sockaddr *)&addr,
+				   sizeof(addr)) != strlen(mark))
+				log_err("Failed to sendto");
+		}
+
+		usleep(10000);
+		fstat(log_fd, &st);
+		/* Check the content of the log file */
+		while (rd_pos + pattern_size <= st.st_size) {
+			lseek(log_fd, rd_pos, SEEK_SET);
+			n = read(log_fd, buf, sizeof(buf) - 1);
+			if (n < pattern_size)
+				break;
+			buf[n] = 0;
+			if (strstr(buf, mark_pkt_pattern)) {
+				found = true;
+				break;
+			}
+			rd_pos += n - pattern_size + 1;
+		}
+	}
+
+	close(log_fd);
+	close(sock);
+
+	if (!found) {
+		log_err("Waited too long for synchronizing traffic monitor");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Start a tcpdump process to monitor traffic.
+ *
+ * netns specifies what network namespace you want to monitor. It will
+ * monitor the current namespace if netns is NULL.
+ */
+struct tmonitor_ctx *traffic_monitor_start(const char *netns)
+{
+	struct tmonitor_ctx *ctx = NULL;
+	struct nstoken *nstoken = NULL;
+	char log_name[PATH_MAX];
+	int status, log_fd;
+	pid_t pid;
+
+	if (netns) {
+		nstoken = open_netns(netns);
+		if (!nstoken)
+			return NULL;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		log_err("Failed to fork");
+		goto error;
+	}
+
+	if (pid == 0) {
+		/* Child */
+		pid = getpid();
+		snprintf(log_name, sizeof(log_name), "/tmp/tmon_tcpdump_%d.log", pid);
+		log_fd = open(log_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+		dup2(log_fd, STDOUT_FILENO);
+		dup2(log_fd, STDERR_FILENO);
+		if (log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO)
+			close(log_fd);
+
+		/* -n don't convert addresses to hostnames.
+		 *
+		 * --immediate-mode handle captured packets immediately.
+		 *
+		 * -l print messages with line buffer. With this option,
+		 * the output will be written at the end of each line
+		 * rather than when the output buffer is full. This is
+		 * needed to sync with tcpdump efficiently.
+		 */
+		execlp("tcpdump", "tcpdump", "-i", "any", "-n", "--immediate-mode", "-l", NULL);
+		log_err("Failed to exec tcpdump");
+		exit(1);
+	}
+
+	ctx = malloc(sizeof(*ctx));
+	if (!ctx) {
+		log_err("Failed to malloc ctx");
+		goto error;
+	}
+
+	ctx->pid = pid;
+	ctx->netns = netns;
+	snprintf(ctx->log_name, sizeof(ctx->log_name), "/tmp/tmon_tcpdump_%d.log", pid);
+
+	/* Wait for tcpdump to be ready */
+	if (traffic_monitor_sync(ctx->log_name, "hello", true)) {
+		status = 0;
+		if (waitpid(pid, &status, WNOHANG) >= 0 &&
+		    !WIFEXITED(status) && !WIFSIGNALED(status))
+			log_err("Wait too long for tcpdump");
+		else
+			log_err("Fail to start tcpdump");
+		goto error;
+	}
+
+	close_netns(nstoken);
+
+	return ctx;
+
+error:
+	close_netns(nstoken);
+	if (pid > 0) {
+		kill(pid, SIGTERM);
+		waitpid(pid, NULL, 0);
+		snprintf(log_name, sizeof(log_name), "/tmp/tmon_tcpdump_%d.log", pid);
+		unlink(log_name);
+	}
+	free(ctx);
+
+	return NULL;
+}
+
+void traffic_monitor_stop(struct tmonitor_ctx *ctx)
+{
+	if (!ctx)
+		return;
+	kill(ctx->pid, SIGTERM);
+	/* Wait the tcpdump process in case that the log file is created
+	 * after this line.
+	 */
+	waitpid(ctx->pid, NULL, 0);
+	unlink(ctx->log_name);
+	free(ctx);
+}
+
+/* Report the traffic monitored by tcpdump.
+ *
+ * The function reads the log file created by tcpdump and writes the
+ * content to stderr.
+ */
+void traffic_monitor_report(struct tmonitor_ctx *ctx)
+{
+	struct nstoken *nstoken = NULL;
+	char buf[4096];
+	int log_fd, n;
+
+	if (!ctx)
+		return;
+
+	/* Make sure all previous packets have been handled by
+	 * tcpdump.
+	 */
+	if (ctx->netns) {
+		nstoken = open_netns(ctx->netns);
+		if (!nstoken) {
+			log_err("Failed to open netns: %s", ctx->netns);
+			goto out;
+		}
+	}
+	traffic_monitor_sync(ctx->log_name, "sync for report", false);
+	close_netns(nstoken);
+
+	/* Read the log file and write to stderr */
+	log_fd = open(ctx->log_name, O_RDONLY);
+	if (log_fd < 0) {
+		log_err("Failed to open log file");
+		return;
+	}
+
+	while ((n = read(log_fd, buf, sizeof(buf))) > 0)
+		fwrite(buf, n, 1, stderr);
+
+out:
+	close(log_fd);
+}
+
 struct send_recv_arg {
 	int		fd;
 	uint32_t	bytes;
diff --git a/tools/testing/selftests/bpf/network_helpers.h b/tools/testing/selftests/bpf/network_helpers.h
index 9ea36524b9db..d757e495fb39 100644
--- a/tools/testing/selftests/bpf/network_helpers.h
+++ b/tools/testing/selftests/bpf/network_helpers.h
@@ -72,6 +72,11 @@  int get_socket_local_port(int sock_fd);
 int get_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
 int set_hw_ring_size(char *ifname, struct ethtool_ringparam *ring_param);
 
+struct tmonitor_ctx;
+struct tmonitor_ctx *traffic_monitor_start(const char *netns);
+void traffic_monitor_stop(struct tmonitor_ctx *ctx);
+void traffic_monitor_report(struct tmonitor_ctx *ctx);
+
 struct nstoken;
 /**
  * open_netns() - Switch to specified network namespace by name.