/*	$KAME: trace.c,v 1.9 2003/09/02 09:57:05 itojun 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.
 */
/*
 * Copyright (c) 1998-2001
 * The University of Southern California/Information Sciences Institute.
 * 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.
 */
/*
 * Part of this program has been derived from mrouted.
 * The mrouted program is covered by the license in the accompanying file
 * named "LICENSE.mrouted".
 *
 * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
 * Leland Stanford Junior University.
 *
 */


#include "defs.h"
#include "trace.h"

/* TODO */
/*
 * Traceroute function which returns traceroute replies to the requesting
 * router. Also forwards the request to downstream routers.
 */
void
accept_mtrace(src, dst, group, ifindex, data, no, datalen)
	struct sockaddr_in6 *src;
	struct in6_addr *dst;
	struct in6_addr *group;
	int ifindex;
	char *data;
	u_int no;   /* promoted u_char */
	int datalen;
{
	u_char type;
	mrtentry_t *mrt;
	struct tr6_query *qry;
	struct tr6_resp  *resp;
	int vifi, ovifi;
	char *p;
	int rcount;
	int errcode = TR_NO_ERR;
	int resptype;
	struct timeval tp;
	struct sioc_mif_req6 mreq;
	struct in6_addr parent_address;
	struct sockaddr_in6 src_sa6;
	struct sockaddr_in6 dst_sa6;
	struct sockaddr_in6 resp_sa6;
	struct sockaddr_in6 grp_sa6;
	struct sockaddr_in6 *sa_global;
#ifdef SM_ONLY
	rpentry_t *rpentry_ptr;
#endif 
	/* Remember qid across invocations */
	static u_int32 oqid = 0;

	init_sin6(&src_sa6);
	init_sin6(&dst_sa6);
	init_sin6(&resp_sa6);
	init_sin6(&grp_sa6);

	/* timestamp the request/response */
	gettimeofday(&tp, 0);

	/*
	 * Check if it is a query or a response
	 */
	if (datalen == QLEN) {
		type = QUERY;
		IF_DEBUG(DEBUG_TRACE)
			log_msg(LOG_DEBUG, 0, "Initial traceroute query rcvd "
			    "from %s to %s",
			    inet6_fmt(&src->sin6_addr),
			    inet6_fmt(dst));
	}
	else if ((datalen - QLEN) % RLEN == 0) {
		type = RESP;
		IF_DEBUG(DEBUG_TRACE)
			log_msg(LOG_DEBUG, 0, "In-transit traceroute query rcvd "
			    "from %s to %s",
			    inet6_fmt(&src->sin6_addr),
			    inet6_fmt(dst));
		if (IN6_IS_ADDR_MULTICAST(dst)) {
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0, "Dropping multicast response");
			return;
		}
	}
	else {
		log_msg(LOG_WARNING, 0, "%s from %s to %s",
		    "Non decipherable traceroute request recieved",
		    inet6_fmt(&src->sin6_addr), inet6_fmt(dst));
		return;
	}

	qry = (struct tr6_query *)data;
	src_sa6.sin6_addr = qry->tr_src;
	src_sa6.sin6_scope_id =
		(IN6_IS_ADDR_LINKLOCAL(&qry->tr_src)
		 || IN6_IS_ADDR_MC_LINKLOCAL(&qry->tr_src)) ? ifindex : 0;
	dst_sa6.sin6_addr = qry->tr_dst;
	dst_sa6.sin6_scope_id =
		(IN6_IS_ADDR_LINKLOCAL(&qry->tr_dst)
		 || IN6_IS_ADDR_MC_LINKLOCAL(&qry->tr_dst)) ? ifindex : 0;
	grp_sa6.sin6_addr = *group;
	grp_sa6.sin6_scope_id = 0;

	/*
	 * if it is a packet with all reports filled, drop it
	 */
	if ((rcount = (datalen - QLEN)/RLEN) == no) {
		IF_DEBUG(DEBUG_TRACE)
			log_msg(LOG_DEBUG, 0, "packet with all reports filled in");
		return;
	}

	IF_DEBUG(DEBUG_TRACE) {
		log_msg(LOG_DEBUG, 0, "s: %s g: %s d: %s ",
		    inet6_fmt(&qry->tr_src),
		    inet6_fmt(group), inet6_fmt(&qry->tr_dst));
		log_msg(LOG_DEBUG, 0, "rhlim: %d rd: %s", qry->tr_rhlim,
		    inet6_fmt(&qry->tr_raddr));
		log_msg(LOG_DEBUG, 0, "rcount:%d, qid:%06x", rcount, qry->tr_qid);
	}

	/* determine the routing table entry for this traceroute */
	mrt = find_route(&src_sa6, &grp_sa6, MRTF_SG | MRTF_WC | MRTF_PMBR,
			 DONT_CREATE);
	IF_DEBUG(DEBUG_TRACE) {
		if (mrt != (mrtentry_t *)NULL) {
			if (mrt->upstream != (pim_nbr_entry_t *)NULL)
				parent_address = mrt->upstream->address.sin6_addr;
			else
				parent_address = in6addr_any;
			log_msg(LOG_DEBUG, 0,
			    "mrt parent mif: %d rtr: %s metric: %d",
			    mrt->incoming,
			    inet6_fmt(&parent_address), mrt->metric);
			/* TODO
			   log_msg(LOG_DEBUG, 0, "mrt origin %s",
			   RT_FMT(rt, s1));
			*/
		} else
			log_msg(LOG_DEBUG, 0, "...no route");
	}
    
	/*
	 * Query type packet - check if rte exists 
	 * Check if the query destination is a vif connected to me.
	 * and if so, whether I should start response back
	 */
	if (type == QUERY) {
		if (oqid == qry->tr_qid) {
			/*
			 * If the multicast router is a member of the group
			 * being queried, and the query is multicasted,
			 * then the router can recieve multiple copies of
			 * the same query.  If we have already replied to
			 * this traceroute, just ignore it this time.
			 *
			 * This is not a total solution, but since if this
			 * fails you only get N copies, N <= the number of
			 * interfaces on the router, it is not fatal.
			 */
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "ignoring duplicate traceroute packet");
			return;
		}

		if (mrt == (mrtentry_t *)NULL) {
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "Mcast traceroute: no route entry %s",
				    inet6_fmt(&qry->tr_src));
