/*	$KAME: mtrace6.c,v 1.24 2005/04/14 06:22:34 suz Exp $	*/

/*
 * Copyright (C) 1999 WIDE Project.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/queue.h>

#include <net/if.h>
#ifdef __FreeBSD__
#include <net/if_var.h>
#endif

#include <netinet/in.h>

#ifdef HAVE_NETINET6_IN6_VAR_H
#include <netinet6/in6_var.h>
#endif
#include <netinet/icmp6.h>

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <err.h>
#include <ifaddrs.h>

#include "trace.h"

static char *gateway, *intface, *source, *group, *receiver, *destination;
static char *linkstr;
static int mldsoc, hops = 64, maxhops = 127, waittime = 3, querylen, opt_n;
static struct sockaddr *gw_sock, *src_sock, *grp_sock, *dst_sock, *rcv_sock; 
static char *querypacket;
static char frombuf[1024];	/* XXX: enough size? */

int main __P((int, char *[]));
static char *proto_type __P((u_int));
static char *pr_addr __P((struct sockaddr *, int));
static void setqid __P((int, char *));
static void mtrace_loop __P((void));
static char *str_rflags __P((int));
static void show_ip6_result __P((struct sockaddr_in6 *, int));
static void show_result __P((struct sockaddr *, int));
static void set_sockaddr __P((char *, struct addrinfo *, struct sockaddr *,
	size_t));
static int is_multicast __P((struct sockaddr *));
static char *all_routers_str __P((int));
static int ip6_validaddr __P((char *, struct sockaddr_in6 *));
static int get_my_sockaddr __P((int, struct sockaddr *, size_t));
static void set_hlim __P((int, struct sockaddr *, int));
static void set_join __P((int, char *, struct sockaddr *));
static void set_filter __P((int, int));
static void open_socket __P((void));
static void make_ip6_packet __P((void));
static void make_packet __P((void));
static void usage __P((void));
static socklen_t get_sa_len(struct sockaddr *);

