/* Print IPv6 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"

static void ipv6_hop_print(const u_char *, int, const u_char *);
static void ipv6_rt_print(const u_char *, int, const u_char *);
static void ipv6_frag_print(const u_char *, int, const u_char *);
static void ipv6_auth_print(const u_char *, int, const u_char *);
static void ipv6_esp_print(const u_char *, int, const u_char *);
static void ipv6_dopt_print(const u_char *, int, const u_char *);
static void ipv6_end_print(const u_char *, int, const u_char *);

/*
 * print an IPv6 hop-by-hop header.
 */
static void
ipv6_hop_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	register int len;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (&bp[7] > snapend) {
		printf("[|ipv6]");
		return;
	}
	len = (bp[1] << 3) + 8;
	if (bp + len > snapend) {
		printf("[|ipv6]");
		return;
	}
	bp1 += len;
	length -= len;

	switch (bp[0]) {
	    case IP6_NHDR_HOP:
		ipv6_hop_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_RT:
		ipv6_rt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_FRAG:
		ipv6_frag_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_AUTH:
		ipv6_auth_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_ESP:
		ipv6_esp_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_DOPT:
		ipv6_dopt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_NONH:
		ipv6_end_print(bp1, length, bp2);
		break;
	    case IPPROTO_TCP:
		tcp_print(bp1, length, bp2);
		break;
	    case IPPROTO_UDP:
		udp_print(bp1, length, bp2);
		break;
	    case IPPROTO_ICMPV6:
		icmpv6_print(bp1, length, bp2);
		break;
	    case IPPROTO_IPV6:
		if (vflag)
			(void)printf("%s > %s: ",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
		ipv6_print(bp2, length);
		if (!vflag) {
			printf(" (encap)");
			return;
		}
		break;
	    default:
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" ipv6-nexthdr-%d %d", bp[0], length);
	}

	if (vflag) {
		/* do more ! */
		(void)printf(" (hop-by-hop %d)", len);
	}
}

/*
 * print an IPv6 routing header.
 */
static void
ipv6_rt_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	register int type, len;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (&bp[7] > snapend) {
		printf("[|ipv6]");
		return;
	}
	len = (bp[1] << 3) + 8;
	type = bp[2];
	if (type != 0) {
		printf(" ipv6-route-%d?", type);
		return;
	}
	if (bp + len > snapend) {
		printf("[|ipv6]");
		return;
	}
	bp1 += len;
	length -= len;

	switch (bp[0]) {
	    case IP6_NHDR_HOP:
		ipv6_hop_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_RT:
		ipv6_rt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_FRAG:
		ipv6_frag_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_AUTH:
		ipv6_auth_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_ESP:
		ipv6_esp_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_DOPT:
		ipv6_dopt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_NONH:
		ipv6_end_print(bp1, length, bp2);
		break;
	    case IPPROTO_TCP:
		tcp_print(bp1, length, bp2);
		break;
	    case IPPROTO_UDP:
		udp_print(bp1, length, bp2);
		break;
	    case IPPROTO_ICMPV6:
		icmpv6_print(bp1, length, bp2);
		break;
	    case IPPROTO_IPV6:
		if (vflag)
			(void)printf("%s > %s: ",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
		ipv6_print(bp2, length);
		if (!vflag) {
			printf(" (encap)");
			return;
		}
		break;
	    default:
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" ipv6-nexthdr-%d %d", bp[0], length);
	}

	if (vflag) {
		/* do more ! */
		(void)printf(" (src-route %d)", len);
	}
}

/*
 * print an IPv6 fragment header.
 */