#if 0
			if (IN6_IS_ADDR_MULTICAST(dst))
				return;
#endif
		}
		vifi = find_vif_direct(&dst_sa6);
	
		if (vifi == NO_VIF) {
			/*
			 * The traceroute destination is not on one of
			 * my subnet vifs.
			 */
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "Destination %s not an interface",
				    inet6_fmt(&qry->tr_dst));
			if (IN6_IS_ADDR_MULTICAST(dst))
				return;
			errcode = TR_WRONG_IF;
		} else if (mrt != (mrtentry_t *)NULL &&
			   !IF_ISSET(vifi, &mrt->oifs)) {
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "Destination %s not on forwarding tree "
				    "for src %s",
				    inet6_fmt(&qry->tr_dst),
				    inet6_fmt(&qry->tr_src));
			if (IN6_IS_ADDR_MULTICAST(dst))
				return;
			errcode = TR_WRONG_IF;
		}
	}
	else {
		/*
		 * determine which interface the packet came in on
		 * RESP packets travel hop-by-hop so this either traversed
		 * a tunnel or came from a directly attached mrouter.
		 */
		if ((vifi = find_vif_direct(src)) == NO_VIF) {
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "Wrong interface for packet");
			errcode = TR_WRONG_IF;
		}
	}   
    
	/* Now that we've decided to send a response, save the qid */
	oqid = qry->tr_qid;

	IF_DEBUG(DEBUG_TRACE)
		log_msg(LOG_DEBUG, 0, "Sending traceroute response");
    
	/* copy the packet to the sending buffer */
	p = mld6_send_buf + sizeof(struct mld_hdr);
    
	bcopy(data, p, datalen);
    
	p += datalen;
    
	/*
	 * If there is no room to insert our reply, coopt the previous hop
	 * error indication to relay this fact.
	 */
	if (p + sizeof(struct tr6_resp) > mld6_send_buf + RECV_BUF_SIZE) {
		resp = (struct tr6_resp *)p - 1;
		resp->tr_rflags = TR_NO_SPACE;
		mrt = NULL;
		goto sendit;
	}

	/*
	 * fill in initial response fields
	 */
	resp = (struct tr6_resp *)p;
	bzero(resp, sizeof(struct tr6_resp));
	datalen += (RLEN + sizeof(struct mld_hdr));

	resp->tr_qarr    = htonl(((tp.tv_sec + JAN_1970) << 16) + 
				 ((tp.tv_usec << 10) / 15625));

	resp->tr_rproto  = PROTO_PIM;
	resp->tr_outifid = (vifi == NO_VIF) ? TR_NO_VIF : htonl(vifi);
	resp->tr_rflags  = errcode;
	if ((sa_global = max_global_address()) == NULL)	/* impossible */
		log_msg(LOG_ERR, 0, "acept_mtrace: max_global_address returns NULL");
	resp->tr_lcladdr = sa_global->sin6_addr;

	/*
	 * obtain # of packets out on interface
	 */
	mreq.mifi = vifi;
	if (vifi != NO_VIF &&
	    ioctl(udp_socket, SIOCGETMIFCNT_IN6, (char *)&mreq) >= 0)
		resp->tr_vifout  =  htonl(mreq.ocount);
	else
		resp->tr_vifout  =  0xffffffff;

	/*
	 * fill in scoping & pruning information
	 */
	/* TODO */