int
main(argc, argv)
	int argc;
	char *argv[];
{
	int op;

	/* get parameters */
	while((op = getopt(argc, argv, "d:g:h:i:l:m:nr:w:")) != -1) {
		switch(op) {
		case 'd':
			destination = optarg;
			break;
		case 'g':
			gateway = optarg;
			break;
		case 'h':
			hops = atoi(optarg);
			if (hops < 0 || hops > 255) {
				warnx("query/response hops must be between 0 and 255");
				usage();
			}
			break;
		case 'i':
			intface = optarg;
			break;
		case 'l':
			linkstr = optarg;
			break;
		case 'm':
			maxhops = atoi(optarg);
			if (maxhops < 0 || maxhops > 255) {
				warnx("maxhops must be between 0 and 255");
				usage();
			}
			break;
		case 'n':
			opt_n = 1;
			break;
		case 'r':
			receiver = optarg;
			break;
		case 'w':
			waittime = atoi(optarg);
			break;
		case '?':
		default:
			usage();
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc < 2)
		usage();
	source = argv[0];
	group = argv[1];

	/* option consistency check */
	if (gateway && linkstr)
		errx(1, "-g and -l are exclusive");

	/* examine addresses and open a socket */
	open_socket();

	/* construct a query packet according to the specified parameters */
	make_packet();

	mtrace_loop();
	exit(0);
	/*NOTREACHED*/
}

static char *
proto_type(type)
	u_int type;
{
	static char buf[80];

	switch (type) {
	case PROTO_DVMRP:
		return ("DVMRP");
	case PROTO_MOSPF:
		return ("MOSPF");
	case PROTO_PIM:
		return ("PIM");
	case PROTO_CBT:
		return ("CBT");
	case PROTO_PIM_SPECIAL:
		return ("PIM/Special");
	case PROTO_PIM_STATIC:
		return ("PIM/Static");
	case PROTO_DVMRP_STATIC:
		return ("DVMRP/Static");
	case 0:
		return ("None");
	default:
		(void) sprintf(buf, "Unknown protocol code %d", type);
		return (buf);
	}
}

static char *
pr_addr(addr, numeric)
	struct sockaddr *addr;
	int numeric;
{
	static char buf[MAXHOSTNAMELEN];
	int flag = 0;

	if (numeric)
		flag |= NI_NUMERICHOST;

	getnameinfo(addr, get_sa_len(addr), buf, sizeof(buf), NULL, 0, flag);

	return (buf);
}

static void
setqid(family, query)
	int family;
	char *query;
{
	struct tr6_query *q6;

	switch(family) {
	case AF_INET6:
		q6 = (struct tr6_query *)((struct mld_hdr *)query + 1);
		q6->tr_qid = (u_int32_t)random();
	}
}

static void
mtrace_loop()
{
	int nsoc, fromlen, rcvcc;
	struct timeval tv, tv_wait;
	struct fd_set *fdsp;
	size_t nfdsp;
	struct sockaddr_storage from_ss;
	struct sockaddr *from_sock = (struct sockaddr *)&from_ss;

	/* initializa random number of query ID */
	gettimeofday(&tv, 0);
	srandom(tv.tv_usec);

	while(1) {		/* XXX */
		setqid(gw_sock->sa_family, querypacket);
		if (sendto(mldsoc, (void *)querypacket, querylen, 0, gw_sock,
			   get_sa_len(gw_sock)) < 0)
			err(1, "sendto");

		tv_wait.tv_sec = waittime;
		tv_wait.tv_usec = 0;

		nfdsp = howmany(mldsoc + 1, NFDBITS) * sizeof(fd_mask);
		fdsp = malloc(nfdsp);
		if (!fdsp)
			err(1, "malloc");
		memset(fdsp, 0, nfdsp);
		FD_SET(mldsoc, fdsp);

		if ((nsoc = select(mldsoc + 1, fdsp, NULL, NULL, &tv_wait)) < 0)
			err(1, "select");
		free(fdsp);

		if (nsoc == 0) {
			printf("Timeout\n");
			exit(0); /* XXX try again? */
		}

		fromlen = sizeof(from_ss);
		if ((rcvcc = recvfrom(mldsoc, frombuf, sizeof(frombuf),  0,
				      from_sock, &fromlen))
		     < 0)
			err(1, "recvfrom");

		show_result(from_sock, rcvcc);
		exit(0);	/* XXX */
	}
}

char *fwd_code[] = {"NOERR", "WRONGIF", "SPRUNE", "RPRUNE", "SCOPED", "NORT",
		    "WRONGLH", "NOFWD", "RP", "RPFIF", "NOMC", "HIDDEN"};
char *fwd_errcode[] = {"", "NOSPC", "OLD", "ADMIN"};

static char *
str_rflags(flag)
	int flag;
{
	if (0x80 & flag) {	/* fatal error */
		flag &= ~0x80;
		if (flag >= sizeof(fwd_errcode) / sizeof(char *) ||
		    flag == 0) {
			warnx("unknown error code(%d)", flag);
			return("UNKNOWN");
		}
		return(fwd_errcode[flag]);
	}

	/* normal code */
	if (flag >= sizeof(fwd_code) / sizeof(char *)) {
		warnx("unknown forward code(%d)", flag);
		return("UNKNOWN");
	}
	return(fwd_code[flag]);
}

static void
show_ip6_result(from6, datalen)
	struct sockaddr_in6 *from6;
	int datalen;
{
	struct mld_hdr *mld6_tr_resp = (struct mld_hdr *)frombuf;
	struct mld_hdr *mld6_tr_query = (struct mld_hdr *)querypacket;
	struct tr6_query *tr6_rquery = (struct tr6_query *)(mld6_tr_resp + 1);
	struct tr6_query *tr6_query = (struct tr6_query *)(mld6_tr_query + 1);
	struct tr6_resp *tr6_resp = (struct tr6_resp *)(tr6_rquery + 1),
		*rp, *rp_end;
	int i;

	if (datalen < sizeof(*mld6_tr_resp) + sizeof(*tr6_rquery) +
	    sizeof(*tr6_resp)) {
		warnx("show_ip6_result: receive data length(%d) is short",
		      datalen);
		return;
	}

	switch(mld6_tr_resp->mld_type) {
	case MLD_MTRACE_RESP:
		if ((datalen - sizeof(*mld6_tr_resp) - sizeof(*tr6_rquery)) %
		    sizeof(*tr6_resp)) {
			warnx("show_ip6_result: incomplete response (%d bytes)",
			      datalen);
			return;
		}
		rp_end = (struct tr6_resp *)((char *)mld6_tr_resp + datalen);

		/* sanity check for the response */
		if (tr6_query->tr_qid != tr6_rquery->tr_qid ||
		    !IN6_ARE_ADDR_EQUAL(&tr6_query->tr_src, &tr6_rquery->tr_src) ||
		    !IN6_ARE_ADDR_EQUAL(&tr6_query->tr_dst, &tr6_rquery->tr_dst))
			return;	/* XXX: bark here? */

		for (i = 0, rp = tr6_resp; rp < rp_end; i++, rp++) {
			struct sockaddr_in6 sa_resp, sa_upstream;

			/* reinitialize the sockaddr. paranoid? */
			memset((void *)&sa_resp, 0, sizeof(sa_resp));
			sa_resp.sin6_family = AF_INET6;
#ifdef HAVE_SA_LEN
			sa_resp.sin6_len = sizeof(sa_resp);
#endif
			memset((void *)&sa_upstream, 0, sizeof(sa_upstream));
			sa_upstream.sin6_family = AF_INET6;
#ifdef HAVE_SA_LEN
			sa_upstream.sin6_len = sizeof(sa_upstream);
#endif

			sa_resp.sin6_addr = rp->tr_lcladdr;
			sa_upstream.sin6_addr = rp->tr_rmtaddr;

			/* print information for the router */
			printf("%3d  ", -i);/* index */
			/* router address and incoming/outgoing interface */
			printf("%s", pr_addr((struct sockaddr *)&sa_resp, opt_n));
			printf("(%s/%ld->%ld) ",
			       pr_addr((struct sockaddr *)&sa_upstream, 1),
			       ntohl(rp->tr_inifid), ntohl(rp->tr_outifid));
			/* multicast routing protocol type */
			printf("%s ", proto_type(rp->tr_rproto));
			/* forwarding error code */
			printf("%s", str_rflags(rp->tr_rflags & 0xff));

			putchar('\n');
		}

		break;
	default:		/* impossible... */
		warnx("show_ip6_result: invalid ICMPv6 type(%d)",
		      mld6_tr_resp->mld_type); /* assert? */
		break;
	}
}

static void
show_result(from, datalen)
	struct sockaddr *from;
	int datalen;
{
	switch(from->sa_family) {
	case AF_INET6:
		show_ip6_result((struct sockaddr_in6 *)from, datalen);
		break;
	default:
		errx(1, "show_result: illegal AF(%d) on recv", from->sa_family);
	}
}

static void
set_sockaddr(addrname, hints, sap, l)
	char *addrname;
	struct addrinfo *hints;
	struct sockaddr *sap;
	size_t l;
{
	struct addrinfo *res;
	int ret_ga;

	ret_ga = getaddrinfo(addrname, NULL, hints, &res);
	if (ret_ga)
		errx(1, "getaddrinfo faild: %s", gai_strerror(ret_ga));
	if (!res->ai_addr)
		errx(1, "getaddrinfo failed");
	if (res->ai_addrlen > l)
		errx(1, "sockaddr too big");
	memcpy((void *)sap, (void *)res->ai_addr, get_sa_len(res->ai_addr));

	freeaddrinfo(res);
}

static int
is_multicast(sa)
	struct sockaddr *sa;
{
	switch(sa->sa_family) {
	case AF_INET6:
		if (IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)sa)->sin6_addr))
			return 1;
		else
			return 0;
		break;
	default:
		return 0;	/* XXX: support IPv4? */
	}
}

