xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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 1991-2001,2003 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 
46 extern 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 
61 static void print_tcpoptions_summary(uchar_t *, int, char *);
62 static void print_tcpoptions(uchar_t *, int);
63 
64 static 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 
77 int
78 interpret_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;		/* 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 *)tcp->th_sport - dlc_header, 2),
142 		"Source port = %d",
143 		ntohs(tcp->th_sport));
144 
145 	if (sunrpc) {
146 		pname = "(Sun RPC)";
147 	} else {
148 		pname = getportname(IPPROTO_TCP, ntohs(tcp->th_dport));
149 		if (pname == NULL) {
150 			pname = "";
151 		} else {
152 			(void) sprintf(buff, "(%s)", pname);
153 			pname = buff;
154 		}
155 	}
156 	(void) sprintf(get_line((char *)tcp->th_dport - dlc_header, 2),
157 		"Destination port = %d %s",
158 		ntohs(tcp->th_dport), pname);
159 	(void) sprintf(get_line((char *)tcp->th_seq - dlc_header, 4),
160 		"Sequence number = %u",
161 		ntohl(tcp->th_seq));
162 	(void) sprintf(get_line((char *)tcp->th_ack - dlc_header, 4),
163 		"Acknowledgement number = %u",
164 		ntohl(tcp->th_ack));
165 	(void) sprintf(get_line(((char *)tcp->th_ack - dlc_header) + 4, 1),
166 		"Data offset = %d bytes",
167 		tcp->th_off * 4);
168 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
169 		"Flags = 0x%02x",
170 		tcp->th_flags);
171 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
172 		"      %s",
173 		getflag(tcp->th_flags, TH_CWR,
174 			"ECN congestion window reduced",
175 			"No ECN congestion window reduced"));
176 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
177 		"      %s",
178 		getflag(tcp->th_flags, TH_ECE,
179 			"ECN echo", "No ECN echo"));
180 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
181 		"      %s",
182 		getflag(tcp->th_flags, TH_URG,
183 			"Urgent pointer", "No urgent pointer"));
184 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
185 		"      %s",
186 		getflag(tcp->th_flags, TH_ACK,
187 			"Acknowledgement", "No acknowledgement"));
188 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
189 		"      %s",
190 		getflag(tcp->th_flags, TH_PUSH,
191 			"Push", "No push"));
192 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
193 		"      %s",
194 		getflag(tcp->th_flags, TH_RST,
195 			"Reset", "No reset"));
196 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
197 		"      %s",
198 		getflag(tcp->th_flags, TH_SYN,
199 			"Syn", "No Syn"));
200 	(void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1),
201 		"      %s",
202 		getflag(tcp->th_flags, TH_FIN,
203 			"Fin", "No Fin"));
204 	(void) sprintf(get_line(((char *)tcp->th_win - dlc_header) + 4, 1),
205 		"Window = %d",
206 		ntohs(tcp->th_win));
207 	/* XXX need to compute checksum and print whether correct */
208 	(void) sprintf(get_line(((char *)tcp->th_sum - dlc_header) + 4, 1),
209 		"Checksum = 0x%04x",
210 		ntohs(tcp->th_sum));
211 	(void) sprintf(get_line(((char *)tcp->th_urp - dlc_header) + 4, 1),
212 		"Urgent pointer = %d",
213 		ntohs(tcp->th_urp));
214 
215 	/* Print TCP options - if any */
216 
217 	print_tcpoptions((uchar_t *)(tcp + 1),
218 	    tcp->th_off * 4 - sizeof (struct tcphdr));
219 
220 	show_space();
221 	}
222 
223 	/* go to the next protocol layer */
224 
225 	if (!interpret_reserved(flags, IPPROTO_TCP,
226 		ntohs(tcp->th_sport),
227 		ntohs(tcp->th_dport),
228 		data, fraglen)) {
229 		if (sunrpc && fraglen > 0)
230 			interpret_rpc(flags, data, fraglen, IPPROTO_TCP);
231 	}
232 
233 	return (tcplen);
234 }
235 
236 static void
237 print_tcpoptions(opt, optlen)
238 	uchar_t *opt;
239 	int optlen;
240 {
241 	int	 len;
242 	char	 *line;
243 	uchar_t	*sack_opt;
244 	uchar_t	*end_opt;
245 	int	sack_len;
246 
247 	if (optlen <= 0) {
248 		(void) sprintf(get_line((char *)&opt - dlc_header, 1),
249 		"No options");
250 		return;
251 	}
252 
253 	(void) sprintf(get_line((char *)&opt - dlc_header, 1),
254 	"Options: (%d bytes)", optlen);
255 
256 	while (optlen > 0) {
257 		line = get_line((char *)&opt - dlc_header, 1);
258 		len = opt[1];
259 		switch (opt[0]) {
260 		case TCPOPT_EOL:
261 			(void) strcpy(line, "  - End of option list");
262 			return;
263 		case TCPOPT_NOP:
264 			(void) strcpy(line, "  - No operation");
265 			len = 1;
266 			break;
267 		case TCPOPT_MAXSEG:
268 			(void) sprintf(line,
269 			"  - Maximum segment size = %d bytes",
270 				(opt[2] << 8) + opt[3]);
271 			break;
272 		case TCPOPT_WSCALE:
273 			(void) sprintf(line, "  - Window scale = %d", opt[2]);
274 			break;
275 		case TCPOPT_TSTAMP:
276 			/* Sanity check. */
277 			if (optlen < TCPOPT_TSTAMP_LEN) {
278 				(void) sprintf(line,
279 				    "  - Incomplete TS option");
280 			} else {
281 				(void) sprintf(line,
282 				    "  - TS Val = %u, TS Echo = %u",
283 				    GET_UINT32(opt + 2),
284 				    GET_UINT32(opt + 6));
285 			}
286 			break;
287 		case TCPOPT_SACK_PERMITTED:
288 			(void) sprintf(line, "  - SACK permitted option");
289 			break;
290 		case TCPOPT_SACK:
291 			/*
292 			 * Sanity check.  Total length should be greater
293 			 * than just the option header length.
294 			 */
295 			if (len <= TCPOPT_HEADER_LEN ||
296 			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
297 				(void) sprintf(line,
298 				    "  - Incomplete SACK option");
299 				break;
300 			}
301 			sack_len = opt[1] - TCPOPT_HEADER_LEN;
302 			sack_opt = opt + TCPOPT_HEADER_LEN;
303 			end_opt = opt + optlen;
304 
305 			(void) sprintf(line, "  - SACK blocks:");
306 			line = get_line((char *)&opt - dlc_header, 1);
307 			(void) sprintf(line, "        ");
308 			while (sack_len > 0) {
309 				char sack_blk[MAXLINE + 1];
310 
311 				/*
312 				 * sack_len may not tell us the truth about
313 				 * the real length...  Need to be careful
314 				 * not to step beyond the option buffer.
315 				 */
316 				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
317 					(void) strcat(line,
318 					    "...incomplete SACK block");
319 					break;
320 				}
321 				(void) sprintf(sack_blk, "(%u-%u) ",
322 				    GET_UINT32(sack_opt),
323 				    GET_UINT32(sack_opt + 4));
324 				(void) strcat(line, sack_blk);
325 				sack_opt += TCPOPT_SACK_LEN;
326 				sack_len -= TCPOPT_SACK_LEN;
327 			}
328 			break;
329 		default:
330 			(void) sprintf(line,
331 			"  - Option %d (unknown - %d bytes) %s",
332 				opt[0],
333 				len - 2,
334 				tohex((char *)&opt[2], len - 2));
335 			break;
336 		}
337 		if (len <= 0) {
338 			(void) sprintf(line, "  - Incomplete option len %d",
339 				len);
340 			break;
341 		}
342 		opt += len;
343 		optlen -= len;
344 	}
345 }
346 
347 /*
348  * This function is basically the same as print_tcpoptions() except that
349  * all options are printed on the same line.
350  */
351 static void
352 print_tcpoptions_summary(uchar_t *opt, int optlen, char *line)
353 {
354 	int	 len;
355 	uchar_t	*sack_opt;
356 	uchar_t	*end_opt;
357 	int	sack_len;
358 	char	options[MAXLINE + 1];
359 
360 	if (optlen <= 0) {
361 		return;
362 	}
363 
364 	(void) strcat(line, " Options=<");
365 	while (optlen > 0) {
366 		len = opt[1];
367 		switch (opt[0]) {
368 		case TCPOPT_EOL:
369 			(void) strcat(line, "eol>");
370 			return;
371 		case TCPOPT_NOP:
372 			(void) strcat(line, "nop");
373 			len = 1;
374 			break;
375 		case TCPOPT_MAXSEG:
376 			(void) sprintf(options, "mss %d",
377 			    (opt[2] << 8) + opt[3]);
378 			(void) strcat(line, options);
379 			break;
380 		case TCPOPT_WSCALE:
381 			(void) sprintf(options, "wscale %d", opt[2]);
382 			(void) strcat(line, options);
383 			break;
384 		case TCPOPT_TSTAMP:
385 			/* Sanity check. */
386 			if (optlen < TCPOPT_TSTAMP_LEN) {
387 				(void) strcat(line, "tstamp|");
388 			} else {
389 				(void) sprintf(options,
390 				    "tstamp %u %u", GET_UINT32(opt + 2),
391 				    GET_UINT32(opt + 6));
392 				(void) strcat(line, options);
393 			}
394 			break;
395 		case TCPOPT_SACK_PERMITTED:
396 			(void) strcat(line, "sackOK");
397 			break;
398 		case TCPOPT_SACK:
399 			/*
400 			 * Sanity check.  Total length should be greater
401 			 * than just the option header length.
402 			 */
403 			if (len <= TCPOPT_HEADER_LEN ||
404 			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
405 				(void) strcat(line, "sack|");
406 				break;
407 			}
408 			sack_len = opt[1] - TCPOPT_HEADER_LEN;
409 			sack_opt = opt + TCPOPT_HEADER_LEN;
410 			end_opt = opt + optlen;
411 
412 			(void) strcat(line, "sack");
413 			while (sack_len > 0) {
414 				/*
415 				 * sack_len may not tell us the truth about
416 				 * the real length...  Need to be careful
417 				 * not to step beyond the option buffer.
418 				 */
419 				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
420 					(void) strcat(line, "|");
421 					break;
422 				}
423 				(void) sprintf(options, " %u-%u",
424 				    GET_UINT32(sack_opt),
425 				    GET_UINT32(sack_opt + 4));
426 				(void) strcat(line, options);
427 				sack_opt += TCPOPT_SACK_LEN;
428 				sack_len -= TCPOPT_SACK_LEN;
429 			}
430 			break;
431 		default:
432 			(void) sprintf(options, "unknown %d", opt[0]);
433 			(void) strcat(line, options);
434 			break;
435 		}
436 		if (len <= 0) {
437 			(void) sprintf(options, "optlen %d", len);
438 			(void) strcat(line, options);
439 			break;
440 		}
441 		opt += len;
442 		optlen -= len;
443 		if (optlen > 0) {
444 			(void) strcat(line, ",");
445 		}
446 	}
447 	(void) strcat(line, ">");
448 }
449