/* print IPv6 ICMP/IGMP/NDP packets */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <stdio.h>
#include <string.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <unistd.h>

#include "interface.h"
#include "addrtoname.h"
#include "ipv6.h"

/*
 * print an IPv6 ICMP/IGMP/NDP datagram.
 */
void
icmpv6_print(const u_char *bp1, int length, const u_char *bp2)
{
	register struct icmpv6 *dp;
	register const struct ipv6 *ip;
	register const u_char *ep;
	int hasnextip = 0;

#define TCHECK(var, l)	if ((u_char *)&(var) > ep - l) goto trunc

	dp = (struct icmpv6 *)bp1;
	ip = (struct ipv6 *)bp2;
	/* 'ep' points to the end of avaible data. */
	ep = snapend;
	if (ep > bp1 + length)
		ep = bp1 + length;

	(void)printf("%s > %s: ",
		     ip6addr_string(ip->ip6_src),
		     ip6addr_string(ip->ip6_dst));
	TCHECK(dp->icmp6_pmtu, 4);
	(void)printf("icmp: ");
	switch (dp->icmp6_type) {
	case ICMP6_UNREACH:
		TCHECK(dp->icmp6_ip.ip6_dst, 16);
		switch (dp->icmp6_code) {
		case ICMP6_UNREACH_NOROUTE:
			(void)printf("no route to %s",
				     ip6addr_string(dp->icmp6_ip.ip6_dst));
			break;
		case ICMP6_UNREACH_ADMIN:
			(void)printf("%s prohibited",
				     ip6addr_string(dp->icmp6_ip.ip6_dst));
			break;
		case ICMP6_UNREACH_RTFAIL:
			(void)printf("%s not a neighbor",
				     ip6addr_string(dp->icmp6_ip.ip6_dst));
			break;
		case ICMP6_UNREACH_ADDRESS:
			(void)printf("address %s unreachable",
				     ip6addr_string(dp->icmp6_ip.ip6_dst));
			break;
		case ICMP6_UNREACH_PORT:
			(void)printf("%s port unreachable",
				     ip6addr_string(dp->icmp6_ip.ip6_dst));
			break;
		}
		hasnextip = 1;
		break;
	case ICMP6_PKTTOBIG:
		TCHECK(dp->icmp6_ip.ip6_dst, 16);
		(void)printf("%s needs mtu %d",
			     ip6addr_string(dp->icmp6_ip.ip6_dst),
			     ntohl(dp->icmp6_pmtu));
		hasnextip = 1;
		break;
	case ICMP6_TIMXCEED:
		TCHECK(dp->icmp6_ip.ip6_dst, 16);
		switch (dp->icmp6_code) {
		case ICMP6_TIMXCEED_INTRANS:
			fputs("time exceeded in-transit", stdout);
			break;
		case ICMP6_TIMXCEED_REASS:
			fputs("ipv6 reassembly time exceeded", stdout);
			break;
		}
		hasnextip = 1;
		break;
	case ICMP6_PARAMPROB:
		switch (dp->icmp6_code) {
		case ICMP6_PARAMPROB_HDR:
			(void)printf("bad header at %d",
				     ntohl(dp->icmp6_pptr));
			break;
		case ICMP6_PARAMPROB_NH:
			(void)printf("bad next header at %d",
				     ntohl(dp->icmp6_pptr));
			break;
		case ICMP6_PARAMPROB_OPT:
			(void)printf("bad IPv6 option at %d",
				     ntohl(dp->icmp6_pptr));
			break;
		}
		hasnextip = 1;
		break;
	case ICMP6_OLDREDIRECT:
		TCHECK(dp->icmp6_rdst, 16);
		(void)printf("old-redirect for %s to %s",
			     ip6addr_string(dp->icmp6_rdst),
			     ip6addr_string(dp->icmp6_tgt));
		(void)printf(" [");
		extv6_print(bp1 + 40, length - 40);
		break;
	case ICMP6_ECHO:
		if (vflag) {
			(void)printf("echo request %d/%d",
				     ntohs(dp->icmp6_id),
				     ntohs(dp->icmp6_seq));
		} else
			fputs("echo request", stdout);
		break;
	case ICMP6_ECHOREPLY:
		if (vflag) {
			(void)printf("echo reply %d/%d",
				     ntohs(dp->icmp6_id),
				     ntohs(dp->icmp6_seq));
		} else
			fputs("echo reply", stdout);
		break;
	case ICMP6_GROUPMEM_QUERY:
		TCHECK(dp->icmp6_grp, 16);
		(void)printf("group membership %s query",
			     ip6addr_string(dp->icmp6_grp));
		break;
	case ICMP6_GROUPMEM_REPORT:
		TCHECK(dp->icmp6_grp, 16);
		(void)printf("group membership %s report",
			     ip6addr_string(dp->icmp6_grp));
		break;
	case ICMP6_GROUPMEM_TERM:
		TCHECK(dp->icmp6_grp, 16);
		(void)printf("group membership %s termination",
			     ip6addr_string(dp->icmp6_grp));
		break;
	case ICMP6_SOLICITATION_RT:
		(void)printf("router solicitation");
		(void)printf(" [");
		extv6_print(bp1 + ICMP6_MINLEN, length - ICMP6_MINLEN);
		break;
	case ICMP6_ADVERTISMENT_RT:
		TCHECK(dp->icmp6_retrans, 4);
		(void)printf("router advertisment");
		(void)printf(" maxhlim=%d %s%s life=%d",
			     dp->icmp6_mhlim,
			     dp->icmp6_aflg & 0x80 ? "M" : "",
			     dp->icmp6_aflg & 0x40 ? "O" : "",
			     ntohs(dp->icmp6_life));
		(void)printf(" nud=%g/%g",
			     ntohl(dp->icmp6_reach) / 1000.0,
			     ntohl(dp->icmp6_retrans) / 1000.0);
		(void)printf(" [");
		extv6_print(bp1 + 16, length - 16);
		break;
	case ICMP6_SOLICITATION_ND:
		TCHECK(dp->icmp6_tgt, 16);
		if ((length >= 40) && (bp1[24] != 1))
			goto old;
		(void)printf("neighbor solicitation for %s",
			     ip6addr_string(dp->icmp6_tgt));
		(void)printf(" [");
		extv6_print(bp1 + 24, length - 24);
		break;
	old:
		(void)printf("old neighbor solicitation for %s from %s",
			     ip6addr_string(dp->icmp6_rdst),
			     ip6addr_string(dp->icmp6_tgt));
		(void)printf(" [");
		extv6_print(bp1 + 40, length - 40);
		break;
	case ICMP6_ADVERTISMENT_ND:
		TCHECK(dp->icmp6_tgt, 16);
		(void)printf("neighbor advertisment for %s%s%s%s%s",
			     ntohl(dp->icmp6_pptr) & 0x80000000 ? "R" : "",
			     ntohl(dp->icmp6_pptr) & 0x40000000 ? "S" : "",
			     ntohl(dp->icmp6_pptr) & 0x20000000 ? "N" : "",
			     ntohl(dp->icmp6_pptr) ? "/" : "",
			     ip6addr_string(dp->icmp6_tgt));
		(void)printf(" [");
		extv6_print(bp1 + 24, length - 24);
		break;
	case ICMP6_REDIRECT:
		TCHECK(dp->icmp6_rdst, 16);
		(void)printf("redirect for %s to %s",
			     ip6addr_string(dp->icmp6_rdst),
			     ip6addr_string(dp->icmp6_tgt));
		(void)printf(" [");
		extv6_print(bp1 + 40, length - 40);
		break;
	default:
		(void)printf("type-#%d", dp->icmp6_type);
	}
	if (hasnextip) {
		if (vflag == 1) {
			TCHECK(dp->icmp6_ip.ip6_nh, 1);
			printf(" (nh=%d)", dp->icmp6_ip.ip6_nh);
		} else if (vflag > 1) {
			int ovflag = vflag;

			printf(" : ");
			vflag = 0;
			ipv6_print((u_char *)&dp->icmp6_ip,
			    ntohs(dp->icmp6_ip.ip6_len) + sizeof(struct ipv6));
			vflag = ovflag;
		}
	}
	return;
trunc:
	fputs("[|icmp]", stdout);
#undef TCHECK
}

