|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1982, 1986 Regents of the University of California. ! 3: * All rights reserved. ! 4: * ! 5: * Redistribution and use in source and binary forms are permitted ! 6: * provided that the above copyright notice and this paragraph are ! 7: * duplicated in all such forms and that any documentation, ! 8: * advertising materials, and other materials related to such ! 9: * distribution and use acknowledge that the software was developed ! 10: * by the University of California, Berkeley. The name of the ! 11: * University may not be used to endorse or promote products derived ! 12: * from this software without specific prior written permission. ! 13: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR ! 14: * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED ! 15: * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. ! 16: * ! 17: * @(#)if_ether.c 7.7 (Berkeley) 6/29/88 ! 18: */ ! 19: ! 20: /* ! 21: * Ethernet address resolution protocol. ! 22: * TODO: ! 23: * run at splnet (add ARP protocol intr.) ! 24: * link entries onto hash chains, keep free list ! 25: * add "inuse/lock" bit (or ref. count) along with valid bit ! 26: */ ! 27: ! 28: #include "param.h" ! 29: #include "systm.h" ! 30: #include "mbuf.h" ! 31: #include "socket.h" ! 32: #include "time.h" ! 33: #include "kernel.h" ! 34: #include "errno.h" ! 35: #include "ioctl.h" ! 36: #include "syslog.h" ! 37: ! 38: #include "../net/if.h" ! 39: #include "in.h" ! 40: #include "in_systm.h" ! 41: #include "ip.h" ! 42: #include "if_ether.h" ! 43: ! 44: #ifdef GATEWAY ! 45: #define ARPTAB_BSIZ 16 /* bucket size */ ! 46: #define ARPTAB_NB 37 /* number of buckets */ ! 47: #else ! 48: #define ARPTAB_BSIZ 9 /* bucket size */ ! 49: #define ARPTAB_NB 19 /* number of buckets */ ! 50: #endif ! 51: #define ARPTAB_SIZE (ARPTAB_BSIZ * ARPTAB_NB) ! 52: struct arptab arptab[ARPTAB_SIZE]; ! 53: int arptab_size = ARPTAB_SIZE; /* for arp command */ ! 54: ! 55: /* ! 56: * ARP trailer negotiation. Trailer protocol is not IP specific, ! 57: * but ARP request/response use IP addresses. ! 58: */ ! 59: #define ETHERTYPE_IPTRAILERS ETHERTYPE_TRAIL ! 60: ! 61: #define ARPTAB_HASH(a) \ ! 62: ((u_long)(a) % ARPTAB_NB) ! 63: ! 64: #define ARPTAB_LOOK(at,addr) { \ ! 65: register n; \ ! 66: at = &arptab[ARPTAB_HASH(addr) * ARPTAB_BSIZ]; \ ! 67: for (n = 0 ; n < ARPTAB_BSIZ ; n++,at++) \ ! 68: if (at->at_iaddr.s_addr == addr) \ ! 69: break; \ ! 70: if (n >= ARPTAB_BSIZ) \ ! 71: at = 0; \ ! 72: } ! 73: ! 74: /* timer values */ ! 75: #define ARPT_AGE (60*1) /* aging timer, 1 min. */ ! 76: #define ARPT_KILLC 20 /* kill completed entry in 20 mins. */ ! 77: #define ARPT_KILLI 3 /* kill incomplete entry in 3 minutes */ ! 78: ! 79: u_char etherbroadcastaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; ! 80: extern struct ifnet loif; ! 81: ! 82: /* ! 83: * Timeout routine. Age arp_tab entries once a minute. ! 84: */ ! 85: arptimer() ! 86: { ! 87: register struct arptab *at; ! 88: register i; ! 89: ! 90: timeout(arptimer, (caddr_t)0, ARPT_AGE * hz); ! 91: at = &arptab[0]; ! 92: for (i = 0; i < ARPTAB_SIZE; i++, at++) { ! 93: if (at->at_flags == 0 || (at->at_flags & ATF_PERM)) ! 94: continue; ! 95: if (++at->at_timer < ((at->at_flags&ATF_COM) ? ! 96: ARPT_KILLC : ARPT_KILLI)) ! 97: continue; ! 98: /* timer has expired, clear entry */ ! 99: arptfree(at); ! 100: } ! 101: } ! 102: ! 103: /* ! 104: * Broadcast an ARP packet, asking who has addr on interface ac. ! 105: */ ! 106: arpwhohas(ac, addr) ! 107: register struct arpcom *ac; ! 108: struct in_addr *addr; ! 109: { ! 110: register struct mbuf *m; ! 111: register struct ether_header *eh; ! 112: register struct ether_arp *ea; ! 113: struct sockaddr sa; ! 114: ! 115: if ((m = m_get(M_DONTWAIT, MT_DATA)) == NULL) ! 116: return; ! 117: m->m_len = sizeof *ea; ! 118: m->m_off = MMAXOFF - m->m_len; ! 119: ea = mtod(m, struct ether_arp *); ! 120: eh = (struct ether_header *)sa.sa_data; ! 121: bzero((caddr_t)ea, sizeof (*ea)); ! 122: bcopy((caddr_t)etherbroadcastaddr, (caddr_t)eh->ether_dhost, ! 123: sizeof(eh->ether_dhost)); ! 124: eh->ether_type = ETHERTYPE_ARP; /* if_output will swap */ ! 125: ea->arp_hrd = htons(ARPHRD_ETHER); ! 126: ea->arp_pro = htons(ETHERTYPE_IP); ! 127: ea->arp_hln = sizeof(ea->arp_sha); /* hardware address length */ ! 128: ea->arp_pln = sizeof(ea->arp_spa); /* protocol address length */ ! 129: ea->arp_op = htons(ARPOP_REQUEST); ! 130: bcopy((caddr_t)ac->ac_enaddr, (caddr_t)ea->arp_sha, ! 131: sizeof(ea->arp_sha)); ! 132: bcopy((caddr_t)&ac->ac_ipaddr, (caddr_t)ea->arp_spa, ! 133: sizeof(ea->arp_spa)); ! 134: bcopy((caddr_t)addr, (caddr_t)ea->arp_tpa, sizeof(ea->arp_tpa)); ! 135: sa.sa_family = AF_UNSPEC; ! 136: (*ac->ac_if.if_output)(&ac->ac_if, m, &sa); ! 137: } ! 138: ! 139: int useloopback = 1; /* use loopback interface for local traffic */ ! 140: ! 141: /* ! 142: * Resolve an IP address into an ethernet address. If success, ! 143: * desten is filled in. If there is no entry in arptab, ! 144: * set one up and broadcast a request for the IP address. ! 145: * Hold onto this mbuf and resend it once the address ! 146: * is finally resolved. A return value of 1 indicates ! 147: * that desten has been filled in and the packet should be sent ! 148: * normally; a 0 return indicates that the packet has been ! 149: * taken over here, either now or for later transmission. ! 150: * ! 151: * We do some (conservative) locking here at splimp, since ! 152: * arptab is also altered from input interrupt service (ecintr/ilintr ! 153: * calls arpinput when ETHERTYPE_ARP packets come in). ! 154: */ ! 155: arpresolve(ac, m, destip, desten, usetrailers) ! 156: register struct arpcom *ac; ! 157: struct mbuf *m; ! 158: register struct in_addr *destip; ! 159: register u_char *desten; ! 160: int *usetrailers; ! 161: { ! 162: register struct arptab *at; ! 163: struct sockaddr_in sin; ! 164: u_long lna; ! 165: int s; ! 166: ! 167: *usetrailers = 0; ! 168: if (in_broadcast(*destip)) { /* broadcast address */ ! 169: bcopy((caddr_t)etherbroadcastaddr, (caddr_t)desten, ! 170: sizeof(etherbroadcastaddr)); ! 171: return (1); ! 172: } ! 173: lna = in_lnaof(*destip); ! 174: /* if for us, use software loopback driver if up */ ! 175: if (destip->s_addr == ac->ac_ipaddr.s_addr) { ! 176: /* ! 177: * This test used to be ! 178: * if (loif.if_flags & IFF_UP) ! 179: * It allowed local traffic to be forced ! 180: * through the hardware by configuring the loopback down. ! 181: * However, it causes problems during network configuration ! 182: * for boards that can't receive packets they send. ! 183: * It is now necessary to clear "useloopback" ! 184: * to force traffic out to the hardware. ! 185: */ ! 186: if (useloopback) { ! 187: sin.sin_family = AF_INET; ! 188: sin.sin_addr = *destip; ! 189: (void) looutput(&loif, m, (struct sockaddr *)&sin); ! 190: /* ! 191: * The packet has already been sent and freed. ! 192: */ ! 193: return (0); ! 194: } else { ! 195: bcopy((caddr_t)ac->ac_enaddr, (caddr_t)desten, ! 196: sizeof(ac->ac_enaddr)); ! 197: return (1); ! 198: } ! 199: } ! 200: s = splimp(); ! 201: ARPTAB_LOOK(at, destip->s_addr); ! 202: if (at == 0) { /* not found */ ! 203: if (ac->ac_if.if_flags & IFF_NOARP) { ! 204: bcopy((caddr_t)ac->ac_enaddr, (caddr_t)desten, 3); ! 205: desten[3] = (lna >> 16) & 0x7f; ! 206: desten[4] = (lna >> 8) & 0xff; ! 207: desten[5] = lna & 0xff; ! 208: splx(s); ! 209: return (1); ! 210: } else { ! 211: at = arptnew(destip); ! 212: if (at == 0) ! 213: panic("arpresolve: no free entry"); ! 214: at->at_hold = m; ! 215: arpwhohas(ac, destip); ! 216: splx(s); ! 217: return (0); ! 218: } ! 219: } ! 220: at->at_timer = 0; /* restart the timer */ ! 221: if (at->at_flags & ATF_COM) { /* entry IS complete */ ! 222: bcopy((caddr_t)at->at_enaddr, (caddr_t)desten, ! 223: sizeof(at->at_enaddr)); ! 224: if (at->at_flags & ATF_USETRAILERS) ! 225: *usetrailers = 1; ! 226: splx(s); ! 227: return (1); ! 228: } ! 229: /* ! 230: * There is an arptab entry, but no ethernet address ! 231: * response yet. Replace the held mbuf with this ! 232: * latest one. ! 233: */ ! 234: if (at->at_hold) ! 235: m_freem(at->at_hold); ! 236: at->at_hold = m; ! 237: arpwhohas(ac, destip); /* ask again */ ! 238: splx(s); ! 239: return (0); ! 240: } ! 241: ! 242: /* ! 243: * Called from 10 Mb/s Ethernet interrupt handlers ! 244: * when ether packet type ETHERTYPE_ARP ! 245: * is received. Common length and type checks are done here, ! 246: * then the protocol-specific routine is called. ! 247: */ ! 248: arpinput(ac, m) ! 249: struct arpcom *ac; ! 250: struct mbuf *m; ! 251: { ! 252: register struct arphdr *ar; ! 253: ! 254: if (ac->ac_if.if_flags & IFF_NOARP) ! 255: goto out; ! 256: IF_ADJ(m); ! 257: if (m->m_len < sizeof(struct arphdr)) ! 258: goto out; ! 259: ar = mtod(m, struct arphdr *); ! 260: if (ntohs(ar->ar_hrd) != ARPHRD_ETHER) ! 261: goto out; ! 262: if (m->m_len < sizeof(struct arphdr) + 2 * ar->ar_hln + 2 * ar->ar_pln) ! 263: goto out; ! 264: ! 265: switch (ntohs(ar->ar_pro)) { ! 266: ! 267: case ETHERTYPE_IP: ! 268: case ETHERTYPE_IPTRAILERS: ! 269: in_arpinput(ac, m); ! 270: return; ! 271: ! 272: default: ! 273: break; ! 274: } ! 275: out: ! 276: m_freem(m); ! 277: } ! 278: ! 279: /* ! 280: * ARP for Internet protocols on 10 Mb/s Ethernet. ! 281: * Algorithm is that given in RFC 826. ! 282: * In addition, a sanity check is performed on the sender ! 283: * protocol address, to catch impersonators. ! 284: * We also handle negotiations for use of trailer protocol: ! 285: * ARP replies for protocol type ETHERTYPE_TRAIL are sent ! 286: * along with IP replies if we want trailers sent to us, ! 287: * and also send them in response to IP replies. ! 288: * This allows either end to announce the desire to receive ! 289: * trailer packets. ! 290: * We reply to requests for ETHERTYPE_TRAIL protocol as well, ! 291: * but don't normally send requests. ! 292: */ ! 293: in_arpinput(ac, m) ! 294: register struct arpcom *ac; ! 295: struct mbuf *m; ! 296: { ! 297: register struct ether_arp *ea; ! 298: struct ether_header *eh; ! 299: register struct arptab *at; /* same as "merge" flag */ ! 300: struct mbuf *mcopy = 0; ! 301: struct sockaddr_in sin; ! 302: struct sockaddr sa; ! 303: struct in_addr isaddr, itaddr, myaddr; ! 304: int proto, op, s, completed = 0; ! 305: ! 306: myaddr = ac->ac_ipaddr; ! 307: ea = mtod(m, struct ether_arp *); ! 308: proto = ntohs(ea->arp_pro); ! 309: op = ntohs(ea->arp_op); ! 310: bcopy((caddr_t)ea->arp_spa, (caddr_t)&isaddr, sizeof (isaddr)); ! 311: bcopy((caddr_t)ea->arp_tpa, (caddr_t)&itaddr, sizeof (itaddr)); ! 312: if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr, ! 313: sizeof (ea->arp_sha))) ! 314: goto out; /* it's from me, ignore it. */ ! 315: if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)etherbroadcastaddr, ! 316: sizeof (ea->arp_sha))) { ! 317: log(LOG_ERR, ! 318: "arp: ether address is broadcast for IP address %x!\n", ! 319: ntohl(isaddr.s_addr)); ! 320: goto out; ! 321: } ! 322: if (isaddr.s_addr == myaddr.s_addr) { ! 323: log(LOG_ERR, "%s: %s\n", ! 324: "duplicate IP address!! sent from ethernet address", ! 325: ether_sprintf(ea->arp_sha)); ! 326: itaddr = myaddr; ! 327: if (op == ARPOP_REQUEST) ! 328: goto reply; ! 329: goto out; ! 330: } ! 331: s = splimp(); ! 332: ARPTAB_LOOK(at, isaddr.s_addr); ! 333: if (at) { ! 334: bcopy((caddr_t)ea->arp_sha, (caddr_t)at->at_enaddr, ! 335: sizeof(ea->arp_sha)); ! 336: if ((at->at_flags & ATF_COM) == 0) ! 337: completed = 1; ! 338: at->at_flags |= ATF_COM; ! 339: if (at->at_hold) { ! 340: sin.sin_family = AF_INET; ! 341: sin.sin_addr = isaddr; ! 342: (*ac->ac_if.if_output)(&ac->ac_if, ! 343: at->at_hold, (struct sockaddr *)&sin); ! 344: at->at_hold = 0; ! 345: } ! 346: } ! 347: if (at == 0 && itaddr.s_addr == myaddr.s_addr) { ! 348: /* ensure we have a table entry */ ! 349: if (at = arptnew(&isaddr)) { ! 350: bcopy((caddr_t)ea->arp_sha, (caddr_t)at->at_enaddr, ! 351: sizeof(ea->arp_sha)); ! 352: completed = 1; ! 353: at->at_flags |= ATF_COM; ! 354: } ! 355: } ! 356: splx(s); ! 357: reply: ! 358: switch (proto) { ! 359: ! 360: case ETHERTYPE_IPTRAILERS: ! 361: /* partner says trailers are OK */ ! 362: if (at) ! 363: at->at_flags |= ATF_USETRAILERS; ! 364: /* ! 365: * Reply to request iff we want trailers. ! 366: */ ! 367: if (op != ARPOP_REQUEST || ac->ac_if.if_flags & IFF_NOTRAILERS) ! 368: goto out; ! 369: break; ! 370: ! 371: case ETHERTYPE_IP: ! 372: /* ! 373: * Reply if this is an IP request, ! 374: * or if we want to send a trailer response. ! 375: * Send the latter only to the IP response ! 376: * that completes the current ARP entry. ! 377: */ ! 378: if (op != ARPOP_REQUEST && ! 379: (completed == 0 || ac->ac_if.if_flags & IFF_NOTRAILERS)) ! 380: goto out; ! 381: } ! 382: if (itaddr.s_addr == myaddr.s_addr) { ! 383: /* I am the target */ ! 384: bcopy((caddr_t)ea->arp_sha, (caddr_t)ea->arp_tha, ! 385: sizeof(ea->arp_sha)); ! 386: bcopy((caddr_t)ac->ac_enaddr, (caddr_t)ea->arp_sha, ! 387: sizeof(ea->arp_sha)); ! 388: } else { ! 389: ARPTAB_LOOK(at, itaddr.s_addr); ! 390: if (at == NULL || (at->at_flags & ATF_PUBL) == 0) ! 391: goto out; ! 392: bcopy((caddr_t)ea->arp_sha, (caddr_t)ea->arp_tha, ! 393: sizeof(ea->arp_sha)); ! 394: bcopy((caddr_t)at->at_enaddr, (caddr_t)ea->arp_sha, ! 395: sizeof(ea->arp_sha)); ! 396: } ! 397: ! 398: bcopy((caddr_t)ea->arp_spa, (caddr_t)ea->arp_tpa, ! 399: sizeof(ea->arp_spa)); ! 400: bcopy((caddr_t)&itaddr, (caddr_t)ea->arp_spa, ! 401: sizeof(ea->arp_spa)); ! 402: ea->arp_op = htons(ARPOP_REPLY); ! 403: /* ! 404: * If incoming packet was an IP reply, ! 405: * we are sending a reply for type IPTRAILERS. ! 406: * If we are sending a reply for type IP ! 407: * and we want to receive trailers, ! 408: * send a trailer reply as well. ! 409: */ ! 410: if (op == ARPOP_REPLY) ! 411: ea->arp_pro = htons(ETHERTYPE_IPTRAILERS); ! 412: else if (proto == ETHERTYPE_IP && ! 413: (ac->ac_if.if_flags & IFF_NOTRAILERS) == 0) ! 414: mcopy = m_copy(m, 0, (int)M_COPYALL); ! 415: eh = (struct ether_header *)sa.sa_data; ! 416: bcopy((caddr_t)ea->arp_tha, (caddr_t)eh->ether_dhost, ! 417: sizeof(eh->ether_dhost)); ! 418: eh->ether_type = ETHERTYPE_ARP; ! 419: sa.sa_family = AF_UNSPEC; ! 420: (*ac->ac_if.if_output)(&ac->ac_if, m, &sa); ! 421: if (mcopy) { ! 422: ea = mtod(mcopy, struct ether_arp *); ! 423: ea->arp_pro = htons(ETHERTYPE_IPTRAILERS); ! 424: (*ac->ac_if.if_output)(&ac->ac_if, mcopy, &sa); ! 425: } ! 426: return; ! 427: out: ! 428: m_freem(m); ! 429: return; ! 430: } ! 431: ! 432: /* ! 433: * Free an arptab entry. ! 434: */ ! 435: arptfree(at) ! 436: register struct arptab *at; ! 437: { ! 438: int s = splimp(); ! 439: ! 440: if (at->at_hold) ! 441: m_freem(at->at_hold); ! 442: at->at_hold = 0; ! 443: at->at_timer = at->at_flags = 0; ! 444: at->at_iaddr.s_addr = 0; ! 445: splx(s); ! 446: } ! 447: ! 448: /* ! 449: * Enter a new address in arptab, pushing out the oldest entry ! 450: * from the bucket if there is no room. ! 451: * This always succeeds since no bucket can be completely filled ! 452: * with permanent entries (except from arpioctl when testing whether ! 453: * another permanent entry will fit). ! 454: * MUST BE CALLED AT SPLIMP. ! 455: */ ! 456: struct arptab * ! 457: arptnew(addr) ! 458: struct in_addr *addr; ! 459: { ! 460: register n; ! 461: int oldest = -1; ! 462: register struct arptab *at, *ato = NULL; ! 463: static int first = 1; ! 464: ! 465: if (first) { ! 466: first = 0; ! 467: timeout(arptimer, (caddr_t)0, hz); ! 468: } ! 469: at = &arptab[ARPTAB_HASH(addr->s_addr) * ARPTAB_BSIZ]; ! 470: for (n = 0; n < ARPTAB_BSIZ; n++,at++) { ! 471: if (at->at_flags == 0) ! 472: goto out; /* found an empty entry */ ! 473: if (at->at_flags & ATF_PERM) ! 474: continue; ! 475: if ((int) at->at_timer > oldest) { ! 476: oldest = at->at_timer; ! 477: ato = at; ! 478: } ! 479: } ! 480: if (ato == NULL) ! 481: return (NULL); ! 482: at = ato; ! 483: arptfree(at); ! 484: out: ! 485: at->at_iaddr = *addr; ! 486: at->at_flags = ATF_INUSE; ! 487: return (at); ! 488: } ! 489: ! 490: arpioctl(cmd, data) ! 491: int cmd; ! 492: caddr_t data; ! 493: { ! 494: register struct arpreq *ar = (struct arpreq *)data; ! 495: register struct arptab *at; ! 496: register struct sockaddr_in *sin; ! 497: int s; ! 498: ! 499: if (ar->arp_pa.sa_family != AF_INET || ! 500: ar->arp_ha.sa_family != AF_UNSPEC) ! 501: return (EAFNOSUPPORT); ! 502: sin = (struct sockaddr_in *)&ar->arp_pa; ! 503: s = splimp(); ! 504: ARPTAB_LOOK(at, sin->sin_addr.s_addr); ! 505: if (at == NULL) { /* not found */ ! 506: if (cmd != SIOCSARP) { ! 507: splx(s); ! 508: return (ENXIO); ! 509: } ! 510: if (ifa_ifwithnet(&ar->arp_pa) == NULL) { ! 511: splx(s); ! 512: return (ENETUNREACH); ! 513: } ! 514: } ! 515: switch (cmd) { ! 516: ! 517: case SIOCSARP: /* set entry */ ! 518: if (at == NULL) { ! 519: at = arptnew(&sin->sin_addr); ! 520: if (at == NULL) { ! 521: splx(s); ! 522: return (EADDRNOTAVAIL); ! 523: } ! 524: if (ar->arp_flags & ATF_PERM) { ! 525: /* never make all entries in a bucket permanent */ ! 526: register struct arptab *tat; ! 527: ! 528: /* try to re-allocate */ ! 529: tat = arptnew(&sin->sin_addr); ! 530: if (tat == NULL) { ! 531: arptfree(at); ! 532: splx(s); ! 533: return (EADDRNOTAVAIL); ! 534: } ! 535: arptfree(tat); ! 536: } ! 537: } ! 538: bcopy((caddr_t)ar->arp_ha.sa_data, (caddr_t)at->at_enaddr, ! 539: sizeof(at->at_enaddr)); ! 540: at->at_flags = ATF_COM | ATF_INUSE | ! 541: (ar->arp_flags & (ATF_PERM|ATF_PUBL|ATF_USETRAILERS)); ! 542: at->at_timer = 0; ! 543: break; ! 544: ! 545: case SIOCDARP: /* delete entry */ ! 546: arptfree(at); ! 547: break; ! 548: ! 549: case SIOCGARP: /* get entry */ ! 550: bcopy((caddr_t)at->at_enaddr, (caddr_t)ar->arp_ha.sa_data, ! 551: sizeof(at->at_enaddr)); ! 552: ar->arp_flags = at->at_flags; ! 553: break; ! 554: } ! 555: splx(s); ! 556: return (0); ! 557: } ! 558: ! 559: /* ! 560: * Convert Ethernet address to printable (loggable) representation. ! 561: */ ! 562: char * ! 563: ether_sprintf(ap) ! 564: register u_char *ap; ! 565: { ! 566: register i; ! 567: static char etherbuf[18]; ! 568: register char *cp = etherbuf; ! 569: static char digits[] = "0123456789abcdef"; ! 570: ! 571: for (i = 0; i < 6; i++) { ! 572: *cp++ = digits[*ap >> 4]; ! 573: *cp++ = digits[*ap++ & 0xf]; ! 574: *cp++ = ':'; ! 575: } ! 576: *--cp = 0; ! 577: return (etherbuf); ! 578: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.