Annotation of nono/host/hostnet.cpp, revision 1.1.1.4

1.1       root        1: //
                      2: // nono
                      3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
                      5: //
                      6: 
                      7: //
                      8: // ネットワークのホストデバイス
                      9: //
                     10: 
                     11: // ログ名
                     12: //       Ethernet    HostDevice       NetDriver
                     13: //         |          |                |
                     14: //         v          v                v
                     15: //        Lance   -> HostNetDevice -> NetDriver***
1.1.1.3   root       16: // 表示名 (Lance)    (HostNet0)      (HostNet0.***)
                     17: // 識別名 "lance"    "hostnet0"      "hostnet0"
1.1       root       18: //
1.1.1.3   root       19: // HostNet は、特に NetDriver 派生クラス選択時はフォールバックも含めて
                     20: // どれが選択されるか複雑なこともあるので、ログ表示名に自分のドライバ名
1.1       root       21: // も表示したい。
                     22: 
                     23: #include "hostnet.h"
                     24: #include "config.h"
                     25: #include "netdriver_none.h"
                     26: #if defined(HAVE_HOSTNET_AFPACKET)
                     27: #include "netdriver_afpacket.h"
                     28: #endif
                     29: #if defined(HAVE_HOSTNET_BPF)
                     30: #include "netdriver_bpf.h"
                     31: #endif
                     32: #if defined(HAVE_HOSTNET_TAP)
                     33: #include "netdriver_tap.h"
                     34: #endif
1.1.1.4 ! root       35: #include <zlib.h>
1.1       root       36: 
                     37: // コンパイル済みのドライバ名一覧を返す (MainApp から呼ばれる)
                     38: /*static*/ std::vector<std::string>
                     39: HostNetDevice::GetDrivers()
                     40: {
                     41:        std::vector<std::string> list;
                     42: 
                     43: #if defined(HAVE_HOSTNET_AFPACKET)
                     44:        list.emplace_back("afpacket");
                     45: #endif
                     46: #if defined(HAVE_HOSTNET_BPF)
                     47:        list.emplace_back("bpf");
                     48: #endif
                     49: #if defined(HAVE_HOSTNET_TAP)
                     50:        list.emplace_back("tap");
                     51: #endif
                     52: 
                     53:        return list;
                     54: }
                     55: 
                     56: 
                     57: // コンストラクタ
1.1.1.3   root       58: HostNetDevice::HostNetDevice(Device *parent_, int n)
                     59:        : inherited(parent_, OBJ_HOSTNET(n))
