/*
 * DF-3526 / DF-3527 trigger -- create the first netgraph7 "iface" node.
 *
 * DF-3526: ng_iface_get_unit() (sys/netgraph7/iface/ng_iface.c:292-303)
 *   passes the initially-NULL ng_iface_units to bcopy(); systm.h maps
 *   bcopy onto __builtin_memmove and documents that GCC may then assume
 *   the operand non-NULL and delete the later NULL test at :300, so the
 *   first node creation calls kfree(NULL) -> "panic: trying to free
 *   NULL pointer" (twin of the v1 DF-3485, same code, ng7 module).
 *
 * DF-3527: with the bcopy guard restored, creation proceeds to
 *   if_attach(ifp, NULL) at ng_iface.c:624 from the ng7 control-message
 *   (netisr) context -> ifnet-lock assert panic
 *   (twin of the v1 DF-3486).
 *
 * modes:
 *   create : mkpeer iface -> GET_IFNAME -> NGM_SHUTDOWN (full lifecycle)
 *   unpriv : same, run as an unprivileged uid (expect EPERM on the
 *            control socket: ng7 ngc_attach has caps_priv_check)
 *
 * Build:  cc -O2 -o iface_con iface_con.c
 * Run:    ./iface_con create
 */

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

/* ---- ng7 netgraph user ABI ---- */
#define AF_NG			32
#define NG_CONTROL		2
#define NG_VERSION		8
#define NGM_GENERIC_COOKIE	1137070366
#define NGM_SHUTDOWN		1
#define NGM_MKPEER		2
#define NGM_NODEINFO		(6 | 0x10000000 | 0x20000000)

#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[1024]; };
struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct nodeinfo_ { char name[NG_NODESIZ]; char type[NG_TYPESIZ]; u_int32_t id; u_int32_t hooks; };
struct sockaddr_ng_ { u_char sg_len; sa_family_t sg_family; char sg_data[64]; };

/* ---- ng_iface ABI ---- */
#define NGM_IFACE_COOKIE	1108312559
#define NGM_IFACE_GET_IFNAME	1

static u_int32_t tok = 0x2100;

static int
ng_send_msg2(int cs, const char *path, u_int cookie, u_int cmd,
    const void *arg, size_t arglen, struct ng_mesg_ *reply)
{
	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);

	if (reply) {
		u_int32_t mytok = m.header.token;
		int tries;
		for (tries = 0; tries < 64; tries++) {
			n = recv(cs, reply, sizeof(*reply), 0);
			if (n < 0)
				return (-1);
			if (reply->header.token != mytok)
				continue;
			if (reply->header.flags & 1 /* NGF_RESP */) {
				if (reply->header.arglen == 4)
					return (-((int)*(u_int32_t *)reply->data ?
					    (int)*(u_int32_t *)reply->data : EIO));
				return (0);
			}
		}
		return (-1);
	}
	return (0);
}

static int
ng_send_msg(int cs, const char *path, u_int cmd, const void *arg,
    size_t arglen, struct ng_mesg_ *reply)
{
	return (ng_send_msg2(cs, path, NGM_GENERIC_COOKIE, cmd, arg, arglen,
	    reply));
}

int
main(int argc, char **argv)
{
	struct ngm_mkpeer_ mkp;
	static struct ng_mesg_ reply;
	int cs;
	const char *mode = (argc > 1) ? argv[1] : "create";

	printf("iface_con: uid=%d euid=%d mode=%s\n", getuid(), geteuid(), 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);
		}
		exit(1);
	}
	printf("STEP control-socket ok\n");
	fflush(stdout);

	/* DF-3526 / DF-3527 trigger: constructor runs HERE */
	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "iface");
	strcpy(mkp.ourhook, "inet");
	strcpy(mkp.peerhook, "inet");
	if (ng_send_msg(cs, ".", NGM_MKPEER, &mkp, sizeof(mkp), NULL) < 0) {
		printf("RESULT mkpeer failed errno=%d (%s)\n",
		    errno, strerror(errno));
		exit(1);
	}
	printf("STEP mkpeer iface survived (no panic)\n");
	fflush(stdout);

	if (ng_send_msg2(cs, "inet", NGM_IFACE_COOKIE, NGM_IFACE_GET_IFNAME,
	    NULL, 0, &reply) < 0)
		printf("STEP get_ifname failed errno=%d\n", errno);
	else
		printf("STEP ifname=%s\n", reply.data);
	fflush(stdout);

	if (ng_send_msg(cs, "inet", NGM_SHUTDOWN, NULL, 0, NULL) < 0)
		printf("STEP shutdown failed errno=%d\n", errno);
	else
		printf("STEP shutdown ok\n");
	fflush(stdout);

	printf("RESULT lifecycle-complete exit=0\n");
	return (0);
}
