|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. ! 3: * ! 4: * @APPLE_LICENSE_HEADER_START@ ! 5: * ! 6: * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights ! 7: * Reserved. This file contains Original Code and/or Modifications of ! 8: * Original Code as defined in and that are subject to the Apple Public ! 9: * Source License Version 1.1 (the "License"). You may not use this file ! 10: * except in compliance with the License. Please obtain a copy of the ! 11: * License at http://www.apple.com/publicsource and read it before using ! 12: * this file. ! 13: * ! 14: * The Original Code and all software distributed under the License are ! 15: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER ! 16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, ! 17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, ! 18: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the ! 19: * License for the specific language governing rights and limitations ! 20: * under the License. ! 21: * ! 22: * @APPLE_LICENSE_HEADER_END@ ! 23: */ ! 24: ! 25: /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ ! 26: /* ! 27: * Copyright (c) 1980, 1986, 1991, 1993 ! 28: * The Regents of the University of California. All rights reserved. ! 29: * ! 30: * Redistribution and use in source and binary forms, with or without ! 31: * modification, are permitted provided that the following conditions ! 32: * are met: ! 33: * 1. Redistributions of source code must retain the above copyright ! 34: * notice, this list of conditions and the following disclaimer. ! 35: * 2. Redistributions in binary form must reproduce the above copyright ! 36: * notice, this list of conditions and the following disclaimer in the ! 37: * documentation and/or other materials provided with the distribution. ! 38: * 3. All advertising materials mentioning features or use of this software ! 39: * must display the following acknowledgement: ! 40: * This product includes software developed by the University of ! 41: * California, Berkeley and its contributors. ! 42: * 4. Neither the name of the University nor the names of its contributors ! 43: * may be used to endorse or promote products derived from this software ! 44: * without specific prior written permission. ! 45: * ! 46: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ! 47: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ! 48: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ! 49: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ! 50: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ! 51: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ! 52: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ! 53: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ! 54: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ! 55: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ! 56: * SUCH DAMAGE. ! 57: * ! 58: * @(#)route.c 8.3 (Berkeley) 1/9/95 ! 59: */ ! 60: ! 61: #include <sys/param.h> ! 62: #include <sys/systm.h> ! 63: #include <sys/proc.h> ! 64: #include <sys/mbuf.h> ! 65: #include <sys/socket.h> ! 66: #include <sys/socketvar.h> ! 67: #include <sys/domain.h> ! 68: #include <sys/protosw.h> ! 69: #include <sys/ioctl.h> ! 70: #include <sys/malloc.h> ! 71: ! 72: #include <net/if.h> ! 73: #include <net/route.h> ! 74: #include <net/raw_cb.h> ! 75: ! 76: #include <netinet/in.h> ! 77: #include <netinet/in_var.h> ! 78: ! 79: #if NS ! 80: #include <netns/ns.h> ! 81: #endif ! 82: ! 83: #define SA(p) ((struct sockaddr *)(p)) ! 84: ! 85: int rttrash; /* routes not in table but not freed */ ! 86: struct sockaddr wildcard; /* zero valued cookie for wildcard searches */ ! 87: struct rtstat rtstat; ! 88: struct radix_node_head *rt_tables[AF_MAX+1]; ! 89: ! 90: /* Should be in spl.h */ ! 91: int splimp(void); ! 92: int splnet(void); ! 93: int splx(int); ! 94: ! 95: void ! 96: rtable_init(table) ! 97: void **table; ! 98: { ! 99: struct domain *dom; ! 100: for (dom = domains; dom; dom = dom->dom_next) ! 101: if (dom->dom_rtattach) ! 102: dom->dom_rtattach(&table[dom->dom_family], ! 103: dom->dom_rtoffset); ! 104: } ! 105: ! 106: void ! 107: route_init() ! 108: { ! 109: rn_init(); /* initialize all zeroes, all ones, mask table */ ! 110: rtable_init((void **)rt_tables); ! 111: } ! 112: ! 113: /* ! 114: * Packet routing routines. ! 115: */ ! 116: void ! 117: rtalloc(ro) ! 118: register struct route *ro; ! 119: { ! 120: if (ro->ro_rt && ro->ro_rt->rt_ifp && (ro->ro_rt->rt_flags & RTF_UP)) ! 121: return; /* XXX */ ! 122: ro->ro_rt = rtalloc1(&ro->ro_dst, 1); ! 123: } ! 124: ! 125: struct rtentry * ! 126: rtalloc1(dst, report) ! 127: register struct sockaddr *dst; ! 128: int report; ! 129: { ! 130: register struct radix_node_head *rnh = rt_tables[dst->sa_family]; ! 131: register struct rtentry *rt; ! 132: register struct radix_node *rn; ! 133: struct rtentry *newrt = 0; ! 134: struct rt_addrinfo info; ! 135: int s = splnet(), err = 0, msgtype = RTM_MISS; ! 136: ! 137: if (rnh && (rn = rnh->rnh_matchaddr((caddr_t)dst, rnh)) && ! 138: ((rn->rn_flags & RNF_ROOT) == 0)) { ! 139: newrt = rt = (struct rtentry *)rn; ! 140: if (report && (rt->rt_flags & RTF_CLONING)) { ! 141: err = rtrequest(RTM_RESOLVE, dst, SA(0), ! 142: SA(0), 0, &newrt); ! 143: if (err) { ! 144: newrt = rt; ! 145: rt->rt_refcnt++; ! 146: goto miss; ! 147: } ! 148: if ((rt = newrt) && (rt->rt_flags & RTF_XRESOLVE)) { ! 149: msgtype = RTM_RESOLVE; ! 150: goto miss; ! 151: } ! 152: } else ! 153: rt->rt_refcnt++; ! 154: } else { ! 155: rtstat.rts_unreach++; ! 156: miss: if (report) { ! 157: bzero((caddr_t)&info, sizeof(info)); ! 158: info.rti_info[RTAX_DST] = dst; ! 159: rt_missmsg(msgtype, &info, 0, err); ! 160: } ! 161: } ! 162: splx(s); ! 163: return (newrt); ! 164: } ! 165: ! 166: void ! 167: rtfree(rt) ! 168: register struct rtentry *rt; ! 169: { ! 170: register struct ifaddr *ifa; ! 171: ! 172: if (rt == 0) ! 173: panic("rtfree"); ! 174: rt->rt_refcnt--; ! 175: if (rt->rt_refcnt <= 0 && (rt->rt_flags & RTF_UP) == 0) { ! 176: if (rt->rt_nodes->rn_flags & (RNF_ACTIVE | RNF_ROOT)) ! 177: panic ("rtfree 2"); ! 178: rttrash--; ! 179: if (rt->rt_refcnt < 0) { ! 180: printf("rtfree: %x not freed (neg refs)\n", ! 181: (unsigned int)rt); ! 182: return; ! 183: } ! 184: ifa = rt->rt_ifa; ! 185: IFAFREE(ifa); ! 186: Free(rt_key(rt)); ! 187: Free(rt); ! 188: } ! 189: } ! 190: ! 191: void ! 192: ifafree(ifa) ! 193: register struct ifaddr *ifa; ! 194: { ! 195: if (ifa == NULL) ! 196: panic("ifafree"); ! 197: if (ifa->ifa_refcnt == 0) ! 198: _FREE(ifa, M_IFADDR); ! 199: else ! 200: ifa->ifa_refcnt--; ! 201: } ! 202: ! 203: /* ! 204: * Force a routing table entry to the specified ! 205: * destination to go through the given gateway. ! 206: * Normally called as a result of a routing redirect ! 207: * message from the network layer. ! 208: * ! 209: * N.B.: must be called at splnet ! 210: */ ! 211: int ! 212: rtredirect(dst, gateway, netmask, flags, src, rtp) ! 213: struct sockaddr *dst, *gateway, *netmask, *src; ! 214: int flags; ! 215: struct rtentry **rtp; ! 216: { ! 217: register struct rtentry *rt; ! 218: int error = 0; ! 219: short *stat = 0; ! 220: struct rt_addrinfo info; ! 221: struct ifaddr *ifa; ! 222: ! 223: /* verify the gateway is directly reachable */ ! 224: if ((ifa = ifa_ifwithnet(gateway)) == 0) { ! 225: error = ENETUNREACH; ! 226: goto out; ! 227: } ! 228: rt = rtalloc1(dst, 0); ! 229: /* ! 230: * If the redirect isn't from our current router for this dst, ! 231: * it's either old or wrong. If it redirects us to ourselves, ! 232: * we have a routing loop, perhaps as a result of an interface ! 233: * going down recently. ! 234: */ ! 235: #define equal(a1, a2) (bcmp((caddr_t)(a1), (caddr_t)(a2), (a1)->sa_len) == 0) ! 236: if (!(flags & RTF_DONE) && rt && ! 237: (!equal(src, rt->rt_gateway) || rt->rt_ifa != ifa)) ! 238: error = EINVAL; ! 239: else if (ifa_ifwithaddr(gateway)) ! 240: error = EHOSTUNREACH; ! 241: if (error) ! 242: goto done; ! 243: /* ! 244: * Create a new entry if we just got back a wildcard entry ! 245: * or the the lookup failed. This is necessary for hosts ! 246: * which use routing redirects generated by smart gateways ! 247: * to dynamically build the routing tables. ! 248: */ ! 249: if ((rt == 0) || (rt_mask(rt) && rt_mask(rt)->sa_len < 2)) ! 250: goto create; ! 251: /* ! 252: * Don't listen to the redirect if it's ! 253: * for a route to an interface. ! 254: */ ! 255: if (rt->rt_flags & RTF_GATEWAY) { ! 256: if (((rt->rt_flags & RTF_HOST) == 0) && (flags & RTF_HOST)) { ! 257: /* ! 258: * Changing from route to net => route to host. ! 259: * Create new route, rather than smashing route to net. ! 260: */ ! 261: create: ! 262: flags |= RTF_GATEWAY | RTF_DYNAMIC; ! 263: error = rtrequest((int)RTM_ADD, dst, gateway, ! 264: netmask, flags, ! 265: (struct rtentry **)0); ! 266: stat = &rtstat.rts_dynamic; ! 267: } else { ! 268: /* ! 269: * Smash the current notion of the gateway to ! 270: * this destination. Should check about netmask!!! ! 271: */ ! 272: rt->rt_flags |= RTF_MODIFIED; ! 273: flags |= RTF_MODIFIED; ! 274: stat = &rtstat.rts_newgateway; ! 275: rt_setgate(rt, rt_key(rt), gateway); ! 276: } ! 277: } else ! 278: error = EHOSTUNREACH; ! 279: done: ! 280: if (rt) { ! 281: if (rtp && !error) ! 282: *rtp = rt; ! 283: else ! 284: rtfree(rt); ! 285: } ! 286: out: ! 287: if (error) ! 288: rtstat.rts_badredirect++; ! 289: else if (stat != NULL) ! 290: (*stat)++; ! 291: bzero((caddr_t)&info, sizeof(info)); ! 292: info.rti_info[RTAX_DST] = dst; ! 293: info.rti_info[RTAX_GATEWAY] = gateway; ! 294: info.rti_info[RTAX_NETMASK] = netmask; ! 295: info.rti_info[RTAX_AUTHOR] = src; ! 296: rt_missmsg(RTM_REDIRECT, &info, flags, error); ! 297: return(0); ! 298: } ! 299: ! 300: /* ! 301: * Routing table ioctl interface. ! 302: */ ! 303: int ! 304: rtioctl(req, data, p) ! 305: u_long req; ! 306: caddr_t data; ! 307: struct proc *p; ! 308: { ! 309: return (EOPNOTSUPP); ! 310: } ! 311: ! 312: struct ifaddr * ! 313: ifa_ifwithroute(flags, dst, gateway) ! 314: int flags; ! 315: struct sockaddr *dst, *gateway; ! 316: { ! 317: register struct ifaddr *ifa; ! 318: if ((flags & RTF_GATEWAY) == 0) { ! 319: /* ! 320: * If we are adding a route to an interface, ! 321: * and the interface is a pt to pt link ! 322: * we should search for the destination ! 323: * as our clue to the interface. Otherwise ! 324: * we can use the local address. ! 325: */ ! 326: ifa = 0; ! 327: if (flags & RTF_HOST) ! 328: ifa = ifa_ifwithdstaddr(dst); ! 329: if (ifa == 0) ! 330: ifa = ifa_ifwithaddr(gateway); ! 331: } else { ! 332: /* ! 333: * If we are adding a route to a remote net ! 334: * or host, the gateway may still be on the ! 335: * other end of a pt to pt link. ! 336: */ ! 337: ifa = ifa_ifwithdstaddr(gateway); ! 338: } ! 339: if (ifa == 0) ! 340: ifa = ifa_ifwithnet(gateway); ! 341: if (ifa == 0) { ! 342: struct rtentry *rt = rtalloc1(dst, 0); ! 343: if (rt == 0) ! 344: return (0); ! 345: rt->rt_refcnt--; ! 346: if ((ifa = rt->rt_ifa) == 0) ! 347: return (0); ! 348: } ! 349: if (ifa->ifa_addr->sa_family != dst->sa_family) { ! 350: struct ifaddr *oifa = ifa; ! 351: ifa = ifaof_ifpforaddr(dst, ifa->ifa_ifp); ! 352: if (ifa == 0) ! 353: ifa = oifa; ! 354: } ! 355: return (ifa); ! 356: } ! 357: ! 358: #define ROUNDUP(a) (a>0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) ! 359: ! 360: int ! 361: rtrequest(req, dst, gateway, netmask, flags, ret_nrt) ! 362: int req, flags; ! 363: struct sockaddr *dst, *gateway, *netmask; ! 364: struct rtentry **ret_nrt; ! 365: { ! 366: int s = splnet(); int error = 0; ! 367: register struct rtentry *rt; ! 368: register struct radix_node *rn; ! 369: register struct radix_node_head *rnh; ! 370: struct ifaddr *ifa; ! 371: struct sockaddr *ndst; ! 372: #define senderr(x) { error = x ; goto bad; } ! 373: ! 374: if ((rnh = rt_tables[dst->sa_family]) == 0) ! 375: senderr(ESRCH); ! 376: if (flags & RTF_HOST) ! 377: netmask = 0; ! 378: switch (req) { ! 379: case RTM_DELETE: ! 380: if ((rn = rnh->rnh_deladdr(dst, netmask, rnh)) == 0) ! 381: senderr(ESRCH); ! 382: if (rn->rn_flags & (RNF_ACTIVE | RNF_ROOT)) ! 383: panic ("rtrequest delete"); ! 384: rt = (struct rtentry *)rn; ! 385: rt->rt_flags &= ~RTF_UP; ! 386: if (rt->rt_gwroute) { ! 387: rt = rt->rt_gwroute; RTFREE(rt); ! 388: (rt = (struct rtentry *)rn)->rt_gwroute = 0; ! 389: } ! 390: if ((ifa = rt->rt_ifa) && ifa->ifa_rtrequest) ! 391: ifa->ifa_rtrequest(RTM_DELETE, rt, SA(0)); ! 392: rttrash++; ! 393: if (ret_nrt) ! 394: *ret_nrt = rt; ! 395: else if (rt->rt_refcnt <= 0) { ! 396: rt->rt_refcnt++; ! 397: rtfree(rt); ! 398: } ! 399: break; ! 400: ! 401: case RTM_RESOLVE: ! 402: if (ret_nrt == 0 || (rt = *ret_nrt) == 0) ! 403: senderr(EINVAL); ! 404: ifa = rt->rt_ifa; ! 405: flags = rt->rt_flags & ~RTF_CLONING; ! 406: gateway = rt->rt_gateway; ! 407: if ((netmask = rt->rt_genmask) == 0) ! 408: flags |= RTF_HOST; ! 409: goto makeroute; ! 410: ! 411: case RTM_ADD: ! 412: if ((ifa = ifa_ifwithroute(flags, dst, gateway)) == 0) ! 413: senderr(ENETUNREACH); ! 414: makeroute: ! 415: R_Malloc(rt, struct rtentry *, sizeof(*rt)); ! 416: if (rt == 0) ! 417: senderr(ENOBUFS); ! 418: Bzero(rt, sizeof(*rt)); ! 419: rt->rt_flags = RTF_UP | flags; ! 420: if (rt_setgate(rt, dst, gateway)) { ! 421: Free(rt); ! 422: senderr(ENOBUFS); ! 423: } ! 424: ndst = rt_key(rt); ! 425: if (netmask) { ! 426: rt_maskedcopy(dst, ndst, netmask); ! 427: } else ! 428: Bcopy(dst, ndst, dst->sa_len); ! 429: rn = rnh->rnh_addaddr((caddr_t)ndst, (caddr_t)netmask, ! 430: rnh, rt->rt_nodes); ! 431: if (rn == 0) { ! 432: if (rt->rt_gwroute) ! 433: rtfree(rt->rt_gwroute); ! 434: Free(rt_key(rt)); ! 435: Free(rt); ! 436: senderr(EEXIST); ! 437: } ! 438: ifa->ifa_refcnt++; ! 439: rt->rt_ifa = ifa; ! 440: rt->rt_ifp = ifa->ifa_ifp; ! 441: if (req == RTM_RESOLVE) ! 442: rt->rt_rmx = (*ret_nrt)->rt_rmx; /* copy metrics */ ! 443: if (ifa->ifa_rtrequest) ! 444: ifa->ifa_rtrequest(req, rt, SA(ret_nrt ? *ret_nrt : 0)); ! 445: if (ret_nrt) { ! 446: *ret_nrt = rt; ! 447: rt->rt_refcnt++; ! 448: } ! 449: break; ! 450: } ! 451: bad: ! 452: splx(s); ! 453: return (error); ! 454: } ! 455: ! 456: int ! 457: rt_setgate(rt0, dst, gate) ! 458: struct rtentry *rt0; ! 459: struct sockaddr *dst, *gate; ! 460: { ! 461: caddr_t new, old; ! 462: int dlen = ROUNDUP(dst->sa_len), glen = ROUNDUP(gate->sa_len); ! 463: register struct rtentry *rt = rt0; ! 464: ! 465: if (rt->rt_gateway == 0 || glen > ROUNDUP(rt->rt_gateway->sa_len)) { ! 466: old = (caddr_t)rt_key(rt); ! 467: R_Malloc(new, caddr_t, dlen + glen); ! 468: if (new == 0) ! 469: return 1; ! 470: rt->rt_nodes->rn_key = new; ! 471: } else { ! 472: new = rt->rt_nodes->rn_key; ! 473: old = 0; ! 474: } ! 475: Bcopy(gate, (rt->rt_gateway = (struct sockaddr *)(new + dlen)), glen); ! 476: if (old) { ! 477: Bcopy(dst, new, dlen); ! 478: Free(old); ! 479: } ! 480: if (rt->rt_gwroute) { ! 481: rt = rt->rt_gwroute; RTFREE(rt); ! 482: rt = rt0; rt->rt_gwroute = 0; ! 483: } ! 484: if (rt->rt_flags & RTF_GATEWAY) { ! 485: rt->rt_gwroute = rtalloc1(gate, 1); ! 486: } ! 487: return 0; ! 488: } ! 489: ! 490: void ! 491: rt_maskedcopy(src, dst, netmask) ! 492: struct sockaddr *src, *dst, *netmask; ! 493: { ! 494: register u_char *cp1 = (u_char *)src; ! 495: register u_char *cp2 = (u_char *)dst; ! 496: register u_char *cp3 = (u_char *)netmask; ! 497: u_char *cplim = cp2 + *cp3; ! 498: u_char *cplim2 = cp2 + *cp1; ! 499: ! 500: *cp2++ = *cp1++; *cp2++ = *cp1++; /* copies sa_len & sa_family */ ! 501: cp3 += 2; ! 502: if (cplim > cplim2) ! 503: cplim = cplim2; ! 504: while (cp2 < cplim) ! 505: *cp2++ = *cp1++ & *cp3++; ! 506: if (cp2 < cplim2) ! 507: bzero((caddr_t)cp2, (unsigned)(cplim2 - cp2)); ! 508: } ! 509: ! 510: /* ! 511: * Set up a routing table entry, normally ! 512: * for an interface. ! 513: */ ! 514: int ! 515: rtinit(ifa, cmd, flags) ! 516: register struct ifaddr *ifa; ! 517: int cmd, flags; ! 518: { ! 519: register struct rtentry *rt; ! 520: register struct sockaddr *dst; ! 521: register struct sockaddr *deldst; ! 522: struct mbuf *m = 0; ! 523: struct rtentry *nrt = 0; ! 524: int error; ! 525: ! 526: dst = flags & RTF_HOST ? ifa->ifa_dstaddr : ifa->ifa_addr; ! 527: if (cmd == RTM_DELETE) { ! 528: if ((flags & RTF_HOST) == 0 && ifa->ifa_netmask) { ! 529: m = m_get(M_WAIT, MT_SONAME); ! 530: deldst = mtod(m, struct sockaddr *); ! 531: rt_maskedcopy(dst, deldst, ifa->ifa_netmask); ! 532: dst = deldst; ! 533: } ! 534: if ((rt = rtalloc1(dst, 0))) { ! 535: rt->rt_refcnt--; ! 536: if (rt->rt_ifa != ifa) { ! 537: if (m) ! 538: (void) m_free(m); ! 539: return (flags & RTF_HOST ? EHOSTUNREACH ! 540: : ENETUNREACH); ! 541: } ! 542: } ! 543: } ! 544: error = rtrequest(cmd, dst, ifa->ifa_addr, ifa->ifa_netmask, ! 545: flags | ifa->ifa_flags, &nrt); ! 546: if (m) ! 547: (void) m_free(m); ! 548: if (cmd == RTM_DELETE && error == 0 && (rt = nrt)) { ! 549: rt_newaddrmsg(cmd, ifa, error, nrt); ! 550: if (rt->rt_refcnt <= 0) { ! 551: rt->rt_refcnt++; ! 552: rtfree(rt); ! 553: } ! 554: } ! 555: if (cmd == RTM_ADD && error == 0 && (rt = nrt)) { ! 556: rt->rt_refcnt--; ! 557: if (rt->rt_ifa != ifa) { ! 558: printf("rtinit: wrong ifa (%x) was (%x)\n", ! 559: (unsigned int)ifa, (unsigned int)rt->rt_ifa); ! 560: if (rt->rt_ifa->ifa_rtrequest) ! 561: rt->rt_ifa->ifa_rtrequest(RTM_DELETE, rt, SA(0)); ! 562: IFAFREE(rt->rt_ifa); ! 563: rt->rt_ifa = ifa; ! 564: rt->rt_ifp = ifa->ifa_ifp; ! 565: ifa->ifa_refcnt++; ! 566: if (ifa->ifa_rtrequest) ! 567: ifa->ifa_rtrequest(RTM_ADD, rt, SA(0)); ! 568: } ! 569: rt_newaddrmsg(cmd, ifa, error, nrt); ! 570: } ! 571: return (error); ! 572: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.