static void
ipv6_frag_print(const u_char *bp1, int length, const u_char *bp2)
{
	register int off;
	register struct ipv6_frag *frg = (struct ipv6_frag *)bp1;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (bp1 + 8 > snapend) {
		printf("[|ipv6]");
		return;
	}
	off = htons(frg->ip6f_off);
	bp1 += sizeof(struct ipv6_frag);
	length -= sizeof(struct ipv6_frag);

	if ((off & 0xfff8) == 0) {
		switch (frg->ip6f_nh) {
		    case IP6_NHDR_HOP:
			ipv6_hop_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_RT:
			ipv6_rt_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_FRAG:
			ipv6_frag_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_AUTH:
			ipv6_auth_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_ESP:
			ipv6_esp_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_DOPT:
			ipv6_dopt_print(bp1, length, bp2);
			break;
		    case IP6_NHDR_NONH:
			ipv6_end_print(bp1, length, bp2);
			break;
		    case IPPROTO_TCP:
			tcp_print(bp1, length, bp2);
			break;
		    case IPPROTO_UDP:
			udp_print(bp1, length, bp2);
			break;
		    case IPPROTO_ICMPV6:
			icmpv6_print(bp1, length, bp2);
			break;
		    case IPPROTO_IPV6:
			if (vflag)
				(void)printf("%s > %s: ",
					     ip6addr_string(ip->ip6_src),
					     ip6addr_string(ip->ip6_dst));
			ipv6_print(bp2, length);
			if (!vflag) {
				printf(" (encap)");
				return;
			}
			break;
		    default:
			(void)printf("%s > %s:",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
			(void)printf(" ipv6-nexthdr-%d %d",
				     frg->ip6f_nh, length);
		}
		(void)printf(" (frag %d:%d@%d%s)",
			     ntohl(frg->ip6f_id),
			     ntohs(ip->ip6_len),
			     off & 0xfff8,
			     (off & 1) ? "+" : "");
	} else {
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" (frag %d:%d@%d%s)",
			     ntohl(frg->ip6f_id),
			     ntohs(ip->ip6_len),
			     off & 0xfff8,
			     (off & 1) ? "+" : "");
		/* do something with frg->ip6f_nh ? */
	}
}

/*
 * print an IPv6 authentication header.
 */
static void
ipv6_auth_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	register int len;
	int said;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (&bp[7] > snapend) {
		printf("[|ipv6]");
		return;
	}
	len = (bp[1] << 3) + 8;
	if (bp + len > snapend) {
		printf("[|ipv6]");
		return;
	}
	bcopy((char *)&bp[4], (char *)&said, 4);
	said = htonl(said);
	bp1 += len;
	length -= len;

	switch (bp[0]) {
	    case IP6_NHDR_HOP:
		ipv6_hop_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_RT:
		ipv6_rt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_FRAG:
		ipv6_frag_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_AUTH:
		ipv6_auth_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_ESP:
		ipv6_esp_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_DOPT:
		ipv6_dopt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_NONH:
		ipv6_end_print(bp1, length, bp2);
		break;
	    case IPPROTO_TCP:
		tcp_print(bp1, length, bp2);
		break;
	    case IPPROTO_UDP:
		udp_print(bp1, length, bp2);
		break;
	    case IPPROTO_ICMPV6:
		icmpv6_print(bp1, length, bp2);
		break;
	    case IPPROTO_IPV6:
		if (vflag)
			(void)printf("%s > %s: ",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
		ipv6_print(bp2, length);
		if (!vflag) {
			printf(" (encap)");
			return;
		}
		break;
	    default:
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" ipv6-nexthdr-%d %d", bp[0], length);
	}

	if (vflag) {
		/* do more ! */
		(void)printf(" (auth-%x %d)", said, len);
	}
}

/*
 * print an IPv6 encapsulating security payload header.
 */
static void
ipv6_esp_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	int said;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (&bp[7] > snapend) {
		printf("[|ipv6]");
		return;
	}
	bcopy((char *)&bp[0], (char *)&said, 4);
	said = htonl(said);

	(void)printf("%s > %s:",
		     ip6addr_string(ip->ip6_src),
		     ip6addr_string(ip->ip6_dst));

	if (vflag) {
		/* do more ! */
		(void)printf(" (esp-%x %d)", said, length);
	}
}

/*
 * print an IPv6 destination options header.
 */
static void
ipv6_dopt_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	register int len;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	if (&bp[7] > snapend) {
		printf("[|ipv6]");
		return;
	}
	len = (bp[1] << 3) + 8;
	if (bp + len > snapend) {
		printf("[|ipv6]");
		return;
	}
	bp1 += len;
	length -= len;

	switch (bp[0]) {
	    case IP6_NHDR_HOP:
		ipv6_hop_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_RT:
		ipv6_rt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_FRAG:
		ipv6_frag_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_AUTH:
		ipv6_auth_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_ESP:
		ipv6_esp_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_DOPT:
		ipv6_dopt_print(bp1, length, bp2);
		break;
	    case IP6_NHDR_NONH:
		ipv6_end_print(bp1, length, bp2);
		break;
	    case IPPROTO_TCP:
		tcp_print(bp1, length, bp2);
		break;
	    case IPPROTO_UDP:
		udp_print(bp1, length, bp2);
		break;
	    case IPPROTO_ICMPV6:
		icmpv6_print(bp1, length, bp2);
		break;
	    case IPPROTO_IPV6:
		if (vflag)
			(void)printf("%s > %s: ",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
		ipv6_print(bp2, length);
		if (!vflag) {
			printf(" (encap)");
			return;
		}
		break;
	    default:
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" ipv6-nexthdr-%d %d", bp[0], length);
	}

	if (vflag) {
		/* do more ! */
		(void)printf(" (dest-opts %d)", len);
	}
}