static char *
all_routers_str(family)
	int family;
{
	static char s[NI_MAXHOST];

	switch(family) {
	case AF_INET6:
		if (linkstr)
			snprintf(s, sizeof(s), "ff02::1%%%s", linkstr);
		else		/* use the default link (assuming it) */
			snprintf(s, sizeof(s), "ff02::1");
		return (s);
	default:
		errx(1, "all_routers_str: unknown AF(%d)", family);
	}
}

static int
ip6_validaddr(ifname, addr)
	char *ifname;
	struct sockaddr_in6 *addr;
{
	int s;
	struct in6_ifreq ifr6;
	u_int32_t flags6;

	/* we need a global address only...XXX: should be flexible? */
	if (IN6_IS_ADDR_LOOPBACK(&addr->sin6_addr) ||
	    IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) ||
	    IN6_IS_ADDR_SITELOCAL(&addr->sin6_addr))
		return(0);

	/* get IPv6 dependent flags and examine them */
	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
		err(1, "ip6_validaddr: socket");

#ifdef HAVE_STRLCPY
	strlcpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name));
#elif HAVE_STRNCPY
	strncpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name));
#else
	strcpy(ifr6.ifr_name, ifname);
#endif
	ifr6.ifr_addr = *addr;
	if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0)
		err(1, "ioctl(SIOCGIFAFLAG_IN6)");
	close(s);
	flags6 = ifr6.ifr_ifru.ifru_flags6;
	if (flags6 & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE |
		      IN6_IFF_DUPLICATED | IN6_IFF_DETACHED))
		return(0);

	return(1);
}

