xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_icmp.c (revision 1b4cc0f0a8c62a4b0428f5565cbc45649f4f8107)
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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright 2024 Bill Sommerfeld <sommerfeld@hamachi.org>
27  */
28 
29 #include <stdio.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <net/if.h>
34 #include <sys/stropts.h>
35 #include <sys/sysmacros.h>
36 #include <netinet/in_systm.h>
37 #include <netinet/in.h>
38 #include <netinet/ip.h>
39 #include <netinet/ip_icmp.h>
40 #include <netinet/udp.h>
41 #include <netinet/tcp.h>
42 #include <netinet/icmp6.h>
43 #include <netinet/ip6.h>
44 #include <inet/ip.h>
45 #include <inet/ip6.h>
46 #include <arpa/inet.h>
47 #include <netdb.h>
48 #include <resolv.h>
49 #include "snoop.h"
50 #include "snoop_mip.h"
51 
52 static void interpret_options(char *, int);
53 static void interpret_mldv2qry(icmp6_t *, int);
54 static void interpret_mldv2rpt(icmp6_t *, int);
55 
56 
57 /* Mobile-IP routines from snoop_mip.c */
58 extern void interpret_icmp_mip_ext(uchar_t *, int);
59 extern const char *get_mip_adv_desc(uint8_t);
60 
61 /* Router advertisement message structure. */
62 struct icmp_ra_addr {
63 	uint32_t addr;
64 	uint32_t preference;
65 };
66 
67 /*ARGSUSED*/
68 void
interpret_icmp(int flags,struct icmp * icmp,int iplen,int ilen)69 interpret_icmp(int flags, struct icmp *icmp, int iplen, int ilen)
70 {
71 	char *pt, *pc, *px;
72 	char *line;
73 	char buff[67627];	/* Router adv. can have 256 routers ....   */
74 				/* Each router has a name 256 char long .. */
75 	char extbuff[MAXHOSTNAMELEN + 1];
76 	struct udphdr *orig_uhdr;
77 	int num_rtr_addrs = 0;
78 	extern char *prot_nest_prefix;
79 
80 	if (ilen < ICMP_MINLEN)
81 		return;		/* incomplete header */
82 
83 	pt = "Unknown";
84 	pc = "";
85 	px = "";
86 
87 	switch (icmp->icmp_type) {
88 	case ICMP_ECHOREPLY:
89 		pt = "Echo reply";
90 		(void) sprintf(buff, "ID: %d Sequence number: %d",
91 		    ntohs(icmp->icmp_id), ntohs(icmp->icmp_seq));
92 		pc = buff;
93 		break;
94 	case ICMP_UNREACH:
95 		pt = "Destination unreachable";
96 		switch (icmp->icmp_code) {
97 		case ICMP_UNREACH_NET:
98 			if (ilen >= ICMP_ADVLENMIN) {
99 				(void) sprintf(buff, "Net %s unreachable",
100 				    addrtoname(AF_INET,
101 				    &icmp->icmp_ip.ip_dst));
102 				pc = buff;
103 			} else {
104 				pc = "Bad net";
105 			}
106 			break;
107 		case ICMP_UNREACH_HOST:
108 			if (ilen >= ICMP_ADVLENMIN) {
109 				(void) sprintf(buff, "Host %s unreachable",
110 				    addrtoname(AF_INET,
111 				    &icmp->icmp_ip.ip_dst));
112 				pc = buff;
113 			} else {
114 				pc = "Bad host";
115 			}
116 			break;
117 		case ICMP_UNREACH_PROTOCOL:
118 			if (ilen >= ICMP_ADVLENMIN) {
119 				(void) sprintf(buff, "Bad protocol %d",
120 				    icmp->icmp_ip.ip_p);
121 				pc = buff;
122 			} else {
123 				pc = "Bad protocol";
124 			}
125 			break;
126 		case ICMP_UNREACH_PORT:
127 			if (ilen >= ICMP_ADVLENMIN) {
128 				orig_uhdr = (struct udphdr *)((uchar_t *)icmp +
129 				    ICMP_MINLEN + icmp->icmp_ip.ip_hl * 4);
130 				switch (icmp->icmp_ip.ip_p) {
131 				case IPPROTO_TCP:
132 					(void) sprintf(buff, "TCP port %d"
133 					    " unreachable",
134 					    ntohs(orig_uhdr->uh_dport));
135 					pc = buff;
136 					break;
137 				case IPPROTO_UDP:
138 					(void) sprintf(buff, "UDP port %d"
139 					    " unreachable",
140 					    ntohs(orig_uhdr->uh_dport));
141 					pc = buff;
142 					break;
143 				default:
144 					pc = "Port unreachable";
145 					break;
146 				}
147 			} else {
148 				pc = "Bad port";
149 			}
150 			break;
151 		case ICMP_UNREACH_NEEDFRAG:
152 			if (ntohs(icmp->icmp_nextmtu) != 0) {
153 				(void) sprintf(buff, "Needed to fragment:"
154 				    " next hop MTU = %d",
155 				    ntohs(icmp->icmp_nextmtu));
156 				pc = buff;
157 			} else {
158 				pc = "Needed to fragment";
159 			}
160 			break;
161 		case ICMP_UNREACH_SRCFAIL:
162 			pc = "Source route failed";
163 			break;
164 		case ICMP_UNREACH_NET_UNKNOWN:
165 			pc = "Unknown network";
166 			break;
167 		case ICMP_UNREACH_HOST_UNKNOWN:
168 			pc = "Unknown host";
169 			break;
170 		case ICMP_UNREACH_ISOLATED:
171 			pc = "Source host isolated";
172 			break;
173 		case ICMP_UNREACH_NET_PROHIB:
174 			pc = "Net administratively prohibited";
175 			break;
176 		case ICMP_UNREACH_HOST_PROHIB:
177 			pc = "Host administratively prohibited";
178 			break;
179 		case ICMP_UNREACH_TOSNET:
180 			pc = "Net unreachable for this TOS";
181 			break;
182 		case ICMP_UNREACH_TOSHOST:
183 			pc = "Host unreachable for this TOS";
184 			break;
185 		case ICMP_UNREACH_FILTER_PROHIB:
186 			pc = "Communication administratively prohibited";
187 			break;
188 		case ICMP_UNREACH_HOST_PRECEDENCE:
189 			pc = "Host precedence violation";
190 			break;
191 		case ICMP_UNREACH_PRECEDENCE_CUTOFF:
192 			pc = "Precedence cutoff in effect";
193 			break;
194 		default:
195 			break;
196 		}
197 		break;
198 	case ICMP_SOURCEQUENCH:
199 		pt = "Packet lost, slow down";
200 		break;
201 	case ICMP_REDIRECT:
202 		pt = "Redirect";
203 		switch (icmp->icmp_code) {
204 		case ICMP_REDIRECT_NET:
205 			pc = "for network";
206 			break;
207 		case ICMP_REDIRECT_HOST:
208 			pc = "for host";
209 			break;
210 		case ICMP_REDIRECT_TOSNET:
211 			pc = "for tos and net";
212 			break;
213 		case ICMP_REDIRECT_TOSHOST:
214 			pc = "for tos and host";
215 			break;
216 		default:
217 			break;
218 		}
219 		(void) sprintf(buff, "%s %s to %s",
220 		    pc, addrtoname(AF_INET, &icmp->icmp_ip.ip_dst),
221 		    addrtoname(AF_INET, &icmp->icmp_gwaddr));
222 		pc = buff;
223 		break;
224 	case ICMP_ECHO:
225 		pt = "Echo request";
226 		(void) sprintf(buff, "ID: %d Sequence number: %d",
227 		    ntohs(icmp->icmp_id), ntohs(icmp->icmp_seq));
228 		pc = buff;
229 		break;
230 	case ICMP_ROUTERADVERT:
231 
232 #define	icmp_num_addrs	icmp_hun.ih_rtradv.irt_num_addrs
233 #define	icmp_wpa	icmp_hun.ih_rtradv.irt_wpa
234 #define	icmp_lifetime	icmp_hun.ih_rtradv.irt_lifetime
235 
236 		pt = "Router advertisement";
237 		(void) sprintf(buff, "Lifetime %ds [%d]:",
238 		    ntohs(icmp->icmp_lifetime), icmp->icmp_num_addrs);
239 		if (icmp->icmp_wpa == 2) {
240 			struct icmp_ra_addr *ra;
241 			char ra_buf[MAXHOSTNAMELEN + 32];
242 			char ra_ext_buf[50];
243 			struct in_addr sin;
244 			int icmp_ra_len;
245 			int i;
246 
247 			/* Cannot trust anything from the network... */
248 			num_rtr_addrs = MIN((ilen - ICMP_MINLEN) / 8,
249 			    icmp->icmp_num_addrs);
250 
251 			ra = (struct icmp_ra_addr *)icmp->icmp_data;
252 			for (i = 0; i < num_rtr_addrs; i++) {
253 				sin.s_addr = ra->addr;
254 				(void) snprintf(ra_buf, sizeof (ra_buf),
255 				    " {%s %u}",
256 				    addrtoname(AF_INET, &sin),
257 				    ntohl(ra->preference));
258 				if (strlcat(buff, ra_buf, sizeof (buff)) >=
259 				    sizeof (buff)) {
260 					buff[sizeof (buff) -
261 					    strlen("<Too Long>)")] = '\0';
262 					(void) strlcat(buff, "<Too Long>",
263 					    sizeof (buff));
264 					break;
265 				}
266 				ra++;
267 			}
268 
269 			icmp_ra_len = ICMP_MINLEN + num_rtr_addrs *
270 			    sizeof (struct icmp_ra_addr);
271 			if (ilen > icmp_ra_len) {
272 				int curr_len = ilen - icmp_ra_len;
273 				int ocurr_len;
274 				exthdr_t *exthdr = (exthdr_t *)ra;
275 
276 				extbuff[0] = '\0';
277 
278 				while (curr_len > 0) {
279 					/* Append Mobile-IP description */
280 					(void) snprintf(ra_ext_buf,
281 					    sizeof (ra_ext_buf), ", %s",
282 					    get_mip_adv_desc(exthdr->type));
283 					(void) strlcat(extbuff, ra_ext_buf,
284 					    sizeof (extbuff));
285 
286 					/* Special case for padding */
287 					if (exthdr->type ==
288 					    ICMP_ADV_MSG_PADDING_EXT) {
289 
290 						curr_len--;
291 						exthdr = (exthdr_t *)
292 						    ((char *)exthdr + 1);
293 						continue;
294 					}
295 
296 					/* else normal extension */
297 					ocurr_len = curr_len;
298 					curr_len -= sizeof (*exthdr) +
299 					    exthdr->length;
300 					/* detect bad length */
301 					if (ocurr_len < curr_len)
302 						break;
303 					exthdr = (exthdr_t *)
304 					    ((char *)exthdr + sizeof (*exthdr) +
305 					    exthdr->length);
306 				}
307 				px = extbuff;
308 			}
309 			pc = buff;
310 		}
311 		break;
312 	case ICMP_ROUTERSOLICIT:
313 		pt = "Router solicitation";
314 		break;
315 	case ICMP_TIMXCEED:
316 		pt = "Time exceeded";
317 		switch (icmp->icmp_code) {
318 		case ICMP_TIMXCEED_INTRANS:
319 			pc = "in transit";
320 			break;
321 		case ICMP_TIMXCEED_REASS:
322 			pc = "in reassembly";
323 			break;
324 		default:
325 			break;
326 		}
327 		break;
328 	case ICMP_PARAMPROB:
329 		pt = "IP parameter problem";
330 		switch (icmp->icmp_code) {
331 		case ICMP_PARAMPROB_OPTABSENT:
332 			pc = "Required option missing";
333 			break;
334 		case ICMP_PARAMPROB_BADLENGTH:
335 			pc = "Bad length";
336 			break;
337 		case 0: /* Should this be the default? */
338 			(void) sprintf(buff, "Problem at octet %d\n",
339 			    icmp->icmp_pptr);
340 			pc = buff;
341 		default:
342 			break;
343 		}
344 		break;
345 	case ICMP_TSTAMP:
346 		pt = "Timestamp request";
347 		break;
348 	case ICMP_TSTAMPREPLY:
349 		pt = "Timestamp reply";
350 		break;
351 	case ICMP_IREQ:
352 		pt = "Information request";
353 		break;
354 	case ICMP_IREQREPLY:
355 		pt = "Information reply";
356 		break;
357 	case ICMP_MASKREQ:
358 		pt = "Address mask request";
359 		break;
360 	case ICMP_MASKREPLY:
361 		pt = "Address mask reply";
362 		(void) sprintf(buff, "Mask = 0x%x", ntohl(icmp->icmp_mask));
363 		pc = buff;
364 		break;
365 	default:
366 		break;
367 	}
368 
369 	if (flags & F_SUM) {
370 		line = get_sum_line();
371 		if (*pc) {
372 			if (*px) {
373 				(void) sprintf(line, "ICMP %s (%s)%s",
374 				    pt, pc, px);
375 			} else {
376 				(void) sprintf(line, "ICMP %s (%s)", pt, pc);
377 			}
378 		} else {
379 			(void) sprintf(line, "ICMP %s", pt);
380 		}
381 	}
382 
383 	if (flags & F_DTAIL) {
384 		show_header("ICMP:  ", "ICMP Header", ilen);
385 		show_space();
386 		(void) sprintf(get_line(0, 0), "Type = %d (%s)",
387 		    icmp->icmp_type, pt);
388 		if (*pc) {
389 			(void) sprintf(get_line(0, 0), "Code = %d (%s)",
390 			    icmp->icmp_code, pc);
391 		} else {
392 			(void) sprintf(get_line(0, 0), "Code = %d",
393 			    icmp->icmp_code);
394 		}
395 		(void) sprintf(get_line(0, 0), "Checksum = %x",
396 		    ntohs(icmp->icmp_cksum));
397 
398 		if (icmp->icmp_type == ICMP_UNREACH ||
399 		    icmp->icmp_type == ICMP_REDIRECT) {
400 			if (ilen > 28) {
401 				show_space();
402 				(void) sprintf(get_line(0, 0),
403 				    "[ subject header follows ]");
404 				show_space();
405 				prot_nest_prefix = "ICMP:";
406 				(void) interpret_ip(flags,
407 				    (struct ip *)icmp->icmp_data, 28);
408 				prot_nest_prefix = "";
409 			}
410 		} else if (icmp->icmp_type == ICMP_PARAMPROB) {
411 			if (ilen > 28) {
412 				show_space();
413 				(void) sprintf(get_line(0, 0),
414 				    "[ subject header follows ]");
415 				show_space();
416 				prot_nest_prefix = "ICMP:";
417 				(void) interpret_ip(flags,
418 				    (struct ip *)icmp->icmp_data, 28);
419 				prot_nest_prefix = "";
420 			}
421 		} else if (icmp->icmp_type == ICMP_ROUTERADVERT) {
422 			if (icmp->icmp_wpa == 2) {
423 				int icmp_ra_len;
424 
425 				show_space();
426 				icmp_ra_len = ICMP_MINLEN +
427 				    num_rtr_addrs *
428 				    sizeof (struct icmp_ra_addr);
429 				prot_nest_prefix = "";
430 				if (ilen > icmp_ra_len) {
431 					interpret_icmp_mip_ext(
432 					    (uchar_t *)icmp + icmp_ra_len,
433 					    ilen - icmp_ra_len);
434 				}
435 			}
436 		}
437 		show_space();
438 	}
439 }
440 
441 /*ARGSUSED*/
442 void
interpret_icmpv6(int flags,icmp6_t * icmp6,int iplen,int ilen)443 interpret_icmpv6(int flags, icmp6_t *icmp6, int iplen, int ilen)
444 {
445 	char *pt, *pc;
446 	char *line;
447 	extern char *prot_nest_prefix;
448 	char addrstr[INET6_ADDRSTRLEN];
449 	char buff[2048];
450 
451 	if (ilen < ICMP6_MINLEN)
452 		return;		/* incomplete header */
453 
454 	pt = "Unknown";
455 	pc = "";
456 
457 	switch (icmp6->icmp6_type) {
458 	case ICMP6_DST_UNREACH:
459 		pt = "Destination unreachable";
460 		switch (icmp6->icmp6_code) {
461 		case ICMP6_DST_UNREACH_NOROUTE:
462 			pc = "No route to destination";
463 			break;
464 		case ICMP6_DST_UNREACH_ADMIN:
465 			pc = "Communication administratively prohibited";
466 			break;
467 		case ICMP6_DST_UNREACH_ADDR:
468 			pc = "Address unreachable";
469 			break;
470 		case ICMP6_DST_UNREACH_NOPORT:
471 			if (ilen >= ICMP6_MINLEN + IPV6_HDR_LEN +
472 				sizeof (struct udphdr)) {
473 
474 				ip6_t *orig_ip6hdr = (ip6_t *)&icmp6[1];
475 
476 				switch (orig_ip6hdr->ip6_nxt) {
477 				case IPPROTO_TCP: {
478 					struct tcphdr *orig_thdr =
479 					    (struct tcphdr *)&orig_ip6hdr[1];
480 
481 					(void) sprintf(buff, "TCP port %hu"
482 					    " unreachable",
483 					    ntohs(orig_thdr->th_dport));
484 					pc = buff;
485 					break;
486 				    }
487 				case IPPROTO_UDP: {
488 					struct udphdr *orig_uhdr =
489 					    (struct udphdr *)&orig_ip6hdr[1];
490 
491 					(void) sprintf(buff, "UDP port %hu"
492 					    " unreachable",
493 					    ntohs(orig_uhdr->uh_dport));
494 					pc = buff;
495 					break;
496 				    }
497 				default:
498 					pc = "Port unreachable";
499 					break;
500 				}
501 			} else {
502 				pc = "Bad port";
503 			}
504 			break;
505 		default:
506 			break;
507 		}
508 		break;
509 	case ICMP6_PACKET_TOO_BIG:
510 		pt = "Packet too big";
511 		break;
512 	case ND_REDIRECT:
513 		pt = "Redirect";
514 		break;
515 	case ICMP6_TIME_EXCEEDED:
516 		pt = "Time exceeded";
517 		switch (icmp6->icmp6_code) {
518 		case ICMP6_TIME_EXCEED_TRANSIT:
519 			pc = "Hop limit exceeded in transit";
520 			break;
521 		case ICMP6_TIME_EXCEED_REASSEMBLY:
522 			pc = "Fragment reassembly time exceeded";
523 			break;
524 		default:
525 			break;
526 		}
527 		break;
528 	case ICMP6_PARAM_PROB:
529 		pt = "Parameter problem";
530 		switch (icmp6->icmp6_code) {
531 		case ICMP6_PARAMPROB_HEADER:
532 			pc = "Erroneous header field";
533 			break;
534 		case ICMP6_PARAMPROB_NEXTHEADER:
535 			pc = "Unrecognized next header type";
536 			break;
537 		case ICMP6_PARAMPROB_OPTION:
538 			pc = "Unrecognized IPv6 option";
539 			break;
540 		}
541 		break;
542 	case ICMP6_ECHO_REQUEST:
543 		pt = "Echo request";
544 		(void) sprintf(buff, "ID: %d Sequence number: %d",
545 		    ntohs(icmp6->icmp6_id), ntohs(icmp6->icmp6_seq));
546 		pc = buff;
547 		break;
548 	case ICMP6_ECHO_REPLY:
549 		pt = "Echo reply";
550 		(void) sprintf(buff, "ID: %d Sequence number: %d",
551 		    ntohs(icmp6->icmp6_id), ntohs(icmp6->icmp6_seq));
552 		pc = buff;
553 		break;
554 	case MLD_LISTENER_QUERY:
555 		if (ilen == MLD_MINLEN)
556 			pt = "Group membership query - MLDv1";
557 		else if (ilen >= MLD_V2_QUERY_MINLEN)
558 			pt = "Group membership query - MLDv2";
559 		else
560 			pt = "Unknown membership query";
561 		break;
562 	case MLD_LISTENER_REPORT:
563 		pt = "Group membership report - MLDv1";
564 		break;
565 	case MLD_LISTENER_REDUCTION:
566 		pt = "Group membership termination - MLDv1";
567 		break;
568 	case MLD_V2_LISTENER_REPORT:
569 		pt = "Group membership report - MLDv2";
570 		break;
571 	case ND_ROUTER_SOLICIT:
572 		pt = "Router solicitation";
573 		break;
574 	case ND_ROUTER_ADVERT:
575 		pt = "Router advertisement";
576 		break;
577 	case ND_NEIGHBOR_SOLICIT:
578 		pt = "Neighbor solicitation";
579 		break;
580 	case ND_NEIGHBOR_ADVERT:
581 		pt = "Neighbor advertisement";
582 		break;
583 	default:
584 		break;
585 	}
586 
587 	if (flags & F_SUM) {
588 		line = get_sum_line();
589 		if (*pc)
590 			(void) sprintf(line, "ICMPv6 %s (%s)", pt, pc);
591 		else
592 			(void) sprintf(line, "ICMPv6 %s", pt);
593 	}
594 
595 	if (flags & F_DTAIL) {
596 		show_header("ICMPv6:  ", "ICMPv6 Header", ilen);
597 		show_space();
598 		(void) sprintf(get_line(0, 0), "Type = %d (%s)",
599 		    icmp6->icmp6_type, pt);
600 		if (*pc)
601 			(void) sprintf(get_line(0, 0), "Code = %d (%s)",
602 			    icmp6->icmp6_code, pc);
603 		else
604 			(void) sprintf(get_line(0, 0), "Code = %d",
605 			    icmp6->icmp6_code);
606 		(void) sprintf(get_line(0, 0), "Checksum = %x",
607 		    ntohs(icmp6->icmp6_cksum));
608 
609 		switch (icmp6->icmp6_type) {
610 		case ICMP6_DST_UNREACH:
611 			if (ilen > ICMP6_MINLEN + IPV6_HDR_LEN) {
612 				show_space();
613 				(void) sprintf(get_line(0, 0),
614 				    "[ subject header follows ]");
615 				show_space();
616 				prot_nest_prefix = "ICMPv6:";
617 				(void) interpret_ipv6(flags, (ip6_t *)&icmp6[1],
618 				    ICMP6_MINLEN + IPV6_HDR_LEN);
619 				prot_nest_prefix = "";
620 			}
621 			break;
622 		case ICMP6_PACKET_TOO_BIG:
623 			show_space();
624 			(void) sprintf(get_line(0, 0),
625 			    " Packet too big MTU = %d",
626 			    ntohl(icmp6->icmp6_mtu));
627 			show_space();
628 			break;
629 		case ND_REDIRECT: {
630 			nd_redirect_t *rd = (nd_redirect_t *)icmp6;
631 
632 			(void) sprintf(get_line(0, 0), "Target address= %s",
633 			    inet_ntop(AF_INET6, (char *)&rd->nd_rd_target,
634 			    addrstr, INET6_ADDRSTRLEN));
635 
636 			(void) sprintf(get_line(0, 0),
637 			    "Destination address= %s",
638 			    inet_ntop(AF_INET6, (char *)&rd->nd_rd_dst,
639 			    addrstr, INET6_ADDRSTRLEN));
640 			show_space();
641 			interpret_options((char *)icmp6 + sizeof (*rd),
642 			    ilen - sizeof (*rd));
643 			break;
644 		}
645 		case ND_NEIGHBOR_SOLICIT: {
646 			struct nd_neighbor_solicit *ns;
647 			if (ilen < sizeof (*ns))
648 				break;
649 			ns = (struct nd_neighbor_solicit *)icmp6;
650 			(void) sprintf(get_line(0, 0), "Target node = %s, %s",
651 			    inet_ntop(AF_INET6, (char *)&ns->nd_ns_target,
652 			    addrstr, INET6_ADDRSTRLEN),
653 			    addrtoname(AF_INET6, &ns->nd_ns_target));
654 			show_space();
655 			interpret_options((char *)icmp6 + sizeof (*ns),
656 			    ilen - sizeof (*ns));
657 			break;
658 		}
659 
660 		case ND_NEIGHBOR_ADVERT: {
661 			struct nd_neighbor_advert *na;
662 
663 			if (ilen < sizeof (*na))
664 				break;
665 			na = (struct nd_neighbor_advert *)icmp6;
666 			(void) sprintf(get_line(0, 0), "Target node = %s, %s",
667 			    inet_ntop(AF_INET6, (char *)&na->nd_na_target,
668 			    addrstr, INET6_ADDRSTRLEN),
669 			    addrtoname(AF_INET6, &na->nd_na_target));
670 			(void) sprintf(get_line(0, 0),
671 			    "Router flag: %s, Solicited flag: %s, "
672 			    "Override flag: %s",
673 			    na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER ?
674 			    "SET" : "NOT SET",
675 			    na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED ?
676 			    "SET" : "NOT SET",
677 			    na->nd_na_flags_reserved & ND_NA_FLAG_OVERRIDE ?
678 			    "SET" : "NOT SET");
679 
680 			show_space();
681 			interpret_options((char *)icmp6 + sizeof (*na),
682 			    ilen - sizeof (*na));
683 		}
684 		break;
685 
686 		case ND_ROUTER_SOLICIT: {
687 			if (ilen < sizeof (struct nd_router_solicit))
688 				break;
689 			interpret_options(
690 			    (char *)icmp6 + sizeof (struct nd_router_solicit),
691 			    ilen - sizeof (struct nd_router_solicit));
692 			break;
693 		}
694 
695 		case ND_ROUTER_ADVERT: {
696 			struct nd_router_advert *ra;
697 
698 			if (ilen < sizeof (*ra))
699 				break;
700 			ra = (struct nd_router_advert *)icmp6;
701 			(void) sprintf(get_line(0, 0),
702 			    "Max hops= %d, Router lifetime= %d",
703 			    ra->nd_ra_curhoplimit,
704 			    ntohs(ra->nd_ra_router_lifetime));
705 
706 			(void) sprintf(get_line(0, 0),
707 			    "Managed addr conf flag: %s, Other conf flag: %s",
708 			    ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED ?
709 			    "SET" : "NOT SET",
710 			    ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER ?
711 			    "SET" : "NOT SET");
712 
713 			(void) sprintf(get_line(0, 0),
714 			    "Reachable time: %u, Reachable retrans time %u",
715 			    ntohl(ra->nd_ra_reachable),
716 			    ntohl(ra->nd_ra_retransmit));
717 			show_space();
718 
719 			interpret_options((char *)icmp6 + sizeof (*ra),
720 			    ilen - sizeof (*ra));
721 			break;
722 		}
723 		case ICMP6_PARAM_PROB:
724 			if (ilen < sizeof (*icmp6))
725 				break;
726 			(void) sprintf(get_line(0, 0), "Ptr = %u",
727 			    ntohl(icmp6->icmp6_pptr));
728 			show_space();
729 			break;
730 
731 		case MLD_LISTENER_QUERY: {
732 			struct mld_hdr *mldg = (struct mld_hdr *)icmp6;
733 
734 			if (ilen < MLD_MINLEN)
735 				break;
736 
737 			if (ilen >= MLD_V2_QUERY_MINLEN) {
738 				interpret_mldv2qry(icmp6, ilen);
739 			} else {
740 				(void) snprintf(get_line(0, 0),
741 				    get_line_remain(),
742 				    "Multicast address= %s",
743 				    inet_ntop(AF_INET6, mldg->mld_addr.s6_addr,
744 				    addrstr, INET6_ADDRSTRLEN));
745 			}
746 			show_space();
747 			break;
748 		}
749 
750 		case MLD_LISTENER_REPORT:
751 		case MLD_LISTENER_REDUCTION: {
752 			struct mld_hdr *mldg;
753 
754 			if (ilen < sizeof (*mldg))
755 				break;
756 			mldg = (struct mld_hdr *)icmp6;
757 			(void) snprintf(get_line(0, 0), get_line_remain(),
758 			    "Multicast address= %s", inet_ntop(AF_INET6,
759 			    mldg->mld_addr.s6_addr, addrstr, INET6_ADDRSTRLEN));
760 			show_space();
761 			break;
762 		}
763 
764 		case MLD_V2_LISTENER_REPORT: {
765 			interpret_mldv2rpt(icmp6, ilen);
766 			show_space();
767 			break;
768 		}
769 
770 		default:
771 			break;
772 		}
773 	}
774 }
775 
776 #define	LIFETIME_INFINITY		0xffffffffUL
777 
778 static void
interpret_lifetime(char * buf,uint32_t net_lifetime)779 interpret_lifetime(char *buf, uint32_t net_lifetime)
780 {
781 	uint32_t lifetime = ntohl(net_lifetime);
782 
783 	if (lifetime == 0) {
784 		sprintf(buf, "INVALID");
785 		return;
786 	}
787 	if (lifetime == LIFETIME_INFINITY) {
788 		sprintf(buf, "INFINITY");
789 		return;
790 	}
791 	sprintf(buf, "%lu", lifetime);
792 }
793 
794 static void
interpret_options(char * optc,int ilen)795 interpret_options(char *optc, int ilen)
796 {
797 #define	PREFIX_OPTION_LENGTH    4
798 #define	MTU_OPTION_LENGTH	1
799 
800 	struct nd_opt_hdr *opt;
801 
802 	for (; ilen >= sizeof (*opt); ) {
803 		opt = (struct nd_opt_hdr *)optc;
804 		if (opt->nd_opt_len == 0)
805 			return;
806 		switch (opt->nd_opt_type) {
807 		case ND_OPT_SOURCE_LINKADDR:
808 		case ND_OPT_TARGET_LINKADDR:
809 		{
810 			struct nd_opt_lla *lopt;
811 			char	*buf, chbuf[128];
812 			uint_t	addr_len;
813 			int	i;
814 
815 			if (ilen < (int)opt->nd_opt_len * 8)
816 				break;
817 
818 			buf = chbuf;
819 
820 			lopt = (struct nd_opt_lla *)opt;
821 			if (lopt->nd_opt_lla_type == ND_OPT_SOURCE_LINKADDR) {
822 				(void) sprintf(get_line(0, 0),
823 				    "+++ ICMPv6 Source LL Addr option +++");
824 			} else {
825 				(void) sprintf(get_line(0, 0),
826 				    "+++ ICMPv6 Target LL Addr option +++");
827 			}
828 
829 			/*
830 			 * The option length is in 8 octet units, and
831 			 * includes the first two bytes (the type and
832 			 * lenght fields) of the option.
833 			 */
834 			addr_len = lopt->nd_opt_lla_len * 8 - 2;
835 			for (i = 0; i < addr_len; i++) {
836 				snprintf(buf, sizeof (chbuf) - (buf - chbuf),
837 				    "%x:", lopt->nd_opt_lla_hdw_addr[i]);
838 				buf += strlen(buf);
839 				if (buf >= &chbuf[sizeof (chbuf)]) {
840 					buf = NULL;
841 					chbuf[sizeof (chbuf) -
842 					    strlen("<Too Long>)")] = '\0';
843 					(void) strlcat(chbuf, "<Too Long>",
844 					    sizeof (chbuf));
845 					break;
846 				}
847 			}
848 			if (buf)
849 				*(buf - 1) = '\0'; /* Erase last colon */
850 			(void) sprintf(get_line(0, 0),
851 			    "Link Layer address: %s", chbuf);
852 			show_space();
853 			break;
854 		}
855 		case ND_OPT_MTU: {
856 			struct nd_opt_mtu *mopt;
857 			if (opt->nd_opt_len != MTU_OPTION_LENGTH ||
858 			    ilen < sizeof (struct nd_opt_mtu))
859 				break;
860 			(void) sprintf(get_line(0, 0),
861 			    "+++ ICMPv6 MTU option +++");
862 			mopt = (struct nd_opt_mtu *)opt;
863 			(void) sprintf(get_line(0, 0),
864 			    "MTU = %u ", ntohl(mopt->nd_opt_mtu_mtu));
865 			show_space();
866 			break;
867 		}
868 		case ND_OPT_PREFIX_INFORMATION: {
869 			struct nd_opt_prefix_info *popt;
870 			char validstr[30];
871 			char preferredstr[30];
872 			char prefixstr[INET6_ADDRSTRLEN];
873 
874 			if (opt->nd_opt_len != PREFIX_OPTION_LENGTH ||
875 			    ilen < sizeof (struct nd_opt_prefix_info))
876 				break;
877 			popt = (struct nd_opt_prefix_info *)opt;
878 			(void) sprintf(get_line(0, 0),
879 			    "+++ ICMPv6 Prefix option +++");
880 			(void) sprintf(get_line(0, 0),
881 			    "Prefix length = %d ", popt->nd_opt_pi_prefix_len);
882 			(void) sprintf(get_line(0, 0),
883 			    "Onlink flag: %s, Autonomous addr conf flag: %s",
884 			    popt->nd_opt_pi_flags_reserved &
885 			    ND_OPT_PI_FLAG_ONLINK ? "SET" : "NOT SET",
886 			    popt->nd_opt_pi_flags_reserved &
887 			    ND_OPT_PI_FLAG_AUTO ? "SET" : "NOT SET");
888 
889 			interpret_lifetime(validstr,
890 			    popt->nd_opt_pi_valid_time);
891 			interpret_lifetime(preferredstr,
892 			    popt->nd_opt_pi_preferred_time);
893 
894 			(void) sprintf(get_line(0, 0),
895 			    "Valid Lifetime %s, Preferred Lifetime %s",
896 			    validstr, preferredstr);
897 			(void) sprintf(get_line(0, 0), "Prefix %s",
898 			    inet_ntop(AF_INET6,
899 			    (char *)&popt->nd_opt_pi_prefix, prefixstr,
900 			    INET6_ADDRSTRLEN));
901 			show_space();
902 			break;
903 		}
904 		case ND_OPT_DNS_RESOLVER: {
905 			char addrstr[INET6_ADDRSTRLEN];
906 			char lifestr[30];
907 			int i, naddr;
908 			struct nd_opt_dns_resolver *optr =
909 			    (struct nd_opt_dns_resolver *)optc;
910 
911 			if (opt->nd_opt_len < 3 || ilen < opt->nd_opt_len * 8)
912 				break;
913 
914 			(void) sprintf(get_line(0, 0),
915 			    "+++ ICMPv6 Recursive DNS Server option +++");
916 
917 			interpret_lifetime(lifestr, optr->nd_opt_dnsr_lifetime);
918 			(void) sprintf(get_line(0, 0), "Lifetime %s", lifestr);
919 
920 			naddr = (opt->nd_opt_len - 1) / 2;
921 
922 			for (i = 0; i < naddr; i++) {
923 				const char *ns = inet_ntop(AF_INET6,
924 				    &optr->nd_opt_dnsr_addr[i],
925 				    addrstr,
926 				    INET6_ADDRSTRLEN);
927 				sprintf(get_line(0, 0), "Nameserver %s", ns);
928 			}
929 			show_space();
930 			break;
931 		}
932 		case ND_OPT_DNS_SEARCHLIST: {
933 			struct nd_opt_dns_sl *opts =
934 			    (struct nd_opt_dns_sl *)optc;
935 			char lifestr[30];
936 			uchar_t *msg, *namep, *end;
937 
938 			(void) sprintf(get_line(0, 0),
939 			    "+++ ICMPv6 DNS Search List option +++");
940 			interpret_lifetime(lifestr, opts->nd_opt_dnss_lifetime);
941 			(void) sprintf(get_line(0, 0), "Lifetime %s", lifestr);
942 
943 			msg = &opts->nd_opt_dnss_names[0];
944 			end = (uint8_t *)(optc + opt->nd_opt_len * 8);
945 			namep = msg;
946 
947 			/*
948 			 * Names are encoded in DNS wire format and then
949 			 * padded with zero bytes to the end of the option.
950 			 * dn_expand() returns the length of the
951 			 * wire-format name so the parser can advance
952 			 * to the next name in the message, or -1 on failure.
953 			 *
954 			 * The only 1-byte encoded DNS name is '.' (the root),
955 			 * which is meaningless in a DNS search path.
956 			 * It is encoded as a single zero byte, so if we
957 			 * see it we can quit parsing.
958 			 */
959 			while (namep < end) {
960 				char namebuf[256];
961 
962 				int count = dn_expand(msg, end, namep,
963 				    namebuf, sizeof (namebuf));
964 
965 				if (count <= 1)
966 					break;
967 
968 				(void) sprintf(get_line(0, 0),
969 				    "Name: %s", namebuf);
970 				namep += count;
971 			}
972 			show_space();
973 			break;
974 		}
975 		default:
976 			break;
977 		}
978 		optc += opt->nd_opt_len * 8;
979 		ilen -= opt->nd_opt_len * 8;
980 	}
981 }
982 
983 static void
interpret_mldv2qry(icmp6_t * icmp6,int ilen)984 interpret_mldv2qry(icmp6_t *icmp6, int ilen)
985 {
986 	mld2q_t *qry;
987 	in6_addr_t *src;
988 	int rem = ilen;
989 	int srccnt;
990 	char addrstr[INET6_ADDRSTRLEN];
991 
992 	if (ilen < sizeof (*qry)) {
993 		(void) snprintf(get_line(0, 0), get_line_remain(),
994 		    "Malformed MLD Query");
995 		return;
996 	}
997 	qry = (mld2q_t *)icmp6;
998 	rem -= sizeof (*qry);
999 	srccnt = ntohs(qry->mld2q_numsrc);
1000 	(void) snprintf(get_line(0, 0), get_line_remain(),
1001 	    "Multicast address= %s", inet_ntop(AF_INET6,
1002 	    &qry->mld2q_addr.s6_addr, addrstr, INET6_ADDRSTRLEN));
1003 	(void) snprintf(get_line(0, 0), get_line_remain(),
1004 	    "%d Source Address%s:", srccnt, (srccnt == 1) ? "" : "es");
1005 
1006 	src = (in6_addr_t *)&qry[1];
1007 	while (srccnt > 0 && rem >= sizeof (*src)) {
1008 		rem -= sizeof (*src);
1009 
1010 		(void) snprintf(get_line(0, 0), get_line_remain(), "    %s",
1011 		    inet_ntop(AF_INET6, src, addrstr, INET6_ADDRSTRLEN));
1012 
1013 		srccnt--;
1014 		src++;
1015 	}
1016 }
1017 
1018 #define	MAX_MLDV2_REPORT_TYPE	6
1019 
1020 const char *mldv2rpt_types[] = {
1021 	"<unknown>",
1022 	"MODE_IS_INCLUDE",
1023 	"MODE_IS_EXCLUDE",
1024 	"CHANGE_TO_INCLUDE",
1025 	"CHANGE_TO_EXCLUDE",
1026 	"ALLOW_NEW_SOURCES",
1027 	"BLOCK_OLD_SOURCES",
1028 };
1029 
1030 static void
interpret_mldv2rpt(icmp6_t * icmp6,int ilen)1031 interpret_mldv2rpt(icmp6_t *icmp6, int ilen)
1032 {
1033 	mld2r_t *rpt;
1034 	mld2mar_t *mar;
1035 	in6_addr_t *src;
1036 	int rem = ilen, auxlen;
1037 	uint16_t marcnt, srccnt;
1038 	char addrstr[INET6_ADDRSTRLEN];
1039 
1040 	if (ilen < sizeof (*rpt)) {
1041 		(void) snprintf(get_line(0, 0), get_line_remain(),
1042 		    "Malformed MLDv2 Report");
1043 		return;
1044 	}
1045 	rpt = (mld2r_t *)icmp6;
1046 	mar = (mld2mar_t *)&rpt[1];
1047 	marcnt = ntohs(rpt->mld2r_nummar);
1048 	(void) snprintf(get_line(0, 0), get_line_remain(),
1049 	    "%d Multicast Address Record%s:", marcnt, (marcnt == 1) ? "" : "s");
1050 	rem -= sizeof (*rpt);
1051 	while (marcnt > 0 && rem >= sizeof (*mar)) {
1052 		rem -= sizeof (*mar);
1053 
1054 		(void) snprintf(get_line(0, 0), get_line_remain(),
1055 		    "Multicast address= %s  type = %s", inet_ntop(AF_INET6,
1056 		    &mar->mld2mar_group.s6_addr, addrstr, INET6_ADDRSTRLEN),
1057 		    (mar->mld2mar_type > MAX_MLDV2_REPORT_TYPE) ?
1058 		    "<unknown>" : mldv2rpt_types[mar->mld2mar_type]);
1059 		srccnt = ntohs(mar->mld2mar_numsrc);
1060 		(void) snprintf(get_line(0, 0), get_line_remain(),
1061 		    "%d Source Address%s:", srccnt, (srccnt == 1) ? "" : "es");
1062 
1063 		src = (in6_addr_t *)&mar[1];
1064 		while (srccnt > 0 && rem >= sizeof (*src)) {
1065 			rem -= sizeof (*src);
1066 
1067 			(void) snprintf(get_line(0, 0), get_line_remain(),
1068 			    "    %s", inet_ntop(AF_INET6, src, addrstr,
1069 			    INET6_ADDRSTRLEN));
1070 
1071 			srccnt--;
1072 			src++;
1073 		}
1074 
1075 		marcnt--;
1076 		auxlen = mar->mld2mar_auxlen * 4;
1077 		rem -= auxlen;
1078 		mar = (mld2mar_t *)((uint8_t *)src + auxlen);
1079 	}
1080 }
1081