/*
 * DF-3371 -- ip_setmoptions IP_ADD_MEMBERSHIP drops the route reference
 * before reading rt_ifp (ip_output.c:1655-1659):
 *
 *	rt = rtlookup((struct sockaddr *)&dst);
 *	if (rt == NULL) ...
 *	--rt->rt_refcnt;		 (manual decrement, not rtfree)
 *	ifp = rt->rt_ifp;			 (use after the drop)
 *
 * Racing RTM_DELETE of the matched route: if our decrement is the last
 * reference while another CPU removed the entry from the tree, the
 * concurrent rtfree() frees the rtentry and the very next instruction
 * reads rt->rt_ifp out of freed memory (and the manual decrement itself
 * writes freed memory).
 *
 * Stress: thread A hammers IP_ADD_MEMBERSHIP (imr_address=INADDR_ANY ->
 * the rtlookup branch) for a multicast dst whose route is 224.0.0.0/4;
 * thread B hammers route add/delete of 224.0.0.0/4.
 * Root.  A clean exit after N iterations = race not hit.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

static volatile int stop;
static long joins_ok, dels_ok, adds_ok;

static void *
joiner(void *v)
{
	struct ip_mreqn mreqn;
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	while (!stop) {
		memset(&mreqn, 0, sizeof(mreqn));
		inet_pton(AF_INET, "239.1.2.3", &mreqn.imr_multiaddr);
		mreqn.imr_address.s_addr = htonl(INADDR_ANY);
		mreqn.imr_ifindex = 0;
		if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		    &mreqn, sizeof(mreqn)) == 0)
			joins_ok++;
		/* matching drop to keep membership slots free */
		setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,
		    &mreqn, sizeof(mreqn));
	}
	return (NULL);
}

static void *
router(void *v)
{
	int s = socket(AF_ROUTE, SOCK_RAW, 0);
	char buf[512];
	struct rt_msghdr *rtm;
	struct sockaddr_in *dst, *mask, *gate;

	while (!stop) {
		memset(buf, 0, sizeof(buf));
		rtm = (struct rt_msghdr *)buf;
		rtm->rtm_version = RTM_VERSION;
		rtm->rtm_type = RTM_ADD;
		rtm->rtm_flags = RTF_UP | RTF_STATIC;
		rtm->rtm_addrs = RTA_DST | RTA_NETMASK | RTA_GATEWAY;
		rtm->rtm_seq = 4242;
		dst = (struct sockaddr_in *)(buf + sizeof(*rtm));
		dst->sin_len = sizeof(*dst); dst->sin_family = AF_INET;
		inet_pton(AF_INET, "239.1.0.0", &dst->sin_addr);
		mask = (struct sockaddr_in *)((char *)dst + RT_ROUNDUP(sizeof(*dst)));
		mask->sin_len = sizeof(*mask); mask->sin_family = AF_INET;
		inet_pton(AF_INET, "255.255.0.0", &mask->sin_addr);
		gate = (struct sockaddr_in *)((char *)mask + RT_ROUNDUP(sizeof(*mask)));
		gate->sin_len = sizeof(*gate); gate->sin_family = AF_INET;
		inet_pton(AF_INET, "127.0.0.1", &gate->sin_addr);
		rtm->rtm_msglen = ((char *)gate + RT_ROUNDUP(sizeof(*gate))) - buf;
		{
			ssize_t w = write(s, buf, rtm->rtm_msglen);
			if (w >= 0) adds_ok++;
			else if (adds_ok + dels_ok < 3)
				printf("ADD err: %s\n", strerror(errno));
		}
		usleep(100);
		rtm->rtm_type = RTM_DELETE;
		{
			ssize_t w = write(s, buf, rtm->rtm_msglen);
			if (w >= 0) dels_ok++;
			else if (adds_ok + dels_ok < 3)
				printf("DEL err: %s\n", strerror(errno));
		}
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 60;
	pthread_t a, b;
	if (geteuid() != 0) { fprintf(stderr, "root only\n"); return (1); }
	pthread_create(&a, NULL, joiner, NULL);
	pthread_create(&b, NULL, router, NULL);
	sleep(secs);
	stop = 1;
	pthread_join(a, NULL); pthread_join(b, NULL);
	printf("joins=%ld route-adds=%ld route-dels=%ld (no crash = window not hit)\n",
	    joins_ok, adds_ok, dels_ok);
	return (0);
}
