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