// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates.*/

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <test_progs.h>
#include "cgrp_ls_tp_btf.skel.h"
#include "cgrp_ls_recursion.skel.h"
#include "cgrp_ls_attach_cgroup.skel.h"
#include "cgrp_ls_negative.skel.h"
#include "cgrp_ls_sleepable.skel.h"
#include "network_helpers.h"
#include "cgroup_helpers.h"

struct socket_cookie {
	__u64 cookie_key;
	__u64 cookie_value;
};

static bool is_cgroup1;
static int target_hid;

#define CGROUP_MODE_SET(skel)			\
{						\
	skel->bss->is_cgroup1 = is_cgroup1;	\
	skel->bss->target_hid = target_hid;	\
}

static void cgroup_mode_value_init(bool cgroup, int hid)
{
	is_cgroup1 = cgroup;
	target_hid = hid;
}

static void test_tp_btf(int cgroup_fd)
{
	struct cgrp_ls_tp_btf *skel;
	long val1 = 1, val2 = 0;
	int err;

	skel = cgrp_ls_tp_btf__open_and_load();
	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
		return;

	CGROUP_MODE_SET(skel);

	/* populate a value in map_b */
	err = bpf_map_update_elem(bpf_map__fd(skel->maps.map_b), &cgroup_fd, &val1, BPF_ANY);
	if (!ASSERT_OK(err, "map_update_elem"))
		goto out;

	/* check value */
	err = bpf_map_lookup_elem(bpf_map__fd(skel->maps.map_b), &cgroup_fd, &val2);
	if (!ASSERT_OK(err, "map_lookup_elem"))
		goto out;
	if (!ASSERT_EQ(val2, 1, "map_lookup_elem, invalid val"))
		goto out;

	/* delete value */
	err = bpf_map_delete_elem(bpf_map__fd(skel->maps.map_b), &cgroup_fd);
	if (!ASSERT_OK(err, "map_delete_elem"))
		goto out;

	skel->bss->target_pid = syscall(SYS_gettid);

	err = cgrp_ls_tp_btf__attach(skel);
	if (!ASSERT_OK(err, "skel_attach"))
		goto out;

	syscall(SYS_gettid);
	syscall(SYS_gettid);

	skel->bss->target_pid = 0;

	/* 3x syscalls: 1x attach and 2x gettid */
	ASSERT_EQ(skel->bss->enter_cnt, 3, "enter_cnt");
	ASSERT_EQ(skel->bss->exit_cnt, 3, "exit_cnt");
	ASSERT_EQ(skel->bss->mismatch_cnt, 0, "mismatch_cnt");
out:
	cgrp_ls_tp_btf__destroy(skel);
}

static void test_attach_cgroup(int cgroup_fd)
{
	int server_fd = 0, client_fd = 0, err = 0;
	socklen_t addr_len = sizeof(struct sockaddr_in6);
	struct cgrp_ls_attach_cgroup *skel;
	__u32 cookie_expected_value;
	struct sockaddr_in6 addr;
	struct socket_cookie val;

	skel = cgrp_ls_attach_cgroup__open_and_load();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	skel->links.set_cookie = bpf_program__attach_cgroup(
		skel->progs.set_cookie, cgroup_fd);
	if (!ASSERT_OK_PTR(skel->links.set_cookie, "prog_attach"))
		goto out;

	skel->links.update_cookie_sockops = bpf_program__attach_cgroup(
		skel->progs.update_cookie_sockops, cgroup_fd);
	if (!ASSERT_OK_PTR(skel->links.update_cookie_sockops, "prog_attach"))
		goto out;

	skel->links.update_cookie_tracing = bpf_program__attach(
		skel->progs.update_cookie_tracing);
	if (!ASSERT_OK_PTR(skel->links.update_cookie_tracing, "prog_attach"))
		goto out;

	server_fd = start_server(AF_INET6, SOCK_STREAM, "::1", 0, 0);
	if (!ASSERT_GE(server_fd, 0, "start_server"))
		goto out;

	client_fd = connect_to_fd(server_fd, 0);
	if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
		goto close_server_fd;

	err = bpf_map_lookup_elem(bpf_map__fd(skel->maps.socket_cookies),
				  &cgroup_fd, &val);
	if (!ASSERT_OK(err, "map_lookup(socket_cookies)"))
		goto close_client_fd;

	err = getsockname(client_fd, (struct sockaddr *)&addr, &addr_len);
	if (!ASSERT_OK(err, "getsockname"))
		goto close_client_fd;

	cookie_expected_value = (ntohs(addr.sin6_port) << 8) | 0xFF;
	ASSERT_EQ(val.cookie_value, cookie_expected_value, "cookie_value");

close_client_fd:
	close(client_fd);
close_server_fd:
	close(server_fd);
out:
	cgrp_ls_attach_cgroup__destroy(skel);
}