/*
 * print an IPv6 no next header.
 */
static void
ipv6_end_print(const u_char *bp1, int length, const u_char *bp2)
{
	register const u_char *bp = bp1;
	int said;
	const struct ipv6 *ip = (struct ipv6 *)bp2;

	(void)printf("%s > %s:",
		     ip6addr_string(ip->ip6_src),
		     ip6addr_string(ip->ip6_dst));

	if (vflag) {
		/* do more ! */
		(void)printf(" (end %d)", length);
	}
}

/*
 * print an IPv6 datagram.
 */
void
ipv6_print(const u_char *bp, int length)
{
	register const struct ipv6 *ip;
	register int len;

	ip = (const struct ipv6 *)bp;
#ifdef TCPDUMP_ALIGN
	/*
	 * The IPv6 datagram is not word aligned, so copy into abuf!
	 * This will never happen with BPF.  It does happen raw packet
	 * dumps from -r.
	 * note: nobody frees abuf, replace C by Lisp ?
	 */
	if ((int)ip & (sizeof(long)-1)) {
		static u_char *abuf;
		int caplen;

		caplen = snapend - bp;
		if (abuf == 0)
			abuf = (u_char *)malloc(snaplen);
		if (caplen > snaplen)
			error("internal error buffer len %d < %d\n",
			      snaplen, caplen);
		bcopy((char *)ip, (char *)abuf, min(length, caplen));
		snapend += abuf - (u_char *)ip;
		packetp = abuf;
		ip = (const struct ipv6 *)abuf;
	}
#endif
	if ((u_char *)(ip + 1) > snapend) {
		printf("[|ipv6]");
		return;
	}
	if (length < sizeof (struct ipv6)) {
		(void)printf("truncated-ipv6 %d", length);
		return;
	}

	len = ntohs(ip->ip6_len);
	if (length < len + sizeof(struct ipv6))
		(void)printf("truncated-ipv6 - %d bytes missing!",
			     len + sizeof(struct ipv6) - length);
	bp = (const u_char *)ip + sizeof(struct ipv6);

	switch (ip->ip6_nh) {
	    case IP6_NHDR_HOP:
		ipv6_hop_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_RT:
		ipv6_rt_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_FRAG:
		ipv6_frag_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_AUTH:
		ipv6_auth_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_ESP:
		ipv6_esp_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_DOPT:
		ipv6_dopt_print(bp, len, (const u_char *)ip);
		break;
	    case IP6_NHDR_NONH:
		ipv6_end_print(bp, len, (const u_char *)ip);
		break;
	    case IPPROTO_TCP:
		tcp_print(bp, len, (const u_char *)ip);
		break;
	    case IPPROTO_UDP:
		udp_print(bp, len, (const u_char *)ip);
		break;
	    case IPPROTO_ICMPV6:
		icmpv6_print(bp, len, (const u_char *)ip);
		break;
	    case IPPROTO_IPV6:
		if (vflag)
			(void)printf("%s > %s: ",
				     ip6addr_string(ip->ip6_src),
				     ip6addr_string(ip->ip6_dst));
		ipv6_print(bp, len);
		if (!vflag) {
			printf(" (encap)");
			return;
		}
		break;
	    default:
		(void)printf("%s > %s:",
			     ip6addr_string(ip->ip6_src),
			     ip6addr_string(ip->ip6_dst));
		(void)printf(" ipv6-nexthdr-%d %d", ip->ip6_nh, len);
	}

	if ((ntohl(ip->ip6_head) & 0x0f000000) != 0)
		(void)printf(" [pri %d]",
			     (ntohl(ip->ip6_head) & 0x0f000000) >> 24);
	if ((ntohl(ip->ip6_head) & 0x00ffffff) != 0)
		(void)printf(" [flow %d]",
			     ntohl(ip->ip6_head) & 0x00ffffff);

	if (ip->ip6_hlim <= 1)
		(void)printf(" [ttl %d]", (int)ip->ip6_hlim);
	else if (vflag)
		(void)printf(" (ttl %d)", (int)ip->ip6_hlim);
}