static int
get_my_sockaddr(family, addrp, l)
	int family;
	struct sockaddr *addrp;
	size_t l;
{
	struct ifaddrs *ifap, *ifa;

	if (getifaddrs(&ifap) != 0) {
		err(1, "getifaddrs");
		/*NOTREACHED */
	}

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr->sa_family != family)
			continue;
		if (get_sa_len(ifa->ifa_addr) > l)
			continue;
		switch(family) {
		case AF_INET6:
			if (ip6_validaddr(ifa->ifa_name,
			    (struct sockaddr_in6 *)ifa->ifa_addr))
				goto found;
		}
	}

	freeifaddrs(ifap);
	return (-1);		/* not found */

  found:
	memcpy((void *)addrp, (void *)ifa->ifa_addr, get_sa_len(ifa->ifa_addr));
	freeifaddrs(ifap);
	return (0);
}

static void
set_hlim(s, addr, hops)
	int s, hops;
	struct sockaddr *addr;
{
	struct sockaddr_in6 *sin6;
	int opt;

	switch(addr->sa_family) {
	case AF_INET6:
		sin6 = (struct sockaddr_in6 *)addr;
		opt = IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) ?
			IPV6_MULTICAST_HOPS : IPV6_UNICAST_HOPS;
		if (setsockopt(s, IPPROTO_IPV6, opt, (char *)&hops,
			       sizeof(hops)) == -1)
			err(1, "setsockopt(%s)",
			    (opt == IPV6_MULTICAST_HOPS) ?
			    "IPV6_MULTICAST_HOPS" : "IPV6_UNICAST_HOPS");
		break;
	}
}

static void
set_join(s, ifname, group)
	int s;
	char *ifname;
	struct sockaddr *group;
{
	struct ipv6_mreq mreq6;
	u_int ifindex;
	
	switch(group->sa_family) {
	case AF_INET6:
		if ((ifindex = if_nametoindex(ifname)) == 0)
			err(1, "set_join: if_nametoindex failed for %s", ifname);
		mreq6.ipv6mr_multiaddr =
			((struct sockaddr_in6 *)group)->sin6_addr;
		mreq6.ipv6mr_interface = ifindex;

		if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
			       sizeof(mreq6)) < 0)
			err(1, "setsockopt(IPV6_JOIN_GROUP)");
		break;
	}
}

static void
set_filter(s, family)
	int s, family;
{
	struct icmp6_filter filter6;

	switch(family) {
	case AF_INET6:
		ICMP6_FILTER_SETBLOCKALL(&filter6);
		ICMP6_FILTER_SETPASS(MLD_MTRACE_RESP, &filter6);
		if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter6,
			       sizeof(filter6)) < 0)
			err(1, "setsockopt(ICMP6_FILTER)");
		break;
	}
}