static void test_recursion(int cgroup_fd)
{
	struct cgrp_ls_recursion *skel;
	int err;

	skel = cgrp_ls_recursion__open_and_load();
	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
		return;

	CGROUP_MODE_SET(skel);

	err = cgrp_ls_recursion__attach(skel);
	if (!ASSERT_OK(err, "skel_attach"))
		goto out;

	/* trigger sys_enter, make sure it does not cause deadlock */
	syscall(SYS_gettid);

out:
	cgrp_ls_recursion__destroy(skel);
}

static void test_negative(void)
{
	struct cgrp_ls_negative *skel;

	skel = cgrp_ls_negative__open_and_load();
	if (!ASSERT_ERR_PTR(skel, "skel_open_and_load")) {
		cgrp_ls_negative__destroy(skel);
		return;
	}
}

static void test_cgroup_iter_sleepable(int cgroup_fd, __u64 cgroup_id)
{
	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
	union bpf_iter_link_info linfo;
	struct cgrp_ls_sleepable *skel;
	struct bpf_link *link;
	int err, iter_fd;
	char buf[16];

	skel = cgrp_ls_sleepable__open();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	CGROUP_MODE_SET(skel);

	bpf_program__set_autoload(skel->progs.cgroup_iter, true);
	err = cgrp_ls_sleepable__load(skel);
	if (!ASSERT_OK(err, "skel_load"))
		goto out;

	memset(&linfo, 0, sizeof(linfo));
	linfo.cgroup.cgroup_fd = cgroup_fd;
	linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
	opts.link_info = &linfo;
	opts.link_info_len = sizeof(linfo);
	link = bpf_program__attach_iter(skel->progs.cgroup_iter, &opts);
	if (!ASSERT_OK_PTR(link, "attach_iter"))
		goto out;

	iter_fd = bpf_iter_create(bpf_link__fd(link));
	if (!ASSERT_GE(iter_fd, 0, "iter_create"))
		goto out;

	/* trigger the program run */
	(void)read(iter_fd, buf, sizeof(buf));

	ASSERT_EQ(skel->bss->cgroup_id, cgroup_id, "cgroup_id");

	close(iter_fd);
out:
	cgrp_ls_sleepable__destroy(skel);
}

static void test_yes_rcu_lock(__u64 cgroup_id)
{
	struct cgrp_ls_sleepable *skel;
	int err;

	skel = cgrp_ls_sleepable__open();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	CGROUP_MODE_SET(skel);
	skel->bss->target_pid = syscall(SYS_gettid);

	bpf_program__set_autoload(skel->progs.yes_rcu_lock, true);
	err = cgrp_ls_sleepable__load(skel);
	if (!ASSERT_OK(err, "skel_load"))
		goto out;

	err = cgrp_ls_sleepable__attach(skel);
	if (!ASSERT_OK(err, "skel_attach"))
		goto out;

	syscall(SYS_getpgid);

	ASSERT_EQ(skel->bss->cgroup_id, cgroup_id, "cgroup_id");
out:
	cgrp_ls_sleepable__destroy(skel);
}

