/* gcc -o test test.c -lmnl -lnftnl */
#include <stdlib.h>
#include <stddef.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <sys/types.h>
#include <errno.h>
#define __kernel_sa_family_t unsigned short	/* old kernel headers */
#include <netinet/in.h>

#include <linux/filter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/if_ether.h>

#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <linux/netlink.h>
#include <libmnl/libmnl.h>

#include <netinet/ip.h>

#define SO_ATTACH_NFT_FILTER 49

static void add_meta(struct nft_rule *r)
{
	struct nft_rule_expr *e;

	e = nft_rule_expr_alloc("meta");
	if (e == NULL) {
		perror("expr payload oom");
		exit(EXIT_FAILURE);
	}

	nft_rule_expr_set_u32(e, NFT_EXPR_META_DREG, NFT_REG_1);
	nft_rule_expr_set_u32(e, NFT_EXPR_META_KEY, NFT_META_PROTOCOL);
	nft_rule_add_expr(r, e);
}

static void add_cmp_meta(struct nft_rule *r)
{
	struct nft_rule_expr *e;
	uint16_t data = htons(ETH_P_IP);

	e = nft_rule_expr_alloc("cmp");
	if (e == NULL) {
		perror("expr cmp oom");
		exit(EXIT_FAILURE);
	}

	nft_rule_expr_set_u32(e, NFT_EXPR_CMP_SREG, NFT_REG_1);
	nft_rule_expr_set_u32(e, NFT_EXPR_CMP_OP, NFT_CMP_EQ);
	nft_rule_expr_set(e, NFT_EXPR_CMP_DATA, &data, sizeof(data));

	nft_rule_add_expr(r, e);
}

static void add_payload(struct nft_rule *r)
{
	struct nft_rule_expr *e;

	e = nft_rule_expr_alloc("payload");
	if (e == NULL) {
		perror("expr payload oom");
		exit(EXIT_FAILURE);
	}

	nft_rule_expr_set_u32(e, NFT_EXPR_PAYLOAD_BASE,
			      NFT_PAYLOAD_NETWORK_HEADER);
	nft_rule_expr_set_u32(e, NFT_EXPR_PAYLOAD_DREG, NFT_REG_1);
	nft_rule_expr_set_u32(e, NFT_EXPR_PAYLOAD_OFFSET,
			      offsetof(struct iphdr, protocol));
	nft_rule_expr_set_u32(e, NFT_EXPR_PAYLOAD_LEN, sizeof(uint8_t));

	nft_rule_add_expr(r, e);
}

static void add_cmp(struct nft_rule *r)
{
	struct nft_rule_expr *e;
	uint8_t data = IPPROTO_TCP;

	e = nft_rule_expr_alloc("cmp");
	if (e == NULL) {
		perror("expr cmp oom");
		exit(EXIT_FAILURE);
	}

	nft_rule_expr_set_u32(e, NFT_EXPR_CMP_SREG, NFT_REG_1);
	nft_rule_expr_set_u32(e, NFT_EXPR_CMP_OP, NFT_CMP_EQ);
	nft_rule_expr_set(e, NFT_EXPR_CMP_DATA, &data, sizeof(data));

	nft_rule_add_expr(r, e);
}

static void add_immediate(struct nft_rule *r)
{
	struct nft_rule_expr *e;
	uint32_t verdict = 0xffffffff;

	e = nft_rule_expr_alloc("immediate");
	if (e == NULL) {
		perror("expr cmp oom");
		exit(EXIT_FAILURE);
	}

	nft_rule_expr_set_u32(e, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT);
	nft_rule_expr_set_u32(e, NFT_EXPR_IMM_VERDICT, verdict);

	nft_rule_add_expr(r, e);
}

int main()
{
	int sock;
	char buf[MNL_SOCKET_BUFFER_SIZE];
	struct nlmsghdr *nlh;
	struct nft_rule *r;
	struct nft_rule_expr *e;
	int ret;

	r = nft_rule_alloc();
	if (r == NULL) {
		perror("nft_rule_alloc");
		exit(EXIT_FAILURE);
	}

	add_payload(r);
	add_cmp(r);
	add_meta(r);
	add_cmp_meta(r);
	add_immediate(r);

	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE, AF_INET, 0, 1234);
	nft_rule_nlmsg_build_payload(nlh, r);
	nft_rule_free(r);

	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));

	sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (sock < 0) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_NFT_FILTER,
			 (char *)nlh + sizeof(struct nlmsghdr) + sizeof(struct nfgenmsg),
			 nlh->nlmsg_len - sizeof(struct nlmsghdr) - sizeof(struct nfgenmsg));
	if (ret < 0) {
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}

	while (1) {
		ret = recv(sock, buf, sizeof(buf), 0);
		if (ret < 0) {
			perror("recv");
			exit(EXIT_FAILURE);
		}

		printf("received packet %d bytes\n", ret);
	}

	close(sock);
}