static void
open_socket()
{
	struct addrinfo hints;
	static struct sockaddr_storage gw_ss, src_ss, grp_ss, dst_ss, rcv_ss;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET6; /* to be independent of AF? */
	hints.ai_socktype = SOCK_RAW;
	hints.ai_protocol = IPPROTO_ICMPV6;

	/* multicast group(must be specified) */
	grp_sock = (struct sockaddr *)&grp_ss;
	set_sockaddr(group, &hints, grp_sock, sizeof(grp_ss));
	if (!is_multicast(grp_sock))
		errx(1, "group(%s) is not a multicast address", group);

	/* multicast source(must be specified) */
	src_sock = (struct sockaddr *)&src_ss;
	set_sockaddr(source, &hints, src_sock, sizeof(src_ss));
	if (is_multicast(src_sock))
		errx(1, "source(%s) is not a unicast address", source);

	/* last hop gateway for the destination(if specified) */
	gw_sock = (struct sockaddr *)&gw_ss;
	if (gateway)		/* can be either multicast or unicast */
		set_sockaddr(gateway, &hints, gw_sock, sizeof(gw_ss));
	else {
		char *r = all_routers_str(grp_sock->sa_family);

		set_sockaddr(r, &hints, gw_sock, sizeof(gw_ss));
	}

	/* destination address for the trace */
	dst_sock = (struct sockaddr *)&dst_ss;
	if (destination) {
		set_sockaddr(destination, &hints, dst_sock, sizeof(dst_ss));
		if (is_multicast(dst_sock))
			errx(1, "destination(%s) is not a unicast address",
			     destination);
	}
	else {
		/* XXX: consider interface? */
		get_my_sockaddr(grp_sock->sa_family, dst_sock, sizeof(dst_ss));
	}

	/* response receiver(if specified) */
	rcv_sock = (struct sockaddr *)&rcv_ss;
	if (receiver) {		/* can be either multicast or unicast */
		set_sockaddr(receiver, &hints, rcv_sock, sizeof(rcv_ss));
		if (is_multicast(rcv_sock) &&
		    intface == NULL) {
#ifdef notyet
			warnx("receive I/F is not specified for multicast"
			      "response(%s)", receiver);
			intface = default_intface;
#else
			errx(1, "receive I/F is not specified for multicast"
			     "response(%s)", receiver);
#endif 
		}
	}
	else {
		/* XXX: consider interface? */
		get_my_sockaddr(grp_sock->sa_family, rcv_sock, sizeof(rcv_ss));
	}

	if ((mldsoc = socket(hints.ai_family, hints.ai_socktype,
			     hints.ai_protocol)) < 0)
		err(1, "socket");

	/* set necessary socket options */
	if (hops)
		set_hlim(mldsoc, gw_sock, hops);
	if (receiver && is_multicast(rcv_sock))
		set_join(mldsoc, intface, rcv_sock);
	set_filter(mldsoc, grp_sock->sa_family);
}

static void
make_ip6_packet()
{
	struct mld_hdr *mld6_tr_query;
	struct tr6_query *tr6_query;

	querylen = sizeof(*mld6_tr_query) + sizeof(*tr6_query);
	if ((querypacket = malloc(querylen)) == NULL)
		errx(1, "make_ip6_packet: malloc failed");
	memset(querypacket, 0, querylen);

	/* fill in MLD header */
	mld6_tr_query = (struct mld_hdr *)querypacket;
	mld6_tr_query->mld_type = MLD_MTRACE;
	mld6_tr_query->mld_code = maxhops & 0xff;
	mld6_tr_query->mld_addr = ((struct sockaddr_in6 *)grp_sock)->sin6_addr;

	/* fill in mtrace query fields */
	tr6_query = (struct tr6_query *)(mld6_tr_query + 1);
	tr6_query->tr_src = ((struct sockaddr_in6 *)src_sock)->sin6_addr;
	tr6_query->tr_dst = ((struct sockaddr_in6 *)dst_sock)->sin6_addr;
	tr6_query->tr_raddr = ((struct sockaddr_in6 *)rcv_sock)->sin6_addr;
	tr6_query->tr_rhlim = 0xff & hops;
}

static void
make_packet()
{
	switch(grp_sock->sa_family) {
	case AF_INET6:
		make_ip6_packet();
		break;
	default:
		errx(1, "make_packet: unsupported AF(%d)", grp_sock->sa_family);
	}
}

static void
usage()
{
	fprintf(stderr, "usage: mtrace6 "
	    "[-d destination] [-g gateway] [-h hops] [-i interface] "
	    "[-l link] [-m maxhops] [-n] [-r response_addr] [-w waittime] "
	    "source group\n");
	exit(1);
}

static socklen_t
get_sa_len(struct sockaddr *addr)
{
#ifdef HAVE_SA_LEN
	return addr->sa_len;
#else
	switch (addr->sa_family) {
	case AF_INET:
		return sizeof(struct sockaddr_in);
	case AF_INET6:
		return sizeof(struct sockaddr_in6);
	default:
		return sizeof(struct sockaddr);
	}
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1