1 /*
2  * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that: (1) source code distributions
7  * retain the above copyright notice and this paragraph in its entirety, (2)
8  * distributions including binary code include the above copyright notice and
9  * this paragraph in its entirety in the documentation or other materials
10  * provided with the distribution, and (3) all advertising materials mentioning
11  * features or use of this software display the following acknowledgement:
12  * ``This product includes software developed by the University of California,
13  * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14  * the University nor the names of its contributors may be used to endorse
15  * or promote products derived from this software without specific prior
16  * written permission.
17  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20  *
21  * Format and print AppleTalk packets.
22  */
23 
24 #define NETDISSECT_REWORKED
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <tcpdump-stdinc.h>
30 
31 #include <stdio.h>
32 #include <string.h>
33 
34 #include "interface.h"
35 #include "addrtoname.h"
36 #include "ethertype.h"
37 #include "extract.h"			/* must come after interface.h */
38 #include "appletalk.h"
39 
40 static const char tstr[] = "[|atalk]";
41 
42 static const struct tok type2str[] = {
43 	{ ddpRTMP,		"rtmp" },
44 	{ ddpRTMPrequest,	"rtmpReq" },
45 	{ ddpECHO,		"echo" },
46 	{ ddpIP,		"IP" },
47 	{ ddpARP,		"ARP" },
48 	{ ddpKLAP,		"KLAP" },
49 	{ 0,			NULL }
50 };
51 
52 struct aarp {
53 	uint16_t	htype, ptype;
54 	uint8_t		halen, palen;
55 	uint16_t	op;
56 	uint8_t		hsaddr[6];
57 	uint8_t		psaddr[4];
58 	uint8_t		hdaddr[6];
59 	uint8_t		pdaddr[4];
60 };
61 
62 static void atp_print(netdissect_options *, const struct atATP *, u_int);
63 static void atp_bitmap_print(netdissect_options *, u_char);
64 static void nbp_print(netdissect_options *, const struct atNBP *, u_int, u_short, u_char, u_char);
65 static const struct atNBPtuple *nbp_tuple_print(netdissect_options *ndo, const struct atNBPtuple *,
66 						const u_char *,
67 						u_short, u_char, u_char);
68 static const struct atNBPtuple *nbp_name_print(netdissect_options *, const struct atNBPtuple *,
69 					       const u_char *);
70 static const char *ataddr_string(netdissect_options *, u_short, u_char);
71 static void ddp_print(netdissect_options *, const u_char *, u_int, int, u_short, u_char, u_char);
72 static const char *ddpskt_string(netdissect_options *, int);
73 
74 /*
75  * Print LLAP packets received on a physical LocalTalk interface.
76  */
77 u_int
ltalk_if_print(netdissect_options * ndo,const struct pcap_pkthdr * h,const u_char * p)78 ltalk_if_print(netdissect_options *ndo,
79                const struct pcap_pkthdr *h, const u_char *p)
80 {
81 	return (llap_print(ndo, p, h->caplen));
82 }
83 
84 /*
85  * Print AppleTalk LLAP packets.
86  */
87 u_int
llap_print(netdissect_options * ndo,register const u_char * bp,u_int length)88 llap_print(netdissect_options *ndo,
89            register const u_char *bp, u_int length)
90 {
91 	register const struct LAP *lp;
92 	register const struct atDDP *dp;
93 	register const struct atShortDDP *sdp;
94 	u_short snet;
95 	u_int hdrlen;
96 
97 	if (length < sizeof(*lp)) {
98 		ND_PRINT((ndo, " [|llap %u]", length));
99 		return (length);
100 	}
101 	lp = (const struct LAP *)bp;
102 	bp += sizeof(*lp);
103 	length -= sizeof(*lp);
104 	hdrlen = sizeof(*lp);
105 	switch (lp->type) {
106 
107 	case lapShortDDP:
108 		if (length < ddpSSize) {
109 			ND_PRINT((ndo, " [|sddp %u]", length));
110 			return (length);
111 		}
112 		sdp = (const struct atShortDDP *)bp;
113 		ND_PRINT((ndo, "%s.%s",
114 		    ataddr_string(ndo, 0, lp->src), ddpskt_string(ndo, sdp->srcSkt)));
115 		ND_PRINT((ndo, " > %s.%s:",
116 		    ataddr_string(ndo, 0, lp->dst), ddpskt_string(ndo, sdp->dstSkt)));
117 		bp += ddpSSize;
118 		length -= ddpSSize;
119 		hdrlen += ddpSSize;
120 		ddp_print(ndo, bp, length, sdp->type, 0, lp->src, sdp->srcSkt);
121 		break;
122 
123 	case lapDDP:
124 		if (length < ddpSize) {
125 			ND_PRINT((ndo, " [|ddp %u]", length));
126 			return (length);
127 		}
128 		dp = (const struct atDDP *)bp;
129 		snet = EXTRACT_16BITS(&dp->srcNet);
130 		ND_PRINT((ndo, "%s.%s", ataddr_string(ndo, snet, dp->srcNode),
131 		    ddpskt_string(ndo, dp->srcSkt)));
132 		ND_PRINT((ndo, " > %s.%s:",
133 		    ataddr_string(ndo, EXTRACT_16BITS(&dp->dstNet), dp->dstNode),
134 		    ddpskt_string(ndo, dp->dstSkt)));
135 		bp += ddpSize;
136 		length -= ddpSize;
137 		hdrlen += ddpSize;
138 		ddp_print(ndo, bp, length, dp->type, snet, dp->srcNode, dp->srcSkt);
139 		break;
140 
141 #ifdef notdef
142 	case lapKLAP:
143 		klap_print(bp, length);
144 		break;
145 #endif
146 
147 	default:
148 		ND_PRINT((ndo, "%d > %d at-lap#%d %u",
149 		    lp->src, lp->dst, lp->type, length));
150 		break;
151 	}
152 	return (hdrlen);
153 }
154 
155 /*
156  * Print EtherTalk/TokenTalk packets (or FDDITalk, or whatever it's called
157  * when it runs over FDDI; yes, I've seen FDDI captures with AppleTalk
158  * packets in them).
159  */
160 void
atalk_print(netdissect_options * ndo,register const u_char * bp,u_int length)161 atalk_print(netdissect_options *ndo,
162             register const u_char *bp, u_int length)
163 {
164 	register const struct atDDP *dp;
165 	u_short snet;
166 
167         if(!ndo->ndo_eflag)
168             ND_PRINT((ndo, "AT "));
169 
170 	if (length < ddpSize) {
171 		ND_PRINT((ndo, " [|ddp %u]", length));
172 		return;
173 	}
174 	dp = (const struct atDDP *)bp;
175 	snet = EXTRACT_16BITS(&dp->srcNet);
176 	ND_PRINT((ndo, "%s.%s", ataddr_string(ndo, snet, dp->srcNode),
177 	       ddpskt_string(ndo, dp->srcSkt)));
178 	ND_PRINT((ndo, " > %s.%s: ",
179 	       ataddr_string(ndo, EXTRACT_16BITS(&dp->dstNet), dp->dstNode),
180 	       ddpskt_string(ndo, dp->dstSkt)));
181 	bp += ddpSize;
182 	length -= ddpSize;
183 	ddp_print(ndo, bp, length, dp->type, snet, dp->srcNode, dp->srcSkt);
184 }
185 
186 /* XXX should probably pass in the snap header and do checks like arp_print() */
187 void
aarp_print(netdissect_options * ndo,register const u_char * bp,u_int length)188 aarp_print(netdissect_options *ndo,
189            register const u_char *bp, u_int length)
190 {
191 	register const struct aarp *ap;
192 
193 #define AT(member) ataddr_string(ndo, (ap->member[1]<<8)|ap->member[2],ap->member[3])
194 
195 	ND_PRINT((ndo, "aarp "));
196 	ap = (const struct aarp *)bp;
197 	if (EXTRACT_16BITS(&ap->htype) == 1 &&
198 	    EXTRACT_16BITS(&ap->ptype) == ETHERTYPE_ATALK &&
199 	    ap->halen == 6 && ap->palen == 4 )
200 		switch (EXTRACT_16BITS(&ap->op)) {
201 
202 		case 1:				/* request */
203 			ND_PRINT((ndo, "who-has %s tell %s", AT(pdaddr), AT(psaddr)));
204 			return;
205 
206 		case 2:				/* response */
207 			ND_PRINT((ndo, "reply %s is-at %s", AT(psaddr), etheraddr_string(ndo, ap->hsaddr)));
208 			return;
209 
210 		case 3:				/* probe (oy!) */
211 			ND_PRINT((ndo, "probe %s tell %s", AT(pdaddr), AT(psaddr)));
212 			return;
213 		}
214 	ND_PRINT((ndo, "len %u op %u htype %u ptype %#x halen %u palen %u",
215 	    length, EXTRACT_16BITS(&ap->op), EXTRACT_16BITS(&ap->htype),
216 	    EXTRACT_16BITS(&ap->ptype), ap->halen, ap->palen));
217 }
218 
219 /*
220  * Print AppleTalk Datagram Delivery Protocol packets.
221  */
222 static void
ddp_print(netdissect_options * ndo,register const u_char * bp,register u_int length,register int t,register u_short snet,register u_char snode,u_char skt)223 ddp_print(netdissect_options *ndo,
224           register const u_char *bp, register u_int length, register int t,
225           register u_short snet, register u_char snode, u_char skt)
226 {
227 
228 	switch (t) {
229 
230 	case ddpNBP:
231 		nbp_print(ndo, (const struct atNBP *)bp, length, snet, snode, skt);
232 		break;
233 
234 	case ddpATP:
235 		atp_print(ndo, (const struct atATP *)bp, length);
236 		break;
237 
238 	case ddpEIGRP:
239 		eigrp_print(ndo, bp, length);
240 		break;
241 
242 	default:
243 		ND_PRINT((ndo, " at-%s %d", tok2str(type2str, NULL, t), length));
244 		break;
245 	}
246 }
247 
248 static void
atp_print(netdissect_options * ndo,register const struct atATP * ap,u_int length)249 atp_print(netdissect_options *ndo,
250           register const struct atATP *ap, u_int length)
251 {
252 	char c;
253 	uint32_t data;
254 
255 	if ((const u_char *)(ap + 1) > ndo->ndo_snapend) {
256 		/* Just bail if we don't have the whole chunk. */
257 		ND_PRINT((ndo, "%s", tstr));
258 		return;
259 	}
260 	if (length < sizeof(*ap)) {
261 		ND_PRINT((ndo, " [|atp %u]", length));
262 		return;
263 	}
264 	length -= sizeof(*ap);
265 	switch (ap->control & 0xc0) {
266 
267 	case atpReqCode:
268 		ND_PRINT((ndo, " atp-req%s %d",
269 			     ap->control & atpXO? " " : "*",
270 			     EXTRACT_16BITS(&ap->transID)));
271 
272 		atp_bitmap_print(ndo, ap->bitmap);
273 
274 		if (length != 0)
275 			ND_PRINT((ndo, " [len=%u]", length));
276 
277 		switch (ap->control & (atpEOM|atpSTS)) {
278 		case atpEOM:
279 			ND_PRINT((ndo, " [EOM]"));
280 			break;
281 		case atpSTS:
282 			ND_PRINT((ndo, " [STS]"));
283 			break;
284 		case atpEOM|atpSTS:
285 			ND_PRINT((ndo, " [EOM,STS]"));
286 			break;
287 		}
288 		break;
289 
290 	case atpRspCode:
291 		ND_PRINT((ndo, " atp-resp%s%d:%d (%u)",
292 			     ap->control & atpEOM? "*" : " ",
293 			     EXTRACT_16BITS(&ap->transID), ap->bitmap, length));
294 		switch (ap->control & (atpXO|atpSTS)) {
295 		case atpXO:
296 			ND_PRINT((ndo, " [XO]"));
297 			break;
298 		case atpSTS:
299 			ND_PRINT((ndo, " [STS]"));
300 			break;
301 		case atpXO|atpSTS:
302 			ND_PRINT((ndo, " [XO,STS]"));
303 			break;
304 		}
305 		break;
306 
307 	case atpRelCode:
308 		ND_PRINT((ndo, " atp-rel  %d", EXTRACT_16BITS(&ap->transID)));
309 
310 		atp_bitmap_print(ndo, ap->bitmap);
311 
312 		/* length should be zero */
313 		if (length)
314 			ND_PRINT((ndo, " [len=%u]", length));
315 
316 		/* there shouldn't be any control flags */
317 		if (ap->control & (atpXO|atpEOM|atpSTS)) {
318 			c = '[';
319 			if (ap->control & atpXO) {
320 				ND_PRINT((ndo, "%cXO", c));
321 				c = ',';
322 			}
323 			if (ap->control & atpEOM) {
324 				ND_PRINT((ndo, "%cEOM", c));
325 				c = ',';
326 			}
327 			if (ap->control & atpSTS) {
328 				ND_PRINT((ndo, "%cSTS", c));
329 				c = ',';
330 			}
331 			ND_PRINT((ndo, "]"));
332 		}
333 		break;
334 
335 	default:
336 		ND_PRINT((ndo, " atp-0x%x  %d (%u)", ap->control,
337 			     EXTRACT_16BITS(&ap->transID), length));
338 		break;
339 	}
340 	data = EXTRACT_32BITS(&ap->userData);
341 	if (data != 0)
342 		ND_PRINT((ndo, " 0x%x", data));
343 }
344 
345 static void
atp_bitmap_print(netdissect_options * ndo,register u_char bm)346 atp_bitmap_print(netdissect_options *ndo,
347                  register u_char bm)
348 {
349 	register char c;
350 	register int i;
351 
352 	/*
353 	 * The '& 0xff' below is needed for compilers that want to sign
354 	 * extend a u_char, which is the case with the Ultrix compiler.
355 	 * (gcc is smart enough to eliminate it, at least on the Sparc).
356 	 */
357 	if ((bm + 1) & (bm & 0xff)) {
358 		c = '<';
359 		for (i = 0; bm; ++i) {
360 			if (bm & 1) {
361 				ND_PRINT((ndo, "%c%d", c, i));
362 				c = ',';
363 			}
364 			bm >>= 1;
365 		}
366 		ND_PRINT((ndo, ">"));
367 	} else {
368 		for (i = 0; bm; ++i)
369 			bm >>= 1;
370 		if (i > 1)
371 			ND_PRINT((ndo, "<0-%d>", i - 1));
372 		else
373 			ND_PRINT((ndo, "<0>"));
374 	}
375 }
376 
377 static void
nbp_print(netdissect_options * ndo,register const struct atNBP * np,u_int length,register u_short snet,register u_char snode,register u_char skt)378 nbp_print(netdissect_options *ndo,
379           register const struct atNBP *np, u_int length, register u_short snet,
380           register u_char snode, register u_char skt)
381 {
382 	register const struct atNBPtuple *tp =
383 		(const struct atNBPtuple *)((u_char *)np + nbpHeaderSize);
384 	int i;
385 	const u_char *ep;
386 
387 	if (length < nbpHeaderSize) {
388 		ND_PRINT((ndo, " truncated-nbp %u", length));
389 		return;
390 	}
391 
392 	length -= nbpHeaderSize;
393 	if (length < 8) {
394 		/* must be room for at least one tuple */
395 		ND_PRINT((ndo, " truncated-nbp %u", length + nbpHeaderSize));
396 		return;
397 	}
398 	/* ep points to end of available data */
399 	ep = ndo->ndo_snapend;
400 	if ((const u_char *)tp > ep) {
401 		ND_PRINT((ndo, "%s", tstr));
402 		return;
403 	}
404 	switch (i = np->control & 0xf0) {
405 
406 	case nbpBrRq:
407 	case nbpLkUp:
408 		ND_PRINT((ndo, i == nbpLkUp? " nbp-lkup %d:":" nbp-brRq %d:", np->id));
409 		if ((const u_char *)(tp + 1) > ep) {
410 			ND_PRINT((ndo, "%s", tstr));
411 			return;
412 		}
413 		(void)nbp_name_print(ndo, tp, ep);
414 		/*
415 		 * look for anomalies: the spec says there can only
416 		 * be one tuple, the address must match the source
417 		 * address and the enumerator should be zero.
418 		 */
419 		if ((np->control & 0xf) != 1)
420 			ND_PRINT((ndo, " [ntup=%d]", np->control & 0xf));
421 		if (tp->enumerator)
422 			ND_PRINT((ndo, " [enum=%d]", tp->enumerator));
423 		if (EXTRACT_16BITS(&tp->net) != snet ||
424 		    tp->node != snode || tp->skt != skt)
425 			ND_PRINT((ndo, " [addr=%s.%d]",
426 			    ataddr_string(ndo, EXTRACT_16BITS(&tp->net),
427 			    tp->node), tp->skt));
428 		break;
429 
430 	case nbpLkUpReply:
431 		ND_PRINT((ndo, " nbp-reply %d:", np->id));
432 
433 		/* print each of the tuples in the reply */
434 		for (i = np->control & 0xf; --i >= 0 && tp; )
435 			tp = nbp_tuple_print(ndo, tp, ep, snet, snode, skt);
436 		break;
437 
438 	default:
439 		ND_PRINT((ndo, " nbp-0x%x  %d (%u)", np->control, np->id, length));
440 		break;
441 	}
442 }
443 
444 /* print a counted string */
445 static const char *
print_cstring(netdissect_options * ndo,register const char * cp,register const u_char * ep)446 print_cstring(netdissect_options *ndo,
447               register const char *cp, register const u_char *ep)
448 {
449 	register u_int length;
450 
451 	if (cp >= (const char *)ep) {
452 		ND_PRINT((ndo, "%s", tstr));
453 		return (0);
454 	}
455 	length = *cp++;
456 
457 	/* Spec says string can be at most 32 bytes long */
458 	if (length > 32) {
459 		ND_PRINT((ndo, "[len=%u]", length));
460 		return (0);
461 	}
462 	while ((int)--length >= 0) {
463 		if (cp >= (const char *)ep) {
464 			ND_PRINT((ndo, "%s", tstr));
465 			return (0);
466 		}
467 		ND_PRINT((ndo, "%c", *cp++));
468 	}
469 	return (cp);
470 }
471 
472 static const struct atNBPtuple *
nbp_tuple_print(netdissect_options * ndo,register const struct atNBPtuple * tp,register const u_char * ep,register u_short snet,register u_char snode,register u_char skt)473 nbp_tuple_print(netdissect_options *ndo,
474                 register const struct atNBPtuple *tp, register const u_char *ep,
475                 register u_short snet, register u_char snode, register u_char skt)
476 {
477 	register const struct atNBPtuple *tpn;
478 
479 	if ((const u_char *)(tp + 1) > ep) {
480 		ND_PRINT((ndo, "%s", tstr));
481 		return 0;
482 	}
483 	tpn = nbp_name_print(ndo, tp, ep);
484 
485 	/* if the enumerator isn't 1, print it */
486 	if (tp->enumerator != 1)
487 		ND_PRINT((ndo, "(%d)", tp->enumerator));
488 
489 	/* if the socket doesn't match the src socket, print it */
490 	if (tp->skt != skt)
491 		ND_PRINT((ndo, " %d", tp->skt));
492 
493 	/* if the address doesn't match the src address, it's an anomaly */
494 	if (EXTRACT_16BITS(&tp->net) != snet || tp->node != snode)
495 		ND_PRINT((ndo, " [addr=%s]",
496 		    ataddr_string(ndo, EXTRACT_16BITS(&tp->net), tp->node)));
497 
498 	return (tpn);
499 }
500 
501 static const struct atNBPtuple *
nbp_name_print(netdissect_options * ndo,const struct atNBPtuple * tp,register const u_char * ep)502 nbp_name_print(netdissect_options *ndo,
503                const struct atNBPtuple *tp, register const u_char *ep)
504 {
505 	register const char *cp = (const char *)tp + nbpTupleSize;
506 
507 	ND_PRINT((ndo, " "));
508 
509 	/* Object */
510 	ND_PRINT((ndo, "\""));
511 	if ((cp = print_cstring(ndo, cp, ep)) != NULL) {
512 		/* Type */
513 		ND_PRINT((ndo, ":"));
514 		if ((cp = print_cstring(ndo, cp, ep)) != NULL) {
515 			/* Zone */
516 			ND_PRINT((ndo, "@"));
517 			if ((cp = print_cstring(ndo, cp, ep)) != NULL)
518 				ND_PRINT((ndo, "\""));
519 		}
520 	}
521 	return ((const struct atNBPtuple *)cp);
522 }
523 
524 
525 #define HASHNAMESIZE 4096
526 
527 struct hnamemem {
528 	int addr;
529 	char *name;
530 	struct hnamemem *nxt;
531 };
532 
533 static struct hnamemem hnametable[HASHNAMESIZE];
534 
535 static const char *
ataddr_string(netdissect_options * ndo,u_short atnet,u_char athost)536 ataddr_string(netdissect_options *ndo,
537               u_short atnet, u_char athost)
538 {
539 	register struct hnamemem *tp, *tp2;
540 	register int i = (atnet << 8) | athost;
541 	char nambuf[256+1];
542 	static int first = 1;
543 	FILE *fp;
544 
545 	/*
546 	 * if this is the first call, see if there's an AppleTalk
547 	 * number to name map file.
548 	 */
549 	if (first && (first = 0, !ndo->ndo_nflag)
550 	    && (fp = fopen("/etc/atalk.names", "r"))) {
551 		char line[256];
552 		int i1, i2;
553 
554 		while (fgets(line, sizeof(line), fp)) {
555 			if (line[0] == '\n' || line[0] == 0 || line[0] == '#')
556 				continue;
557 			if (sscanf(line, "%d.%d %256s", &i1, &i2, nambuf) == 3)
558 				/* got a hostname. */
559 				i2 |= (i1 << 8);
560 			else if (sscanf(line, "%d %256s", &i1, nambuf) == 2)
561 				/* got a net name */
562 				i2 = (i1 << 8) | 255;
563 			else
564 				continue;
565 
566 			for (tp = &hnametable[i2 & (HASHNAMESIZE-1)];
567 			     tp->nxt; tp = tp->nxt)
568 				;
569 			tp->addr = i2;
570 			tp->nxt = newhnamemem();
571 			tp->name = strdup(nambuf);
572 		}
573 		fclose(fp);
574 	}
575 
576 	for (tp = &hnametable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt)
577 		if (tp->addr == i)
578 			return (tp->name);
579 
580 	/* didn't have the node name -- see if we've got the net name */
581 	i |= 255;
582 	for (tp2 = &hnametable[i & (HASHNAMESIZE-1)]; tp2->nxt; tp2 = tp2->nxt)
583 		if (tp2->addr == i) {
584 			tp->addr = (atnet << 8) | athost;
585 			tp->nxt = newhnamemem();
586 			(void)snprintf(nambuf, sizeof(nambuf), "%s.%d",
587 			    tp2->name, athost);
588 			tp->name = strdup(nambuf);
589 			return (tp->name);
590 		}
591 
592 	tp->addr = (atnet << 8) | athost;
593 	tp->nxt = newhnamemem();
594 	if (athost != 255)
595 		(void)snprintf(nambuf, sizeof(nambuf), "%d.%d", atnet, athost);
596 	else
597 		(void)snprintf(nambuf, sizeof(nambuf), "%d", atnet);
598 	tp->name = strdup(nambuf);
599 
600 	return (tp->name);
601 }
602 
603 static const struct tok skt2str[] = {
604 	{ rtmpSkt,	"rtmp" },	/* routing table maintenance */
605 	{ nbpSkt,	"nis" },	/* name info socket */
606 	{ echoSkt,	"echo" },	/* AppleTalk echo protocol */
607 	{ zipSkt,	"zip" },	/* zone info protocol */
608 	{ 0,		NULL }
609 };
610 
611 static const char *
ddpskt_string(netdissect_options * ndo,register int skt)612 ddpskt_string(netdissect_options *ndo,
613               register int skt)
614 {
615 	static char buf[8];
616 
617 	if (ndo->ndo_nflag) {
618 		(void)snprintf(buf, sizeof(buf), "%d", skt);
619 		return (buf);
620 	}
621 	return (tok2str(skt2str, "%d", skt));
622 }
623