/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>

#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include "snoop.h"

extern char *dlc_header;

#define	TCPOPT_HEADER_LEN	2
#define	TCPOPT_TSTAMP_LEN	10
#define	TCPOPT_SACK_LEN		8

/*
 * Convert a network byte order 32 bit integer to a host order integer.
 * ntohl() cannot be used because option values may not be aligned properly.
 */
#define	GET_UINT32(opt)	(((uint_t)*((uchar_t *)(opt) + 0) << 24) | \
	((uint_t)*((uchar_t *)(opt) + 1) << 16) | \
	((uint_t)*((uchar_t *)(opt) + 2) << 8) | \
	((uint_t)*((uchar_t *)(opt) + 3)))

static void print_tcpoptions_summary(uchar_t *, int, char *);
static void print_tcpoptions(uchar_t *, int);

static const struct {
	unsigned int	tf_flag;
	const char	*tf_name;
} tcp_flags[] = {
	{ TH_SYN, 	"Syn"	},
	{ TH_FIN, 	"Fin"	},
	{ TH_RST, 	"Rst"	},
	{ TH_PUSH,	"Push"	},
	{ TH_ECE,	"ECE"	},
	{ TH_CWR,	"CWR"	},
	{ 0,		NULL	}
};

int
interpret_tcp(int flags, struct tcphdr *tcp, int iplen, int fraglen)
{
	char *data;
	int hdrlen, tcplen;
	int sunrpc = 0;
	char *pname;
	char buff[32];
	char *line, *endline;
	unsigned int i;

	hdrlen = tcp->th_off * 4;
	data = (char *)tcp + hdrlen;
	tcplen = iplen - hdrlen;
	fraglen -= hdrlen;
	if (fraglen < 0)
		return (fraglen + hdrlen);	/* incomplete header */
	if (fraglen > tcplen)
		fraglen = tcplen;

	if (flags & F_SUM) {
		line = get_sum_line();
		endline = line + MAXLINE;
		(void) snprintf(line, endline - line, "TCP D=%d S=%d",
		    ntohs(tcp->th_dport), ntohs(tcp->th_sport));
		line += strlen(line);

		for (i = 0; tcp_flags[i].tf_name != NULL; i++) {
			if (tcp->th_flags & tcp_flags[i].tf_flag) {
				(void) snprintf(line, endline - line, " %s",
				    tcp_flags[i].tf_name);
				line += strlen(line);
			}
		}

		if (tcp->th_flags & TH_URG) {
			(void) snprintf(line, endline - line, " Urg=%u",
			    ntohs(tcp->th_urp));
			line += strlen(line);
		}
		if (tcp->th_flags & TH_ACK) {
			(void) snprintf(line, endline - line, " Ack=%u",
				ntohl(tcp->th_ack));
			line += strlen(line);
		}
		if (ntohl(tcp->th_seq)) {
			(void) snprintf(line, endline - line, " Seq=%u Len=%d",
				ntohl(tcp->th_seq), tcplen);
			line += strlen(line);
		}
		(void) snprintf(line, endline - line, " Win=%d",
		    ntohs(tcp->th_win));
		print_tcpoptions_summary((uchar_t *)(tcp + 1),
		    (int)(tcp->th_off * 4 - sizeof (struct tcphdr)), line);
	}

	sunrpc = !reservedport(IPPROTO_TCP, ntohs(tcp->th_dport)) &&
		!reservedport(IPPROTO_TCP, ntohs(tcp->th_sport)) &&
		valid_rpc(data + 4, fraglen - 4);

	if (flags & F_DTAIL) {

	show_header("TCP:  ", "TCP Header", tcplen);
	show_space();
	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_sport -
		dlc_header, 2),	"Source port = %d", ntohs(tcp->th_sport));

	if (sunrpc) {
		pname = "(Sun RPC)";
	} else {
		pname = getportname(IPPROTO_TCP, ntohs(tcp->th_dport));
		if (pname == NULL) {
			pname = "";
		} else {
			(void) sprintf(buff, "(%s)", pname);
			pname = buff;
		}
	}
	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_dport -
		dlc_header, 2), "Destination port = %d %s",
		ntohs(tcp->th_dport), pname);
	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_seq -
		dlc_header, 4),	"Sequence number = %u",
		ntohl(tcp->th_seq));
	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_ack - dlc_header, 4),
		"Acknowledgement number = %u",
		ntohl(tcp->th_ack));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_ack - dlc_header) +
		4, 1), "Data offset = %d bytes", tcp->th_off * 4);
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "Flags = 0x%02x", tcp->th_flags);
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_CWR,
		"ECN congestion window reduced",
		"No ECN congestion window reduced"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_ECE,
		"ECN echo", "No ECN echo"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s",
		getflag(tcp->th_flags, TH_URG,
		"Urgent pointer", "No urgent pointer"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_ACK,
		"Acknowledgement", "No acknowledgement"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_PUSH,
		"Push", "No push"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_RST,
		"Reset", "No reset"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_SYN,
		"Syn", "No Syn"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_FIN,
		"Fin", "No Fin"));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_win - dlc_header) +
		4, 1), "Window = %d", ntohs(tcp->th_win));
	/* XXX need to compute checksum and print whether correct */
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_sum - dlc_header) +
		4, 1), "Checksum = 0x%04x", ntohs(tcp->th_sum));
	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_urp - dlc_header) +
		4, 1), "Urgent pointer = %d", ntohs(tcp->th_urp));

	/* Print TCP options - if any */

	print_tcpoptions((uchar_t *)(tcp + 1),
	    tcp->th_off * 4 - sizeof (struct tcphdr));

	show_space();
	}

	/* go to the next protocol layer */

	if (!interpret_reserved(flags, IPPROTO_TCP,
		ntohs(tcp->th_sport),
		ntohs(tcp->th_dport),
		data, fraglen)) {
		if (sunrpc && fraglen > 0)
			interpret_rpc(flags, data, fraglen, IPPROTO_TCP);
	}

	return (tcplen);
}