1.1       root       60: {
                     61:        monitor.func = ToMonitorCallback(&HostNetDevice::MonitorUpdate);
1.1.1.4 ! root       62:        monitor.SetSize(48, 22);
1.1.1.3   root       63:        monitor.Regist(ID_MONITOR_HOSTNET(n));
1.1       root       64: }
                     65: 
                     66: // デストラクタ
                     67: HostNetDevice::~HostNetDevice()
                     68: {
                     69: }
                     70: 
                     71: // ログレベル設定
                     72: void
                     73: HostNetDevice::SetLogLevel(int loglevel_)
                     74: {
                     75:        inherited::SetLogLevel(loglevel_);
                     76: 
                     77:        if ((bool)driver) {
                     78:                driver->SetLogLevel(loglevel_);
                     79:        }
                     80: }
                     81: 
                     82: // 初期化
                     83: bool
                     84: HostNetDevice::Init()
                     85: {
                     86:        if (inherited::Init() == false) {
                     87:                return false;
                     88:        }
                     89: 
                     90:        if (SelectDriver() == false) {
                     91:                return false;
                     92:        }
                     93: 
                     94:        return true;
                     95: }
                     96: 
                     97: // ドライバ(再)選択
                     98: bool
                     99: HostNetDevice::SelectDriver()
                    100: {
1.1.1.3   root      101:        int n = GetId() - OBJ_HOSTNET0;
                    102:        const std::string key_driver = string_format("hostnet%d-driver", n);
                    103:        const std::string key_fallback = string_format("hostnet%d-fallback", n);
                    104:        const ConfigItem& item = gConfig->Find(key_driver);
1.1       root      105:        const std::string& type = item.AsString();
1.1.1.3   root      106:        bool fallback = gConfig->Find(key_fallback).AsInt();
1.1       root      107: 
                    108:        driver.reset();
                    109: 
                    110:        if (type != "none") {
                    111:                if (type == "tap") {
                    112: #if defined(HAVE_HOSTNET_TAP)
                    113:                        CreateTap();
                    114: #else
1.1.1.2   root      115:                        item.Err("Hostnet driver %s not compiled", type.c_str());
1.1       root      116: #endif
                    117: 
                    118:                } else if (type == "bpf") {
                    119: #if defined(HAVE_HOSTNET_BPF)
                    120:                        CreateBPF();
                    121: #else
1.1.1.2   root      122:                        item.Err("Hostnet driver %s not compiled", type.c_str());
1.1       root      123: #endif
                    124: 
                    125:                } else if (type == "afpacket") {
                    126: #if defined(HAVE_HOSTNET_AFPACKET)
                    127:                        CreateAFPacket();
                    128: #else
1.1.1.2   root      129:                        item.Err("Hostnet driver %s not compiled", type.c_str());
1.1       root      130: #endif
                    131: 
                    132:                } else if (type == "auto") {
                    133:                        // auto なら Tap -> { BPF, AFPacket } の順で試す。
                    134:                        //
                    135:                        // Tap は事前の準備が面倒だが制約がない。
                    136:                        // BPF, AFPacket (この2つは OS の違いだけでほぼ同じもの) は
                    137:                        // 準備に手間がかからないが自ホストと通信できない制約がある。
                    138:                        // そのためどの環境でも Tap が使えれば Tap を、そうでなければ
                    139:                        // BPF, AFPacket を使う、という順にしておく。
                    140:                        bool r;
                    141: 
                    142:                        r = CreateTap();
                    143:                        if (r == false) {
                    144:                                r = CreateAFPacket();
                    145:                        }
                    146:                        if (r == false) {
                    147:                                r = CreateBPF();
                    148:                        }
                    149: 
                    150:                } else {
                    151:                        // 知らないドライバ種別
                    152:                        item.Err();
                    153:                }
                    154: 
                    155:                if ((bool)driver == false) {
                    156:                        putmsg(1, "hostnet-fallback=%d", fallback ? 1 : 0);
                    157: 
                    158:                        if (fallback == false) {
                    159:                                // フォールバックしないならエラー終了
                    160:                                errmsg = "No host network driver found";
                    161:                                putmsg(1, "%s", errmsg.c_str());
                    162:                                warnx("%s (See details with options -C -Lhostnet=1)",
                    163:                                        errmsg.c_str());
                    164: 
                    165:                                return false;
                    166:                        }
                    167:                }
                    168:        }
                    169: 
                    170:        if ((bool)driver == false) {
                    171:                CreateNone();
                    172:        }
                    173:        assert((bool)driver);
                    174: 
                    175:        // ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える…
                    176:        putmsg(1, "selected host driver: %s",
                    177:                string_tolower(driver->GetDriverName()).c_str());
                    178: 
                    179:        return true;
                    180: }
                    181: 
                    182: // None ドライバを生成する
                    183: bool
                    184: HostNetDevice::CreateNone()
                    185: {
                    186:        driver.reset(new NetDriverNone(this));
                    187:        if ((bool)driver) {
                    188:                if (driver->InitDriver()) {
                    189:                        // 成功
                    190:                        return true;
                    191:                }
                    192:                errmsg = driver->errmsg;
                    193:                driver.reset();
                    194:        }
                    195:        return false;
                    196: }
                    197: 
                    198: // Tap ドライバを生成する
                    199: bool
                    200: HostNetDevice::CreateTap()
                    201: {
                    202: #if defined(HAVE_HOSTNET_TAP)
1.1.1.3   root      203:        int n = GetId() - OBJ_HOSTNET0;
                    204:        const std::string keyname = string_format("hostnet%d-tap-devpath", n);
                    205:        const ConfigItem& item = gConfig->Find(keyname);
1.1       root      206:        const std::string& devpath = item.AsString();
                    207: 
                    208:        driver.reset(new NetDriverTap(this, devpath));
                    209:        if ((bool)driver) {
                    210:                if (driver->InitDriver()) {
                    211:                        // 成功
                    212:                        return true;
                    213:                }
                    214:                errmsg = driver->errmsg;
                    215:                driver.reset();
                    216:        }
                    217: #endif
                    218:        return false;
                    219: }
                    220: 
                    221: // bpf ドライバを生成する
                    222: bool
                    223: HostNetDevice::CreateBPF()
                    224: {
                    225: #if defined(HAVE_HOSTNET_BPF)
1.1.1.3   root      226:        int n = GetId() - OBJ_HOSTNET0;
                    227:        const std::string keyname = string_format("hostnet%d-bpf-ifname", n);
                    228:        const ConfigItem& item = gConfig->Find(keyname);
1.1       root      229:        const std::string& ifname = item.AsString();
                    230: 
                    231:        driver.reset(new NetDriverBPF(this, ifname));
                    232:        if ((bool)driver) {
                    233:                if (driver->InitDriver()) {
                    234:                        // 成功
                    235:                        return true;
                    236:                }
                    237:                errmsg = driver->errmsg;
                    238:                driver.reset();
                    239:        }
                    240: #endif
                    241:        return false;
                    242: }
                    243: 
                    244: // AFPacket ドライバを生成する
                    245: bool
                    246: HostNetDevice::CreateAFPacket()
                    247: {
                    248: #if defined(HAVE_HOSTNET_AFPACKET)
1.1.1.3   root      249:        int n = GetId() - OBJ_HOSTNET0;
                    250:        const std::string keyname = string_format("hostnet%d-afpacket-ifname", n);
                    251:        const ConfigItem& item = gConfig->Find(keyname);
1.1       root      252:        const std::string& ifname = item.AsString();
                    253: 
                    254:        driver.reset(new NetDriverAFPacket(this, ifname));
                    255:        if ((bool)driver) {
                    256:                if (driver->InitDriver()) {
                    257:                        // 成功
                    258:                        return true;
                    259:                }
                    260:                errmsg = driver->errmsg;
                    261:                driver.reset();
                    262:        }
                    263: #endif
                    264:        return false;
                    265: }
                    266: 
                    267: // VM からの送信 (VM スレッドで呼ばれる)
                    268: bool
                    269: HostNetDevice::Tx(const NetPacket& packet)
                    270: {
                    271:        // 送信キューに入れて..
                    272:        if (txq.Enqueue(packet) == false) {
                    273:                stat.txqfull_pkts++;
                    274:                stat.txqfull_bytes += packet.length;
                    275:                putlog(2, "txq exhausted");
                    276:                return false;
                    277:        }
                    278: 
                    279:        stat.tx_pkts++;
                    280:        stat.tx_bytes += packet.length;
                    281: 
                    282:        // ざっくりピーク値
                    283:        stat.txq_peak = std::max((int)txq.Length(), stat.txq_peak);
                    284: 
                    285:        // パイプに通知 (値はダミー)
                    286:        return WritePipe(0);
                    287: }
                    288: 
                    289: // rxq への投入を行う場合は true
                    290: void
                    291: HostNetDevice::EnableRx(bool enable)
                    292: {
                    293:        rx_enable = enable;
                    294: }
                    295: 
