/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "libnetlink.h"
#include "ssfilter.h"
#include "ss_util.h"

static int dummy_filter(struct nlmsghdr *n, void *arg)
{
	/* just stops rtnl_dump_filter() */
	return -1;
}

static bool cgroup_filter_check(void)
{
	struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
	DIAG_REQUEST(req, struct inet_diag_req_v2 r);
	struct instr {
		struct inet_diag_bc_op op;
		__u64 cgroup_id;
	} __attribute__((packed));
	int inslen = sizeof(struct instr);
	struct instr instr = {
		{ INET_DIAG_BC_CGROUP_COND, inslen, inslen + 4 },
		0
	};
	struct rtnl_handle rth;
	struct iovec iov[3];
	struct msghdr msg;
	struct rtattr rta;
	int ret = false;
	int iovlen = 3;

	if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
		return false;
	rth.dump = MAGIC_SEQ;
	rth.flags = RTNL_HANDLE_F_SUPPRESS_NLERR;

	memset(&req.r, 0, sizeof(req.r));
	req.r.sdiag_family = AF_INET;
	req.r.sdiag_protocol = IPPROTO_TCP;
	req.nlh.nlmsg_len += RTA_LENGTH(inslen);

	rta.rta_type = INET_DIAG_REQ_BYTECODE;
	rta.rta_len = RTA_LENGTH(inslen);

	iov[0] = (struct iovec) { &req, sizeof(req) };
	iov[1] = (struct iovec) { &rta, sizeof(rta) };
	iov[2] = (struct iovec) { &instr, inslen };

	msg = (struct msghdr) {
		.msg_name = (void *)&nladdr,
		.msg_namelen = sizeof(nladdr),
		.msg_iov = iov,
		.msg_iovlen = iovlen,
	};

	if (sendmsg(rth.fd, &msg, 0) < 0)
		goto out;

	if (rtnl_dump_filter(&rth, dummy_filter, NULL) < 0) {
		ret = (errno != EINVAL);
		goto out;
	}

	ret = true;

out:
	rtnl_close(&rth);

	return ret;
}


struct filter_check_t {
	bool (*check)(void);
	int checked:1,
	    supported:1;
};

static struct filter_check_t filter_checks[SSF__MAX] = {
	[SSF_CGROUPCOND] = { cgroup_filter_check, 0 },
};

bool ssfilter_is_supported(int type)
{
	struct filter_check_t f;

	if (type >= SSF__MAX)
		return false;

	f = filter_checks[type];
	if (!f.check)
		return true;

	if (!f.checked) {
		f.supported = f.check();
		f.checked = 1;
	}

	return f.supported;
}
