Commit 81427a62 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'bpf-add-support-for-cgroup1-bpf-part'

Yafang Shao says:

====================
bpf: Add support for cgroup1, BPF part

This is the BPF part of the series "bpf, cgroup: Add BPF support for
cgroup1 hierarchy" with adjustment in the last two patches compared
to the previous one.

v3->v4:
  - use subsys_name instead of cgrp_name in get_cgroup_hierarchy_id()
    (Tejun)
  - use local bpf_link instead of modifying the skeleton in the
    selftests
v3: https://lwn.net/Articles/949264/
====================

Link: https://lore.kernel.org/r/20231111090034.4248-1-laoar.shao@gmail.com


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 727a92d6 36076923
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -2228,6 +2228,25 @@ __bpf_kfunc long bpf_task_under_cgroup(struct task_struct *task,
	rcu_read_unlock();
	return ret;
}

/**
 * bpf_task_get_cgroup1 - Acquires the associated cgroup of a task within a
 * specific cgroup1 hierarchy. The cgroup1 hierarchy is identified by its
 * hierarchy ID.
 * @task: The target task
 * @hierarchy_id: The ID of a cgroup1 hierarchy
 *
 * On success, the cgroup is returen. On failure, NULL is returned.
 */
__bpf_kfunc struct cgroup *
bpf_task_get_cgroup1(struct task_struct *task, int hierarchy_id)
{
	struct cgroup *cgrp = task_get_cgroup1(task, hierarchy_id);

	if (IS_ERR(cgrp))
		return NULL;
	return cgrp;
}
#endif /* CONFIG_CGROUPS */

/**
@@ -2534,6 +2553,7 @@ BTF_ID_FLAGS(func, bpf_cgroup_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_cgroup_ancestor, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_cgroup_from_id, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_under_cgroup, KF_RCU)
BTF_ID_FLAGS(func, bpf_task_get_cgroup1, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
#endif
BTF_ID_FLAGS(func, bpf_task_from_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_throw)
+99 −17
Original line number Diff line number Diff line
@@ -45,9 +45,12 @@
#define format_parent_cgroup_path(buf, path) \
	format_cgroup_path_pid(buf, path, getppid())

#define format_classid_path_pid(buf, pid)				\
	snprintf(buf, sizeof(buf), "%s%s%d", NETCLS_MOUNT_PATH,	\
		 CGROUP_WORK_DIR, pid)

#define format_classid_path(buf)	\
	snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH,	\
		 CGROUP_WORK_DIR)
	format_classid_path_pid(buf, getpid())

static __thread bool cgroup_workdir_mounted;

@@ -419,26 +422,23 @@ int create_and_get_cgroup(const char *relative_path)
}

/**
 * get_cgroup_id() - Get cgroup id for a particular cgroup path
 * @relative_path: The cgroup path, relative to the workdir, to join
 * get_cgroup_id_from_path - Get cgroup id for a particular cgroup path
 * @cgroup_workdir: The absolute cgroup path
 *
 * On success, it returns the cgroup id. On failure it returns 0,
 * which is an invalid cgroup id.
 * If there is a failure, it prints the error to stderr.
 */
unsigned long long get_cgroup_id(const char *relative_path)
unsigned long long get_cgroup_id_from_path(const char *cgroup_workdir)
{
	int dirfd, err, flags, mount_id, fhsize;
	union {
		unsigned long long cgid;
		unsigned char raw_bytes[8];
	} id;
	char cgroup_workdir[PATH_MAX + 1];
	struct file_handle *fhp, *fhp2;
	unsigned long long ret = 0;

	format_cgroup_path(cgroup_workdir, relative_path);

	dirfd = AT_FDCWD;
	flags = 0;
	fhsize = sizeof(*fhp);
@@ -474,6 +474,14 @@ unsigned long long get_cgroup_id(const char *relative_path)
	return ret;
}

unsigned long long get_cgroup_id(const char *relative_path)
{
	char cgroup_workdir[PATH_MAX + 1];

	format_cgroup_path(cgroup_workdir, relative_path);
	return get_cgroup_id_from_path(cgroup_workdir);
}

int cgroup_setup_and_join(const char *path) {
	int cg_fd;

@@ -523,12 +531,22 @@ int setup_classid_environment(void)
		return 1;
	}

	if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls") &&
	    errno != EBUSY) {
	if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls")) {
		if (errno != EBUSY) {
			log_err("mount cgroup net_cls");
			return 1;
		}

		if (rmdir(NETCLS_MOUNT_PATH)) {
			log_err("rmdir cgroup net_cls");
			return 1;
		}
		if (umount(CGROUP_MOUNT_DFLT)) {
			log_err("umount cgroup base");
			return 1;
		}
	}

	cleanup_classid_environment();

	if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