/*
 * print an IPv6 option extension.
 */
void
extv6_print(const u_char *bp, int length)
{
	register const struct ndx6_any *ap;
	register const u_char *next;
	int len;

	if (length == 0) {
		printf("]");
		return;
	}
	ap = (struct ndx6_any *)bp;
	next = bp + sizeof(struct ndx6_any);
	if (next > snapend) {
		printf("|]");
		return;
	}
	len = ap->x6any_len << 3;
	next = bp + len;
	if ((ap->x6any_ext != NDX6_RDRT_HDR) && (next > snapend)) {
		printf("|]");
		return;
	}
	if (len > length) {
		(void)printf(" trunc]");
		return;
	}

	switch (ap->x6any_ext) {
	    next:
		length -= len;
		if (length > 0)
			printf(" ");
		extv6_print(next, length);
		return;

	    case NDX6_LLADDR_SRC:
	    case NDX6_LLADDR_TGT: {
		register const struct ndx6_lladdr *xp;

		xp = (struct ndx6_lladdr *)bp;
		printf("(%s LLaddr %s)",
		       xp->lla_ext == NDX6_LLADDR_SRC ? "src" : "tgt",
			etheraddr_string(xp->lla_addr));
		}
		goto next;

	    case NDX6_PREF_INFO: {
		register const struct ndx6_pref *xp;

		xp = (struct ndx6_pref *)bp;
		printf("(Prefix-Information %s%s (%d/%d) %s/%d)",
		       xp->pref_flg & NDX6_PREF_FLG_L ? "L" : "",
		       xp->pref_flg & NDX6_PREF_FLG_A ? "A" : "",
		       ntohl(xp->pref_ilife),
		       ntohl(xp->pref_dlife),
		       ip6addr_string(xp->pref_pref),
		       xp->pref_plen);
		}
		goto next;

	    case NDX6_RDRT_HDR: {
		register const struct ipv6 *ip;

		ip = (struct ipv6 *)(bp + sizeof(struct ndx6_any));
		if ((u_char *)(ip + 1) > snapend) {
			printf("|]");
			return;
		}
		printf("(Redirected-Header %d %s -> %s)]",
		       htons(ip->ip6_len),
		       ip6addr_string(ip->ip6_src),
		       ip6addr_string(ip->ip6_dst));
		return;
	    }

	    case NDX6_MTU: {
		register const struct ndx6_mtu *xp;

		xp = (struct ndx6_mtu *)bp;
		printf("(MTU %d)", ntohl(xp->mtu_mtu));
		}
		goto next;

	    /* No more/not yet correct */
	    case NDX6_JUMBO: {
		register struct ndx6_jbo *xp;

		xp = (struct ndx6_jbo *)bp;
		printf("(Jumbo-Payload %ud)", ntohl(xp->jbo_plen));
		}
		goto next;
	}
}