static void
print_tcpoptions(opt, optlen)
	uchar_t *opt;
	int optlen;
{
	int	 len;
	char	 *line;
	uchar_t	*sack_opt;
	uchar_t	*end_opt;
	int	sack_len;

	if (optlen <= 0) {
		(void) sprintf(get_line((char *)&opt - dlc_header, 1),
		"No options");
		return;
	}

	(void) sprintf(get_line((char *)&opt - dlc_header, 1),
	"Options: (%d bytes)", optlen);

	while (optlen > 0) {
		line = get_line((char *)&opt - dlc_header, 1);
		len = opt[1];
		switch (opt[0]) {
		case TCPOPT_EOL:
			(void) strcpy(line, "  - End of option list");
			return;
		case TCPOPT_NOP:
			(void) strcpy(line, "  - No operation");
			len = 1;
			break;
		case TCPOPT_MAXSEG:
			(void) sprintf(line,
			"  - Maximum segment size = %d bytes",
				(opt[2] << 8) + opt[3]);
			break;
		case TCPOPT_WSCALE:
			(void) sprintf(line, "  - Window scale = %d", opt[2]);
			break;
		case TCPOPT_TSTAMP:
			/* Sanity check. */
			if (optlen < TCPOPT_TSTAMP_LEN) {
				(void) sprintf(line,
				    "  - Incomplete TS option");
			} else {
				(void) sprintf(line,
				    "  - TS Val = %u, TS Echo = %u",
				    GET_UINT32(opt + 2),
				    GET_UINT32(opt + 6));
			}
			break;
		case TCPOPT_SACK_PERMITTED:
			(void) sprintf(line, "  - SACK permitted option");
			break;
		case TCPOPT_SACK:
			/*
			 * Sanity check.  Total length should be greater
			 * than just the option header length.
			 */
			if (len <= TCPOPT_HEADER_LEN ||
			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
				(void) sprintf(line,
				    "  - Incomplete SACK option");
				break;
			}
			sack_len = opt[1] - TCPOPT_HEADER_LEN;
			sack_opt = opt + TCPOPT_HEADER_LEN;
			end_opt = opt + optlen;

			(void) sprintf(line, "  - SACK blocks:");
			line = get_line((char *)&opt - dlc_header, 1);
			(void) sprintf(line, "        ");
			while (sack_len > 0) {
				char sack_blk[MAXLINE + 1];

				/*
				 * sack_len may not tell us the truth about
				 * the real length...  Need to be careful
				 * not to step beyond the option buffer.
				 */
				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
					(void) strcat(line,
					    "...incomplete SACK block");
					break;
				}
				(void) sprintf(sack_blk, "(%u-%u) ",
				    GET_UINT32(sack_opt),
				    GET_UINT32(sack_opt + 4));
				(void) strcat(line, sack_blk);
				sack_opt += TCPOPT_SACK_LEN;
				sack_len -= TCPOPT_SACK_LEN;
			}
			break;
		default:
			(void) sprintf(line,
			"  - Option %d (unknown - %d bytes) %s",
				opt[0],
				len - 2,
				tohex((char *)&opt[2], len - 2));
			break;
		}
		if (len <= 0) {
			(void) sprintf(line, "  - Incomplete option len %d",
				len);
			break;
		}
		opt += len;
		optlen -= len;
	}
}

