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