#if 0
	if (mrt != (mrtentry_t *)NULL)
		for (gt = rt->rt_groups; gt; gt = gt->gt_next) {
			if (gt->gt_mcastgrp >= group)
				break;
		}
	else
		gt = NULL;

	if (gt && gt->gt_mcastgrp == group) {
		struct stable *st;

		for (st = gt->gt_srctbl; st; st = st->st_next)
			if (qry->tr_src == st->st_origin)
				break;

		sg_req.src.s_addr = qry->tr_src;
		sg_req.grp.s_addr = group;
		if (st && st->st_ctime != 0 &&
		    ioctl(udp_socket, SIOCGETSGCNT, (char *)&sg_req) >= 0)
			resp->tr_pktcnt = htonl(sg_req.pktcnt + st->st_savpkt);
		else
			resp->tr_pktcnt = htonl(st ? st->st_savpkt : 0xffffffff);

		if (VIFM_ISSET(vifi, gt->gt_scope))
			resp->tr_rflags = TR_SCOPED;
		else if (gt->gt_prsent_timer)
			resp->tr_rflags = TR_PRUNED;
		else if (!VIFM_ISSET(vifi, gt->gt_grpmems))
			if (VIFM_ISSET(vifi, rt->rt_children) &&
			    NBRM_ISSETMASK(uvifs[vifi].uv_nbrmap,
					   rt->rt_subordinates)) /*XXX*/
				resp->tr_rflags = TR_OPRUNED;
			else
				resp->tr_rflags = TR_NO_FWD;
	} else {
		if (scoped_addr(vifi, group))
			resp->tr_rflags = TR_SCOPED;
		else if (rt && !VIFM_ISSET(vifi, rt->rt_children))
			resp->tr_rflags = TR_NO_FWD;
	}
#endif /* 0 */

	/*
	 *  if no rte exists, set NO_RTE error
	 */
	if (mrt == (mrtentry_t *)NULL) {
		src->sin6_addr = *dst;	/* the dst address of resp. pkt */
		resp->tr_inifid = TR_NO_VIF;
		resp->tr_rflags   = TR_NO_RTE;
		memset(&resp->tr_rmtaddr, 0, sizeof(struct in6_addr));
	} else {
		/* get # of packets in on interface */
		mreq.mifi = mrt->incoming;
		if (ioctl(udp_socket, SIOCGETMIFCNT_IN6, (char *)&mreq) >= 0)
			resp->tr_vifin = htonl(mreq.icount);
		else
			resp->tr_vifin = 0xffffffff;

		/*
		 * TODO
		 * MASK_TO_VAL(rt->rt_originmask, resp->tr_smask);
		 */
		resp->tr_inifid = htonl(mrt->incoming);
		if (mrt->upstream != (pim_nbr_entry_t *)NULL)
			parent_address = mrt->upstream->address.sin6_addr;
		else
			parent_address = in6addr_any;

		resp->tr_rmtaddr = parent_address;
		if (!IF_ISSET(vifi, &mrt->oifs)) {
			IF_DEBUG(DEBUG_TRACE)
				log_msg(LOG_DEBUG, 0,
				    "Destination %s not on forwarding tree "
				    "for src %s",
				    inet6_fmt(&qry->tr_dst),
				    inet6_fmt(&qry->tr_src));
			resp->tr_rflags = TR_WRONG_IF;
		}
#if 0
		if (rt->rt_metric >= UNREACHABLE) {
			resp->tr_rflags = TR_NO_RTE;
			/* Hack to send reply directly */
			rt = NULL;
		}
#endif /* 0 */
	}

#ifdef SM_ONLY
	/*
	 * If we're the RP for the trace group, note it.
	 */
	rpentry_ptr = rp_match(&grp_sa6);
	if (rpentry_ptr && local_address(&rpentry_ptr->address) != NO_VIF)
		resp->tr_rflags = TR_RP;
