xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c (revision 851677fbe21ab9cad62cea9e299167ea661fd1c8)
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  * Copyright 2024 Oxide Computer Company
27  */
28 
29 #include <stdio.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/time.h>
35 
36 #include <sys/socket.h>
37 #include <net/if.h>
38 #include <netinet/in_systm.h>
39 #include <netinet/in.h>
40 #include <netinet/ip.h>
41 #include <netinet/if_ether.h>
42 #include <netinet/tcp.h>
43 #include "snoop.h"
44 
45 extern char *dlc_header;
46 
47 #define	TCPOPT_HEADER_LEN	2
48 #define	TCPOPT_TSTAMP_LEN	10
49 #define	TCPOPT_SACK_LEN		8
50 #define	TCPOPT_MD5_LEN		18
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 (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(
176 	    get_line(((char *)(uintptr_t)tcp->th_flags - dlc_header) + 4, 1),
177 	    "      %s", 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 	/* is there any data? */
211 	if (tcplen == 0)
212 		return (tcplen);
213 
214 	/* go to the next protocol layer */
215 
216 	if (!interpret_reserved(flags, IPPROTO_TCP,
217 	    ntohs(tcp->th_sport), ntohs(tcp->th_dport), data, fraglen)) {
218 		if (sunrpc && fraglen > 0)
219 			interpret_rpc(flags, data, fraglen, IPPROTO_TCP);
220 	}
221 
222 	return (tcplen);
223 }
224 
225 static void
226 print_tcpoptions(uchar_t *opt, int optlen)
227 {
228 	int	 len;
229 	char	 *line;
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 			uchar_t *sack_opt, *end_opt;
276 			int sack_len;
277 
278 			/*
279 			 * Sanity check.  Total length should be greater
280 			 * than just the option header length.
281 			 */
282 			if (optlen <= TCPOPT_HEADER_LEN ||
283 			    len < TCPOPT_HEADER_LEN) {
284 				(void) sprintf(line,
285 				    "  - Incomplete SACK option");
286 				break;
287 			}
288 			sack_len = len - 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 		}
317 		case TCPOPT_MD5: {
318 			uint_t i;
319 
320 			if (optlen < TCPOPT_MD5_LEN || len != TCPOPT_MD5_LEN) {
321 				(void) sprintf(line,
322 				    "  - Incomplete MD5 option");
323 				break;
324 			}
325 
326 			(void) sprintf(line, "  - TCP MD5 Signature = 0x");
327 			for (i = 2; i < len; i++) {
328 				char options[3];
329 
330 				(void) sprintf(options, "%02x", opt[i]);
331 				(void) strcat(line, options);
332 			}
333 			break;
334 		}
335 		default:
336 			(void) sprintf(line,
337 			    "  - Option %d (unknown - %d bytes) %s",
338 			    opt[0], len - 2, tohex((char *)&opt[2], len - 2));
339 			break;
340 		}
341 		if (len <= 0) {
342 			(void) sprintf(line, "  - Incomplete option len %d",
343 			    len);
344 			break;
345 		}
346 		opt += len;
347 		optlen -= len;
348 	}
349 }
350 
351 /*
352  * This function is basically the same as print_tcpoptions() except that
353  * all options are printed on the same line.
354  */
355 static void
356 print_tcpoptions_summary(uchar_t *opt, int optlen, char *line)
357 {
358 	int	 len;
359 	char	options[MAXLINE + 1];
360 
361 	if (optlen <= 0) {
362 		return;
363 	}
364 
365 	(void) strcat(line, " Options=<");
366 	while (optlen > 0) {
367 		len = opt[1];
368 		switch (opt[0]) {
369 		case TCPOPT_EOL:
370 			(void) strcat(line, "eol>");
371 			return;
372 		case TCPOPT_NOP:
373 			(void) strcat(line, "nop");
374 			len = 1;
375 			break;
376 		case TCPOPT_MAXSEG:
377 			(void) sprintf(options, "mss %d",
378 			    (opt[2] << 8) + opt[3]);
379 			(void) strcat(line, options);
380 			break;
381 		case TCPOPT_WSCALE:
382 			(void) sprintf(options, "wscale %d", opt[2]);
383 			(void) strcat(line, options);
384 			break;
385 		case TCPOPT_TSTAMP:
386 			/* Sanity check. */
387 			if (optlen < TCPOPT_TSTAMP_LEN) {
388 				(void) strcat(line, "tstamp|");
389 			} else {
390 				(void) sprintf(options,
391 				    "tstamp %u %u", GET_UINT32(opt + 2),
392 				    GET_UINT32(opt + 6));
393 				(void) strcat(line, options);
394 			}
395 			break;
396 		case TCPOPT_SACK_PERMITTED:
397 			(void) strcat(line, "sackOK");
398 			break;
399 		case TCPOPT_SACK: {
400 			uchar_t *sack_opt, *end_opt;
401 			int sack_len;
402 
403 			/*
404 			 * Sanity check.  Total length should be greater
405 			 * than just the option header length.
406 			 */
407 			if (optlen <= TCPOPT_HEADER_LEN ||
408 			    len < TCPOPT_HEADER_LEN) {
409 				(void) strcat(line, "sack|");
410 				break;
411 			}
412 			sack_len = len - TCPOPT_HEADER_LEN;
413 			sack_opt = opt + TCPOPT_HEADER_LEN;
414 			end_opt = opt + optlen;
415 
416 			(void) strcat(line, "sack");
417 			while (sack_len > 0) {
418 				/*
419 				 * sack_len may not tell us the truth about
420 				 * the real length...  Need to be careful
421 				 * not to step beyond the option buffer.
422 				 */
423 				if (sack_opt + TCPOPT_SACK_LEN > end_opt) {
424 					(void) strcat(line, "|");
425 					break;
426 				}
427 				(void) sprintf(options, " %u-%u",
428 				    GET_UINT32(sack_opt),
429 				    GET_UINT32(sack_opt + 4));
430 				(void) strcat(line, options);
431 				sack_opt += TCPOPT_SACK_LEN;
432 				sack_len -= TCPOPT_SACK_LEN;
433 			}
434 			break;
435 		}
436 		case TCPOPT_MD5: {
437 			uint_t i;
438 
439 			if (optlen < TCPOPT_MD5_LEN || len != TCPOPT_MD5_LEN) {
440 				(void) strcat(line, "md5|");
441 				break;
442 			}
443 
444 			(void) strcat(line, "md5 0x");
445 			for (i = 2; i < len; i++) {
446 				(void) sprintf(options, "%02x", opt[i]);
447 				(void) strcat(line, options);
448 			}
449 			break;
450 		}
451 		default:
452 			(void) sprintf(options, "unknown %d", opt[0]);
453 			(void) strcat(line, options);
454 			break;
455 		}
456 		if (len <= 0) {
457 			(void) sprintf(options, "optlen %d", len);
458 			(void) strcat(line, options);
459 			break;
460 		}
461 		opt += len;
462 		optlen -= len;
463 		if (optlen > 0) {
464 			(void) strcat(line, ",");
465 		}
466 	}
467 	(void) strcat(line, ">");
468 }
469