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 22 /* \summary: AppleTalk printer */ 23 24 #include <sys/cdefs.h> 25 #ifndef lint 26 __RCSID("$NetBSD: print-atalk.c,v 1.9 2024/09/02 16:15:30 christos Exp $"); 27 #endif 28 29 #include <config.h> 30 31 #include "netdissect-stdinc.h" 32 33 #include <stdio.h> 34 #include <string.h> 35 36 #include "netdissect.h" 37 #include "addrtoname.h" 38 #include "ethertype.h" 39 #include "extract.h" 40 #include "appletalk.h" 41 42 43 static const struct tok type2str[] = { 44 { ddpRTMP, "rtmp" }, 45 { ddpRTMPrequest, "rtmpReq" }, 46 { ddpECHO, "echo" }, 47 { ddpIP, "IP" }, 48 { ddpARP, "ARP" }, 49 { ddpKLAP, "KLAP" }, 50 { 0, NULL } 51 }; 52 53 struct aarp { 54 nd_uint16_t htype, ptype; 55 nd_uint8_t halen, palen; 56 nd_uint16_t op; 57 nd_mac_addr hsaddr; 58 uint8_t psaddr[4]; 59 nd_mac_addr hdaddr; 60 uint8_t pdaddr[4]; 61 }; 62 63 static void atp_print(netdissect_options *, const struct atATP *, u_int); 64 static void atp_bitmap_print(netdissect_options *, u_char); 65 static void nbp_print(netdissect_options *, const struct atNBP *, u_int, u_short, u_char, u_char); 66 static const struct atNBPtuple *nbp_tuple_print(netdissect_options *ndo, const struct atNBPtuple *, 67 const u_char *, 68 u_short, u_char, u_char); 69 static const struct atNBPtuple *nbp_name_print(netdissect_options *, const struct atNBPtuple *, 70 const u_char *); 71 static const char *ataddr_string(netdissect_options *, u_short, u_char); 72 static void ddp_print(netdissect_options *, const u_char *, u_int, u_int, u_short, u_char, u_char); 73 static const char *ddpskt_string(netdissect_options *, u_int); 74 75 /* 76 * Print LLAP packets received on a physical LocalTalk interface. 77 */ 78 void 79 ltalk_if_print(netdissect_options *ndo, 80 const struct pcap_pkthdr *h, const u_char *p) 81 { 82 u_int hdrlen; 83 84 ndo->ndo_protocol = "ltalk"; 85 hdrlen = llap_print(ndo, p, h->len); 86 if (hdrlen == 0) { 87 /* Cut short by the snapshot length. */ 88 ndo->ndo_ll_hdr_len += h->caplen; 89 return; 90 } 91 ndo->ndo_ll_hdr_len += hdrlen; 92 } 93 94 /* 95 * Print AppleTalk LLAP packets. 96 */ 97 u_int 98 llap_print(netdissect_options *ndo, 99 const u_char *bp, u_int length) 100 { 101 const struct LAP *lp; 102 const struct atDDP *dp; 103 const struct atShortDDP *sdp; 104 u_short snet; 105 u_int hdrlen; 106 107 ndo->ndo_protocol = "llap"; 108 if (length < sizeof(*lp)) { 109 ND_PRINT(" [|llap %u]", length); 110 return (length); 111 } 112 if (!ND_TTEST_LEN(bp, sizeof(*lp))) { 113 nd_print_trunc(ndo); 114 return (0); /* cut short by the snapshot length */ 115 } 116 lp = (const struct LAP *)bp; 117 bp += sizeof(*lp); 118 length -= sizeof(*lp); 119 hdrlen = sizeof(*lp); 120 switch (GET_U_1(lp->type)) { 121 122 case lapShortDDP: 123 if (length < ddpSSize) { 124 ND_PRINT(" [|sddp %u]", length); 125 return (length); 126 } 127 if (!ND_TTEST_LEN(bp, ddpSSize)) { 128 ND_PRINT(" [|sddp]"); 129 return (0); /* cut short by the snapshot length */ 130 } 131 sdp = (const struct atShortDDP *)bp; 132 ND_PRINT("%s.%s", 133 ataddr_string(ndo, 0, GET_U_1(lp->src)), 134 ddpskt_string(ndo, GET_U_1(sdp->srcSkt))); 135 ND_PRINT(" > %s.%s:", 136 ataddr_string(ndo, 0, GET_U_1(lp->dst)), 137 ddpskt_string(ndo, GET_U_1(sdp->dstSkt))); 138 bp += ddpSSize; 139 length -= ddpSSize; 140 hdrlen += ddpSSize; 141 ddp_print(ndo, bp, length, GET_U_1(sdp->type), 0, 142 GET_U_1(lp->src), GET_U_1(sdp->srcSkt)); 143 break; 144 145 case lapDDP: 146 if (length < ddpSize) { 147 ND_PRINT(" [|ddp %u]", length); 148 return (length); 149 } 150 if (!ND_TTEST_LEN(bp, ddpSize)) { 151 ND_PRINT(" [|ddp]"); 152 return (0); /* cut short by the snapshot length */ 153 } 154 dp = (const struct atDDP *)bp; 155 snet = GET_BE_U_2(dp->srcNet); 156 ND_PRINT("%s.%s", 157 ataddr_string(ndo, snet, GET_U_1(dp->srcNode)), 158 ddpskt_string(ndo, GET_U_1(dp->srcSkt))); 159 ND_PRINT(" > %s.%s:", 160 ataddr_string(ndo, GET_BE_U_2(dp->dstNet), GET_U_1(dp->dstNode)), 161 ddpskt_string(ndo, GET_U_1(dp->dstSkt))); 162 bp += ddpSize; 163 length -= ddpSize; 164 hdrlen += ddpSize; 165 ddp_print(ndo, bp, length, GET_U_1(dp->type), snet, 166 GET_U_1(dp->srcNode), GET_U_1(dp->srcSkt)); 167 break; 168 169 #ifdef notdef 170 case lapKLAP: 171 klap_print(bp, length); 172 break; 173 #endif 174 175 default: 176 ND_PRINT("%u > %u at-lap#%u %u", 177 GET_U_1(lp->src), GET_U_1(lp->dst), GET_U_1(lp->type), 178 length); 179 break; 180 } 181 return (hdrlen); 182 } 183 184 /* 185 * Print EtherTalk/TokenTalk packets (or FDDITalk, or whatever it's called 186 * when it runs over FDDI; yes, I've seen FDDI captures with AppleTalk 187 * packets in them). 188 */ 189 void 190 atalk_print(netdissect_options *ndo, 191 const u_char *bp, u_int length) 192 { 193 const struct atDDP *dp; 194 u_short snet; 195 196 ndo->ndo_protocol = "atalk"; 197 if(!ndo->ndo_eflag) 198 ND_PRINT("AT "); 199 200 if (length < ddpSize) { 201 ND_PRINT(" [|ddp %u]", length); 202 return; 203 } 204 if (!ND_TTEST_LEN(bp, ddpSize)) { 205 ND_PRINT(" [|ddp]"); 206 return; 207 } 208 dp = (const struct atDDP *)bp; 209 snet = GET_BE_U_2(dp->srcNet); 210 ND_PRINT("%s.%s", ataddr_string(ndo, snet, GET_U_1(dp->srcNode)), 211 ddpskt_string(ndo, GET_U_1(dp->srcSkt))); 212 ND_PRINT(" > %s.%s: ", 213 ataddr_string(ndo, GET_BE_U_2(dp->dstNet), GET_U_1(dp->dstNode)), 214 ddpskt_string(ndo, GET_U_1(dp->dstSkt))); 215 bp += ddpSize; 216 length -= ddpSize; 217 ddp_print(ndo, bp, length, GET_U_1(dp->type), snet, 218 GET_U_1(dp->srcNode), GET_U_1(dp->srcSkt)); 219 } 220 221 /* XXX should probably pass in the snap header and do checks like arp_print() */ 222 void 223 aarp_print(netdissect_options *ndo, 224 const u_char *bp, u_int length) 225 { 226 const struct aarp *ap; 227 228 #define AT(member) ataddr_string(ndo, (ap->member[1]<<8)|ap->member[2],ap->member[3]) 229 230 ndo->ndo_protocol = "aarp"; 231 ND_PRINT("aarp "); 232 ap = (const struct aarp *)bp; 233 if (!ND_TTEST_SIZE(ap)) { 234 /* Just bail if we don't have the whole chunk. */ 235 nd_print_trunc(ndo); 236 return; 237 } 238 if (length < sizeof(*ap)) { 239 ND_PRINT(" [|aarp %u]", length); 240 return; 241 } 242 if (GET_BE_U_2(ap->htype) == 1 && 243 GET_BE_U_2(ap->ptype) == ETHERTYPE_ATALK && 244 GET_U_1(ap->halen) == MAC_ADDR_LEN && GET_U_1(ap->palen) == 4) 245 switch (GET_BE_U_2(ap->op)) { 246 247 case 1: /* request */ 248 ND_PRINT("who-has %s tell %s", AT(pdaddr), AT(psaddr)); 249 return; 250 251 case 2: /* response */ 252 ND_PRINT("reply %s is-at %s", AT(psaddr), GET_ETHERADDR_STRING(ap->hsaddr)); 253 return; 254 255 case 3: /* probe (oy!) */ 256 ND_PRINT("probe %s tell %s", AT(pdaddr), AT(psaddr)); 257 return; 258 } 259 ND_PRINT("len %u op %u htype %u ptype %#x halen %u palen %u", 260 length, GET_BE_U_2(ap->op), GET_BE_U_2(ap->htype), 261 GET_BE_U_2(ap->ptype), GET_U_1(ap->halen), GET_U_1(ap->palen)); 262 } 263 264 /* 265 * Print AppleTalk Datagram Delivery Protocol packets. 266 */ 267 static void 268 ddp_print(netdissect_options *ndo, 269 const u_char *bp, u_int length, u_int t, 270 u_short snet, u_char snode, u_char skt) 271 { 272 273 switch (t) { 274 275 case ddpNBP: 276 nbp_print(ndo, (const struct atNBP *)bp, length, snet, snode, skt); 277 break; 278 279 case ddpATP: 280 atp_print(ndo, (const struct atATP *)bp, length); 281 break; 282 283 case ddpEIGRP: 284 eigrp_print(ndo, bp, length); 285 break; 286 287 default: 288 ND_PRINT(" at-%s %u", tok2str(type2str, NULL, t), length); 289 break; 290 } 291 } 292 293 static void 294 atp_print(netdissect_options *ndo, 295 const struct atATP *ap, u_int length) 296 { 297 uint8_t control; 298 uint32_t data; 299 300 if ((const u_char *)(ap + 1) > ndo->ndo_snapend) { 301 /* Just bail if we don't have the whole chunk. */ 302 nd_print_trunc(ndo); 303 return; 304 } 305 if (length < sizeof(*ap)) { 306 ND_PRINT(" [|atp %u]", length); 307 return; 308 } 309 length -= sizeof(*ap); 310 control = GET_U_1(ap->control); 311 switch (control & 0xc0) { 312 313 case atpReqCode: 314 ND_PRINT(" atp-req%s %u", 315 control & atpXO? " " : "*", 316 GET_BE_U_2(ap->transID)); 317 318 atp_bitmap_print(ndo, GET_U_1(ap->bitmap)); 319 320 if (length != 0) 321 ND_PRINT(" [len=%u]", length); 322 323 switch (control & (atpEOM|atpSTS)) { 324 case atpEOM: 325 ND_PRINT(" [EOM]"); 326 break; 327 case atpSTS: 328 ND_PRINT(" [STS]"); 329 break; 330 case atpEOM|atpSTS: 331 ND_PRINT(" [EOM,STS]"); 332 break; 333 } 334 break; 335 336 case atpRspCode: 337 ND_PRINT(" atp-resp%s%u:%u (%u)", 338 control & atpEOM? "*" : " ", 339 GET_BE_U_2(ap->transID), GET_U_1(ap->bitmap), 340 length); 341 switch (control & (atpXO|atpSTS)) { 342 case atpXO: 343 ND_PRINT(" [XO]"); 344 break; 345 case atpSTS: 346 ND_PRINT(" [STS]"); 347 break; 348 case atpXO|atpSTS: 349 ND_PRINT(" [XO,STS]"); 350 break; 351 } 352 break; 353 354 case atpRelCode: 355 ND_PRINT(" atp-rel %u", GET_BE_U_2(ap->transID)); 356 357 atp_bitmap_print(ndo, GET_U_1(ap->bitmap)); 358 359 /* length should be zero */ 360 if (length) 361 ND_PRINT(" [len=%u]", length); 362 363 /* there shouldn't be any control flags */ 364 if (control & (atpXO|atpEOM|atpSTS)) { 365 char c = '['; 366 if (control & atpXO) { 367 ND_PRINT("%cXO", c); 368 c = ','; 369 } 370 if (control & atpEOM) { 371 ND_PRINT("%cEOM", c); 372 c = ','; 373 } 374 if (control & atpSTS) { 375 ND_PRINT("%cSTS", c); 376 } 377 ND_PRINT("]"); 378 } 379 break; 380 381 default: 382 ND_PRINT(" atp-0x%x %u (%u)", control, 383 GET_BE_U_2(ap->transID), length); 384 break; 385 } 386 data = GET_BE_U_4(ap->userData); 387 if (data != 0) 388 ND_PRINT(" 0x%x", data); 389 } 390 391 static void 392 atp_bitmap_print(netdissect_options *ndo, 393 u_char bm) 394 { 395 u_int i; 396 397 /* 398 * The '& 0xff' below is needed for compilers that want to sign 399 * extend a u_char, which is the case with the Ultrix compiler. 400 * (gcc is smart enough to eliminate it, at least on the Sparc). 401 */ 402 if ((bm + 1) & (bm & 0xff)) { 403 char c = '<'; 404 for (i = 0; bm; ++i) { 405 if (bm & 1) { 406 ND_PRINT("%c%u", c, i); 407 c = ','; 408 } 409 bm >>= 1; 410 } 411 ND_PRINT(">"); 412 } else { 413 for (i = 0; bm; ++i) 414 bm >>= 1; 415 if (i > 1) 416 ND_PRINT("<0-%u>", i - 1); 417 else 418 ND_PRINT("<0>"); 419 } 420 } 421 422 static void 423 nbp_print(netdissect_options *ndo, 424 const struct atNBP *np, u_int length, u_short snet, 425 u_char snode, u_char skt) 426 { 427 const struct atNBPtuple *tp = 428 (const struct atNBPtuple *)((const u_char *)np + nbpHeaderSize); 429 uint8_t control; 430 u_int i; 431 const u_char *ep; 432 433 if (length < nbpHeaderSize) { 434 ND_PRINT(" truncated-nbp %u", length); 435 return; 436 } 437 438 length -= nbpHeaderSize; 439 if (length < 8) { 440 /* must be room for at least one tuple */ 441 ND_PRINT(" truncated-nbp %u", length + nbpHeaderSize); 442 return; 443 } 444 /* ep points to end of available data */ 445 ep = ndo->ndo_snapend; 446 if ((const u_char *)tp > ep) { 447 nd_print_trunc(ndo); 448 return; 449 } 450 control = GET_U_1(np->control); 451 switch (i = (control & 0xf0)) { 452 453 case nbpBrRq: 454 case nbpLkUp: 455 ND_PRINT(i == nbpLkUp? " nbp-lkup %u:":" nbp-brRq %u:", 456 GET_U_1(np->id)); 457 if ((const u_char *)(tp + 1) > ep) { 458 nd_print_trunc(ndo); 459 return; 460 } 461 (void)nbp_name_print(ndo, tp, ep); 462 /* 463 * look for anomalies: the spec says there can only 464 * be one tuple, the address must match the source 465 * address and the enumerator should be zero. 466 */ 467 if ((control & 0xf) != 1) 468 ND_PRINT(" [ntup=%u]", control & 0xf); 469 if (GET_U_1(tp->enumerator)) 470 ND_PRINT(" [enum=%u]", GET_U_1(tp->enumerator)); 471 if (GET_BE_U_2(tp->net) != snet || 472 GET_U_1(tp->node) != snode || 473 GET_U_1(tp->skt) != skt) 474 ND_PRINT(" [addr=%s.%u]", 475 ataddr_string(ndo, GET_BE_U_2(tp->net), 476 GET_U_1(tp->node)), 477 GET_U_1(tp->skt)); 478 break; 479 480 case nbpLkUpReply: 481 ND_PRINT(" nbp-reply %u:", GET_U_1(np->id)); 482 483 /* print each of the tuples in the reply */ 484 for (i = control & 0xf; i != 0 && tp; i--) 485 tp = nbp_tuple_print(ndo, tp, ep, snet, snode, skt); 486 break; 487 488 default: 489 ND_PRINT(" nbp-0x%x %u (%u)", control, GET_U_1(np->id), 490 length); 491 break; 492 } 493 } 494 495 /* print a counted string */ 496 static const u_char * 497 print_cstring(netdissect_options *ndo, 498 const u_char *cp, const u_char *ep) 499 { 500 u_int length; 501 502 if (cp >= ep) { 503 nd_print_trunc(ndo); 504 return (0); 505 } 506 length = GET_U_1(cp); 507 cp++; 508 509 /* Spec says string can be at most 32 bytes long */ 510 if (length > 32) { 511 ND_PRINT("[len=%u]", length); 512 return (0); 513 } 514 while (length != 0) { 515 if (cp >= ep) { 516 nd_print_trunc(ndo); 517 return (0); 518 } 519 fn_print_char(ndo, GET_U_1(cp)); 520 cp++; 521 length--; 522 } 523 return (cp); 524 } 525 526 static const struct atNBPtuple * 527 nbp_tuple_print(netdissect_options *ndo, 528 const struct atNBPtuple *tp, const u_char *ep, 529 u_short snet, u_char snode, u_char skt) 530 { 531 const struct atNBPtuple *tpn; 532 533 if ((const u_char *)(tp + 1) > ep) { 534 nd_print_trunc(ndo); 535 return 0; 536 } 537 tpn = nbp_name_print(ndo, tp, ep); 538 539 /* if the enumerator isn't 1, print it */ 540 if (GET_U_1(tp->enumerator) != 1) 541 ND_PRINT("(%u)", GET_U_1(tp->enumerator)); 542 543 /* if the socket doesn't match the src socket, print it */ 544 if (GET_U_1(tp->skt) != skt) 545 ND_PRINT(" %u", GET_U_1(tp->skt)); 546 547 /* if the address doesn't match the src address, it's an anomaly */ 548 if (GET_BE_U_2(tp->net) != snet || 549 GET_U_1(tp->node) != snode) 550 ND_PRINT(" [addr=%s]", 551 ataddr_string(ndo, GET_BE_U_2(tp->net), GET_U_1(tp->node))); 552 553 return (tpn); 554 } 555 556 static const struct atNBPtuple * 557 nbp_name_print(netdissect_options *ndo, 558 const struct atNBPtuple *tp, const u_char *ep) 559 { 560 const u_char *cp = (const u_char *)tp + nbpTupleSize; 561 562 ND_PRINT(" "); 563 564 /* Object */ 565 ND_PRINT("\""); 566 if ((cp = print_cstring(ndo, cp, ep)) != NULL) { 567 /* Type */ 568 ND_PRINT(":"); 569 if ((cp = print_cstring(ndo, cp, ep)) != NULL) { 570 /* Zone */ 571 ND_PRINT("@"); 572 if ((cp = print_cstring(ndo, cp, ep)) != NULL) 573 ND_PRINT("\""); 574 } 575 } 576 return ((const struct atNBPtuple *)cp); 577 } 578 579 580 #define HASHNAMESIZE 4096 581 582 struct hnamemem { 583 u_int addr; 584 char *name; 585 struct hnamemem *nxt; 586 }; 587 588 static struct hnamemem hnametable[HASHNAMESIZE]; 589 590 static const char * 591 ataddr_string(netdissect_options *ndo, 592 u_short atnet, u_char athost) 593 { 594 struct hnamemem *tp, *tp2; 595 u_int i = (atnet << 8) | athost; 596 char nambuf[256+1]; 597 static int first = 1; 598 FILE *fp; 599 600 /* 601 * Are we doing address to name resolution? 602 */ 603 if (!ndo->ndo_nflag) { 604 /* 605 * Yes. Have we tried to open and read an AppleTalk 606 * number to name map file? 607 */ 608 if (!first) { 609 /* 610 * No; try to do so. 611 */ 612 first = 0; 613 fp = fopen("/etc/atalk.names", "r"); 614 if (fp != NULL) { 615 char line[256]; 616 u_int i1, i2; 617 618 while (fgets(line, sizeof(line), fp)) { 619 if (line[0] == '\n' || line[0] == 0 || 620 line[0] == '#') 621 continue; 622 if (sscanf(line, "%u.%u %256s", &i1, 623 &i2, nambuf) == 3) 624 /* got a hostname. */ 625 i2 |= (i1 << 8); 626 else if (sscanf(line, "%u %256s", &i1, 627 nambuf) == 2) 628 /* got a net name */ 629 i2 = (i1 << 8) | 255; 630 else 631 continue; 632 633 for (tp = &hnametable[i2 & (HASHNAMESIZE-1)]; 634 tp->nxt; tp = tp->nxt) 635 ; 636 tp->addr = i2; 637 tp->nxt = newhnamemem(ndo); 638 tp->name = strdup(nambuf); 639 if (tp->name == NULL) 640 (*ndo->ndo_error)(ndo, 641 S_ERR_ND_MEM_ALLOC, 642 "%s: strdup(nambuf)", __func__); 643 } 644 fclose(fp); 645 } 646 } 647 } 648 649 /* 650 * Now try to look up the address in the table. 651 */ 652 for (tp = &hnametable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt) 653 if (tp->addr == i) 654 return (tp->name); 655 656 /* didn't have the node name -- see if we've got the net name */ 657 i |= 255; 658 for (tp2 = &hnametable[i & (HASHNAMESIZE-1)]; tp2->nxt; tp2 = tp2->nxt) 659 if (tp2->addr == i) { 660 tp->addr = (atnet << 8) | athost; 661 tp->nxt = newhnamemem(ndo); 662 (void)snprintf(nambuf, sizeof(nambuf), "%s.%u", 663 tp2->name, athost); 664 tp->name = strdup(nambuf); 665 if (tp->name == NULL) 666 (*ndo->ndo_error)(ndo, S_ERR_ND_MEM_ALLOC, 667 "%s: strdup(nambuf)", __func__); 668 return (tp->name); 669 } 670 671 tp->addr = (atnet << 8) | athost; 672 tp->nxt = newhnamemem(ndo); 673 if (athost != 255) 674 (void)snprintf(nambuf, sizeof(nambuf), "%u.%u", atnet, athost); 675 else 676 (void)snprintf(nambuf, sizeof(nambuf), "%u", atnet); 677 tp->name = strdup(nambuf); 678 if (tp->name == NULL) 679 (*ndo->ndo_error)(ndo, S_ERR_ND_MEM_ALLOC, 680 "%s: strdup(nambuf)", __func__); 681 682 return (tp->name); 683 } 684 685 static const struct tok skt2str[] = { 686 { rtmpSkt, "rtmp" }, /* routing table maintenance */ 687 { nbpSkt, "nis" }, /* name info socket */ 688 { echoSkt, "echo" }, /* AppleTalk echo protocol */ 689 { zipSkt, "zip" }, /* zone info protocol */ 690 { 0, NULL } 691 }; 692 693 static const char * 694 ddpskt_string(netdissect_options *ndo, 695 u_int skt) 696 { 697 static char buf[8]; 698 699 if (ndo->ndo_nflag) { 700 (void)snprintf(buf, sizeof(buf), "%u", skt); 701 return (buf); 702 } 703 return (tok2str(skt2str, "%u", skt)); 704 } 705