#endif /* SM_ONLY */

  sendit:
	/*
	 * if metric is 1 or no. of reports is 1, send response to requestor
	 * else send to upstream router.  If the upstream router can't handle
	 * mtrace, set an error code and send to requestor anyway.
	 */
	IF_DEBUG(DEBUG_TRACE)
		log_msg(LOG_DEBUG, 0, "rcount:%d, no:%d", rcount, no);

	ovifi = NO_VIF;		/* unspecified */
	if ((rcount + 1 == no) || (mrt == NULL) || (mrt->metric == 1)) {
		resptype = MLD_MTRACE_RESP;
		resp_sa6.sin6_addr = qry->tr_raddr;
		if (IN6_IS_ADDR_LINKLOCAL(&resp_sa6.sin6_addr) ||
		    IN6_IS_ADDR_MC_LINKLOCAL(&resp_sa6.sin6_addr)) {
			if ((ovifi = find_vif_direct(&dst_sa6)) == NO_VIF) {
				log_msg(LOG_INFO, 0,
				    "can't determine outgoing i/f for mtrace "
				    "response.");
				return;
			}
		}
	} else
		/* TODO */
	{
#if 0
		if (!can_mtrace(rt->rt_parent, rt->rt_gateway)) {
			resp_sa6.sin6_addr = qry->tr_raddr;
			resp->tr_rflags = TR_OLD_ROUTER;
			resptype = MLD_MTRACE_RESP;
		} else
#endif /* 0 */
#ifdef SM_ONLY
		if (mrt->incoming &&
		    (uvifs[mrt->incoming].uv_flags & MIFF_REGISTER)) {
			log_msg(LOG_DEBUG, 0,
			    "incoming i/f is for register. "
			    "Can't be forwarded anymore.");
				resp_sa6.sin6_addr = qry->tr_raddr;
				resptype = MLD_MTRACE_RESP;
		} else
#endif /* SM_ONLY */
		{
			if (mrt->upstream != (pim_nbr_entry_t *)NULL)
				parent_address =
					mrt->upstream->address.sin6_addr;
			else
				parent_address = allrouters_group.sin6_addr;
			resp_sa6.sin6_addr = parent_address;
			ovifi = mrt->incoming;
			resptype = MLD_MTRACE;
		}
	}

	if (IN6_IS_ADDR_MULTICAST(&resp_sa6.sin6_addr)) {
		struct sockaddr_in6 *sa6;

		/*
		 * Send the reply on a known multicast capable vif.
		 * If we don't have one, we can't source any
		 * multicasts anyway.
		 */
		if (IN6_IS_ADDR_MC_LINKLOCAL(&resp_sa6.sin6_addr)) {
			sa6 = &uvifs[ovifi].uv_linklocal->pa_addr;
			ifindex = uvifs[ovifi].uv_ifindex;
		}
		else {
			if (phys_vif != -1 &&
			    (sa6 = uv_global(phys_vif)) != NULL) {
				IF_DEBUG(DEBUG_TRACE)
					log_msg(LOG_DEBUG, 0,
					    "Sending reply to %s from %s",
					    inet6_fmt(dst),
					    inet6_fmt(&sa6->sin6_addr));
				ifindex = uvifs[phys_vif].uv_ifindex;
			}
			else {
				log_msg(LOG_INFO, 0, "No enabled phyints -- %s",
				    "dropping traceroute reply");
				return;
			}
		}
		k_set_hlim(mld6_socket, qry->tr_rhlim);
		send_mld6(resptype, no, sa6, &resp_sa6, group,
			  ifindex, 0, datalen, 0);
		k_set_hlim(mld6_socket, 1);
	} else {
		struct sockaddr_in6 *sa6 = NULL;
		ifindex = -1;	/* unspecified by default */

		if (IN6_IS_ADDR_LINKLOCAL(&resp_sa6.sin6_addr)) {
			/* ovifi must be valid in this case */
			ifindex = uvifs[ovifi].uv_ifindex;
			sa6 = &uvifs[ovifi].uv_linklocal->pa_addr;
		}

		IF_DEBUG(DEBUG_TRACE)
			log_msg(LOG_DEBUG, 0, "Sending %s to %s from %s",
			    resptype == MLD_MTRACE_RESP ?
			    "reply" : "request on",
			    inet6_fmt(dst),
			    sa6 ? inet6_fmt(&sa6->sin6_addr) : "unspecified");
	
		send_mld6(resptype, no, sa6, &resp_sa6, group, ifindex,
			  0, datalen, 0);
	}
	return;
}


syntax highlighted by Code2HTML, v. 0.9.1