diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c new file mode 100644 index 0000000000..4ed56b8c52 --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_tcp.c @@ -0,0 +1,448 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 1991-2001,2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <string.h> +#include <sys/types.h> +#include <sys/time.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#include <netinet/tcp.h> +#include "snoop.h" + +extern char *dlc_header; + +#define TCPOPT_HEADER_LEN 2 +#define TCPOPT_TSTAMP_LEN 10 +#define TCPOPT_SACK_LEN 8 + +/* + * Convert a network byte order 32 bit integer to a host order integer. + * ntohl() cannot be used because option values may not be aligned properly. + */ +#define GET_UINT32(opt) (((uint_t)*((uchar_t *)(opt) + 0) << 24) | \ + ((uint_t)*((uchar_t *)(opt) + 1) << 16) | \ + ((uint_t)*((uchar_t *)(opt) + 2) << 8) | \ + ((uint_t)*((uchar_t *)(opt) + 3))) + +static void print_tcpoptions_summary(uchar_t *, int, char *); +static void print_tcpoptions(uchar_t *, int); + +static const struct { + unsigned int tf_flag; + const char *tf_name; +} tcp_flags[] = { + { TH_SYN, "Syn" }, + { TH_FIN, "Fin" }, + { TH_RST, "Rst" }, + { TH_PUSH, "Push" }, + { TH_ECE, "ECE" }, + { TH_CWR, "CWR" }, + { 0, NULL } +}; + +int +interpret_tcp(int flags, struct tcphdr *tcp, int iplen, int fraglen) +{ + char *data; + int hdrlen, tcplen; + int sunrpc = 0; + char *pname; + char buff[32]; + char *line, *endline; + unsigned int i; + + hdrlen = tcp->th_off * 4; + data = (char *)tcp + hdrlen; + tcplen = iplen - hdrlen; + fraglen -= hdrlen; + if (fraglen < 0) + return; /* incomplete header */ + if (fraglen > tcplen) + fraglen = tcplen; + + if (flags & F_SUM) { + line = get_sum_line(); + endline = line + MAXLINE; + (void) snprintf(line, endline - line, "TCP D=%d S=%d", + ntohs(tcp->th_dport), ntohs(tcp->th_sport)); + line += strlen(line); + + for (i = 0; tcp_flags[i].tf_name != NULL; i++) { + if (tcp->th_flags & tcp_flags[i].tf_flag) { + (void) snprintf(line, endline - line, " %s", + tcp_flags[i].tf_name); + line += strlen(line); + } + } + + if (tcp->th_flags & TH_URG) { + (void) snprintf(line, endline - line, " Urg=%u", + ntohs(tcp->th_urp)); + line += strlen(line); + } + if (tcp->th_flags & TH_ACK) { + (void) snprintf(line, endline - line, " Ack=%u", + ntohl(tcp->th_ack)); + line += strlen(line); + } + if (ntohl(tcp->th_seq)) { + (void) snprintf(line, endline - line, " Seq=%u Len=%d", + ntohl(tcp->th_seq), tcplen); + line += strlen(line); + } + (void) snprintf(line, endline - line, " Win=%d", + ntohs(tcp->th_win)); + print_tcpoptions_summary((uchar_t *)(tcp + 1), + (int)(tcp->th_off * 4 - sizeof (struct tcphdr)), line); + } + + sunrpc = !reservedport(IPPROTO_TCP, ntohs(tcp->th_dport)) && + !reservedport(IPPROTO_TCP, ntohs(tcp->th_sport)) && + valid_rpc(data + 4, fraglen - 4); + + if (flags & F_DTAIL) { + + show_header("TCP: ", "TCP Header", tcplen); + show_space(); + (void) sprintf(get_line((char *)tcp->th_sport - dlc_header, 2), + "Source port = %d", + ntohs(tcp->th_sport)); + + if (sunrpc) { + pname = "(Sun RPC)"; + } else { + pname = getportname(IPPROTO_TCP, ntohs(tcp->th_dport)); + if (pname == NULL) { + pname = ""; + } else { + (void) sprintf(buff, "(%s)", pname); + pname = buff; + } + } + (void) sprintf(get_line((char *)tcp->th_dport - dlc_header, 2), + "Destination port = %d %s", + ntohs(tcp->th_dport), pname); + (void) sprintf(get_line((char *)tcp->th_seq - dlc_header, 4), + "Sequence number = %u", + ntohl(tcp->th_seq)); + (void) sprintf(get_line((char *)tcp->th_ack - dlc_header, 4), + "Acknowledgement number = %u", + ntohl(tcp->th_ack)); + (void) sprintf(get_line(((char *)tcp->th_ack - dlc_header) + 4, 1), + "Data offset = %d bytes", + tcp->th_off * 4); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + "Flags = 0x%02x", + tcp->th_flags); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_CWR, + "ECN congestion window reduced", + "No ECN congestion window reduced")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_ECE, + "ECN echo", "No ECN echo")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_URG, + "Urgent pointer", "No urgent pointer")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_ACK, + "Acknowledgement", "No acknowledgement")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_PUSH, + "Push", "No push")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_RST, + "Reset", "No reset")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_SYN, + "Syn", "No Syn")); + (void) sprintf(get_line(((char *)tcp->th_flags - dlc_header) + 4, 1), + " %s", + getflag(tcp->th_flags, TH_FIN, + "Fin", "No Fin")); + (void) sprintf(get_line(((char *)tcp->th_win - dlc_header) + 4, 1), + "Window = %d", + ntohs(tcp->th_win)); + /* XXX need to compute checksum and print whether correct */ + (void) sprintf(get_line(((char *)tcp->th_sum - dlc_header) + 4, 1), + "Checksum = 0x%04x", + ntohs(tcp->th_sum)); + (void) sprintf(get_line(((char *)tcp->th_urp - dlc_header) + 4, 1), + "Urgent pointer = %d", + ntohs(tcp->th_urp)); + + /* Print TCP options - if any */ + + print_tcpoptions((uchar_t *)(tcp + 1), + tcp->th_off * 4 - sizeof (struct tcphdr)); + + show_space(); + } + + /* go to the next protocol layer */ + + if (!interpret_reserved(flags, IPPROTO_TCP, + ntohs(tcp->th_sport), + ntohs(tcp->th_dport), + data, fraglen)) { + if (sunrpc && fraglen > 0) + interpret_rpc(flags, data, fraglen, IPPROTO_TCP); + } + + return (tcplen); +} + +static void +print_tcpoptions(opt, optlen) + uchar_t *opt; + int optlen; +{ + int len; + char *line; + uchar_t *sack_opt; + uchar_t *end_opt; + int sack_len; + + if (optlen <= 0) { + (void) sprintf(get_line((char *)&opt - dlc_header, 1), + "No options"); + return; + } + + (void) sprintf(get_line((char *)&opt - dlc_header, 1), + "Options: (%d bytes)", optlen); + + while (optlen > 0) { + line = get_line((char *)&opt - dlc_header, 1); + len = opt[1]; + switch (opt[0]) { + case TCPOPT_EOL: + (void) strcpy(line, " - End of option list"); + return; + case TCPOPT_NOP: + (void) strcpy(line, " - No operation"); + len = 1; + break; + case TCPOPT_MAXSEG: + (void) sprintf(line, + " - Maximum segment size = %d bytes", + (opt[2] << 8) + opt[3]); + break; + case TCPOPT_WSCALE: + (void) sprintf(line, " - Window scale = %d", opt[2]); + break; + case TCPOPT_TSTAMP: + /* Sanity check. */ + if (optlen < TCPOPT_TSTAMP_LEN) { + (void) sprintf(line, + " - Incomplete TS option"); + } else { + (void) sprintf(line, + " - TS Val = %u, TS Echo = %u", + GET_UINT32(opt + 2), + GET_UINT32(opt + 6)); + } + break; + case TCPOPT_SACK_PERMITTED: + (void) sprintf(line, " - SACK permitted option"); + break; + case TCPOPT_SACK: + /* + * Sanity check. Total length should be greater + * than just the option header length. + */ + if (len <= TCPOPT_HEADER_LEN || + opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) { + (void) sprintf(line, + " - Incomplete SACK option"); + break; + } + sack_len = opt[1] - TCPOPT_HEADER_LEN; + sack_opt = opt + TCPOPT_HEADER_LEN; + end_opt = opt + optlen; + + (void) sprintf(line, " - SACK blocks:"); + line = get_line((char *)&opt - dlc_header, 1); + (void) sprintf(line, " "); + while (sack_len > 0) { + char sack_blk[MAXLINE + 1]; + + /* + * sack_len may not tell us the truth about + * the real length... Need to be careful + * not to step beyond the option buffer. + */ + if (sack_opt + TCPOPT_SACK_LEN > end_opt) { + (void) strcat(line, + "...incomplete SACK block"); + break; + } + (void) sprintf(sack_blk, "(%u-%u) ", + GET_UINT32(sack_opt), + GET_UINT32(sack_opt + 4)); + (void) strcat(line, sack_blk); + sack_opt += TCPOPT_SACK_LEN; + sack_len -= TCPOPT_SACK_LEN; + } + break; + default: + (void) sprintf(line, + " - Option %d (unknown - %d bytes) %s", + opt[0], + len - 2, + tohex((char *)&opt[2], len - 2)); + break; + } + if (len <= 0) { + (void) sprintf(line, " - Incomplete option len %d", + len); + break; + } + opt += len; + optlen -= len; + } +} + +/* + * This function is basically the same as print_tcpoptions() except that + * all options are printed on the same line. + */ +static void +print_tcpoptions_summary(uchar_t *opt, int optlen, char *line) +{ + int len; + uchar_t *sack_opt; + uchar_t *end_opt; + int sack_len; + char options[MAXLINE + 1]; + + if (optlen <= 0) { + return; + } + + (void) strcat(line, " Options=<"); + while (optlen > 0) { + len = opt[1]; + switch (opt[0]) { + case TCPOPT_EOL: + (void) strcat(line, "eol>"); + return; + case TCPOPT_NOP: + (void) strcat(line, "nop"); + len = 1; + break; + case TCPOPT_MAXSEG: + (void) sprintf(options, "mss %d", + (opt[2] << 8) + opt[3]); + (void) strcat(line, options); + break; + case TCPOPT_WSCALE: + (void) sprintf(options, "wscale %d", opt[2]); + (void) strcat(line, options); + break; + case TCPOPT_TSTAMP: + /* Sanity check. */ + if (optlen < TCPOPT_TSTAMP_LEN) { + (void) strcat(line, "tstamp|"); + } else { + (void) sprintf(options, + "tstamp %u %u", GET_UINT32(opt + 2), + GET_UINT32(opt + 6)); + (void) strcat(line, options); + } + break; + case TCPOPT_SACK_PERMITTED: + (void) strcat(line, "sackOK"); + break; + case TCPOPT_SACK: + /* + * Sanity check. Total length should be greater + * than just the option header length. + */ + if (len <= TCPOPT_HEADER_LEN || + opt[1] <= TCPOPT_HEADER_LEN || len < opt[1]) { + (void) strcat(line, "sack|"); + break; + } + sack_len = opt[1] - TCPOPT_HEADER_LEN; + sack_opt = opt + TCPOPT_HEADER_LEN; + end_opt = opt + optlen; + + (void) strcat(line, "sack"); + while (sack_len > 0) { + /* + * sack_len may not tell us the truth about + * the real length... Need to be careful + * not to step beyond the option buffer. + */ + if (sack_opt + TCPOPT_SACK_LEN > end_opt) { + (void) strcat(line, "|"); + break; + } + (void) sprintf(options, " %u-%u", + GET_UINT32(sack_opt), + GET_UINT32(sack_opt + 4)); + (void) strcat(line, options); + sack_opt += TCPOPT_SACK_LEN; + sack_len -= TCPOPT_SACK_LEN; + } + break; + default: + (void) sprintf(options, "unknown %d", opt[0]); + (void) strcat(line, options); + break; + } + if (len <= 0) { + (void) sprintf(options, "optlen %d", len); + (void) strcat(line, options); + break; + } + opt += len; + optlen -= len; + if (optlen > 0) { + (void) strcat(line, ","); + } + } + (void) strcat(line, ">"); +} |