/* ngrace.c -- DF-3484 hammer: multi-CPU producers into the single global
 * v1 netgraph queue (ngqbase/ngqlast under crit_enter only).
 * Each instance: ctrl socket (own node) + data socket, mkpeer echo via
 * generic NGM_MKPEER, then N writes to the data socket addressed to hook
 * "e0" (each write -> ngd_send -> ng_queue_data; echo reply -> another).
 * Run 2+ instances concurrently on different CPUs; watch netstat -m drift.
 * usage: ngrace <iters>
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <errno.h>
#include <err.h>

static int cs, ds;

static void
mkpeer_ctrl(const char *type, const char *ourhook, const char *peerhook)
{
	static char mbuf[512];
	struct ng_mesg *msg = (struct ng_mesg *)mbuf;
	struct sockaddr_ng dst;
	struct iovec iov[2];
	struct msghdr mhdr;
	struct ngm_mkpeer mk;
	int t;

	memset(&mk, 0, sizeof(mk));
	strcpy(mk.type, type);
	strcpy(mk.ourhook, ourhook);
	strcpy(mk.peerhook, peerhook);
	memset(msg, 0, sizeof(*msg) + sizeof(mk));
	msg->header.version = NG_VERSION;
	msg->header.typecookie = 851672668;
	msg->header.cmd = 2;
	msg->header.arglen = sizeof(mk);
	memcpy(msg->data, &mk, sizeof(mk));
	memset(&dst, 0, sizeof(dst));
	dst.sg_len = offsetof(struct sockaddr_ng, sg_data) + 1;
	dst.sg_family = AF_NETGRAPH;
	dst.sg_data[0] = 0;
	iov[0].iov_base = (caddr_t)&msg->header;
	iov[0].iov_len = sizeof(struct ng_msghdr);
	iov[1].iov_base = (caddr_t)msg->data;
	iov[1].iov_len = sizeof(mk);
	memset(&mhdr, 0, sizeof(mhdr));
	mhdr.msg_name = (caddr_t)&dst;
	mhdr.msg_namelen = dst.sg_len;
	mhdr.msg_iov = iov;
	mhdr.msg_iovlen = 2;
	if (sendmsg(cs, &mhdr, 0) < 0)
		err(1, "mkpeer");
	{
		struct timeval tv = {0, 200000};
		setsockopt(cs, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
		read(cs, mbuf, sizeof(mbuf));
	}
	(void)t;
}

int main(int argc, char **argv)
{
	long i, n = argc > 1 ? atol(argv[1]) : 100000;
	char buf[256];
	struct sockaddr_ng dst;
	ssize_t r;

	static const char *myname;
	cs = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) err(1, "socket ctrl");
	{
		struct timeval tvz = {0, 300000};
		setsockopt(cs, SOL_SOCKET, SO_RCVTIMEO, &tvz, sizeof(tvz));
	}
	ds = socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA);
	if (ds < 0) err(1, "socket data");
	{
		struct timeval tvz = {0, 500000};
		setsockopt(ds, SOL_SOCKET, SO_SNDTIMEO, &tvz, sizeof(tvz));
	}
	/* name our own node first (path "" = self) */
	{
		static char nb[512];
		struct ng_mesg *msg = (struct ng_mesg *)nb;
		struct ngm_name nm;
		struct sockaddr_ng d2;
		struct iovec iov[2];
		struct msghdr mh;
		char nmbuf[32];
		snprintf(nmbuf, sizeof(nmbuf), "ng%d", (int)getpid());
		myname = strdup(nmbuf);
		memset(&nm, 0, sizeof(nm));
		strlcpy(nm.name, nmbuf, sizeof(nm.name));
		memset(msg, 0, sizeof(*msg) + sizeof(nm));
		msg->header.version = NG_VERSION;
		msg->header.typecookie = 851672668;
		msg->header.cmd = 4;
		msg->header.arglen = sizeof(nm);
		memcpy(msg->data, &nm, sizeof(nm));
		memset(&d2, 0, sizeof(d2));
		d2.sg_len = offsetof(struct sockaddr_ng, sg_data) + 1;
		d2.sg_family = AF_NETGRAPH;
		d2.sg_data[0] = 0;
		iov[0].iov_base = (caddr_t)&msg->header;
		iov[0].iov_len = sizeof(struct ng_msghdr);
		iov[1].iov_base = (caddr_t)msg->data;
		iov[1].iov_len = sizeof(nm);
		memset(&mh, 0, sizeof(mh));
		mh.msg_name = (caddr_t)&d2;
		mh.msg_namelen = d2.sg_len;
		mh.msg_iov = iov;
		mh.msg_iovlen = 2;
		if (sendmsg(cs, &mh, 0) < 0)
			err(1, "self-name sendmsg");
		read(cs, nb, sizeof(nb));
	}
	mkpeer_ctrl("echo", "e0", "e0");


	/* drain data socket */
	memset(&dst, 0, sizeof(dst));
	dst.sg_len = offsetof(struct sockaddr_ng, sg_data) + strlen(myname) + 1;
	dst.sg_family = AF_NETGRAPH;
	strcpy(dst.sg_data, myname);

	if (connect(ds, (struct sockaddr *)&dst, dst.sg_len) < 0)
		err(1, "connect data");
	/* per-send hook address */
	memset(&dst, 0, sizeof(dst));
	dst.sg_len = offsetof(struct sockaddr_ng, sg_data) + strlen("e0") + 1;
	dst.sg_family = AF_NETGRAPH;
	strcpy(dst.sg_data, "e0");
	for (i = 0; i < n; i++) {
		if (send(ds, buf, sizeof(buf), 0) < 0) {
			if (errno == ENOBUFS || errno == ENOMEM) continue;
			err(1, "sendto");
		}
		/* drain the echo replies so rcvbuf does not fill */
		r = recv(ds, buf, sizeof(buf), MSG_DONTWAIT);
		(void)r;
	}
	printf("NGRACE-DONE %ld sends\n", n);
	return (0);
}