@@ -541,15 +559,16 @@ int setup_classid_environment(void)

/**
 * set_classid() - Set a cgroupv1 net_cls classid
 * @id: the numeric classid
 *
 * Writes the passed classid into the cgroup work dir's net_cls.classid
 * Writes the classid into the cgroup work dir's net_cls.classid
 * file in order to later on trigger socket tagging.
 *
 * We leverage the current pid as the classid, ensuring unique identification.
 *
 * On success, it returns 0, otherwise on failure it returns 1. If there
 * is a failure, it prints the error to stderr.
 */
int set_classid(unsigned int id)
int set_classid(void)
{
	char cgroup_workdir[PATH_MAX - 42];
	char cgroup_classid_path[PATH_MAX + 1];
@@ -565,7 +584,7 @@ int set_classid(unsigned int id)
		return 1;
	}

	if (dprintf(fd, "%u\n", id) < 0) {
	if (dprintf(fd, "%u\n", getpid()) < 0) {
		log_err("Setting cgroup classid");
		rc = 1;
	}
@@ -607,3 +626,66 @@ void cleanup_classid_environment(void)
	join_cgroup_from_top(NETCLS_MOUNT_PATH);
	nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
}

/**
 * get_classid_cgroup_id - Get the cgroup id of a net_cls cgroup
 */
unsigned long long get_classid_cgroup_id(void)
{
	char cgroup_workdir[PATH_MAX + 1];

	format_classid_path(cgroup_workdir);
	return get_cgroup_id_from_path(cgroup_workdir);
}

/**
 * get_cgroup1_hierarchy_id - Retrieves the ID of a cgroup1 hierarchy from the cgroup1 subsys name.
 * @subsys_name: The cgroup1 subsys name, which can be retrieved from /proc/self/cgroup. It can be
 * a named cgroup like "name=systemd", a controller name like "net_cls", or multi-contollers like
 * "net_cls,net_prio".
 */
int get_cgroup1_hierarchy_id(const char *subsys_name)
{
	char *c, *c2, *c3, *c4;
	bool found = false;
	char line[1024];
	FILE *file;
	int i, id;

	if (!subsys_name)
		return -1;

	file = fopen("/proc/self/cgroup", "r");
	if (!file) {
		log_err("fopen /proc/self/cgroup");
		return -1;
	}

	while (fgets(line, 1024, file)) {
		i = 0;
		for (c = strtok_r(line, ":", &c2); c && i < 2; c = strtok_r(NULL, ":", &c2)) {
			if (i == 0) {
				id = strtol(c, NULL, 10);
			} else if (i == 1) {
				if (!strcmp(c, subsys_name)) {
					found = true;
					break;
				}

				/* Multiple subsystems may share one single mount point */
				for (c3 = strtok_r(c, ",", &c4); c3;
				     c3 = strtok_r(NULL, ",", &c4)) {
					if (!strcmp(c, subsys_name)) {
						found = true;
						break;
					}
				}
			}
			i++;
		}
		if (found)
			break;
	}
	fclose(file);
	return found ? id : -1;
}
+3 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ int get_root_cgroup(void);
int create_and_get_cgroup(const char *relative_path);
void remove_cgroup(const char *relative_path);
unsigned long long get_cgroup_id(const char *relative_path);
int get_cgroup1_hierarchy_id(const char *subsys_name);

int join_cgroup(const char *relative_path);
int join_root_cgroup(void);
@@ -29,8 +30,9 @@ int setup_cgroup_environment(void);
void cleanup_cgroup_environment(void);

/* cgroupv1 related */
int set_classid(unsigned int id);
int set_classid(void);
int join_classid(void);
unsigned long long get_classid_cgroup_id(void);

int setup_classid_environment(void);
void cleanup_classid_environment(void);
+158 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2023 Yafang Shao <laoar.shao@gmail.com> */

#include <sys/types.h>
#include <unistd.h>
#include <test_progs.h>
#include "cgroup_helpers.h"
#include "test_cgroup1_hierarchy.skel.h"

static void bpf_cgroup1(struct test_cgroup1_hierarchy *skel)
{
	struct bpf_link *lsm_link, *fentry_link;
	int err;

	/* Attach LSM prog first */
	lsm_link = bpf_program__attach_lsm(skel->progs.lsm_run);
	if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
		return;

	/* LSM prog will be triggered when attaching fentry */
	fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
	ASSERT_NULL(fentry_link, "fentry_attach_fail");

	err = bpf_link__destroy(lsm_link);
	ASSERT_OK(err, "destroy_lsm");
}