1.1.1.3   root      296: // 受信用の自身の MAC アドレスを設定する。
1.1       root      297: void
                    298: HostNetDevice::SetMyAddr(const macaddr_t& myaddr_)
                    299: {
                    300:        myaddr = myaddr_;
                    301: }
                    302: 
1.1.1.4 ! root      303: // マルチキャストフィルタを設定する。
        !           304: void
        !           305: HostNetDevice::SetMCastFilter(uint64 filter_)
        !           306: {
        !           307:        multicast_filter = filter_;
        !           308: }
        !           309: 
1.1       root      310: // プロミスキャスモードを設定する。
                    311: void
                    312: HostNetDevice::SetPromisc(bool promisc_)
                    313: {
                    314:        promisc = promisc_;
                    315: }
                    316: 
                    317: // パケットをキューから取り出す。
                    318: // VM 側から呼ばれる。
                    319: bool
                    320: HostNetDevice::Rx(NetPacket *dst)
                    321: {
                    322:        if (rxq.Dequeue(dst) == false) {
                    323:                // キューが空
                    324:                return false;
                    325:        }
                    326:        stat.rx_pkts++;
                    327:        stat.rx_bytes += dst->length;
1.1.1.3   root      328:        if (__predict_false(loglevel >= 2)) {
                    329:                putlogn("Recv %d bytes", dst->length);
                    330:                if (loglevel >= 3) {
                    331:                        std::string buf;
                    332:                        for (int i = 0; i < dst->length; i++) {
                    333:                                if (i % 16 == 0) {
                    334:                                        buf += string_format("%04x:", i);
                    335:                                }
                    336:                                buf += string_format(" %02x", (*dst)[i]);
                    337:                                if (i % 16 == 7) {
                    338:                                        buf += " ";
                    339:                                } else if (i % 16 == 15) {
                    340:                                        putlogn("%s", buf.c_str());
                    341:                                        buf = "";
                    342:                                }
                    343:                        }
                    344:                        if (buf.empty() == false) {
                    345:                                putlogn("%s", buf.c_str());
                    346:                        }
                    347:                }
                    348:        }
1.1       root      349: 
                    350:        return true;
                    351: }
                    352: 
                    353: // 外部からの読み込み (パケットを受信)。
                    354: // 戻り値はキューに投入したパケット数。
                    355: int
                    356: HostNetDevice::Read()
                    357: {
                    358:        int queued = 0;
                    359:        int left = 0;
                    360: 
                    361:        assert((bool)driver);
                    362: 
                    363:        do {
                    364:                NetPacket *p;
                    365: 
                    366:                p = rxq.BeginWrite();
                    367:                if (p == NULL) {
                    368:                        NetPacket discard;
                    369:                        left = driver->Read(&discard);
                    370:                        if (left < 0) {
                    371:                                break;
                    372:                        }
                    373:                        stat.rxqfull_pkts++;
                    374:                        stat.rxqfull_bytes += discard.length;
                    375:                        continue;
                    376:                }
                    377: 
                    378:                // driver->Read() は VM に投入すべきパケットがなければ負数を返す。
                    379:                // パケットがあれば p に書き戻して、driver 側のバッファに残っている
                    380:                // パケット数 (といっても 0 か 1以上) を返してくる。
                    381:                left = driver->Read(p);
                    382:                if (left < 0) {
                    383:                        rxq.CancelWrite();
                    384:                        break;
                    385:                }
                    386: 
                    387:                NetPacket& packet = *p;
                    388: 
                    389:                stat.read_pkts++;
                    390:                stat.read_bytes += packet.length;
                    391: 
                    392:                if (rx_enable == false) {
                    393:                        rxq.CancelWrite();
                    394:                        stat.rxdisable_pkts++;
                    395:                        stat.rxdisable_bytes += packet.length;
                    396:                        continue;
                    397:                }
                    398: 
                    399:                // イーサネットフレームより長いパケットはゲスト OS が期待していない。
                    400:                // ここでドロップしてみる。
                    401:                // これによりゲストでの dropping chained buffer が出なくなる。
                    402:                if (packet.length > 1518) {
                    403:                        rxq.CancelWrite();
                    404:                        stat.rxjumbo_pkts++;
                    405:                        stat.rxjumbo_bytes += packet.length;
                    406:                        continue;
                    407:                }
                    408: 
                    409:                // ホストカーネルから直接着信したパケット(というかフレームか)は
                    410:                // 60バイトパディングされないまま読み出せるので、ここでパディングする。
                    411:                while (packet.length < 60) {
                    412:                        packet.Append(0x00);
                    413:                }
                    414: 
                    415:                // パケットフィルタリング
                    416:                if (__predict_true(promisc == false)) {
1.1.1.4 ! root      417:                        static std::array<uint8, 6> bcast {
        !           418:                                0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        !           419:                        };
        !           420:                        if (memcmp(&packet[0], &bcast[0], bcast.size()) == 0) {
        !           421:                                // ブロードキャストは素通し。
        !           422:                        } else if (__predict_false(packet[0] & 1)) {
        !           423:                                // マルチキャスト。
1.1       root      424:                                // LANCE では LADRF レジスタ、NE2000 では MAR0-7 レジスタで
1.1.1.4 ! root      425:                                // データシートを見る限りはロジックは同じもののようだ。
1.1       root      426: 
1.1.1.4 ! root      427:                                // 宛先アドレスの CRC32 を計算する。
        !           428:                                // イーサネットの CRC は zlib の crc32() と同じもの。
        !           429:                                ulong crc = crc32(0, Z_NULL, 0);
        !           430:                                crc = crc32(crc, &packet[0], 6);
        !           431: 
        !           432:                                // crc の上位6ビットをとりだして検査ビット位置とする(=pos)。
        !           433:                                // multicast_filter の pos の位置のビットが 1 なら受信、
        !           434:                                // 0 なら破棄。
        !           435:                                // multicast_filter は64ビットで LSB が0番目、MSB が63番目
        !           436:                                // としたもの (NIC 側でこの通りに並べ替えて渡してある)。
        !           437:                                uint pos = (crc >> 26) & 0x3f;
        !           438:                                uint64 bit = 1U << pos;
1.1       root      439:                                if ((multicast_filter & bit) == 0) {
1.1.1.4 ! root      440:                                        putlog(2, "mcast %02x:%02x:%02x:%02x:%02x:%02x pos=%d drop",
        !           441:                                                packet[0], packet[1], packet[2],
        !           442:                                                packet[3], packet[4], packet[5], pos);
        !           443:                                        stat.rxmcast_pkts++;
        !           444:                                        stat.rxmcast_bytes += packet.length;
        !           445:                                        rxq.CancelWrite();
        !           446:                                        continue;
1.1       root      447:                                }
1.1.1.4 ! root      448:                                putlog(2, "mcast %02x:%02x:%02x:%02x:%02x:%02x pos=%d pass",
        !           449:                                        packet[0], packet[1], packet[2],
        !           450:                                        packet[3], packet[4], packet[5], pos);
1.1       root      451:                        } else {
                    452:                                // ユニキャスト
                    453:                                if (memcmp(&packet[0], &myaddr[0], 6) != 0) {
1.1.1.4 ! root      454:                                        stat.rxnotme_pkts++;
        !           455:                                        stat.rxnotme_bytes += packet.length;
        !           456:                                        rxq.CancelWrite();
        !           457:                                        continue;
1.1       root      458:                                }
                    459:                        }
                    460:                }
                    461: 
                    462:                // CRC XXX 計算すること
                    463:                for (int i = 0; i < 4; i++) {
                    464:                        packet.Append(0x00);
                    465:                }
                    466: 
                    467:                rxq.EndWrite();
                    468:                queued++;
                    469:        } while (left > 0);
                    470: 
                    471:        // ざっくりピーク値
                    472:        stat.rxq_peak = std::max((int)rxq.Length(), stat.rxq_peak);
                    473: 
                    474:        return queued;
                    475: }
                    476: 
                    477: // 外部への書き出し(パケットを送信)
                    478: void
                    479: HostNetDevice::Write(uint32 data)
                    480: {
                    481:        const NetPacket *p;
                    482: 
                    483:        assert((bool)driver);
                    484: 
                    485:        // 送信キューから取り出す
                    486:        while ((p = txq.BeginRead()) != NULL) {
                    487:                const NetPacket& packet = *p;
                    488: 
1.1.1.3   root      489:                if (__predict_false(loglevel >= 2)) {
                    490:                        putlogn("Send %d bytes", packet.length);
                    491:                        if (loglevel >= 3) {
                    492:                                std::string buf;
                    493:                                for (int i = 0; i < packet.length; i++) {
                    494:                                        if (i % 16 == 0) {
                    495:                                                buf += string_format("%04x:", i);
                    496:                                        }
                    497:                                        buf += string_format(" %02x", packet[i]);
                    498:                                        if (i % 16 == 7) {
                    499:                                                buf += " ";
                    500:                                        } else if (i % 16 == 15) {
                    501:                                                putlogn("%s", buf.c_str());
                    502:                                                buf = "";
                    503:                                        }
                    504:                                }
                    505:                                if (buf.empty() == false) {
                    506:                                        putlogn("%s", buf.c_str());
                    507:                                }
                    508:                        }
                    509:                }
1.1       root      510:                stat.write_pkts++;
                    511:                stat.write_bytes += packet.length;
                    512: 
                    513:                driver->Write(packet.data(), packet.length);
                    514:                txq.EndRead();
                    515:        }
                    516: }
                    517: 
                    518: // ドライバ名を返す
                    519: const std::string
                    520: HostNetDevice::GetDriverName() const
                    521: {
                    522:        assert((bool)driver);
                    523:        return driver->GetDriverName();
                    524: }
                    525: 
                    526: // モニタ
                    527: void
                    528: HostNetDevice::MonitorUpdate(Monitor *, TextScreen& screen)
                    529: {
                    530:        screen.Clear();
                    531: 
1.1.1.3   root      532:        screen.Print(0, 0, "Parent Device : %s", parent->GetName().c_str());
                    533:        screen.Print(0, 1, "HostNet Driver: %s", driver->GetDriverName());
1.1       root      534:        // 次の2行はドライバ依存情報
1.1.1.3   root      535:        driver->MonitorUpdateMD(screen, 2);
1.1       root      536: 
                    537:        // 01234567890123456
                    538:        // Write to host
                    539:        // Drop:TxQ Full
                    540:        // Drop:Rx Disabled
                    541:        // Drop:Jumbo Frame
                    542:        // Drop:Addr Filter
                    543: 
1.1.1.3   root      544:        int y = 5;
1.1       root      545:        screen.Print(0, y++, "%-17s%13s %17s", "<Tx>", "Packets", "Bytes");
                    546:        screen.Print(0, y++, "%-17s%13s %17s", "VM sends",
                    547:                format_number(stat.tx_pkts).c_str(),
                    548:                format_number(stat.tx_bytes).c_str());
                    549:        screen.Print(0, y++, "%-17s%13s %17s", "Write to host",
                    550:                format_number(stat.write_pkts).c_str(),
                    551:                format_number(stat.write_bytes).c_str());
                    552:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:TxQ Full",
                    553:                format_number(stat.txqfull_pkts).c_str(),
                    554:                format_number(stat.txqfull_bytes).c_str());
                    555:        y++;
                    556: 
                    557:        screen.Print(0, y++, "%-17s%13s %17s", "<Rx>", "Packets", "Bytes");
                    558:        screen.Print(0, y++, "%-17s%13s %17s", "Read from host",
                    559:                format_number(stat.read_pkts).c_str(),
                    560:                format_number(stat.read_bytes).c_str());
                    561:        screen.Print(0, y++, "%-17s%13s %17s", "VM receives",
                    562:                format_number(stat.rx_pkts).c_str(),
                    563:                format_number(stat.rx_bytes).c_str());
                    564:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:Rx Disabled",
                    565:                format_number(stat.rxdisable_pkts).c_str(),
                    566:                format_number(stat.rxdisable_bytes).c_str());
                    567:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:Jumbo Frame",
                    568:                format_number(stat.rxjumbo_pkts).c_str(),
                    569:                format_number(stat.rxjumbo_bytes).c_str());
                    570:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:RxQ Full",
                    571:                format_number(stat.rxqfull_pkts).c_str(),
                    572:                format_number(stat.rxqfull_bytes).c_str());
1.1.1.4 ! root      573:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:Unicast",
        !           574:                format_number(stat.rxnotme_pkts).c_str(),
        !           575:                format_number(stat.rxnotme_bytes).c_str());
        !           576:        screen.Print(0, y++, "%-17s%13s %17s", "Drop:Multicast",
        !           577:                format_number(stat.rxmcast_pkts).c_str(),
        !           578:                format_number(stat.rxmcast_bytes).c_str());
1.1       root      579:        y++;
                    580: 
                    581:        screen.Print(5, y++, "Capacity Peak");
                    582:        screen.Print(0, y++, "TxQ %4d/%4d %4d",
                    583:                (int)txq.Length(), (int)txq.Capacity(), stat.txq_peak);
                    584:        screen.Print(0, y++, "RxQ %4d/%4d %4d",
                    585:                (int)rxq.Length(), (int)rxq.Capacity(), stat.rxq_peak);
                    586: }

unix.superglobalmegacorp.com

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