static void test_no_rcu_lock(void)
{
	struct cgrp_ls_sleepable *skel;
	int err;

	skel = cgrp_ls_sleepable__open();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	CGROUP_MODE_SET(skel);

	bpf_program__set_autoload(skel->progs.no_rcu_lock, true);
	err = cgrp_ls_sleepable__load(skel);
	ASSERT_ERR(err, "skel_load");

	cgrp_ls_sleepable__destroy(skel);
}

static void test_cgrp1_no_rcu_lock(void)
{
	struct cgrp_ls_sleepable *skel;
	int err;

	skel = cgrp_ls_sleepable__open();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	CGROUP_MODE_SET(skel);

	bpf_program__set_autoload(skel->progs.cgrp1_no_rcu_lock, true);
	err = cgrp_ls_sleepable__load(skel);
	ASSERT_OK(err, "skel_load");

	cgrp_ls_sleepable__destroy(skel);
}

static void cgrp2_local_storage(void)
{
	__u64 cgroup_id;
	int cgroup_fd;

	cgroup_fd = test__join_cgroup("/cgrp_local_storage");
	if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup /cgrp_local_storage"))
		return;

	cgroup_mode_value_init(0, -1);

	cgroup_id = get_cgroup_id("/cgrp_local_storage");
	if (test__start_subtest("tp_btf"))
		test_tp_btf(cgroup_fd);
	if (test__start_subtest("attach_cgroup"))
		test_attach_cgroup(cgroup_fd);
	if (test__start_subtest("recursion"))
		test_recursion(cgroup_fd);
	if (test__start_subtest("negative"))
		test_negative();
	if (test__start_subtest("cgroup_iter_sleepable"))
		test_cgroup_iter_sleepable(cgroup_fd, cgroup_id);
	if (test__start_subtest("yes_rcu_lock"))
		test_yes_rcu_lock(cgroup_id);
	if (test__start_subtest("no_rcu_lock"))
		test_no_rcu_lock();

	close(cgroup_fd);
}

static void cgrp1_local_storage(void)
{
	int cgrp1_fd, cgrp1_hid, cgrp1_id, err;

	/* Setup cgroup1 hierarchy */
	err = setup_classid_environment();
	if (!ASSERT_OK(err, "setup_classid_environment"))
		return;

	err = join_classid();
	if (!ASSERT_OK(err, "join_cgroup1"))
		goto cleanup;

	cgrp1_fd = open_classid();
	if (!ASSERT_GE(cgrp1_fd, 0, "cgroup1 fd"))
		goto cleanup;

	cgrp1_id = get_classid_cgroup_id();
	if (!ASSERT_GE(cgrp1_id, 0, "cgroup1 id"))
		goto close_fd;

	cgrp1_hid = get_cgroup1_hierarchy_id("net_cls");
	if (!ASSERT_GE(cgrp1_hid, 0, "cgroup1 hid"))
		goto close_fd;

	cgroup_mode_value_init(1, cgrp1_hid);

	if (test__start_subtest("cgrp1_tp_btf"))
		test_tp_btf(cgrp1_fd);
	if (test__start_subtest("cgrp1_recursion"))
		test_recursion(cgrp1_fd);
	if (test__start_subtest("cgrp1_negative"))
		test_negative();
	if (test__start_subtest("cgrp1_iter_sleepable"))
		test_cgroup_iter_sleepable(cgrp1_fd, cgrp1_id);
	if (test__start_subtest("cgrp1_yes_rcu_lock"))
		test_yes_rcu_lock(cgrp1_id);
	if (test__start_subtest("cgrp1_no_rcu_lock"))
		test_cgrp1_no_rcu_lock();

close_fd:
	close(cgrp1_fd);
cleanup:
	cleanup_classid_environment();
}

void test_cgrp_local_storage(void)
{
	cgrp2_local_storage();
	cgrp1_local_storage();
}