static void bpf_cgroup1_sleepable(struct test_cgroup1_hierarchy *skel)
{
	struct bpf_link *lsm_link, *fentry_link;
	int err;

	/* Attach LSM prog first */
	lsm_link = bpf_program__attach_lsm(skel->progs.lsm_s_run);
	if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
		return;

	/* LSM prog will be triggered when attaching fentry */
	fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
	ASSERT_NULL(fentry_link, "fentry_attach_fail");

	err = bpf_link__destroy(lsm_link);
	ASSERT_OK(err, "destroy_lsm");
}

static void bpf_cgroup1_invalid_id(struct test_cgroup1_hierarchy *skel)
{
	struct bpf_link *lsm_link, *fentry_link;
	int err;

	/* Attach LSM prog first */
	lsm_link = bpf_program__attach_lsm(skel->progs.lsm_run);
	if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
		return;

	/* LSM prog will be triggered when attaching fentry */
	fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
	if (!ASSERT_OK_PTR(fentry_link, "fentry_attach_success"))
		goto cleanup;

	err = bpf_link__destroy(fentry_link);
	ASSERT_OK(err, "destroy_lsm");

cleanup:
	err = bpf_link__destroy(lsm_link);
	ASSERT_OK(err, "destroy_fentry");
}

void test_cgroup1_hierarchy(void)
{
	struct test_cgroup1_hierarchy *skel;
	__u64 current_cgid;
	int hid, err;

	skel = test_cgroup1_hierarchy__open();
	if (!ASSERT_OK_PTR(skel, "open"))
		return;

	skel->bss->target_pid = getpid();

	err = bpf_program__set_attach_target(skel->progs.fentry_run, 0, "bpf_fentry_test1");
	if (!ASSERT_OK(err, "fentry_set_target"))
		goto destroy;

	err = test_cgroup1_hierarchy__load(skel);
	if (!ASSERT_OK(err, "load"))
		goto destroy;

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

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

	current_cgid = get_classid_cgroup_id();
	if (!ASSERT_GE(current_cgid, 0, "cgroup1 id"))
		goto cleanup;

	hid = get_cgroup1_hierarchy_id("net_cls");
	if (!ASSERT_GE(hid, 0, "cgroup1 id"))
		goto cleanup;
	skel->bss->target_hid = hid;

	if (test__start_subtest("test_cgroup1_hierarchy")) {
		skel->bss->target_ancestor_cgid = current_cgid;
		bpf_cgroup1(skel);
	}

	if (test__start_subtest("test_root_cgid")) {
		skel->bss->target_ancestor_cgid = 1;
		skel->bss->target_ancestor_level = 0;
		bpf_cgroup1(skel);
	}

	if (test__start_subtest("test_invalid_level")) {
		skel->bss->target_ancestor_cgid = 1;
		skel->bss->target_ancestor_level = 1;
		bpf_cgroup1_invalid_id(skel);
	}

	if (test__start_subtest("test_invalid_cgid")) {
		skel->bss->target_ancestor_cgid = 0;
		bpf_cgroup1_invalid_id(skel);
	}

	if (test__start_subtest("test_invalid_hid")) {
		skel->bss->target_ancestor_cgid = 1;
		skel->bss->target_ancestor_level = 0;
		skel->bss->target_hid = -1;
		bpf_cgroup1_invalid_id(skel);
	}

	if (test__start_subtest("test_invalid_cgrp_name")) {
		skel->bss->target_hid = get_cgroup1_hierarchy_id("net_cl");
		skel->bss->target_ancestor_cgid = current_cgid;
		bpf_cgroup1_invalid_id(skel);
	}

	if (test__start_subtest("test_invalid_cgrp_name2")) {
		skel->bss->target_hid = get_cgroup1_hierarchy_id("net_cls,");
		skel->bss->target_ancestor_cgid = current_cgid;
		bpf_cgroup1_invalid_id(skel);
	}

	if (test__start_subtest("test_sleepable_prog")) {
		skel->bss->target_hid = hid;
		skel->bss->target_ancestor_cgid = current_cgid;
		bpf_cgroup1_sleepable(skel);
	}

cleanup:
	cleanup_classid_environment();
destroy:
	test_cgroup1_hierarchy__destroy(skel);
}
+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ void test_cgroup_v1v2(void)
	}
	ASSERT_OK(run_test(cgroup_fd, server_fd, false), "cgroup-v2-only");
	setup_classid_environment();
	set_classid(42);
	set_classid();
	ASSERT_OK(run_test(cgroup_fd, server_fd, true), "cgroup-v1v2");
	cleanup_classid_environment();
	close(server_fd);
Loading