Annotation of 43BSDReno/sys/netinet/ip_output.c, revision 1.1.1.1

1.1       root        1: /*
                      2:  * Copyright (c) 1982, 1986, 1988, 1990 Regents of the University of California.
                      3:  * All rights reserved.
                      4:  *
                      5:  * Redistribution is only permitted until one year after the first shipment
                      6:  * of 4.4BSD by the Regents.  Otherwise, redistribution and use in source and
                      7:  * binary forms are permitted provided that: (1) source distributions retain
                      8:  * this entire copyright notice and comment, and (2) distributions including
                      9:  * binaries display the following acknowledgement:  This product includes
                     10:  * software developed by the University of California, Berkeley and its
                     11:  * contributors'' in the documentation or other materials provided with the
                     12:  * distribution and in all advertising materials mentioning features or use
                     13:  * of this software.  Neither the name of the University nor the names of
                     14:  * its contributors may be used to endorse or promote products derived from
                     15:  * this software without specific prior written permission.
                     16:  * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
                     17:  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
                     18:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
                     19:  *
                     20:  *     @(#)ip_output.c 7.22 (Berkeley) 7/28/90
                     21:  */
                     22: 
                     23: #include "param.h"
                     24: #include "malloc.h"
                     25: #include "mbuf.h"
                     26: #include "errno.h"
                     27: #include "protosw.h"
                     28: #include "socket.h"
                     29: #include "socketvar.h"
                     30: 
                     31: #include "../net/if.h"
                     32: #include "../net/route.h"
                     33: 
                     34: #include "in.h"
                     35: #include "in_systm.h"
                     36: #include "ip.h"
                     37: #include "in_pcb.h"
                     38: #include "in_var.h"
                     39: #include "ip_var.h"
                     40: 
                     41: #ifdef vax
                     42: #include "machine/mtpr.h"
                     43: #endif
                     44: 
                     45: struct mbuf *ip_insertoptions();
                     46: 
                     47: /*
                     48:  * IP output.  The packet in mbuf chain m contains a skeletal IP
                     49:  * header (with len, off, ttl, proto, tos, src, dst).
                     50:  * The mbuf chain containing the packet will be freed.
                     51:  * The mbuf opt, if present, will not be freed.
                     52:  */
                     53: ip_output(m0, opt, ro, flags)
                     54:        struct mbuf *m0;
                     55:        struct mbuf *opt;
                     56:        struct route *ro;
                     57:        int flags;
                     58: {
                     59:        register struct ip *ip, *mhip;
                     60:        register struct ifnet *ifp;
                     61:        register struct mbuf *m = m0;
                     62:        register int hlen = sizeof (struct ip);
                     63:        int len, off, error = 0;
                     64:        struct route iproute;
                     65:        struct sockaddr_in *dst;
                     66:        struct in_ifaddr *ia;
                     67: 
                     68: #ifdef DIAGNOSTIC
                     69:        if ((m->m_flags & M_PKTHDR) == 0)
                     70:                panic("ip_output no HDR");
                     71: #endif
                     72:        if (opt) {
                     73:                m = ip_insertoptions(m, opt, &len);
                     74:                hlen = len;
                     75:        }
                     76:        ip = mtod(m, struct ip *);
                     77:        /*
                     78:         * Fill in IP header.
                     79:         */
                     80:        if ((flags & IP_FORWARDING) == 0) {
                     81:                ip->ip_v = IPVERSION;
                     82:                ip->ip_off &= IP_DF;
                     83:                ip->ip_id = htons(ip_id++);
                     84:                ip->ip_hl = hlen >> 2;
                     85:        } else {
                     86:                hlen = ip->ip_hl << 2;
                     87:                ipstat.ips_localout++;
                     88:        }
                     89:        /*
                     90:         * Route packet.
                     91:         */
                     92:        if (ro == 0) {
                     93:                ro = &iproute;
                     94:                bzero((caddr_t)ro, sizeof (*ro));
                     95:        }
                     96:        dst = (struct sockaddr_in *)&ro->ro_dst;
                     97:        /*
                     98:         * If there is a cached route,
                     99:         * check that it is to the same destination
                    100:         * and is still up.  If not, free it and try again.
                    101:         */
                    102:        if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 ||
                    103:           dst->sin_addr.s_addr != ip->ip_dst.s_addr)) {
                    104:                RTFREE(ro->ro_rt);
                    105:                ro->ro_rt = (struct rtentry *)0;
                    106:        }
                    107:        if (ro->ro_rt == 0) {
                    108:                dst->sin_family = AF_INET;
                    109:                dst->sin_len = sizeof(*dst);
                    110:                dst->sin_addr = ip->ip_dst;
                    111:        }
                    112:        /*
                    113:         * If routing to interface only,
                    114:         * short circuit routing lookup.
                    115:         */
                    116:        if (flags & IP_ROUTETOIF) {
                    117: 
                    118:                ia = (struct in_ifaddr *)ifa_ifwithdstaddr((struct sockaddr *)dst);
                    119:                if (ia == 0)
                    120:                        ia = in_iaonnetof(in_netof(ip->ip_dst));
                    121:                if (ia == 0) {
                    122:                        error = ENETUNREACH;
                    123:                        goto bad;
                    124:                }
                    125:                ifp = ia->ia_ifp;
                    126:        } else {
                    127:                if (ro->ro_rt == 0)
                    128:                        rtalloc(ro);
                    129:                if (ro->ro_rt == 0) {
                    130:                        error = EHOSTUNREACH;
                    131:                        goto bad;
                    132:                }
                    133:                ia = (struct in_ifaddr *)ro->ro_rt->rt_ifa;
                    134:                ifp = ro->ro_rt->rt_ifp;
                    135:                ro->ro_rt->rt_use++;
                    136:                if (ro->ro_rt->rt_flags & RTF_GATEWAY)
                    137:                        dst = (struct sockaddr_in *)ro->ro_rt->rt_gateway;
                    138:        }
                    139: #ifndef notdef
                    140:        /*
                    141:         * If source address not specified yet, use address
                    142:         * of outgoing interface.
                    143:         */
                    144:        if (ip->ip_src.s_addr == INADDR_ANY)
                    145:                ip->ip_src = IA_SIN(ia)->sin_addr;
                    146: #endif
                    147:        /*
                    148:         * Look for broadcast address and
                    149:         * and verify user is allowed to send
                    150:         * such a packet.
                    151:         */
                    152:        if (in_broadcast(dst->sin_addr)) {
                    153:                if ((ifp->if_flags & IFF_BROADCAST) == 0) {
                    154:                        error = EADDRNOTAVAIL;
                    155:                        goto bad;
                    156:                }
                    157:                if ((flags & IP_ALLOWBROADCAST) == 0) {
                    158:                        error = EACCES;
                    159:                        goto bad;
                    160:                }
                    161:                /* don't allow broadcast messages to be fragmented */
                    162:                if ((u_short)ip->ip_len > ifp->if_mtu) {
                    163:                        error = EMSGSIZE;
                    164:                        goto bad;
                    165:                }
                    166:                m->m_flags |= M_BCAST;
                    167:        }
                    168: 
                    169:        /*
                    170:         * If small enough for interface, can just send directly.
                    171:         */
                    172:        if ((u_short)ip->ip_len <= ifp->if_mtu) {
                    173:                ip->ip_len = htons((u_short)ip->ip_len);
                    174:                ip->ip_off = htons((u_short)ip->ip_off);
                    175:                ip->ip_sum = 0;
                    176:                ip->ip_sum = in_cksum(m, hlen);
                    177:                error = (*ifp->if_output)(ifp, m,
                    178:                                (struct sockaddr *)dst, ro->ro_rt);
                    179:                goto done;
                    180:        }
                    181:        ipstat.ips_fragmented++;
                    182:        /*
                    183:         * Too large for interface; fragment if possible.
                    184:         * Must be able to put at least 8 bytes per fragment.
                    185:         */
                    186:        if (ip->ip_off & IP_DF) {
                    187:                error = EMSGSIZE;
                    188:                goto bad;
                    189:        }
                    190:        len = (ifp->if_mtu - hlen) &~ 7;
                    191:        if (len < 8) {
                    192:                error = EMSGSIZE;
                    193:                goto bad;
                    194:        }
                    195: 
                    196:     {
                    197:        int mhlen, firstlen = len;
                    198:        struct mbuf **mnext = &m->m_nextpkt;
                    199: 
                    200:        /*
                    201:         * Loop through length of segment after first fragment,
                    202:         * make new header and copy data of each part and link onto chain.
                    203:         */
                    204:        m0 = m;
                    205:        mhlen = sizeof (struct ip);
                    206:        for (off = hlen + len; off < (u_short)ip->ip_len; off += len) {
                    207:                MGETHDR(m, M_DONTWAIT, MT_HEADER);
                    208:                if (m == 0) {
                    209:                        error = ENOBUFS;
                    210:                        goto sendorfree;
                    211:                }
                    212:                m->m_data += max_linkhdr;
                    213:                mhip = mtod(m, struct ip *);
                    214:                *mhip = *ip;
                    215:                if (hlen > sizeof (struct ip)) {
                    216:                        mhlen = ip_optcopy(ip, mhip) + sizeof (struct ip);
                    217:                        mhip->ip_hl = mhlen >> 2;
                    218:                }
                    219:                m->m_len = mhlen;
                    220:                mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF);
                    221:                if (ip->ip_off & IP_MF)
                    222:                        mhip->ip_off |= IP_MF;
                    223:                if (off + len >= (u_short)ip->ip_len)
                    224:                        len = (u_short)ip->ip_len - off;
                    225:                else
                    226:                        mhip->ip_off |= IP_MF;
                    227:                mhip->ip_len = htons((u_short)(len + mhlen));
                    228:                m->m_next = m_copy(m0, off, len);
                    229:                if (m->m_next == 0) {
                    230:                        error = ENOBUFS;        /* ??? */
                    231:                        goto sendorfree;
                    232:                }
                    233:                m->m_pkthdr.len = mhlen + len;
                    234:                m->m_pkthdr.rcvif = (struct ifnet *)0;
                    235:                mhip->ip_off = htons((u_short)mhip->ip_off);
                    236:                mhip->ip_sum = 0;
                    237:                mhip->ip_sum = in_cksum(m, mhlen);
                    238:                *mnext = m;
                    239:                mnext = &m->m_nextpkt;
                    240:                ipstat.ips_ofragments++;
                    241:        }
                    242:        /*
                    243:         * Update first fragment by trimming what's been copied out
                    244:         * and updating header, then send each fragment (in order).
                    245:         */
                    246:        m = m0;
                    247:        m_adj(m, hlen + firstlen - (u_short)ip->ip_len);
                    248:        m->m_pkthdr.len = hlen + firstlen;
                    249:        ip->ip_len = htons((u_short)m->m_pkthdr.len);
                    250:        ip->ip_off = htons((u_short)(ip->ip_off | IP_MF));
                    251:        ip->ip_sum = 0;
                    252:        ip->ip_sum = in_cksum(m, hlen);
                    253: sendorfree:
                    254:        for (m = m0; m; m = m0) {
                    255:                m0 = m->m_nextpkt;
                    256:                m->m_nextpkt = 0;
                    257:                if (error == 0)
                    258:                        error = (*ifp->if_output)(ifp, m,
                    259:                            (struct sockaddr *)dst, ro->ro_rt);
                    260:                else
                    261:                        m_freem(m);
                    262:        }
                    263:     }
                    264: done:
                    265:        if (ro == &iproute && (flags & IP_ROUTETOIF) == 0 && ro->ro_rt)
                    266:                RTFREE(ro->ro_rt);
                    267:        return (error);
                    268: bad:
                    269:        m_freem(m0);
                    270:        goto done;
                    271: }
                    272: 
                    273: /*
                    274:  * Insert IP options into preformed packet.
                    275:  * Adjust IP destination as required for IP source routing,
                    276:  * as indicated by a non-zero in_addr at the start of the options.
                    277:  */
                    278: struct mbuf *
                    279: ip_insertoptions(m, opt, phlen)
                    280:        register struct mbuf *m;
                    281:        struct mbuf *opt;
                    282:        int *phlen;
                    283: {
                    284:        register struct ipoption *p = mtod(opt, struct ipoption *);
                    285:        struct mbuf *n;
                    286:        register struct ip *ip = mtod(m, struct ip *);
                    287:        unsigned optlen;
                    288: 
                    289:        optlen = opt->m_len - sizeof(p->ipopt_dst);
                    290:        if (optlen + (u_short)ip->ip_len > IP_MAXPACKET)
                    291:                return (m);             /* XXX should fail */
                    292:        if (p->ipopt_dst.s_addr)
                    293:                ip->ip_dst = p->ipopt_dst;
                    294:        if (m->m_flags & M_EXT || m->m_data - optlen < m->m_pktdat) {
                    295:                MGETHDR(n, M_DONTWAIT, MT_HEADER);
                    296:                if (n == 0)
                    297:                        return (m);
                    298:                n->m_pkthdr.len = m->m_pkthdr.len + optlen;
                    299:                m->m_len -= sizeof(struct ip);
                    300:                m->m_data += sizeof(struct ip);
                    301:                n->m_next = m;
                    302:                m = n;
                    303:                m->m_len = optlen + sizeof(struct ip);
                    304:                m->m_data += max_linkhdr;
                    305:                bcopy((caddr_t)ip, mtod(m, caddr_t), sizeof(struct ip));
                    306:        } else {
                    307:                m->m_data -= optlen;
                    308:                m->m_len += optlen;
                    309:                m->m_pkthdr.len += optlen;
                    310:                ovbcopy((caddr_t)ip, mtod(m, caddr_t), sizeof(struct ip));
                    311:        }
                    312:        ip = mtod(m, struct ip *);
                    313:        bcopy((caddr_t)p->ipopt_list, (caddr_t)(ip + 1), (unsigned)optlen);
                    314:        *phlen = sizeof(struct ip) + optlen;
                    315:        ip->ip_len += optlen;
                    316:        return (m);
                    317: }
                    318: 
                    319: /*
                    320:  * Copy options from ip to jp,
                    321:  * omitting those not copied during fragmentation.
                    322:  */
                    323: ip_optcopy(ip, jp)
                    324:        struct ip *ip, *jp;
                    325: {
                    326:        register u_char *cp, *dp;
                    327:        int opt, optlen, cnt;
                    328: 
                    329:        cp = (u_char *)(ip + 1);
                    330:        dp = (u_char *)(jp + 1);
                    331:        cnt = (ip->ip_hl << 2) - sizeof (struct ip);
                    332:        for (; cnt > 0; cnt -= optlen, cp += optlen) {
                    333:                opt = cp[0];
                    334:                if (opt == IPOPT_EOL)
                    335:                        break;
                    336:                if (opt == IPOPT_NOP)
                    337:                        optlen = 1;
                    338:                else
                    339:                        optlen = cp[IPOPT_OLEN];
                    340:                /* bogus lengths should have been caught by ip_dooptions */
                    341:                if (optlen > cnt)
                    342:                        optlen = cnt;
                    343:                if (IPOPT_COPIED(opt)) {
                    344:                        bcopy((caddr_t)cp, (caddr_t)dp, (unsigned)optlen);
                    345:                        dp += optlen;
                    346:                }
                    347:        }
                    348:        for (optlen = dp - (u_char *)(jp+1); optlen & 0x3; optlen++)
                    349:                *dp++ = IPOPT_EOL;
                    350:        return (optlen);
                    351: }
                    352: 
                    353: /*
                    354:  * IP socket option processing.
                    355:  */
                    356: ip_ctloutput(op, so, level, optname, mp)
                    357:        int op;
                    358:        struct socket *so;
                    359:        int level, optname;
                    360:        struct mbuf **mp;
                    361: {
                    362:        register struct inpcb *inp = sotoinpcb(so);
                    363:        register struct mbuf *m = *mp;
                    364:        register int optval;
                    365:        int error = 0;
                    366: 
                    367:        if (level != IPPROTO_IP)
                    368:                error = EINVAL;
                    369:        else switch (op) {
                    370: 
                    371:        case PRCO_SETOPT:
                    372:                switch (optname) {
                    373:                case IP_OPTIONS:
                    374: #ifdef notyet
                    375:                case IP_RETOPTS:
                    376:                        return (ip_pcbopts(optname, &inp->inp_options, m));
                    377: #else
                    378:                        return (ip_pcbopts(&inp->inp_options, m));
                    379: #endif
                    380: 
                    381:                case IP_TOS:
                    382:                case IP_TTL:
                    383:                case IP_RECVOPTS:
                    384:                case IP_RECVRETOPTS:
                    385:                case IP_RECVDSTADDR:
                    386:                        if (m->m_len != sizeof(int))
                    387:                                error = EINVAL;
                    388:                        else {
                    389:                                optval = *mtod(m, int *);
                    390:                                switch (optname) {
                    391: 
                    392:                                case IP_TOS:
                    393:                                        inp->inp_ip.ip_tos = optval;
                    394:                                        break;
                    395: 
                    396:                                case IP_TTL:
                    397:                                        inp->inp_ip.ip_tos = optval;
                    398:                                        break;
                    399: #define        OPTSET(bit) \
                    400:        if (optval) \
                    401:                inp->inp_flags |= bit; \
                    402:        else \
                    403:                inp->inp_flags &= ~bit;
                    404: 
                    405:                                case IP_RECVOPTS:
                    406:                                        OPTSET(INP_RECVOPTS);
                    407:                                        break;
                    408: 
                    409:                                case IP_RECVRETOPTS:
                    410:                                        OPTSET(INP_RECVRETOPTS);
                    411:                                        break;
                    412: 
                    413:                                case IP_RECVDSTADDR:
                    414:                                        OPTSET(INP_RECVDSTADDR);
                    415:                                        break;
                    416:                                }
                    417:                        }
                    418:                        break;
                    419: #undef OPTSET
                    420: 
                    421:                default:
                    422:                        error = EINVAL;
                    423:                        break;
                    424:                }
                    425:                if (m)
                    426:                        (void)m_free(m);
                    427:                break;
                    428: 
                    429:        case PRCO_GETOPT:
                    430:                switch (optname) {
                    431:                case IP_OPTIONS:
                    432:                case IP_RETOPTS:
                    433:                        *mp = m = m_get(M_WAIT, MT_SOOPTS);
                    434:                        if (inp->inp_options) {
                    435:                                m->m_len = inp->inp_options->m_len;
                    436:                                bcopy(mtod(inp->inp_options, caddr_t),
                    437:                                    mtod(m, caddr_t), (unsigned)m->m_len);
                    438:                        } else
                    439:                                m->m_len = 0;
                    440:                        break;
                    441: 
                    442:                case IP_TOS:
                    443:                case IP_TTL:
                    444:                case IP_RECVOPTS:
                    445:                case IP_RECVRETOPTS:
                    446:                case IP_RECVDSTADDR:
                    447:                        *mp = m = m_get(M_WAIT, MT_SOOPTS);
                    448:                        m->m_len = sizeof(int);
                    449:                        switch (optname) {
                    450: 
                    451:                        case IP_TOS:
                    452:                                optval = inp->inp_ip.ip_tos;
                    453:                                break;
                    454: 
                    455:                        case IP_TTL:
                    456:                                optval = inp->inp_ip.ip_tos;
                    457:                                break;
                    458: 
                    459: #define        OPTBIT(bit)     (inp->inp_flags & bit ? 1 : 0)
                    460: 
                    461:                        case IP_RECVOPTS:
                    462:                                optval = OPTBIT(INP_RECVOPTS);
                    463:                                break;
                    464: 
                    465:                        case IP_RECVRETOPTS:
                    466:                                optval = OPTBIT(INP_RECVRETOPTS);
                    467:                                break;
                    468: 
                    469:                        case IP_RECVDSTADDR:
                    470:                                optval = OPTBIT(INP_RECVDSTADDR);
                    471:                                break;
                    472:                        }
                    473:                        *mtod(m, int *) = optval;
                    474:                        break;
                    475: 
                    476:                default:
                    477:                        error = EINVAL;
                    478:                        break;
                    479:                }
                    480:                break;
                    481:        }
                    482:        return (error);
                    483: }
                    484: 
                    485: /*
                    486:  * Set up IP options in pcb for insertion in output packets.
                    487:  * Store in mbuf with pointer in pcbopt, adding pseudo-option
                    488:  * with destination address if source routed.
                    489:  */
                    490: #ifdef notyet
                    491: ip_pcbopts(optname, pcbopt, m)
                    492:        int optname;
                    493: #else
                    494: ip_pcbopts(pcbopt, m)
                    495: #endif
                    496:        struct mbuf **pcbopt;
                    497:        register struct mbuf *m;
                    498: {
                    499:        register cnt, optlen;
                    500:        register u_char *cp;
                    501:        u_char opt;
                    502: 
                    503:        /* turn off any old options */
                    504:        if (*pcbopt)
                    505:                (void)m_free(*pcbopt);
                    506:        *pcbopt = 0;
                    507:        if (m == (struct mbuf *)0 || m->m_len == 0) {
                    508:                /*
                    509:                 * Only turning off any previous options.
                    510:                 */
                    511:                if (m)
                    512:                        (void)m_free(m);
                    513:                return (0);
                    514:        }
                    515: 
                    516: #ifndef        vax
                    517:        if (m->m_len % sizeof(long))
                    518:                goto bad;
                    519: #endif
                    520:        /*
                    521:         * IP first-hop destination address will be stored before
                    522:         * actual options; move other options back
                    523:         * and clear it when none present.
                    524:         */
                    525:        if (m->m_data + m->m_len + sizeof(struct in_addr) >= &m->m_dat[MLEN])
                    526:                goto bad;
                    527:        cnt = m->m_len;
                    528:        m->m_len += sizeof(struct in_addr);
                    529:        cp = mtod(m, u_char *) + sizeof(struct in_addr);
                    530:        ovbcopy(mtod(m, caddr_t), (caddr_t)cp, (unsigned)cnt);
                    531:        bzero(mtod(m, caddr_t), sizeof(struct in_addr));
                    532: 
                    533:        for (; cnt > 0; cnt -= optlen, cp += optlen) {
                    534:                opt = cp[IPOPT_OPTVAL];
                    535:                if (opt == IPOPT_EOL)
                    536:                        break;
                    537:                if (opt == IPOPT_NOP)
                    538:                        optlen = 1;
                    539:                else {
                    540:                        optlen = cp[IPOPT_OLEN];
                    541:                        if (optlen <= IPOPT_OLEN || optlen > cnt)
                    542:                                goto bad;
                    543:                }
                    544:                switch (opt) {
                    545: 
                    546:                default:
                    547:                        break;
                    548: 
                    549:                case IPOPT_LSRR:
                    550:                case IPOPT_SSRR:
                    551:                        /*
                    552:                         * user process specifies route as:
                    553:                         *      ->A->B->C->D
                    554:                         * D must be our final destination (but we can't
                    555:                         * check that since we may not have connected yet).
                    556:                         * A is first hop destination, which doesn't appear in
                    557:                         * actual IP option, but is stored before the options.
                    558:                         */
                    559:                        if (optlen < IPOPT_MINOFF - 1 + sizeof(struct in_addr))
                    560:                                goto bad;
                    561:                        m->m_len -= sizeof(struct in_addr);
                    562:                        cnt -= sizeof(struct in_addr);
                    563:                        optlen -= sizeof(struct in_addr);
                    564:                        cp[IPOPT_OLEN] = optlen;
                    565:                        /*
                    566:                         * Move first hop before start of options.
                    567:                         */
                    568:                        bcopy((caddr_t)&cp[IPOPT_OFFSET+1], mtod(m, caddr_t),
                    569:                            sizeof(struct in_addr));
                    570:                        /*
                    571:                         * Then copy rest of options back
                    572:                         * to close up the deleted entry.
                    573:                         */
                    574:                        ovbcopy((caddr_t)(&cp[IPOPT_OFFSET+1] +
                    575:                            sizeof(struct in_addr)),
                    576:                            (caddr_t)&cp[IPOPT_OFFSET+1],
                    577:                            (unsigned)cnt + sizeof(struct in_addr));
                    578:                        break;
                    579:                }
                    580:        }
                    581:        if (m->m_len > MAX_IPOPTLEN + sizeof(struct in_addr))
                    582:                goto bad;
                    583:        *pcbopt = m;
                    584:        return (0);
                    585: 
                    586: bad:
                    587:        (void)m_free(m);
                    588:        return (EINVAL);
                    589: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.