xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_sctp.c (revision 4f364e7c95ee7fd9d5bbeddc1940e92405bb0e72)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/sysmacros.h>
34 #include <inet/common.h>
35 #include <netinet/in.h>
36 #include <netinet/sctp.h>
37 #include <arpa/inet.h>
38 #include <string.h>
39 #include "snoop.h"
40 
41 /*
42  * Snoop interpreter for SCTP (rfc2960).
43  *
44  * To add support for an upper-layer protocol, modify either
45  * the port-dispatcher in snoop_rport.c, or the protocol ID
46  * dispatcher at the bottom of this file (or both).
47  */
48 
49 static void interpret_protoid(int, uint32_t, char *, int);
50 extern char *prot_prefix;
51 
52 /*
53  * This defines the length of internal, unbounded buffers. We set
54  * this to be MAXLINE (the maximum verbose display line length) -
55  * 64, which should be enough for all necessary descriptions. 64
56  * bytes seems like a reasonably conservative estimate of the
57  * maximum prefix length snoop may add to any text buffer it hands out.
58  */
59 #define	BUFLEN	MAXLINE - 64
60 
61 /*
62  * Common structure to hold descriptions and parsers for all
63  * chunks, parameters, and errors. Each parser should implement
64  * this interface:
65  *
66  * void parse(int flags, uint8_t cflags, void *data, int datalen);
67  *
68  * Where flags is the snoop flags, cflags are the chunk flags, data
69  * is the chunk or parameter data (not including the chunk or
70  * parameter header), and datalen is the length of the chunk or
71  * parameter data (again not including any headers).
72  */
73 typedef void parse_func_t(int, uint8_t, const void *, int);
74 
75 typedef struct {
76 	uint16_t id;
77 	const char *sdesc;	/* short description */
78 	const char *vdesc;	/* verbose description */
79 	parse_func_t *parse;	/* parser function */
80 } dispatch_t;
81 
82 static void interpret_params(const void *, int, char *, const dispatch_t *,
83     int, int);
84 
85 /*
86  * Chunk parsers
87  */
88 static parse_func_t parse_abort_chunk, parse_data_chunk, parse_error_chunk,
89     parse_init_chunk, parse_opaque_chunk, parse_sack_chunk,
90     parse_shutdone_chunk, parse_shutdown_chunk, parse_asconf_chunk,
91     parse_ftsn_chunk;
92 
93 
94 /*
95  * Chunk parser dispatch table. There are few enough chunks defined
96  * in the core protocol, and they are sequential, so the chunk code
97  * can be used as the index into this array for the common case.
98  * It is still necessary to check that the code and index match,
99  * since optional extensions will not follow sequentially the
100  * core chunks.
101  */
102 static const dispatch_t chunk_dispatch_table[] = {
103 /*	code	F_SUM desc	F_DTAIL desc		parser function */
104 	{ CHUNK_DATA,			"Data",		"Data Chunk",
105 	    parse_data_chunk },
106 	{ CHUNK_INIT,			"Init",		"Init Chunk",
107 	    parse_init_chunk },
108 	{ CHUNK_INIT_ACK,		"Init ACK",	"Init ACK Chunk",
109 	    parse_init_chunk },
110 	{ CHUNK_SACK,			"SACK",		"SACK Chunk",
111 	    parse_sack_chunk },
112 	{ CHUNK_HEARTBEAT,		"Heartbeat",	"Heartbeat Chunk",
113 	    parse_opaque_chunk },
114 	{ CHUNK_HEARTBEAT_ACK,		"Heartbeat ACK", "Heartbeat ACK Chunk",
115 	    parse_opaque_chunk },
116 	{ CHUNK_ABORT,			"Abort",	"Abort Chunk",
117 	    parse_abort_chunk },
118 	{ CHUNK_SHUTDOWN,		"Shutdown",	"Shutdown Chunk",
119 	    parse_shutdown_chunk },
120 	{ CHUNK_SHUTDOWN_ACK,		"Shutdown ACK",	"Shutdown ACK Chunk",
121 	    NULL },
122 	{ CHUNK_ERROR,			"Err",		"Error Chunk",
123 	    parse_error_chunk },
124 	{ CHUNK_COOKIE,			"Cookie",	"Cookie Chunk",
125 	    parse_opaque_chunk },
126 	{ CHUNK_COOKIE_ACK,		"Cookie ACK",	"Cookie ACK Chunk",
127 	    parse_opaque_chunk },
128 	{ CHUNK_ECNE,			"ECN Echo",	"ECN Echo Chunk",
129 	    parse_opaque_chunk },
130 	{ CHUNK_CWR,			"CWR",		"CWR Chunk",
131 	    parse_opaque_chunk },
132 	{ CHUNK_SHUTDOWN_COMPLETE,	"Shutdown Done", "Shutdown Done",
133 	    parse_shutdone_chunk },
134 	{ CHUNK_FORWARD_TSN,		"FORWARD TSN", 	"Forward TSN Chunk",
135 	    parse_ftsn_chunk },
136 	{ CHUNK_ASCONF_ACK,		"ASCONF ACK", 	"ASCONF ACK Chunk",
137 	    parse_asconf_chunk },
138 	{ CHUNK_ASCONF,			"ASCONF", 	"ASCONF Chunk",
139 	    parse_asconf_chunk }
140 };
141 
142 /*
143  * Parameter Parsers
144  */
145 static parse_func_t parse_encap_param, parse_int32_param, parse_ip4_param,
146     parse_ip6_param, parse_opaque_param, parse_suppaddr_param,
147     parse_unrec_chunk, parse_addip_param, parse_asconferr_param,
148     parse_asconfok_param, parse_addiperr_param;
149 
150 /*
151  * Parameter parser dispatch table. The summary description is not
152  * used here. Strictly speaking, parameter types are defined within
153  * the context of a chunk type. However, thus far the IETF WG has
154  * agreed to follow the convention that parameter types are globally
155  * unique (and why not, with a 16-bit namespace). However, if this
156  * ever changes, there will need to be different parameter dispatch
157  * tables for each chunk type.
158  */
159 static const dispatch_t parm_dispatch_table[] = {
160 /*	code	F_SUM desc	F_DTAIL desc		parser function */
161 	{ PARM_UNKNOWN,	"",		"Unknown Parameter",
162 	    parse_opaque_param },
163 	{ PARM_HBINFO,	"",		"Heartbeat Info",
164 	    parse_opaque_param },
165 	{ PARM_ADDR4,	"",		"IPv4 Address",
166 	    parse_ip4_param },
167 	{ PARM_ADDR6,	"",		"IPv6 Address",
168 	    parse_ip6_param },
169 	{ PARM_COOKIE,	"",		"Cookie",
170 	    parse_opaque_param },
171 	{ PARM_UNRECOGNIZED,	"",	"Unrecognized Param",
172 	    parse_encap_param },
173 	{ PARM_COOKIE_PRESERVE,	"",	"Cookie Preservative",
174 	    parse_opaque_param },
175 	{ 10,	"",			"Reserved for ECN",
176 	    parse_opaque_param },
177 	{ PARM_ADDR_HOST_NAME,	"",	"Host Name Parameter",
178 	    parse_opaque_param },
179 	{ PARM_SUPP_ADDRS,	"",	"Supported Addresses",
180 	    parse_suppaddr_param },
181 	{ PARM_ECN_CAPABLE,	"",	"ECN Capable",
182 	    parse_opaque_param },
183 	{ PARM_ADD_IP,	"",		"Add IP",
184 	    parse_addip_param },
185 	{ PARM_DEL_IP,	"",		"Del IP",
186 	    parse_addip_param },
187 	{ PARM_ASCONF_ERROR,	"",	"ASCONF Error Ind",
188 	    parse_asconferr_param },
189 	{ PARM_PRIMARY_ADDR,	"",	"Set Primary Address",
190 	    parse_addip_param },
191 	{ PARM_FORWARD_TSN,	"",	"Forward TSN",
192 	    NULL },
193 	{ PARM_ASCONF_SUCCESS,	"",	"ASCONF Success Ind",
194 	    parse_asconfok_param }
195 };
196 
197 /*
198  * Errors have the same wire format at parameters.
199  */
200 static const dispatch_t err_dispatch_table[] = {
201 /*	code	F_SUM desc	F_DTAIL desc		parser function */
202 	{ SCTP_ERR_UNKNOWN,	"",		"Unknown Error",
203 	    parse_opaque_param },
204 	{ SCTP_ERR_BAD_SID,	"",		"Invalid Stream ID",
205 	    parse_opaque_param },
206 	{ SCTP_ERR_MISSING_PARM,	"",	"Missing Parameter",
207 	    parse_opaque_param },
208 	{ SCTP_ERR_STALE_COOKIE,	"",	"Stale Cookie",
209 	    parse_int32_param },
210 	{ SCTP_ERR_NO_RESOURCES,	"",	"Out Of Resources",
211 	    parse_opaque_param },
212 	{ SCTP_ERR_BAD_ADDR,	"",		"Unresolvable Address",
213 	    parse_opaque_param },
214 	{ SCTP_ERR_UNREC_CHUNK,	"",		"Unrecognized Chunk",
215 	    parse_unrec_chunk },
216 	{ SCTP_ERR_BAD_MANDPARM,	"",	"Bad Mandatory Parameter",
217 	    parse_opaque_param },
218 	{ SCTP_ERR_UNREC_PARM,	"",		"Unrecognized Parameter",
219 	    parse_opaque_param },
220 	{ SCTP_ERR_NO_USR_DATA,	"",		"No User Data",
221 	    parse_int32_param },
222 	{ SCTP_ERR_COOKIE_SHUT,	"",		"Cookie During Shutdown",
223 	    parse_opaque_param },
224 	{ SCTP_ERR_DELETE_LASTADDR,	"",	"Delete Last Remaining Address",
225 	    parse_addiperr_param },
226 	{ SCTP_ERR_RESOURCE_SHORTAGE,	"",	"Resource Shortage",
227 	    parse_addiperr_param },
228 	{ SCTP_ERR_DELETE_SRCADDR,	"",	"Delete Source IP Address",
229 	    parse_addiperr_param },
230 	{ SCTP_ERR_AUTH_ERR,	"",		"Not authorized",
231 	    parse_addiperr_param }
232 };
233 
234 /*
235  * These are global because the data chunk parser needs them to dispatch
236  * to ULPs. The alternative is to add source and dest port arguments
237  * to every parser, which seems even messier (since *only* the data
238  * chunk parser needs it)...
239  */
240 static in_port_t sport, dport;
241 
242 /* Summary line miscellany */
243 static int sumlen;
244 static char scratch[MAXLINE];
245 static char *sumline;
246 
247 #define	SUMAPPEND(fmt) \
248 	sumlen -= snprintf fmt; \
249 	(void) strlcat(sumline, scratch, sumlen)
250 
251 #define	DUMPHEX_MAX	16
252 
253 static const dispatch_t *
254 lookup_dispatch(int id, const dispatch_t *tbl, int tblsz)
255 {
256 	int i;
257 
258 	/*
259 	 * Try fast lookup first. The common chunks defined in RFC2960
260 	 * will have indices aligned with their IDs, so this works for
261 	 * the common case.
262 	 */
263 	if (id < (tblsz - 1)) {
264 		if (id == tbl[id].id) {
265 			return (tbl + id);
266 		}
267 	}
268 
269 	/*
270 	 * Nope - probably an extension. Search the whole table,
271 	 * starting from the end, since extensions are at the end.
272 	 */
273 	for (i = tblsz - 1; i >= 0; i--) {
274 		if (id == tbl[i].id) {
275 			return (tbl + i);
276 		}
277 	}
278 
279 	return (NULL);
280 }
281 
282 /*
283  * Dumps no more than the first DUMPHEX_MAX bytes in hex. If
284  * the user wants more, they can use the -x option to snoop.
285  */
286 static void
287 dumphex(const uchar_t *payload, int payload_len, char *msg)
288 {
289 	int index;
290 	int end;
291 	char buf[BUFLEN];
292 
293 	if (payload_len == 0) {
294 		return;
295 	}
296 
297 	end = payload_len > DUMPHEX_MAX ? DUMPHEX_MAX : payload_len;
298 
299 	for (index = 0; index < end; index++) {
300 		(void) snprintf(&buf[index * 3], 4, " %.2x", payload[index]);
301 	}
302 
303 	if (payload_len > DUMPHEX_MAX) {
304 		(void) strlcat(buf, " ...", BUFLEN);
305 	}
306 
307 	(void) snprintf(get_line(0, 0), BUFLEN, msg, buf);
308 }
309 
310 /*
311  * Present perscribed action for unknowns according to rfc2960. Works
312  * for chunks and parameters as well if the parameter type is
313  * shifted 8 bits right.
314  */
315 static const char *
316 get_action_desc(uint8_t id)
317 {
318 	if ((id & 0xc0) == 0xc0) {
319 		return (": skip on unknown, return error");
320 	} else if ((id & 0x80) == 0x80) {
321 		return (": skip on unknown, no error");
322 	} else if ((id & 0x40) == 0x40) {
323 		return (": stop on unknown, return error");
324 	}
325 
326 	/* Top two bits are clear */
327 	return (": stop on unknown, no error");
328 }
329 
330 /* ARGSUSED */
331 static void
332 parse_asconfok_param(int flags, uint8_t notused, const void *data, int dlen)
333 {
334 	uint32_t	*cid;
335 
336 	if (dlen < sizeof (*cid)) {
337 		(void) snprintf(get_line(0, 0), get_line_remain(),
338 		    "  ==> Incomplete ASCONF Success Ind parameter");
339 		return;
340 	}
341 	cid = (uint32_t *)data;
342 	(void) snprintf(get_line(0, 0), get_line_remain(), "  ASCONF CID = %u",
343 	    ntohl(*cid));
344 }
345 
346 /* ARGSUSED */
347 static void
348 parse_asconferr_param(int flags, uint8_t notused, const void *data, int dlen)
349 {
350 	uint32_t	*cid;
351 
352 	if (dlen < sizeof (*cid)) {
353 		(void) snprintf(get_line(0, 0), get_line_remain(),
354 		    "  ==> Incomplete ASCONF Error Ind parameter");
355 		return;
356 	}
357 	cid = (uint32_t *)data;
358 	(void) snprintf(get_line(0, 0), get_line_remain(), "  ASCONF CID = %u",
359 	    ntohl(*cid));
360 
361 	interpret_params(cid + 1, dlen - sizeof (*cid), "Error",
362 	    err_dispatch_table, A_CNT(err_dispatch_table), flags);
363 }
364 
365 /* ARGSUSED */
366 static void
367 parse_addiperr_param(int flags, uint8_t notused, const void *data, int dlen)
368 {
369 
370 	interpret_params(data, dlen, "Parameter",
371 	    parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
372 }
373 
374 /* ARGSUSED */
375 static void
376 parse_addip_param(int flags, uint8_t notused, const void *data, int dlen)
377 {
378 
379 	uint32_t	*cid;
380 
381 	if (dlen < sizeof (*cid)) {
382 		(void) snprintf(get_line(0, 0), get_line_remain(),
383 		    "  ==> Incomplete ASCONF Error Ind parameter");
384 		return;
385 	}
386 	cid = (uint32_t *)data;
387 	(void) snprintf(get_line(0, 0), get_line_remain(), "  ASCONF CID = %u",
388 	    ntohl(*cid));
389 
390 	interpret_params(cid + 1, dlen - sizeof (*cid), "Parameter",
391 	    parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
392 }
393 
394 /* ARGSUSED */
395 static void
396 parse_ip4_param(int flags, uint8_t notused, const void *data, int datalen)
397 {
398 	char abuf[INET_ADDRSTRLEN];
399 	char *ap;
400 
401 	if (datalen < sizeof (in_addr_t)) {
402 		(void) snprintf(get_line(0, 0), get_line_remain(),
403 		    "  ==> Incomplete IPv4 Addr parameter");
404 		return;
405 	}
406 
407 	ap = (char *)inet_ntop(AF_INET, data, abuf, INET_ADDRSTRLEN);
408 	if (ap == NULL) {
409 		ap = "<Bad Address>";
410 	}
411 
412 	(void) snprintf(get_line(0, 0), get_line_remain(), "  Addr = %s", ap);
413 }
414 
415 /* ARGSUSED */
416 static void
417 parse_ip6_param(int flags, uint8_t notused, const void *data, int datalen)
418 {
419 	char abuf[INET6_ADDRSTRLEN];
420 	char *ap;
421 
422 	if (datalen < sizeof (in6_addr_t)) {
423 		(void) snprintf(get_line(0, 0), get_line_remain(),
424 		    "  ==> Incomplete IPv6 Addr parameter");
425 		return;
426 	}
427 
428 	ap = (char *)inet_ntop(AF_INET6, data, abuf, INET6_ADDRSTRLEN);
429 	if (ap == NULL) {
430 		ap = "<Bad Address>";
431 	}
432 
433 	(void) snprintf(get_line(0, 0), get_line_remain(), "  Addr = %s", ap);
434 }
435 
436 /* ARGSUSED */
437 static void
438 parse_int32_param(int flags, uint8_t notused, const void *data, int datalen)
439 {
440 	if (datalen < 4) {
441 		(void) snprintf(get_line(0, 0), get_line_remain(),
442 		    "  ==> Incomplete INT32 parameter");
443 		return;
444 	}
445 	(void) snprintf(get_line(0, 0), get_line_remain(), "  INT32 = %u",
446 	    ntohl(*(uint32_t *)data));
447 }
448 
449 /* ARGSUSED */
450 static void
451 parse_suppaddr_param(int flags, uint8_t notused, const void *data, int dlen)
452 {
453 	const uint16_t *type;
454 
455 	if (dlen < 2) {
456 		(void) snprintf(get_line(0, 0), get_line_remain(),
457 		    "==> Incomplete Supported Addr parameter");
458 		return;
459 	}
460 
461 	type = data;
462 	while (dlen > 0) {
463 		switch (ntohs(*type)) {
464 		case PARM_ADDR4:
465 			(void) snprintf(get_line(0, 0), get_line_remain(),
466 			    "  IPv4");
467 			break;
468 		case PARM_ADDR6:
469 			(void) snprintf(get_line(0, 0), get_line_remain(),
470 			    "  IPv6");
471 			break;
472 		case PARM_ADDR_HOST_NAME:
473 			(void) snprintf(get_line(0, 0), get_line_remain(),
474 			    "  Host Name");
475 			break;
476 		default:
477 			(void) snprintf(get_line(0, 0), get_line_remain(),
478 			    "Unknown Type (%hu)", ntohs(*type));
479 			break;
480 		}
481 		dlen -= sizeof (*type);
482 		type++;
483 	}
484 }
485 
486 /*ARGSUSED*/
487 static void
488 parse_encap_param(int flags, uint8_t notused, const void *data, int dlen)
489 {
490 	if (dlen < sizeof (sctp_parm_hdr_t)) {
491 		(void) snprintf(get_line(0, 0), get_line_remain(),
492 		    "==> Incomplete Parameter");
493 		return;
494 	}
495 
496 	interpret_params(data, dlen, "Parameter",
497 	    parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
498 }
499 
500 /* ARGSUSED */
501 static void
502 parse_unrec_chunk(int flags, uint8_t cflags, const void *data, int datalen)
503 {
504 	const sctp_chunk_hdr_t *cp = data;
505 	const dispatch_t *dp;
506 	const char *actstr;
507 
508 	if (datalen < sizeof (*cp)) {
509 		(void) snprintf(get_line(0, 0), get_line_remain(),
510 		    "==> Incomplete Unrecognized Chunk Error");
511 		return;
512 	}
513 
514 	/* Maybe snoop knows about this chunk? */
515 	dp = lookup_dispatch(cp->sch_id, chunk_dispatch_table,
516 	    A_CNT(chunk_dispatch_table));
517 	if (dp != NULL) {
518 		(void) snprintf(get_line(0, 0), get_line_remain(),
519 		    "  Chunk Type = %u (%s)", cp->sch_id, dp->vdesc);
520 	} else {
521 		actstr = get_action_desc(cp->sch_id);
522 		(void) snprintf(get_line(0, 0), get_line_remain(),
523 		    "  Chunk Type = %u%s", cp->sch_id, actstr);
524 	}
525 }
526 
527 /*
528  * Same as parse_opaque_chunk except for the indentation.
529  */
530 /* ARGSUSED */
531 static void
532 parse_opaque_param(int flags, uint8_t cflags, const void *data, int datalen)
533 {
534 	dumphex(data, datalen, " Data = %s");
535 }
536 
537 /*
538  * Loops through all parameters (or errors) until it has read
539  * datalen bytes of information, finding a parser for each.
540  * The tbl argument allows the caller to specify which dispatch
541  * table to use, making this function useful for both parameters
542  * and errors. The type argument is used to denote whether this
543  * is an error or parameter in detailed mode.
544  */
545 static void
546 interpret_params(const void *data, int datalen, char *type,
547     const dispatch_t *tbl, int tbl_size, int flags)
548 {
549 	const sctp_parm_hdr_t *hdr = data;
550 	uint16_t plen;
551 	uint16_t ptype;
552 	const char *desc;
553 	parse_func_t *parse;
554 	int pad;
555 	const dispatch_t *dp;
556 	const char *actstr;
557 
558 	for (;;) {
559 		/*
560 		 * Adjust for padding: if the address isn't aligned, there
561 		 * should be some padding. So skip over the padding and
562 		 * adjust hdr accordingly. RFC2960 mandates that all
563 		 * parameters must be 32-bit aligned WRT the enclosing chunk,
564 		 * which ensures that this parameter header will
565 		 * be 32-bit aligned in memory. We must, of course, bounds
566 		 * check fraglen before actually trying to use hdr, in
567 		 * case the packet has been mangled or is the product
568 		 * of a buggy implementation.
569 		 */
570 		if ((pad = (uintptr_t)hdr % SCTP_ALIGN) != 0) {
571 			pad = SCTP_ALIGN - pad;
572 			datalen -= pad;
573 		/* LINTED pointer cast may result in improper alignment */
574 			hdr = (sctp_parm_hdr_t *)((char *)hdr + pad);
575 		}
576 
577 		/* Need to compare against 0 1st, since sizeof is unsigned */
578 		if (datalen < 0 || datalen < sizeof (*hdr)) {
579 			if (datalen > 0) {
580 				(void) snprintf(get_line(0, 0),
581 				    get_line_remain(),
582 				    "==> Extra data after last parameter");
583 			}
584 			return;
585 		}
586 		plen = ntohs(hdr->sph_len);
587 		if (datalen < plen || plen < sizeof (*hdr)) {
588 			(void) snprintf(get_line(0, 0), get_line_remain(),
589 			    "  ==> Incomplete %s", type);
590 			return;
591 		}
592 
593 		/* Get description and parser */
594 		ptype = ntohs(hdr->sph_type);
595 		desc = "Unknown Parameter Type";
596 		parse = parse_opaque_param;
597 		dp = lookup_dispatch(ptype, tbl, tbl_size);
598 		if (dp != NULL) {
599 			desc = dp->vdesc;
600 			parse = dp->parse;
601 		}
602 
603 		show_space();
604 		if (dp != NULL) {
605 			actstr = "";
606 		} else {
607 			actstr = get_action_desc((uint8_t)(ptype >> 8));
608 		}
609 		(void) snprintf(get_line(0, 0), get_line_remain(),
610 		    "  ------- SCTP %s Type = %s (%u%s)", type, desc, ptype,
611 		    actstr);
612 		(void) snprintf(get_line(0, 0), get_line_remain(),
613 		    "  Data length = %hu", plen - sizeof (*hdr));
614 
615 		if (parse != NULL) {
616 			parse(flags, 0, (char *)(hdr + 1),
617 			    plen - sizeof (*hdr));
618 		}
619 		datalen -= plen;
620 		/* LINTED pointer cast may result in improper alignment */
621 		hdr = (sctp_parm_hdr_t *)((char *)hdr + plen);
622 	}
623 }
624 
625 /* ARGSUSED */
626 static void
627 parse_ftsn_chunk(int flags, uint8_t cflags, const void *data, int datalen)
628 {
629 	uint32_t	*ftsn;
630 	ftsn_entry_t	*ftsn_entry;
631 
632 	if (datalen < (sizeof (*ftsn) + sizeof (*ftsn_entry))) {
633 		if (flags & F_DTAIL) {
634 			(void) snprintf(get_line(0, 0), get_line_remain(),
635 			    "==> Incomplete FORWARD-TSN chunk");
636 		}
637 		return;
638 	}
639 
640 	ftsn = (uint32_t *)data;
641 	if (flags & F_SUM) {
642 		SUMAPPEND((scratch, MAXLINE, "CTSN %x ", ntohl(*ftsn)));
643 		return;
644 	}
645 	(void) snprintf(get_line(0, 0), get_line_remain(), "Cum TSN=  %x",
646 	    ntohl(*ftsn));
647 
648 	datalen -= sizeof (*ftsn);
649 	ftsn_entry = (ftsn_entry_t *)(ftsn + 1);
650 	while (datalen >= sizeof (*ftsn_entry)) {
651 		(void) snprintf(get_line(0, 0), get_line_remain(),
652 		    "SID =  %u : SSN = %u", ntohs(ftsn_entry->ftsn_sid),
653 		    ntohs(ftsn_entry->ftsn_ssn));
654 		datalen -= sizeof (*ftsn_entry);
655 		ftsn_entry++;
656 	}
657 }
658 
659 /* ARGSUSED */
660 static void
661 parse_asconf_chunk(int flags, uint8_t cflags, const void *data, int datalen)
662 {
663 	uint32_t	*sn;
664 
665 	if (datalen < sizeof (*sn)) {
666 		if (flags & F_DTAIL) {
667 			(void) snprintf(get_line(0, 0), get_line_remain(),
668 			    "==> Incomplete ASCONF chunk");
669 		}
670 		return;
671 	}
672 
673 	sn = (uint32_t *)data;
674 	if (flags & F_SUM) {
675 		SUMAPPEND((scratch, MAXLINE, "sn %x ", ntohl(*sn)));
676 		return;
677 	}
678 	(void) snprintf(get_line(0, 0), get_line_remain(), "Serial Number=  %x",
679 	    ntohl(*sn));
680 	interpret_params(sn + 1, datalen - sizeof (*sn), "Parameter",
681 	    parm_dispatch_table, A_CNT(parm_dispatch_table), flags);
682 }
683 
684 static void
685 parse_init_chunk(int flags, uint8_t cflags, const void *data, int datalen)
686 {
687 	const sctp_init_chunk_t *icp = data;
688 
689 	if (datalen < sizeof (*icp)) {
690 		if (flags & F_DTAIL) {
691 			(void) snprintf(get_line(0, 0), get_line_remain(),
692 			    "==> Incomplete INIT chunk");
693 		}
694 		return;
695 	}
696 
697 	if (flags & F_SUM) {
698 		SUMAPPEND((scratch, MAXLINE, "tsn %x str %hu/%hu win %u ",
699 		    ntohl(icp->sic_inittsn), ntohs(icp->sic_outstr),
700 		    ntohs(icp->sic_instr), ntohl(icp->sic_a_rwnd)));
701 		return;
702 	}
703 
704 	(void) snprintf(get_line(0, 0), get_line_remain(), "Flags = 0x%.2x",
705 	    cflags);
706 	(void) snprintf(get_line(0, 0), get_line_remain(),
707 	    "Initiate tag = 0x%.8x", ntohl(icp->sic_inittag));
708 	(void) snprintf(get_line(0, 0), get_line_remain(),
709 	    "Advertised receiver window credit = %u", ntohl(icp->sic_a_rwnd));
710 	(void) snprintf(get_line(0, 0), get_line_remain(),
711 	    "Outbound streams = %hu", ntohs(icp->sic_outstr));
712 	(void) snprintf(get_line(0, 0), get_line_remain(),
713 	    "Inbound streams = %hu", ntohs(icp->sic_instr));
714 	(void) snprintf(get_line(0, 0), get_line_remain(),
715 	    "Initial TSN = 0x%.8x", ntohl(icp->sic_inittsn));
716 
717 	if (datalen > sizeof (*icp)) {
718 		interpret_params(icp + 1, datalen - sizeof (*icp),
719 		    "Parameter", parm_dispatch_table,
720 		    A_CNT(parm_dispatch_table), flags);
721 	}
722 }
723 
724 static void
725 parse_data_chunk(int flags, uint8_t cflags, const void *data, int datalen)
726 {
727 	const sctp_data_chunk_t	*dcp = data;
728 	char			*payload;
729 	uint32_t		ppid;
730 
731 	if (datalen < sizeof (*dcp)) {
732 		if (flags & F_DTAIL) {
733 			(void) snprintf(get_line(0, 0), get_line_remain(),
734 			    "==> Incomplete DATA chunk %d (%d)", datalen,
735 			    sizeof (*dcp));
736 		}
737 		return;
738 	}
739 
740 	ppid = ntohl(dcp->sdc_payload_id);
741 	/* This is the actual data len, excluding the data chunk header. */
742 	datalen -= sizeof (*dcp);
743 
744 	if (flags & F_DTAIL) {
745 		(void) snprintf(get_line(0, 0), get_line_remain(),
746 		    "flags = 0x%.2x", cflags);
747 		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
748 		    getflag(cflags, SCTP_DATA_UBIT, "unordered", "ordered"));
749 		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
750 		    getflag(cflags, SCTP_DATA_BBIT,
751 		    "beginning", "(beginning unset)"));
752 		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
753 		    getflag(cflags, SCTP_DATA_EBIT, "end", "(end unset)"));
754 		(void) snprintf(get_line(0, 0), get_line_remain(),
755 		    "TSN = 0x%.8x", ntohl(dcp->sdc_tsn));
756 		(void) snprintf(get_line(0, 0), get_line_remain(),
757 		    "Stream ID = %hu", ntohs(dcp->sdc_sid));
758 		(void) snprintf(get_line(0, 0), get_line_remain(),
759 		    "Stream Sequence Number = %hu", ntohs(dcp->sdc_ssn));
760 		(void) snprintf(get_line(0, 0), get_line_remain(),
761 		    "Payload Protocol ID = 0x%.8x", ppid);
762 		(void) snprintf(get_line(0, 0), get_line_remain(),
763 		    "Data Length = %d", datalen);
764 		show_space();
765 	}
766 	if (flags & F_SUM) {
767 		SUMAPPEND((scratch, MAXLINE, "len %d tsn %x str %hu/%hu "
768 		    "ppid %x ", datalen, ntohl(dcp->sdc_tsn),
769 		    ntohs(dcp->sdc_sid), ntohs(dcp->sdc_ssn), ppid));
770 	}
771 
772 	/*
773 	 * Go to the next protocol layer, but not if we are in
774 	 * summary mode only. In summary mode, each ULP parse would
775 	 * create a new line, and if there were several data chunks
776 	 * bundled together in the packet, this would confuse snoop's
777 	 * packet numbering and timestamping.
778 	 *
779 	 * SCTP carries two ways to determine an ULP: ports and the
780 	 * payload protocol identifier (ppid). Since ports are the
781 	 * better entrenched convention, we first try interpret_reserved().
782 	 * If that fails to find a parser, we try by the PPID.
783 	 */
784 	if (!(flags & F_ALLSUM) && !(flags & F_DTAIL)) {
785 		return;
786 	}
787 
788 	payload = (char *)(dcp + 1);
789 	if (!interpret_reserved(flags, IPPROTO_SCTP, sport, dport, payload,
790 	    datalen) && ppid != 0) {
791 
792 		interpret_protoid(flags, ppid, payload, datalen);
793 	}
794 
795 	/*
796 	 * Reset the protocol prefix, since it may have been changed
797 	 * by a ULP interpreter.
798 	 */
799 	prot_prefix = "SCTP:  ";
800 }
801 
802 /* ARGSUSED */
803 static void
804 parse_sack_chunk(int flags, uint8_t cflags, const void *data, int datalen)
805 {
806 	const sctp_sack_chunk_t *scp = data;
807 	uint16_t numfrags, numdups;
808 	sctp_sack_frag_t *frag;
809 	int i;
810 	uint32_t *tsn;
811 
812 	if (datalen < sizeof (*scp)) {
813 		if (flags & F_DTAIL) {
814 			(void) snprintf(get_line(0, 0), get_line_remain(),
815 			    "==> Incomplete SACK chunk");
816 		}
817 		return;
818 	}
819 
820 	if (flags & F_DTAIL) {
821 		(void) snprintf(get_line(0, 0), get_line_remain(),
822 		    "Cumulative TSN ACK = 0x%.8x", ntohl(scp->ssc_cumtsn));
823 		(void) snprintf(get_line(0, 0), get_line_remain(),
824 		    "Advertised Receiver Window Credit = %u",
825 		    ntohl(scp->ssc_a_rwnd));
826 		numfrags = ntohs(scp->ssc_numfrags);
827 		numdups = ntohs(scp->ssc_numdups);
828 		(void) snprintf(get_line(0, 0), get_line_remain(),
829 		    "Number of Fragments = %hu", numfrags);
830 		(void) snprintf(get_line(0, 0), get_line_remain(),
831 		    "Number of Duplicates = %hu", numdups);
832 
833 		/* Display any gap reports */
834 		datalen -= sizeof (*scp);
835 		if (datalen < (numfrags * sizeof (*frag))) {
836 			(void) snprintf(get_line(0, 0), get_line_remain(),
837 			    "  ==> Malformed gap report listing");
838 			return;
839 		}
840 		frag = (sctp_sack_frag_t *)(scp + 1);
841 		for (i = 0; i < numfrags; i++) {
842 			(void) snprintf(get_line(0, 0), get_line_remain(),
843 			    "  Fragment #%d: Start = %hu, end = %hu", i,
844 			    ntohs(frag->ssf_start), ntohs(frag->ssf_end));
845 			frag += 1;
846 		}
847 
848 		/* Display any duplicate reports */
849 		datalen -= numfrags * sizeof (*frag);
850 		if (datalen < (numdups * sizeof (*tsn))) {
851 			(void) snprintf(get_line(0, 0), get_line_remain(),
852 			    "  ==> Malformed duplicate report listing");
853 			return;
854 		}
855 		/* LINTED pointer cast may result in improper alignment */
856 		tsn = (uint32_t *)frag;
857 		for (i = 0; i < numdups; i++) {
858 			(void) snprintf(get_line(0, 0), get_line_remain(),
859 			    "  Duplicate #%d: TSN = %x", i, *tsn);
860 			tsn++;
861 		}
862 	}
863 	if (flags & F_SUM) {
864 		SUMAPPEND((scratch, MAXLINE,
865 		    "tsn %x win %u gaps/dups %hu/%hu ", ntohl(scp->ssc_cumtsn),
866 		    ntohl(scp->ssc_a_rwnd), ntohs(scp->ssc_numfrags),
867 		    ntohs(scp->ssc_numdups)));
868 	}
869 }
870 
871 /* ARGSUSED */
872 static void
873 parse_shutdown_chunk(int flags, uint8_t cflags, const void *data, int datalen)
874 {
875 	const uint32_t *cumtsn = data;
876 
877 	if (datalen < sizeof (*cumtsn)) {
878 		if (flags & F_DTAIL) {
879 			(void) snprintf(get_line(0, 0), get_line_remain(),
880 			    "==> Incomplete Shutdown chunk");
881 		}
882 		return;
883 	}
884 
885 	if (flags & F_DTAIL) {
886 		(void) snprintf(get_line(0, 0), get_line_remain(),
887 		    "Cumulative TSN = 0x%.8x", ntohl(*cumtsn));
888 	}
889 	if (flags & F_SUM) {
890 		SUMAPPEND((scratch, MAXLINE, "tsn %x", ntohl(*cumtsn)));
891 	}
892 }
893 
894 /* ARGSUSED */
895 static void
896 parse_error_chunk(int flags, uint8_t cflags, const void *data, int datalen)
897 {
898 	if (!(flags & F_DTAIL)) {
899 		return;
900 	}
901 
902 	interpret_params(data, datalen, "Error", err_dispatch_table,
903 	    A_CNT(err_dispatch_table), flags);
904 }
905 
906 static void
907 parse_abort_chunk(int flags, uint8_t cflags, const void *data, int datalen)
908 {
909 	if (!(flags & F_DTAIL)) {
910 		return;
911 	}
912 
913 	(void) snprintf(get_line(0, 0), get_line_remain(), "flags = 0x%.2x",
914 	    cflags);
915 	(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
916 	    getflag(cflags, SCTP_TBIT, "TCB not destroyed", "TCB destroyed"));
917 
918 	interpret_params(data, datalen, "Error", err_dispatch_table,
919 	    A_CNT(err_dispatch_table), flags);
920 }
921 
922 /* ARGSUSED2 */
923 static void
924 parse_shutdone_chunk(int flags, uint8_t cflags, const void *data, int datalen)
925 {
926 	if (!(flags & F_DTAIL)) {
927 		return;
928 	}
929 
930 	(void) snprintf(get_line(0, 0), get_line_remain(), "flags = 0x%.2x",
931 	    cflags);
932 	(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
933 	    getflag(cflags, SCTP_TBIT, "TCB not destroyed", "TCB destroyed"));
934 }
935 
936 /* ARGSUSED */
937 static void
938 parse_opaque_chunk(int flags, uint8_t cflags, const void *data, int datalen)
939 {
940 	if (!(flags & F_DTAIL)) {
941 		return;
942 	}
943 	if (datalen == 0) {
944 		return;
945 	}
946 
947 	dumphex(data, datalen, "Data = %s");
948 }
949 
950 /*
951  * Loops through all chunks until it has read fraglen bytes of
952  * information, finding a parser for each. If any parameters are
953  * present, interpret_params() is then called. Returns the remaining
954  * fraglen.
955  */
956 static int
957 interpret_chunks(int flags, sctp_chunk_hdr_t *cp, int fraglen)
958 {
959 	uint16_t clen;
960 	int signed_len;
961 	int pad;
962 	const char *desc;
963 	parse_func_t *parse;
964 	const dispatch_t *dp;
965 	const char *actstr;
966 
967 	for (;;) {
968 		/*
969 		 * Adjust for padding: if the address isn't aligned, there
970 		 * should be some padding. So skip over the padding and
971 		 * adjust hdr accordingly. RFC2960 mandates that all
972 		 * chunks must be 32-bit aligned WRT the SCTP common hdr,
973 		 * which ensures that this chunk header will
974 		 * be 32-bit aligned in memory. We must, of course, bounds
975 		 * check fraglen before actually trying to use hdr, in
976 		 * case the packet has been mangled or is the product
977 		 * of a buggy implementation.
978 		 */
979 		if ((pad = (uintptr_t)cp % SCTP_ALIGN) != 0) {
980 			pad = SCTP_ALIGN - pad;
981 			fraglen -= pad;
982 		/* LINTED pointer cast may result in improper alignment */
983 			cp = (sctp_chunk_hdr_t *)((char *)cp + pad);
984 		}
985 
986 		/* Need to compare against 0 1st, since sizeof is unsigned */
987 		if (fraglen < 0 || fraglen < sizeof (*cp)) {
988 			if (fraglen > 0 && flags & F_DTAIL) {
989 				(void) snprintf(get_line(0, 0),
990 				    get_line_remain(),
991 				    "==> Extra data after last chunk");
992 			}
993 			return (fraglen);
994 		}
995 
996 		clen = ntohs(cp->sch_len);
997 		if (fraglen < clen) {
998 			if (flags & F_DTAIL) {
999 				(void) snprintf(get_line(0, 0),
1000 				    get_line_remain(), "==> Corrupted chunk");
1001 			}
1002 			return (fraglen);
1003 		}
1004 
1005 		signed_len = clen - sizeof (*cp);
1006 		if (signed_len < 0) {
1007 			if (flags & F_DTAIL) {
1008 				(void) snprintf(get_line(0, 0),
1009 				    get_line_remain(),
1010 				    "==> Incomplete or corrupted chunk");
1011 			}
1012 			return (0);
1013 		}
1014 
1015 		/* Get description and parser */
1016 		dp = lookup_dispatch(cp->sch_id, chunk_dispatch_table,
1017 		    A_CNT(chunk_dispatch_table));
1018 		if (dp != NULL) {
1019 			if (flags & F_SUM) {
1020 				desc = dp->sdesc;
1021 			} else if (flags & F_DTAIL) {
1022 				desc = dp->vdesc;
1023 			}
1024 			parse = dp->parse;
1025 		} else {
1026 			if (flags & F_SUM) {
1027 				desc = "UNK";
1028 			} else if (flags & F_DTAIL) {
1029 				desc = "Unknown Chunk Type";
1030 			}
1031 			parse = parse_opaque_chunk;
1032 		}
1033 
1034 		if (flags & F_SUM) {
1035 			SUMAPPEND((scratch, MAXLINE, "%s ", desc));
1036 		}
1037 		if (flags & F_DTAIL) {
1038 			show_space();
1039 
1040 			if (dp != NULL) {
1041 				actstr = "";
1042 			} else {
1043 				actstr = get_action_desc(cp->sch_id);
1044 			}
1045 			(void) snprintf(get_line(0, 0), get_line_remain(),
1046 			    "------- SCTP Chunk Type = %s (%u%s)", desc,
1047 			    cp->sch_id, actstr);
1048 
1049 			(void) snprintf(get_line(0, 0), get_line_remain(),
1050 			    "Chunk length = %hu", clen);
1051 		}
1052 
1053 		if (parse != NULL) {
1054 			parse(flags, cp->sch_flags, (char *)(cp + 1),
1055 			    signed_len);
1056 		}
1057 
1058 		fraglen -= clen;
1059 
1060 		/* LINTED pointer cast may result in improper alignment */
1061 		cp = (sctp_chunk_hdr_t *)((char *)cp + clen);
1062 	}
1063 }
1064 
1065 void
1066 interpret_sctp(int flags, sctp_hdr_t *sctp, int iplen, int fraglen)
1067 {
1068 	int len_from_iphdr;
1069 	sctp_chunk_hdr_t *cp;
1070 	char *pn;
1071 	char buff[32];
1072 
1073 	/*
1074 	 * Alignment check. If the header is 32-bit aligned, all other
1075 	 * protocol units will also be aligned, as mandated by rfc2960.
1076 	 * Buggy packets will be caught and flagged by chunk and
1077 	 * parameter bounds checking.
1078 	 * If the header is not aligned, however, we drop the packet.
1079 	 */
1080 	if (!IS_P2ALIGNED(sctp, SCTP_ALIGN)) {
1081 		if (flags & F_DTAIL) {
1082 			(void) snprintf(get_line(0, 0), get_line_remain(),
1083 			    "==> SCTP header not aligned, dropping");
1084 		}
1085 		return;
1086 	}
1087 
1088 	fraglen -= sizeof (*sctp);
1089 	if (fraglen < 0) {
1090 		if (flags & F_DTAIL) {
1091 			(void) snprintf(get_line(0, 0), get_line_remain(),
1092 			    "==> Incomplete sctp header");
1093 		}
1094 		return;
1095 	}
1096 	/* If fraglen is somehow longer than the IP payload, adjust it */
1097 	len_from_iphdr = iplen - sizeof (*sctp);
1098 	if (fraglen > len_from_iphdr) {
1099 		fraglen = len_from_iphdr;
1100 	}
1101 
1102 	/* Keep track of the ports */
1103 	sport = ntohs(sctp->sh_sport);
1104 	dport = ntohs(sctp->sh_dport);
1105 
1106 	/* Set pointer to first chunk */
1107 	cp = (sctp_chunk_hdr_t *)(sctp + 1);
1108 
1109 	if (flags & F_SUM) {
1110 		sumline = get_sum_line();
1111 		*sumline = '\0';
1112 		sumlen = MAXLINE;
1113 
1114 		SUMAPPEND((scratch, MAXLINE, "SCTP D=%d S=%d ", dport, sport));
1115 	}
1116 
1117 	if (flags & F_DTAIL) {
1118 		show_header("SCTP:  ", "SCTP Header", fraglen);
1119 		show_space();
1120 
1121 		pn = getportname(IPPROTO_SCTP, (ushort_t)sport);
1122 		if (pn == NULL) {
1123 			pn = "";
1124 		} else {
1125 			(void) snprintf(buff, sizeof (buff), "(%s)", pn);
1126 			pn = buff;
1127 		}
1128 		(void) snprintf(get_line(0, 0), get_line_remain(),
1129 		    "Source port = %hu %s", sport, pn);
1130 
1131 		pn = getportname(IPPROTO_SCTP, (ushort_t)dport);
1132 		if (pn == NULL) {
1133 			pn = "";
1134 		} else {
1135 			(void) snprintf(buff, sizeof (buff), "(%s)", pn);
1136 			pn = buff;
1137 		}
1138 		(void) snprintf(get_line(0, 0), get_line_remain(),
1139 		    "Destination port = %hu %s", dport, pn);
1140 		(void) snprintf(get_line(0, 0), get_line_remain(),
1141 		    "Verification tag = 0x%.8x", ntohl(sctp->sh_verf));
1142 		(void) snprintf(get_line(0, 0), get_line_remain(),
1143 		    "CRC-32c = 0x%.8x", ntohl(sctp->sh_chksum));
1144 	}
1145 
1146 	(void) interpret_chunks(flags, cp, fraglen);
1147 
1148 	if (flags & F_DTAIL) {
1149 		show_space();
1150 	}
1151 }
1152 
1153 /*
1154  * Payload protocol ID table. Add new ULP information and parsers
1155  * here.
1156  */
1157 
1158 struct protoid_table {
1159 	int	pid_num;
1160 	char	*pid_short;
1161 	char	*pid_long;
1162 };
1163 
1164 static struct protoid_table pid_sctp[] = {
1165 	1,	"IUA",		"ISDN Q.921 User Adaption Layer",
1166 	2,	"M2UA",		"SS7 MTP2 User Adaption Layer",
1167 	3,	"M3UA",		"SS7 MTP3 User Adaption Layer",
1168 	4,	"SUA",		"SS7 SCCP User Adaption Layer",
1169 	5,	"M2PA",		"SS7 MTP2-User Peer-to-Peer Adaption Layer",
1170 	6,	"V5UA",		"V5UA",
1171 	0,	NULL,		"",
1172 };
1173 
1174 static void
1175 interpret_protoid(int flags, uint32_t ppid, char *data, int dlen)
1176 {
1177 	struct protoid_table *p;
1178 	char pbuf[16];
1179 
1180 	/*
1181 	 * Branch to a ULP interpreter here, or continue on to
1182 	 * the default parser, which just tries to display
1183 	 * printable characters from the payload.
1184 	 */
1185 
1186 	for (p = pid_sctp; p->pid_num; p++) {
1187 		if (ppid == p->pid_num) {
1188 			if (flags & F_SUM) {
1189 				(void) snprintf(get_sum_line(), MAXLINE,
1190 				    "D=%d S=%d %s %s", dport, sport,
1191 				    p->pid_short, show_string(data, dlen, 20));
1192 			}
1193 
1194 			if (flags & F_DTAIL) {
1195 				(void) snprintf(pbuf, MAXLINE, "%s:  ",
1196 				    p->pid_short);
1197 				show_header(pbuf, p->pid_long, dlen);
1198 				show_space();
1199 				(void) snprintf(get_line(0, 0),
1200 				    get_line_remain(), "\"%s\"",
1201 				    show_string(data, dlen, 60));
1202 				show_trailer();
1203 			}
1204 
1205 			return;
1206 		}
1207 	}
1208 }
1209