/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <inet/common.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include "snoop.h"
/*
* Snoop interpreter for SCTP (rfc2960).
*
* To add support for an upper-layer protocol, modify either
* the port-dispatcher in snoop_rport.c, or the protocol ID
* dispatcher at the bottom of this file (or both).
*/
static void interpret_protoid(int, uint32_t, char *, int);
extern char *prot_prefix;
/*
* This defines the length of internal, unbounded buffers. We set
* this to be MAXLINE (the maximum verbose display line length) -
* 64, which should be enough for all necessary descriptions. 64
* bytes seems like a reasonably conservative estimate of the
* maximum prefix length snoop may add to any text buffer it hands out.
*/
#define BUFLEN MAXLINE - 64
/*
* Common structure to hold descriptions and parsers for all
* chunks, parameters, and errors. Each parser should implement
* this interface:
*
* void parse(int flags, uint8_t cflags, void *data, int datalen);
*
* Where flags is the snoop flags, cflags are the chunk flags, data
* is the chunk or parameter data (not including the chunk or
* parameter header), and datalen is the length of the chunk or
* parameter data (again not including any headers).
*/
typedef void parse_func_t(int, uint8_t, const void *, int);
typedef struct {
uint16_t id;
const char *sdesc; /* short description */
const char *vdesc; /* verbose description */
parse_func_t *parse; /* parser function */
} dispatch_t;
static void interpret_params(const void *, int, char *, const dispatch_t *,
int, int);
/*
* Chunk parsers
*/
static parse_func_t parse_abort_chunk, parse_data_chunk, parse_error_chunk,
parse_init_chunk, parse_opaque_chunk, parse_sack_chunk,
parse_shutdone_chunk, parse_shutdown_chunk, parse_asconf_chunk,
parse_ftsn_chunk;
/*
* Chunk parser dispatch table. There are few enough chunks defined
* in the core protocol, and they are sequential, so the chunk code
* can be used as the index into this array for the common case.
* It is still necessary to check that the code and index match,
* since optional extensions will not follow sequentially the
* core chunks.
*/
static const dispatch_t chunk_dispatch_table[] = {
/* code F_SUM desc F_DTAIL desc parser function */
{ CHUNK_DATA, "Data", "Data Chunk",
parse_data_chunk },
{ CHUNK_INIT, "Init", "Init Chunk",
parse_init_chunk },
{ CHUNK_INIT_ACK, "Init ACK", "Init ACK Chunk",
parse_init_chunk },
{ CHUNK_SACK, "SACK", "SACK Chunk",
parse_sack_chunk },
{ CHUNK_HEARTBEAT, "Heartbeat", "Heartbeat Chunk",
parse_opaque_chunk },
{ CHUNK_HEARTBEAT_ACK, "Heartbeat ACK", "Heartbeat ACK Chunk",
parse_opaque_chunk },
{ CHUNK_ABORT, "Abort", "Abort Chunk",
parse_abort_chunk },
{ CHUNK_SHUTDOWN, "Shutdown", "Shutdown Chunk",
parse_shutdown_chunk },
{ CHUNK_SHUTDOWN_ACK, "Shutdown ACK", "Shutdown ACK Chunk",
NULL },
{ CHUNK_ERROR, "Err", "Error Chunk",
parse_error_chunk },
{ CHUNK_COOKIE, "Cookie", "Cookie Chunk",
parse_opaque_chunk },
{ CHUNK_COOKIE_ACK, "Cookie ACK", "Cookie ACK Chunk",
parse_opaque_chunk },
{ CHUNK_ECNE, "ECN Echo", "ECN Echo Chunk",
parse_opaque_chunk },
{ CHUNK_CWR, "CWR", "CWR Chunk",
parse_opaque_chunk },
{ CHUNK_SHUTDOWN_COMPLETE, "Shutdown Done", "Shutdown Done",
parse_shutdone_chunk },
{ CHUNK_FORWARD_TSN, "FORWARD TSN", "Forward TSN Chunk",
parse_ftsn_chunk },
{ CHUNK_ASCONF_ACK, "ASCONF ACK", "ASCONF ACK Chunk",
parse_asconf_chunk },
{ CHUNK_ASCONF, "ASCONF", "ASCONF Chunk",
parse_asconf_chunk }
};
/*
* Parameter Parsers
*/
static parse_func_t parse_encap_param, parse_int32_param, parse_ip4_param,
parse_ip6_param, parse_opaque_param, parse_suppaddr_param,
parse_unrec_chunk, parse_addip_param, parse_asconferr_param,
parse_asconfok_param, parse_addiperr_param;
/*
* Parameter parser dispatch table. The summary description is not
* used here. Strictly speaking, parameter types are defined within
* the context of a chunk type. However, thus far the IETF WG has
* agreed to follow the convention that parameter types are globally
* unique (and why not, with a 16-bit namespace). However, if this
* ever changes, there will need to be different parameter dispatch
* tables for each chunk type.
*/
static const dispatch_t parm_dispatch_table[] = {
/* code F_SUM desc F_DTAIL desc parser function */
{ PARM_UNKNOWN, "", "Unknown Parameter",
parse_opaque_param },
{ PARM_HBINFO, "", "Heartbeat Info",
parse_opaque_param },
{ PARM_ADDR4, "", "IPv4 Address",
parse_ip4_param },
{ PARM_ADDR6, "", "IPv6 Address",
parse_ip6_param },
{ PARM_COOKIE, "", "Cookie",
parse_opaque_param },
{ PARM_UNRECOGNIZED, "", "Unrecognized Param",
parse_encap_param },
{ PARM_COOKIE_PRESERVE, "", "Cookie Preservative",
parse_opaque_param },
{ 10, "", "Reserved for ECN",
parse_opaque_param },
{ PARM_ADDR_HOST_NAME, "", "Host Name Parameter",
parse_opaque_param },
{ PARM_SUPP_ADDRS, "", "Supported Addresses",
parse_suppaddr_param },
{ PARM_ECN_CAPABLE, "", "ECN Capable",
parse_opaque_param },
{ PARM_ADD_IP, "", "Add IP",
parse_addip_param },
{ PARM_DEL_IP, "", "Del IP",
parse_addip_param },
{ PARM_ASCONF_ERROR, "", "ASCONF Error Ind",
parse_asconferr_param },
{ PARM_PRIMARY_ADDR, "", "Set Primary Address",
parse_addip_param },
{ PARM_FORWARD_TSN, "", "Forward TSN",
NULL },
{ PARM_ASCONF_SUCCESS, "", "ASCONF Success Ind",
parse_asconfok_param }
};
/*
* Errors have the same wire format at parameters.
*/
static const dispatch_t err_dispatch_table[] = {
/* code F_SUM desc F_DTAIL desc parser function */
{ SCTP_ERR_UNKNOWN, "", "Unknown Error",
parse_opaque_param },
{ SCTP_ERR_BAD_SID, "", "Invalid Stream ID",
parse_opaque_param },
{ SCTP_ERR_MISSING_PARM, "", "Missing Parameter",
parse_opaque_param },
{ SCTP_ERR_STALE_COOKIE, "", "Stale Cookie",
parse_int32_param },
{ SCTP_ERR_NO_RESOURCES, "", "Out Of Resources",
parse_opaque_param },
{ SCTP_ERR_BAD_ADDR, "", "Unresolvable Address",
parse_opaque_param },
{ SCTP_ERR_UNREC_CHUNK, "", "Unrecognized Chunk",
parse_unrec_chunk },
{ SCTP_ERR_BAD_MANDPARM, "", "Bad Mandatory Parameter",
parse_opaque_param },
{ SCTP_ERR_UNREC_PARM, "", "Unrecognized Parameter",
parse_opaque_param },
{ SCTP_ERR_NO_USR_DATA, "", "No User Data",
parse_int32_param },
{ SCTP_ERR_COOKIE_SHUT, "", "Cookie During Shutdown",
parse_opaque_param },
{ SCTP_ERR_DELETE_LASTADDR, "", "Delete Last Remaining Address",
parse_addiperr_param },
{ SCTP_ERR_RESOURCE_SHORTAGE, "", "Resource Shortage",
parse_addiperr_param },
{ SCTP_ERR_DELETE_SRCADDR, "", "Delete Source IP Address",
parse_addiperr_param },
{ SCTP_ERR_AUTH_ERR, "", "Not authorized",
parse_addiperr_param }
};
/*
* These are global because the data chunk parser needs them to dispatch
* to ULPs. The alternative is to add source and dest port arguments
* to every parser, which seems even messier (since *only* the data
* chunk parser needs it)...
*/
static in_port_t sport, dport;
/* Summary line miscellany */
static int sumlen;
static char scratch[MAXLINE];
static char *sumline;
#define SUMAPPEND(fmt) \
sumlen -= snprintf fmt; \
(void) strlcat(sumline, scratch, sumlen)
#define DUMPHEX_MAX 16
static const dispatch_t *
lookup_dispatch(int id, const dispatch_t *tbl, int tblsz)
{
int i;
/*
* Try fast lookup first. The common chunks defined in RFC2960
* will have indices aligned with their IDs, so this works for
* the common case.
*/
if (id < (tblsz - 1)) {
if (id == tbl[id].id) {
return (tbl + id);
}
}
/*
* Nope - probably an extension. Search the whole table,
* starting from the end, since extensions are at the end.
*/
for (i = tblsz - 1; i >= 0; i--) {
if (id == tbl[i].id) {
return (tbl + i);
}
}
return (NULL);
}
/*
* Dumps no more than the first DUMPHEX_MAX bytes in hex. If
* the user wants more, they can use the -x option to snoop.
*/
static void
dumphex(const uchar_t *payload, int payload_len, char *msg)
{
int index;
int end;
char buf[BUFLEN];
if (payload_len == 0) {
return;
}
end = payload_len > DUMPHEX_MAX ? DUMPHEX_MAX : payload_len;
for (index = 0; index < end; index++) {
(void) snprintf(&buf[index * 3], 4, " %.2x", payload[index]);
}
if (payload_len > DUMPHEX_MAX) {
(void) strlcat(buf, " ...", BUFLEN);
}
(void) snprintf(get_line(0, 0), BUFLEN, msg, buf);
}
/*
* Present perscribed action for unknowns according to rfc2960. Works
* for chunks and parameters as well if the parameter type is
* shifted 8 bits right.
*/
static const char *
get_action_desc(uint8_t id)
{
if ((id & 0xc0) == 0xc0) {
return (": skip on unknown, return error");
} else if ((id & 0x80) == 0x80) {
return (": skip on unknown, no error");
} else if ((id & 0x40) == 0x40) {
return (": stop on unknown, return error");
}
/* Top two bits are clear */
return (": stop on unknown, no error");
}
/* ARGSUSED */
static void
parse_asconfok_param(int flags, uint8_t notused, const void *data, int dlen)
{
uint32_t *cid;
if (dlen < sizeof (*cid)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete ASCONF Success Ind parameter");
return;
}
cid = (uint32_t *)data;
(void) snprintf(get_line(0, 0), get_line_remain(), " ASCONF CID = %u",
ntohl(*cid));
}
/* ARGSUSED */
static void
parse_asconferr_param(int flags, uint8_t notused, const void *data, int dlen)
{
uint32_t *cid;
if (dlen < sizeof (*cid)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete ASCONF Error Ind parameter");
return;
}
cid = (uint32_t *)data;
(void) snprintf(get_line(0, 0), get_line_remain(), " ASCONF CID = %u",
ntohl(*cid));
interpret_params(cid + 1, dlen - sizeof (*cid), "Error",
err_dispatch_table, A_CNT(err_dispatch_table), flags);
}
/* ARGSUSED */
static void
parse_addiperr_param(int flags, uint8_t notused, const void *data, int dlen)
{
interpret_params(data, dlen, "Parameter",
parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
}
/* ARGSUSED */
static void
parse_addip_param(int flags, uint8_t notused, const void *data, int dlen)
{
uint32_t *cid;
if (dlen < sizeof (*cid)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete ASCONF Error Ind parameter");
return;
}
cid = (uint32_t *)data;
(void) snprintf(get_line(0, 0), get_line_remain(), " ASCONF CID = %u",
ntohl(*cid));
interpret_params(cid + 1, dlen - sizeof (*cid), "Parameter",
parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
}
/* ARGSUSED */
static void
parse_ip4_param(int flags, uint8_t notused, const void *data, int datalen)
{
char abuf[INET_ADDRSTRLEN];
char *ap;
if (datalen < sizeof (in_addr_t)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete IPv4 Addr parameter");
return;
}
ap = (char *)inet_ntop(AF_INET, data, abuf, INET_ADDRSTRLEN);
if (ap == NULL) {
ap = "<Bad Address>";
}
(void) snprintf(get_line(0, 0), get_line_remain(), " Addr = %s", ap);
}
/* ARGSUSED */
static void
parse_ip6_param(int flags, uint8_t notused, const void *data, int datalen)
{
char abuf[INET6_ADDRSTRLEN];
char *ap;
if (datalen < sizeof (in6_addr_t)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete IPv6 Addr parameter");
return;
}
ap = (char *)inet_ntop(AF_INET6, data, abuf, INET6_ADDRSTRLEN);
if (ap == NULL) {
ap = "<Bad Address>";
}
(void) snprintf(get_line(0, 0), get_line_remain(), " Addr = %s", ap);
}
/* ARGSUSED */
static void
parse_int32_param(int flags, uint8_t notused, const void *data, int datalen)
{
if (datalen < 4) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete INT32 parameter");
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), " INT32 = %u",
ntohl(*(uint32_t *)data));
}
/* ARGSUSED */
static void
parse_suppaddr_param(int flags, uint8_t notused, const void *data, int dlen)
{
const uint16_t *type;
if (dlen < 2) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete Supported Addr parameter");
return;
}
type = data;
while (dlen > 0) {
switch (ntohs(*type)) {
case PARM_ADDR4:
(void) snprintf(get_line(0, 0), get_line_remain(),
" IPv4");
break;
case PARM_ADDR6:
(void) snprintf(get_line(0, 0), get_line_remain(),
" IPv6");
break;
case PARM_ADDR_HOST_NAME:
(void) snprintf(get_line(0, 0), get_line_remain(),
" Host Name");
break;
default:
(void) snprintf(get_line(0, 0), get_line_remain(),
"Unknown Type (%hu)", ntohs(*type));
break;
}
dlen -= sizeof (*type);
type++;
}
}
/*ARGSUSED*/
static void
parse_encap_param(int flags, uint8_t notused, const void *data, int dlen)
{
if (dlen < sizeof (sctp_parm_hdr_t)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete Parameter");
return;
}
interpret_params(data, dlen, "Parameter",
parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
}
/* ARGSUSED */
static void
parse_unrec_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
const sctp_chunk_hdr_t *cp = data;
const dispatch_t *dp;
const char *actstr;
if (datalen < sizeof (*cp)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete Unrecognized Chunk Error");
return;
}
/* Maybe snoop knows about this chunk? */
dp = lookup_dispatch(cp->sch_id, chunk_dispatch_table,
A_CNT(chunk_dispatch_table));
if (dp != NULL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" Chunk Type = %u (%s)", cp->sch_id, dp->vdesc);
} else {
actstr = get_action_desc(cp->sch_id);
(void) snprintf(get_line(0, 0), get_line_remain(),
" Chunk Type = %u%s", cp->sch_id, actstr);
}
}
/*
* Same as parse_opaque_chunk except for the indentation.
*/
/* ARGSUSED */
static void
parse_opaque_param(int flags, uint8_t cflags, const void *data, int datalen)
{
dumphex(data, datalen, " Data = %s");
}
/*
* Loops through all parameters (or errors) until it has read
* datalen bytes of information, finding a parser for each.
* The tbl argument allows the caller to specify which dispatch
* table to use, making this function useful for both parameters
* and errors. The type argument is used to denote whether this
* is an error or parameter in detailed mode.
*/
static void
interpret_params(const void *data, int datalen, char *type,
const dispatch_t *tbl, int tbl_size, int flags)
{
const sctp_parm_hdr_t *hdr = data;
uint16_t plen;
uint16_t ptype;
const char *desc;
parse_func_t *parse;
int pad;
const dispatch_t *dp;
const char *actstr;
for (;;) {
/*
* Adjust for padding: if the address isn't aligned, there
* should be some padding. So skip over the padding and
* adjust hdr accordingly. RFC2960 mandates that all
* parameters must be 32-bit aligned WRT the enclosing chunk,
* which ensures that this parameter header will
* be 32-bit aligned in memory. We must, of course, bounds
* check fraglen before actually trying to use hdr, in
* case the packet has been mangled or is the product
* of a buggy implementation.
*/
if ((pad = (uintptr_t)hdr % SCTP_ALIGN) != 0) {
pad = SCTP_ALIGN - pad;
datalen -= pad;
/* LINTED pointer cast may result in improper alignment */
hdr = (sctp_parm_hdr_t *)((char *)hdr + pad);
}
/* Need to compare against 0 1st, since sizeof is unsigned */
if (datalen < 0 || datalen < sizeof (*hdr)) {
if (datalen > 0) {
(void) snprintf(get_line(0, 0),
get_line_remain(),
"==> Extra data after last parameter");
}
return;
}
plen = ntohs(hdr->sph_len);
if (datalen < plen || plen < sizeof (*hdr)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Incomplete %s", type);
return;
}
/* Get description and parser */
ptype = ntohs(hdr->sph_type);
desc = "Unknown Parameter Type";
parse = parse_opaque_param;
dp = lookup_dispatch(ptype, tbl, tbl_size);
if (dp != NULL) {
desc = dp->vdesc;
parse = dp->parse;
}
show_space();
if (dp != NULL) {
actstr = "";
} else {
actstr = get_action_desc((uint8_t)(ptype >> 8));
}
(void) snprintf(get_line(0, 0), get_line_remain(),
" ------- SCTP %s Type = %s (%u%s)", type, desc, ptype,
actstr);
(void) snprintf(get_line(0, 0), get_line_remain(),
" Data length = %hu", plen - sizeof (*hdr));
if (parse != NULL) {
parse(flags, 0, (char *)(hdr + 1),
plen - sizeof (*hdr));
}
datalen -= plen;
/* LINTED pointer cast may result in improper alignment */
hdr = (sctp_parm_hdr_t *)((char *)hdr + plen);
}
}
/* ARGSUSED */
static void
parse_ftsn_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
uint32_t *ftsn;
ftsn_entry_t *ftsn_entry;
if (datalen < (sizeof (*ftsn) + sizeof (*ftsn_entry))) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete FORWARD-TSN chunk");
}
return;
}
ftsn = (uint32_t *)data;
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "CTSN %x ", ntohl(*ftsn)));
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), "Cum TSN= %x",
ntohl(*ftsn));
datalen -= sizeof (*ftsn);
ftsn_entry = (ftsn_entry_t *)(ftsn + 1);
while (datalen >= sizeof (*ftsn_entry)) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"SID = %u : SSN = %u", ntohs(ftsn_entry->ftsn_sid),
ntohs(ftsn_entry->ftsn_ssn));
datalen -= sizeof (*ftsn_entry);
ftsn_entry++;
}
}
/* ARGSUSED */
static void
parse_asconf_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
uint32_t *sn;
if (datalen < sizeof (*sn)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete ASCONF chunk");
}
return;
}
sn = (uint32_t *)data;
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "sn %x ", ntohl(*sn)));
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), "Serial Number= %x",
ntohl(*sn));
interpret_params(sn + 1, datalen - sizeof (*sn), "Parameter",
parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
}
static void
parse_init_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
const sctp_init_chunk_t *icp = data;
if (datalen < sizeof (*icp)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete INIT chunk");
}
return;
}
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "tsn %x str %hu/%hu win %u ",
ntohl(icp->sic_inittsn), ntohs(icp->sic_outstr),
ntohs(icp->sic_instr), ntohl(icp->sic_a_rwnd)));
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), "Flags = 0x%.2x",
cflags);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Initiate tag = 0x%.8x", ntohl(icp->sic_inittag));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Advertised receiver window credit = %u", ntohl(icp->sic_a_rwnd));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Outbound streams = %hu", ntohs(icp->sic_outstr));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Inbound streams = %hu", ntohs(icp->sic_instr));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Initial TSN = 0x%.8x", ntohl(icp->sic_inittsn));
if (datalen > sizeof (*icp)) {
interpret_params(icp + 1, datalen - sizeof (*icp),
"Parameter", parm_dispatch_table,
A_CNT(parm_dispatch_table), flags);
}
}
static void
parse_data_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
const sctp_data_chunk_t *dcp = data;
char *payload;
uint32_t ppid;
if (datalen < sizeof (*dcp)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete DATA chunk %d (%d)", datalen,
sizeof (*dcp));
}
return;
}
ppid = ntohl(dcp->sdc_payload_id);
/* This is the actual data len, excluding the data chunk header. */
datalen -= sizeof (*dcp);
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"flags = 0x%.2x", cflags);
(void) snprintf(get_line(0, 0), get_line_remain(), " %s",
getflag(cflags, SCTP_DATA_UBIT, "unordered", "ordered"));
(void) snprintf(get_line(0, 0), get_line_remain(), " %s",
getflag(cflags, SCTP_DATA_BBIT,
"beginning", "(beginning unset)"));
(void) snprintf(get_line(0, 0), get_line_remain(), " %s",
getflag(cflags, SCTP_DATA_EBIT, "end", "(end unset)"));
(void) snprintf(get_line(0, 0), get_line_remain(),
"TSN = 0x%.8x", ntohl(dcp->sdc_tsn));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Stream ID = %hu", ntohs(dcp->sdc_sid));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Stream Sequence Number = %hu", ntohs(dcp->sdc_ssn));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Payload Protocol ID = 0x%.8x", ppid);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Data Length = %d", datalen);
show_space();
}
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "len %d tsn %x str %hu/%hu "
"ppid %x ", datalen, ntohl(dcp->sdc_tsn),
ntohs(dcp->sdc_sid), ntohs(dcp->sdc_ssn), ppid));
}
/*
* Go to the next protocol layer, but not if we are in
* summary mode only. In summary mode, each ULP parse would
* create a new line, and if there were several data chunks
* bundled together in the packet, this would confuse snoop's
* packet numbering and timestamping.
*
* SCTP carries two ways to determine an ULP: ports and the
* payload protocol identifier (ppid). Since ports are the
* better entrenched convention, we first try interpret_reserved().
* If that fails to find a parser, we try by the PPID.
*/
if (!(flags & F_ALLSUM) && !(flags & F_DTAIL)) {
return;
}
payload = (char *)(dcp + 1);
if (!interpret_reserved(flags, IPPROTO_SCTP, sport, dport, payload,
datalen) && ppid != 0) {
interpret_protoid(flags, ppid, payload, datalen);
}
/*
* Reset the protocol prefix, since it may have been changed
* by a ULP interpreter.
*/
prot_prefix = "SCTP: ";
}
/* ARGSUSED */
static void
parse_sack_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
const sctp_sack_chunk_t *scp = data;
uint16_t numfrags, numdups;
sctp_sack_frag_t *frag;
int i;
uint32_t *tsn;
if (datalen < sizeof (*scp)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete SACK chunk");
}
return;
}
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"Cumulative TSN ACK = 0x%.8x", ntohl(scp->ssc_cumtsn));
(void) snprintf(get_line(0, 0), get_line_remain(),
"Advertised Receiver Window Credit = %u",
ntohl(scp->ssc_a_rwnd));
numfrags = ntohs(scp->ssc_numfrags);
numdups = ntohs(scp->ssc_numdups);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Number of Fragments = %hu", numfrags);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Number of Duplicates = %hu", numdups);
/* Display any gap reports */
datalen -= sizeof (*scp);
if (datalen < (numfrags * sizeof (*frag))) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Malformed gap report listing");
return;
}
frag = (sctp_sack_frag_t *)(scp + 1);
for (i = 0; i < numfrags; i++) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" Fragment #%d: Start = %hu, end = %hu", i,
ntohs(frag->ssf_start), ntohs(frag->ssf_end));
frag += 1;
}
/* Display any duplicate reports */
datalen -= numfrags * sizeof (*frag);
if (datalen < (numdups * sizeof (*tsn))) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" ==> Malformed duplicate report listing");
return;
}
/* LINTED pointer cast may result in improper alignment */
tsn = (uint32_t *)frag;
for (i = 0; i < numdups; i++) {
(void) snprintf(get_line(0, 0), get_line_remain(),
" Duplicate #%d: TSN = %x", i, *tsn);
tsn++;
}
}
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE,
"tsn %x win %u gaps/dups %hu/%hu ", ntohl(scp->ssc_cumtsn),
ntohl(scp->ssc_a_rwnd), ntohs(scp->ssc_numfrags),
ntohs(scp->ssc_numdups)));
}
}
/* ARGSUSED */
static void
parse_shutdown_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
const uint32_t *cumtsn = data;
if (datalen < sizeof (*cumtsn)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete Shutdown chunk");
}
return;
}
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"Cumulative TSN = 0x%.8x", ntohl(*cumtsn));
}
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "tsn %x", ntohl(*cumtsn)));
}
}
/* ARGSUSED */
static void
parse_error_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
if (!(flags & F_DTAIL)) {
return;
}
interpret_params(data, datalen, "Error", err_dispatch_table,
A_CNT(err_dispatch_table), flags);
}
static void
parse_abort_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
if (!(flags & F_DTAIL)) {
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), "flags = 0x%.2x",
cflags);
(void) snprintf(get_line(0, 0), get_line_remain(), " %s",
getflag(cflags, SCTP_TBIT, "TCB not destroyed", "TCB destroyed"));
interpret_params(data, datalen, "Error", err_dispatch_table,
A_CNT(err_dispatch_table), flags);
}
/* ARGSUSED2 */
static void
parse_shutdone_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
if (!(flags & F_DTAIL)) {
return;
}
(void) snprintf(get_line(0, 0), get_line_remain(), "flags = 0x%.2x",
cflags);
(void) snprintf(get_line(0, 0), get_line_remain(), " %s",
getflag(cflags, SCTP_TBIT, "TCB not destroyed", "TCB destroyed"));
}
/* ARGSUSED */
static void
parse_opaque_chunk(int flags, uint8_t cflags, const void *data, int datalen)
{
if (!(flags & F_DTAIL)) {
return;
}
if (datalen == 0) {
return;
}
dumphex(data, datalen, "Data = %s");
}
/*
* Loops through all chunks until it has read fraglen bytes of
* information, finding a parser for each. If any parameters are
* present, interpret_params() is then called. Returns the remaining
* fraglen.
*/
static int
interpret_chunks(int flags, sctp_chunk_hdr_t *cp, int fraglen)
{
uint16_t clen;
int signed_len;
int pad;
const char *desc;
parse_func_t *parse;
const dispatch_t *dp;
const char *actstr;
for (;;) {
/*
* Adjust for padding: if the address isn't aligned, there
* should be some padding. So skip over the padding and
* adjust hdr accordingly. RFC2960 mandates that all
* chunks must be 32-bit aligned WRT the SCTP common hdr,
* which ensures that this chunk header will
* be 32-bit aligned in memory. We must, of course, bounds
* check fraglen before actually trying to use hdr, in
* case the packet has been mangled or is the product
* of a buggy implementation.
*/
if ((pad = (uintptr_t)cp % SCTP_ALIGN) != 0) {
pad = SCTP_ALIGN - pad;
fraglen -= pad;
/* LINTED pointer cast may result in improper alignment */
cp = (sctp_chunk_hdr_t *)((char *)cp + pad);
}
/* Need to compare against 0 1st, since sizeof is unsigned */
if (fraglen < 0 || fraglen < sizeof (*cp)) {
if (fraglen > 0 && flags & F_DTAIL) {
(void) snprintf(get_line(0, 0),
get_line_remain(),
"==> Extra data after last chunk");
}
return (fraglen);
}
clen = ntohs(cp->sch_len);
if (fraglen < clen) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0),
get_line_remain(), "==> Corrupted chunk");
}
return (fraglen);
}
signed_len = clen - sizeof (*cp);
if (signed_len < 0) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0),
get_line_remain(),
"==> Incomplete or corrupted chunk");
}
return (0);
}
/* Get description and parser */
dp = lookup_dispatch(cp->sch_id, chunk_dispatch_table,
A_CNT(chunk_dispatch_table));
if (dp != NULL) {
if (flags & F_SUM) {
desc = dp->sdesc;
} else if (flags & F_DTAIL) {
desc = dp->vdesc;
}
parse = dp->parse;
} else {
if (flags & F_SUM) {
desc = "UNK";
} else if (flags & F_DTAIL) {
desc = "Unknown Chunk Type";
}
parse = parse_opaque_chunk;
}
if (flags & F_SUM) {
SUMAPPEND((scratch, MAXLINE, "%s ", desc));
}
if (flags & F_DTAIL) {
show_space();
if (dp != NULL) {
actstr = "";
} else {
actstr = get_action_desc(cp->sch_id);
}
(void) snprintf(get_line(0, 0), get_line_remain(),
"------- SCTP Chunk Type = %s (%u%s)", desc,
cp->sch_id, actstr);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Chunk length = %hu", clen);
}
if (parse != NULL) {
parse(flags, cp->sch_flags, (char *)(cp + 1),
signed_len);
}
fraglen -= clen;
/* LINTED pointer cast may result in improper alignment */
cp = (sctp_chunk_hdr_t *)((char *)cp + clen);
}
}
void
interpret_sctp(int flags, sctp_hdr_t *sctp, int iplen, int fraglen)
{
int len_from_iphdr;
sctp_chunk_hdr_t *cp;
char *pn;
char buff[32];
/*
* Alignment check. If the header is 32-bit aligned, all other
* protocol units will also be aligned, as mandated by rfc2960.
* Buggy packets will be caught and flagged by chunk and
* parameter bounds checking.
* If the header is not aligned, however, we drop the packet.
*/
if (!IS_P2ALIGNED(sctp, SCTP_ALIGN)) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> SCTP header not aligned, dropping");
}
return;
}
fraglen -= sizeof (*sctp);
if (fraglen < 0) {
if (flags & F_DTAIL) {
(void) snprintf(get_line(0, 0), get_line_remain(),
"==> Incomplete sctp header");
}
return;
}
/* If fraglen is somehow longer than the IP payload, adjust it */
len_from_iphdr = iplen - sizeof (*sctp);
if (fraglen > len_from_iphdr) {
fraglen = len_from_iphdr;
}
/* Keep track of the ports */
sport = ntohs(sctp->sh_sport);
dport = ntohs(sctp->sh_dport);
/* Set pointer to first chunk */
cp = (sctp_chunk_hdr_t *)(sctp + 1);
if (flags & F_SUM) {
sumline = get_sum_line();
*sumline = '\0';
sumlen = MAXLINE;
SUMAPPEND((scratch, MAXLINE, "SCTP D=%d S=%d ", dport, sport));
}
if (flags & F_DTAIL) {
show_header("SCTP: ", "SCTP Header", fraglen);
show_space();
pn = getportname(IPPROTO_SCTP, (ushort_t)sport);
if (pn == NULL) {
pn = "";
} else {
(void) snprintf(buff, sizeof (buff), "(%s)", pn);
pn = buff;
}
(void) snprintf(get_line(0, 0), get_line_remain(),
"Source port = %hu %s", sport, pn);
pn = getportname(IPPROTO_SCTP, (ushort_t)dport);
if (pn == NULL) {
pn = "";
} else {
(void) snprintf(buff, sizeof (buff), "(%s)", pn);
pn = buff;
}
(void) snprintf(get_line(0, 0), get_line_remain(),
"Destination port = %hu %s", dport, pn);
(void) snprintf(get_line(0, 0), get_line_remain(),
"Verification tag = 0x%.8x", ntohl(sctp->sh_verf));
(void) snprintf(get_line(0, 0), get_line_remain(),
"CRC-32c = 0x%.8x", ntohl(sctp->sh_chksum));
}
(void) interpret_chunks(flags, cp, fraglen);
if (flags & F_DTAIL) {
show_space();
}
}
/*
* Payload protocol ID table. Add new ULP information and parsers
* here.
*/
struct protoid_table {
int pid_num;
char *pid_short;
char *pid_long;
};
static struct protoid_table pid_sctp[] = {
1, "IUA", "ISDN Q.921 User Adaption Layer",
2, "M2UA", "SS7 MTP2 User Adaption Layer",
3, "M3UA", "SS7 MTP3 User Adaption Layer",
4, "SUA", "SS7 SCCP User Adaption Layer",
5, "M2PA", "SS7 MTP2-User Peer-to-Peer Adaption Layer",
6, "V5UA", "V5UA",
0, NULL, "",
};
static void
interpret_protoid(int flags, uint32_t ppid, char *data, int dlen)
{
struct protoid_table *p;
char pbuf[16];
/*
* Branch to a ULP interpreter here, or continue on to
* the default parser, which just tries to display
* printable characters from the payload.
*/
for (p = pid_sctp; p->pid_num; p++) {
if (ppid == p->pid_num) {
if (flags & F_SUM) {
(void) snprintf(get_sum_line(), MAXLINE,
"D=%d S=%d %s %s", dport, sport,
p->pid_short, show_string(data, dlen, 20));
}
if (flags & F_DTAIL) {
(void) snprintf(pbuf, MAXLINE, "%s: ",
p->pid_short);
show_header(pbuf, p->pid_long, dlen);
show_space();
(void) snprintf(get_line(0, 0),
get_line_remain(), "\"%s\"",
show_string(data, dlen, 60));
show_trailer();
}
return;
}
}
}