/*
 * This function is basically the same as print_tcpoptions() except that
 * all options are printed on the same line.
 */
static void
print_tcpoptions_summary(uchar_t *opt, int optlen, char *line)
{
	int	 len;
	uchar_t	*sack_opt;
	uchar_t	*end_opt;
	int	sack_len;
	char	options[MAXLINE + 1];

	if (optlen <= 0) {
		return;
	}

	(void) strcat(line, " Options=<");
	while (optlen > 0) {
		len = opt[1];
		switch (opt[0]) {
		case TCPOPT_EOL:
			(void) strcat(line, "eol>");
			return;
		case TCPOPT_NOP:
			(void) strcat(line, "nop");
			len = 1;
			break;
		case TCPOPT_MAXSEG:
			(void) sprintf(options, "mss %d",
			    (opt[2] << 8) + opt[3]);
			(void) strcat(line, options);
			break;
		case TCPOPT_WSCALE:
			(void) sprintf(options, "wscale %d", opt[2]);
			(void) strcat(line, options);
			break;
		case TCPOPT_TSTAMP:
			/* Sanity check. */
			if (optlen < TCPOPT_TSTAMP_LEN) {
				(void) strcat(line, "tstamp|");
			} else {
				(void) sprintf(options,
				    "tstamp %u %u", GET_UINT32(opt + 2),
				    GET_UINT32(opt + 6));
				(void) strcat(line, options);
			}
			break;
		case TCPOPT_SACK_PERMITTED:
			(void) strcat(line, "sackOK");
			break;
		case TCPOPT_SACK:
			/*
			 * Sanity check.  Total length should be greater
			 * than just the option header length.
			 */
			if (len <= TCPOPT_HEADER_LEN ||
			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
				(void) strcat(line, "sack|");
				break;
			}
			sack_len = opt[1] - TCPOPT_HEADER_LEN;
			sack_opt = opt + TCPOPT_HEADER_LEN;
			end_opt = opt + optlen;

			(void) strcat(line, "sack");
			while (sack_len > 0) {
				/*
				 * sack_len may not tell us the truth about
				 * the real length...  Need to be careful
				 * not to step beyond the option buffer.
				 */
				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
					(void) strcat(line, "|");
					break;
				}
				(void) sprintf(options, " %u-%u",
				    GET_UINT32(sack_opt),
				    GET_UINT32(sack_opt + 4));
				(void) strcat(line, options);
				sack_opt += TCPOPT_SACK_LEN;
				sack_len -= TCPOPT_SACK_LEN;
			}
			break;
		default:
			(void) sprintf(options, "unknown %d", opt[0]);
			(void) strcat(line, options);
			break;
		}
		if (len <= 0) {
			(void) sprintf(options, "optlen %d", len);
			(void) strcat(line, options);
			break;
		}
		opt += len;
		optlen -= len;
		if (optlen > 0) {
			(void) strcat(line, ",");
		}
	}
	(void) strcat(line, ">");
}