/*
* 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 2005 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 (fraglen + hdrlen); /* 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 *)(uintptr_t)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 *)(uintptr_t)tcp->th_dport -
dlc_header, 2), "Destination port = %d %s",
ntohs(tcp->th_dport), pname);
(void) sprintf(get_line((char *)(uintptr_t)tcp->th_seq -
dlc_header, 4), "Sequence number = %u",
ntohl(tcp->th_seq));
(void) sprintf(get_line((char *)(uintptr_t)tcp->th_ack - dlc_header, 4),
"Acknowledgement number = %u",
ntohl(tcp->th_ack));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_ack - dlc_header) +
4, 1), "Data offset = %d bytes", tcp->th_off * 4);
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), "Flags = 0x%02x", tcp->th_flags);
(void) sprintf(get_line(((char *)(uintptr_t)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 *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_ECE,
"ECN echo", "No ECN echo"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s",
getflag(tcp->th_flags, TH_URG,
"Urgent pointer", "No urgent pointer"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_ACK,
"Acknowledgement", "No acknowledgement"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_PUSH,
"Push", "No push"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_RST,
"Reset", "No reset"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_SYN,
"Syn", "No Syn"));
(void) sprintf(get_line(((char *)(uintptr_t)tcp->th_flags -
dlc_header) + 4, 1), " %s", getflag(tcp->th_flags, TH_FIN,
"Fin", "No Fin"));
(void) sprintf(get_line(((char *)(uintptr_t)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 *)(uintptr_t)tcp->th_sum - dlc_header) +
4, 1), "Checksum = 0x%04x", ntohs(tcp->th_sum));
(void) sprintf(get_line(((char *)(uintptr_t)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, ">");
}