1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27
28#pragma ident	"%Z%%M%	%I%	%E% SMI"
29
30#include <stdio.h>
31#include <string.h>
32#include <fcntl.h>
33#include <string.h>
34#include <sys/types.h>
35#include <sys/time.h>
36
37#include <sys/socket.h>
38#include <net/if.h>
39#include <netinet/in_systm.h>
40#include <netinet/in.h>
41#include <netinet/ip.h>
42#include <netinet/if_ether.h>
43#include <netinet/tcp.h>
44#include "snoop.h"
45
46extern char *dlc_header;
47
48#define	TCPOPT_HEADER_LEN	2
49#define	TCPOPT_TSTAMP_LEN	10
50#define	TCPOPT_SACK_LEN		8
51
52/*
53 * Convert a network byte order 32 bit integer to a host order integer.
54 * ntohl() cannot be used because option values may not be aligned properly.
55 */
56#define	GET_UINT32(opt)	(((uint_t)*((uchar_t *)(opt) + 0) << 24) | \
57	((uint_t)*((uchar_t *)(opt) + 1) << 16) | \
58	((uint_t)*((uchar_t *)(opt) + 2) << 8) | \
59	((uint_t)*((uchar_t *)(opt) + 3)))
60
61static void print_tcpoptions_summary(uchar_t *, int, char *);
62static void print_tcpoptions(uchar_t *, int);
63
64static const struct {
65	unsigned int	tf_flag;
66	const char	*tf_name;
67} tcp_flags[] = {
68	{ TH_SYN, 	"Syn"	},
69	{ TH_FIN, 	"Fin"	},
70	{ TH_RST, 	"Rst"	},
71	{ TH_PUSH,	"Push"	},
72	{ TH_ECE,	"ECE"	},
73	{ TH_CWR,	"CWR"	},
74	{ 0,		NULL	}
75};
76
77int
78interpret_tcp(int flags, struct tcphdr *tcp, int iplen, int fraglen)
79{
80	char *data;
81	int hdrlen, tcplen;
82	int sunrpc = 0;
83	char *pname;
84	char buff[32];
85	char *line, *endline;
86	unsigned int i;
87
88	hdrlen = tcp->th_off * 4;
89	data = (char *)tcp + hdrlen;
90	tcplen = iplen - hdrlen;
91	fraglen -= hdrlen;
92	if (fraglen < 0)
93		return (fraglen + hdrlen);	/* incomplete header */
94	if (fraglen > tcplen)
95		fraglen = tcplen;
96
97	if (flags & F_SUM) {
98		line = get_sum_line();
99		endline = line + MAXLINE;
100		(void) snprintf(line, endline - line, "TCP D=%d S=%d",
101		    ntohs(tcp->th_dport), ntohs(tcp->th_sport));
102		line += strlen(line);
103
104		for (i = 0; tcp_flags[i].tf_name != NULL; i++) {
105			if (tcp->th_flags & tcp_flags[i].tf_flag) {
106				(void) snprintf(line, endline - line, " %s",
107				    tcp_flags[i].tf_name);
108				line += strlen(line);
109			}
110		}
111
112		if (tcp->th_flags & TH_URG) {
113			(void) snprintf(line, endline - line, " Urg=%u",
114			    ntohs(tcp->th_urp));
115			line += strlen(line);
116		}
117		if (tcp->th_flags & TH_ACK) {
118			(void) snprintf(line, endline - line, " Ack=%u",
119				ntohl(tcp->th_ack));
120			line += strlen(line);
121		}
122		if (ntohl(tcp->th_seq)) {
123			(void) snprintf(line, endline - line, " Seq=%u Len=%d",
124				ntohl(tcp->th_seq), tcplen);
125			line += strlen(line);
126		}
127		(void) snprintf(line, endline - line, " Win=%d",
128		    ntohs(tcp->th_win));
129		print_tcpoptions_summary((uchar_t *)(tcp + 1),
130		    (int)(tcp->th_off * 4 - sizeof (struct tcphdr)), line);
131	}
132
133	sunrpc = !reservedport(IPPROTO_TCP, ntohs(tcp->th_dport)) &&
134		!reservedport(IPPROTO_TCP, ntohs(tcp->th_sport)) &&
135		valid_rpc(data + 4, fraglen - 4);
136
137	if (flags & F_DTAIL) {
138
139	show_header("TCP:  ", "TCP Header", tcplen);
140	show_space();
141	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_sport -
142		dlc_header, 2),	"Source port = %d", ntohs(tcp->th_sport));
143
144	if (sunrpc) {
145		pname = "(Sun RPC)";
146	} else {
147		pname = getportname(IPPROTO_TCP, ntohs(tcp->th_dport));
148		if (pname == NULL) {
149			pname = "";
150		} else {
151			(void) sprintf(buff, "(%s)", pname);
152			pname = buff;
153		}
154	}
155	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_dport -
156		dlc_header, 2), "Destination port = %d %s",
157		ntohs(tcp->th_dport), pname);
158	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_seq -
159		dlc_header, 4),	"Sequence number = %u",
160		ntohl(tcp->th_seq));
161	(void) sprintf(get_line((char *)(uintptr_t)tcp->th_ack - dlc_header, 4),
162		"Acknowledgement number = %u",
163		ntohl(tcp->th_ack));
164	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_ack - dlc_header) +
165		4, 1), "Data offset = %d bytes", tcp->th_off * 4);
166	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
167		dlc_header) + 4, 1), "Flags = 0x%02x", tcp->th_flags);
168	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
169		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_CWR,
170		"ECN congestion window reduced",
171		"No ECN congestion window reduced"));
172	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
173		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_ECE,
174		"ECN echo", "No ECN echo"));
175	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
176		dlc_header) + 4, 1), "      %s",
177		getflag(tcp->th_flags, TH_URG,
178		"Urgent pointer", "No urgent pointer"));
179	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
180		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_ACK,
181		"Acknowledgement", "No acknowledgement"));
182	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
183		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_PUSH,
184		"Push", "No push"));
185	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
186		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_RST,
187		"Reset", "No reset"));
188	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
189		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_SYN,
190		"Syn", "No Syn"));
191	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
192		dlc_header) + 4, 1), "      %s", getflag(tcp->th_flags, TH_FIN,
193		"Fin", "No Fin"));
194	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_win - dlc_header) +
195		4, 1), "Window = %d", ntohs(tcp->th_win));
196	/* XXX need to compute checksum and print whether correct */
197	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_sum - dlc_header) +
198		4, 1), "Checksum = 0x%04x", ntohs(tcp->th_sum));
199	(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_urp - dlc_header) +
200		4, 1), "Urgent pointer = %d", ntohs(tcp->th_urp));
201
202	/* Print TCP options - if any */
203
204	print_tcpoptions((uchar_t *)(tcp + 1),
205	    tcp->th_off * 4 - sizeof (struct tcphdr));
206
207	show_space();
208	}
209
210	/* go to the next protocol layer */
211
212	if (!interpret_reserved(flags, IPPROTO_TCP,
213		ntohs(tcp->th_sport),
214		ntohs(tcp->th_dport),
215		data, fraglen)) {
216		if (sunrpc && fraglen > 0)
217			interpret_rpc(flags, data, fraglen, IPPROTO_TCP);
218	}
219
220	return (tcplen);
221}
222
223static void
224print_tcpoptions(opt, optlen)
225	uchar_t *opt;
226	int optlen;
227{
228	int	 len;
229	char	 *line;
230	uchar_t	*sack_opt;
231	uchar_t	*end_opt;
232	int	sack_len;
233
234	if (optlen <= 0) {
235		(void) sprintf(get_line((char *)&opt - dlc_header, 1),
236		"No options");
237		return;
238	}
239
240	(void) sprintf(get_line((char *)&opt - dlc_header, 1),
241	"Options: (%d bytes)", optlen);
242
243	while (optlen > 0) {
244		line = get_line((char *)&opt - dlc_header, 1);
245		len = opt[1];
246		switch (opt[0]) {
247		case TCPOPT_EOL:
248			(void) strcpy(line, "  - End of option list");
249			return;
250		case TCPOPT_NOP:
251			(void) strcpy(line, "  - No operation");
252			len = 1;
253			break;
254		case TCPOPT_MAXSEG:
255			(void) sprintf(line,
256			"  - Maximum segment size = %d bytes",
257				(opt[2] << 8) + opt[3]);
258			break;
259		case TCPOPT_WSCALE:
260			(void) sprintf(line, "  - Window scale = %d", opt[2]);
261			break;
262		case TCPOPT_TSTAMP:
263			/* Sanity check. */
264			if (optlen < TCPOPT_TSTAMP_LEN) {
265				(void) sprintf(line,
266				    "  - Incomplete TS option");
267			} else {
268				(void) sprintf(line,
269				    "  - TS Val = %u, TS Echo = %u",
270				    GET_UINT32(opt + 2),
271				    GET_UINT32(opt + 6));
272			}
273			break;
274		case TCPOPT_SACK_PERMITTED:
275			(void) sprintf(line, "  - SACK permitted option");
276			break;
277		case TCPOPT_SACK:
278			/*
279			 * Sanity check.  Total length should be greater
280			 * than just the option header length.
281			 */
282			if (len <= TCPOPT_HEADER_LEN ||
283			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
284				(void) sprintf(line,
285				    "  - Incomplete SACK option");
286				break;
287			}
288			sack_len = opt[1] - TCPOPT_HEADER_LEN;
289			sack_opt = opt + TCPOPT_HEADER_LEN;
290			end_opt = opt + optlen;
291
292			(void) sprintf(line, "  - SACK blocks:");
293			line = get_line((char *)&opt - dlc_header, 1);
294			(void) sprintf(line, "        ");
295			while (sack_len > 0) {
296				char sack_blk[MAXLINE + 1];
297
298				/*
299				 * sack_len may not tell us the truth about
300				 * the real length...  Need to be careful
301				 * not to step beyond the option buffer.
302				 */
303				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
304					(void) strcat(line,
305					    "...incomplete SACK block");
306					break;
307				}
308				(void) sprintf(sack_blk, "(%u-%u) ",
309				    GET_UINT32(sack_opt),
310				    GET_UINT32(sack_opt + 4));
311				(void) strcat(line, sack_blk);
312				sack_opt += TCPOPT_SACK_LEN;
313				sack_len -= TCPOPT_SACK_LEN;
314			}
315			break;
316		default:
317			(void) sprintf(line,
318			"  - Option %d (unknown - %d bytes) %s",
319				opt[0],
320				len - 2,
321				tohex((char *)&opt[2], len - 2));
322			break;
323		}
324		if (len <= 0) {
325			(void) sprintf(line, "  - Incomplete option len %d",
326				len);
327			break;
328		}
329		opt += len;
330		optlen -= len;
331	}
332}
333
334/*
335 * This function is basically the same as print_tcpoptions() except that
336 * all options are printed on the same line.
337 */
338static void
339print_tcpoptions_summary(uchar_t *opt, int optlen, char *line)
340{
341	int	 len;
342	uchar_t	*sack_opt;
343	uchar_t	*end_opt;
344	int	sack_len;
345	char	options[MAXLINE + 1];
346
347	if (optlen <= 0) {
348		return;
349	}
350
351	(void) strcat(line, " Options=<");
352	while (optlen > 0) {
353		len = opt[1];
354		switch (opt[0]) {
355		case TCPOPT_EOL:
356			(void) strcat(line, "eol>");
357			return;
358		case TCPOPT_NOP:
359			(void) strcat(line, "nop");
360			len = 1;
361			break;
362		case TCPOPT_MAXSEG:
363			(void) sprintf(options, "mss %d",
364			    (opt[2] << 8) + opt[3]);
365			(void) strcat(line, options);
366			break;
367		case TCPOPT_WSCALE:
368			(void) sprintf(options, "wscale %d", opt[2]);
369			(void) strcat(line, options);
370			break;
371		case TCPOPT_TSTAMP:
372			/* Sanity check. */
373			if (optlen < TCPOPT_TSTAMP_LEN) {
374				(void) strcat(line, "tstamp|");
375			} else {
376				(void) sprintf(options,
377				    "tstamp %u %u", GET_UINT32(opt + 2),
378				    GET_UINT32(opt + 6));
379				(void) strcat(line, options);
380			}
381			break;
382		case TCPOPT_SACK_PERMITTED:
383			(void) strcat(line, "sackOK");
384			break;
385		case TCPOPT_SACK:
386			/*
387			 * Sanity check.  Total length should be greater
388			 * than just the option header length.
389			 */
390			if (len <= TCPOPT_HEADER_LEN ||
391			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
392				(void) strcat(line, "sack|");
393				break;
394			}
395			sack_len = opt[1] - TCPOPT_HEADER_LEN;
396			sack_opt = opt + TCPOPT_HEADER_LEN;
397			end_opt = opt + optlen;
398
399			(void) strcat(line, "sack");
400			while (sack_len > 0) {
401				/*
402				 * sack_len may not tell us the truth about
403				 * the real length...  Need to be careful
404				 * not to step beyond the option buffer.
405				 */
406				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
407					(void) strcat(line, "|");
408					break;
409				}
410				(void) sprintf(options, " %u-%u",
411				    GET_UINT32(sack_opt),
412				    GET_UINT32(sack_opt + 4));
413				(void) strcat(line, options);
414				sack_opt += TCPOPT_SACK_LEN;
415				sack_len -= TCPOPT_SACK_LEN;
416			}
417			break;
418		default:
419			(void) sprintf(options, "unknown %d", opt[0]);
420			(void) strcat(line, options);
421			break;
422		}
423		if (len <= 0) {
424			(void) sprintf(options, "optlen %d", len);
425			(void) strcat(line, options);
426			break;
427		}
428		opt += len;
429		optlen -= len;
430		if (optlen > 0) {
431			(void) strcat(line, ",");
432		}
433	}
434	(void) strcat(line, ">");
435}
436