xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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 	/* is there any data? */
208 	if (tcplen == 0)
209 		return (tcplen);
210 
211 	/* go to the next protocol layer */
212 
213 	if (!interpret_reserved(flags, IPPROTO_TCP,
214 		ntohs(tcp->th_sport),
215 		ntohs(tcp->th_dport),
216 		data, fraglen)) {
217 		if (sunrpc && fraglen > 0)
218 			interpret_rpc(flags, data, fraglen, IPPROTO_TCP);
219 	}
220 
221 	return (tcplen);
222 }
223 
224 static void
225 print_tcpoptions(opt, optlen)
226 	uchar_t *opt;
227 	int optlen;
228 {
229 	int	 len;
230 	char	 *line;
231 	uchar_t	*sack_opt;
232 	uchar_t	*end_opt;
233 	int	sack_len;
234 
235 	if (optlen <= 0) {
236 		(void) sprintf(get_line((char *)&opt - dlc_header, 1),
237 		"No options");
238 		return;
239 	}
240 
241 	(void) sprintf(get_line((char *)&opt - dlc_header, 1),
242 	"Options: (%d bytes)", optlen);
243 
244 	while (optlen > 0) {
245 		line = get_line((char *)&opt - dlc_header, 1);
246 		len = opt[1];
247 		switch (opt[0]) {
248 		case TCPOPT_EOL:
249 			(void) strcpy(line, "  - End of option list");
250 			return;
251 		case TCPOPT_NOP:
252 			(void) strcpy(line, "  - No operation");
253 			len = 1;
254 			break;
255 		case TCPOPT_MAXSEG:
256 			(void) sprintf(line,
257 			"  - Maximum segment size = %d bytes",
258 				(opt[2] << 8) + opt[3]);
259 			break;
260 		case TCPOPT_WSCALE:
261 			(void) sprintf(line, "  - Window scale = %d", opt[2]);
262 			break;
263 		case TCPOPT_TSTAMP:
264 			/* Sanity check. */
265 			if (optlen < TCPOPT_TSTAMP_LEN) {
266 				(void) sprintf(line,
267 				    "  - Incomplete TS option");
268 			} else {
269 				(void) sprintf(line,
270 				    "  - TS Val = %u, TS Echo = %u",
271 				    GET_UINT32(opt + 2),
272 				    GET_UINT32(opt + 6));
273 			}
274 			break;
275 		case TCPOPT_SACK_PERMITTED:
276 			(void) sprintf(line, "  - SACK permitted option");
277 			break;
278 		case TCPOPT_SACK:
279 			/*
280 			 * Sanity check.  Total length should be greater
281 			 * than just the option header length.
282 			 */
283 			if (len <= TCPOPT_HEADER_LEN ||
284 			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
285 				(void) sprintf(line,
286 				    "  - Incomplete SACK option");
287 				break;
288 			}
289 			sack_len = opt[1] - TCPOPT_HEADER_LEN;
290 			sack_opt = opt + TCPOPT_HEADER_LEN;
291 			end_opt = opt + optlen;
292 
293 			(void) sprintf(line, "  - SACK blocks:");
294 			line = get_line((char *)&opt - dlc_header, 1);
295 			(void) sprintf(line, "        ");
296 			while (sack_len > 0) {
297 				char sack_blk[MAXLINE + 1];
298 
299 				/*
300 				 * sack_len may not tell us the truth about
301 				 * the real length...  Need to be careful
302 				 * not to step beyond the option buffer.
303 				 */
304 				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
305 					(void) strcat(line,
306 					    "...incomplete SACK block");
307 					break;
308 				}
309 				(void) sprintf(sack_blk, "(%u-%u) ",
310 				    GET_UINT32(sack_opt),
311 				    GET_UINT32(sack_opt + 4));
312 				(void) strcat(line, sack_blk);
313 				sack_opt += TCPOPT_SACK_LEN;
314 				sack_len -= TCPOPT_SACK_LEN;
315 			}
316 			break;
317 		default:
318 			(void) sprintf(line,
319 			"  - Option %d (unknown - %d bytes) %s",
320 				opt[0],
321 				len - 2,
322 				tohex((char *)&opt[2], len - 2));
323 			break;
324 		}
325 		if (len <= 0) {
326 			(void) sprintf(line, "  - Incomplete option len %d",
327 				len);
328 			break;
329 		}
330 		opt += len;
331 		optlen -= len;
332 	}
333 }
334 
335 /*
336  * This function is basically the same as print_tcpoptions() except that
337  * all options are printed on the same line.
338  */
339 static void
340 print_tcpoptions_summary(uchar_t *opt, int optlen, char *line)
341 {
342 	int	 len;
343 	uchar_t	*sack_opt;
344 	uchar_t	*end_opt;
345 	int	sack_len;
346 	char	options[MAXLINE + 1];
347 
348 	if (optlen <= 0) {
349 		return;
350 	}
351 
352 	(void) strcat(line, " Options=<");
353 	while (optlen > 0) {
354 		len = opt[1];
355 		switch (opt[0]) {
356 		case TCPOPT_EOL:
357 			(void) strcat(line, "eol>");
358 			return;
359 		case TCPOPT_NOP:
360 			(void) strcat(line, "nop");
361 			len = 1;
362 			break;
363 		case TCPOPT_MAXSEG:
364 			(void) sprintf(options, "mss %d",
365 			    (opt[2] << 8) + opt[3]);
366 			(void) strcat(line, options);
367 			break;
368 		case TCPOPT_WSCALE:
369 			(void) sprintf(options, "wscale %d", opt[2]);
370 			(void) strcat(line, options);
371 			break;
372 		case TCPOPT_TSTAMP:
373 			/* Sanity check. */
374 			if (optlen < TCPOPT_TSTAMP_LEN) {
375 				(void) strcat(line, "tstamp|");
376 			} else {
377 				(void) sprintf(options,
378 				    "tstamp %u %u", GET_UINT32(opt + 2),
379 				    GET_UINT32(opt + 6));
380 				(void) strcat(line, options);
381 			}
382 			break;
383 		case TCPOPT_SACK_PERMITTED:
384 			(void) strcat(line, "sackOK");
385 			break;
386 		case TCPOPT_SACK:
387 			/*
388 			 * Sanity check.  Total length should be greater
389 			 * than just the option header length.
390 			 */
391 			if (len <= TCPOPT_HEADER_LEN ||
392 			    opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) {
393 				(void) strcat(line, "sack|");
394 				break;
395 			}
396 			sack_len = opt[1] - TCPOPT_HEADER_LEN;
397 			sack_opt = opt + TCPOPT_HEADER_LEN;
398 			end_opt = opt + optlen;
399 
400 			(void) strcat(line, "sack");
401 			while (sack_len > 0) {
402 				/*
403 				 * sack_len may not tell us the truth about
404 				 * the real length...  Need to be careful
405 				 * not to step beyond the option buffer.
406 				 */
407 				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
408 					(void) strcat(line, "|");
409 					break;
410 				}
411 				(void) sprintf(options, " %u-%u",
412 				    GET_UINT32(sack_opt),
413 				    GET_UINT32(sack_opt + 4));
414 				(void) strcat(line, options);
415 				sack_opt += TCPOPT_SACK_LEN;
416 				sack_len -= TCPOPT_SACK_LEN;
417 			}
418 			break;
419 		default:
420 			(void) sprintf(options, "unknown %d", opt[0]);
421 			(void) strcat(line, options);
422 			break;
423 		}
424 		if (len <= 0) {
425 			(void) sprintf(options, "optlen %d", len);
426 			(void) strcat(line, options);
427 			break;
428 		}
429 		opt += len;
430 		optlen -= len;
431 		if (optlen > 0) {
432 			(void) strcat(line, ",");
433 		}
434 	}
435 	(void) strcat(line, ">");
436 }
437