--- nono/host/hostnet.cpp 2026/04/29 17:05:11 1.1 +++ nono/host/hostnet.cpp 2026/04/29 17:06:02 1.1.1.12 @@ -8,21 +8,66 @@ // ネットワークのホストデバイス // +// 送信時のフロー +// +// VM thread : Host thread +// +// 各デバイス::送信() : +// | +// HostNetDevice::Tx() : HostDevice::ThreadRun +// | +// +-------------->| queue |----+ … 送信キューに追加し +// +-------------->| pipe |----+ … パイプでホストスレッドに通知 +// | | +// <-+ : v +// --- kevent +// : | +// HostNetDevice::Write() +// : | +// NetDriver*::Write() +// : | +// +-----> Send to the real world + +// 受信のフロー +// +// VM thread : Host thread +// +// : HostDevice::ThreadRun +// +// : +-----< Recv from the real world +// | +// : v +// --- kevent +// : | +// HostNetDevice::Read() +// | +// | queue |<---+ … 受信キューに追加 +// ‖ +// ‖ HostNetDevice::*(rx_func)() +// | +// +-------------| Message |<---+ … メッセージで VM スレッドに通知 +// v +// 各デバイス::RxMessage() ‖ … メッセージコールバック +// | ‖ +// 各デバイス::Rx() ‖ … イベントコールバック +// | ‖ +// HostCOMDevice::Rx() <==++ … ここで queue から読み出す + // ログ名 // Ethernet HostDevice NetDriver // | | | // v v v // Lance -> HostNetDevice -> NetDriver*** -// 表示名 (Lance) (HostNet) (HostNet.***) -// 識別名 "lance" "hostnet" "hostnet" +// 表示名 (Lance) (HostNet0) (HostNet0.***) +// 識別名 "lance" "hostnet0" "hostnet0" // -// HostNet は 1VM あたり高々1つなので COMDriver のように同じインスタンス間で -// 互いを区別する必要がないのと、NetDriver 派生クラス選択時はフォールバックも -// 含めて、どれが選択されるか複雑なこともあるのでログ表示名に自分のドライバ名 +// HostNet は、特に NetDriver 派生クラス選択時はフォールバックも含めて +// どれが選択されるか複雑なこともあるので、ログ表示名に自分のドライバ名 // も表示したい。 #include "hostnet.h" #include "config.h" +#include "monitor.h" #include "netdriver_none.h" #if defined(HAVE_HOSTNET_AFPACKET) #include "netdriver_afpacket.h" @@ -30,9 +75,18 @@ #if defined(HAVE_HOSTNET_BPF) #include "netdriver_bpf.h" #endif +#if defined(HAVE_HOSTNET_SLIRP) +#include "netdriver_slirp.h" +#include +#endif #if defined(HAVE_HOSTNET_TAP) #include "netdriver_tap.h" #endif +#include "uimessage.h" +#include +#include +#include +#include // コンパイル済みのドライバ名一覧を返す (MainApp から呼ばれる) /*static*/ std::vector @@ -46,6 +100,9 @@ HostNetDevice::GetDrivers() #if defined(HAVE_HOSTNET_BPF) list.emplace_back("bpf"); #endif +#if defined(HAVE_HOSTNET_SLIRP) + list.emplace_back("usermode"); +#endif #if defined(HAVE_HOSTNET_TAP) list.emplace_back("tap"); #endif @@ -53,14 +110,41 @@ HostNetDevice::GetDrivers() return list; } +// libslirp のバージョン文字列を返す。 +// libslirp が有効でなければ NULL を返す。 +/*static*/ const char * +HostNetDevice::GetSlirpVersion() +{ +#if defined(SLIRP_VERSION_STRING) + return SLIRP_VERSION_STRING; +#else + return NULL; +#endif +} + // コンストラクタ -HostNetDevice::HostNetDevice() - : inherited("HostNet") +HostNetDevice::HostNetDevice(Device *parent_, uint n, + const std::string& portname_) + : inherited(parent_, OBJ_HOSTNET(n), portname_) { - monitor.func = ToMonitorCallback(&HostNetDevice::MonitorUpdate); - monitor.SetSize(48, 20); - monitor.Regist(ID_MONITOR_HOSTNET); + ihwaddrfilter = dynamic_cast(parent); + assert(ihwaddrfilter); + + monitor = gMonitorManager->Regist(ID_MONITOR_HOSTNET(n), this); + monitor->SetCallback(&HostNetDevice::MonitorScreen); + monitor->SetSize(48, 24); + +#if defined(HAVE_HOSTNET_SLIRP) + // SLIRP モニタは usermode を使う時のみ必要だが、現状モニタは + // 起動時に1回登録しなければならない (以後増減できない) という制約が + // あるのでここで登録だけしておく。うーんこの…。 + // usermode は現状高々1つなので、登録されてない時だけ登録する。 + Monitor *mon = gMonitorManager->Find(ID_MONITOR_SLIRP); + if (mon == NULL) { + gMonitorManager->Regist(ID_MONITOR_SLIRP, NULL); + } +#endif } // デストラクタ @@ -74,99 +158,123 @@ HostNetDevice::SetLogLevel(int loglevel_ { inherited::SetLogLevel(loglevel_); + RWLockGuard_Read guard(driverlock); if ((bool)driver) { driver->SetLogLevel(loglevel_); } } -// 初期化 +// 動的コンストラクションその2 bool -HostNetDevice::Init() +HostNetDevice::Create2() { - if (inherited::Init() == false) { + if (inherited::Create2() == false) { return false; } - if (SelectDriver() == false) { + if (SelectDriver(true) == false) { return false; } return true; } -// ドライバ(再)選択 +// ドライバ(再)選択。 +// エラーが起きた場合、 +// 起動時なら、warn() 等で表示して false を返す(終了する)。 +// 実行中なら、warn() 等で表示してもいいが、必ず None にフォールバックして +// true を返すこと。 bool -HostNetDevice::SelectDriver() +HostNetDevice::SelectDriver(bool startup) { - const ConfigItem& item = gConfig->Find("hostnet-driver"); - const std::string& type = item.AsString(); - bool fallback = gConfig->Find("hostnet-fallback").AsInt(); + RWLockGuard_Write guard(driverlock); driver.reset(); + errmsg.clear(); + + std::string key = GetConfigKey(); + const ConfigItem& item = gConfig->Find(key + "-driver"); + std::string type = item.AsString(); + + // auto は usermode か none と同義にする。 + if (type == "auto") { +#if defined(HAVE_HOSTNET_SLIRP) + type = "usermode"; +#else + type = "none"; +#endif + } + + enum { + ERR_CONFIG, // 設定でのエラー + ERR_INVALID, // 知らないドライバ + ERR_NOTSUPP, // コンパイルされてない + } reason = ERR_CONFIG; + + if (type == "none") { + CreateNone(); - if (type != "none") { - if (type == "tap") { + } else if (type == "usermode") { +#if defined(HAVE_HOSTNET_SLIRP) + CreateSlirp(startup); +#else + reason = ERR_NOTSUPP; +#endif + + } else if (type == "tap") { #if defined(HAVE_HOSTNET_TAP) - CreateTap(); + CreateTap(); #else - item.Err("hostnet driver %s not compiled", type.c_str()); + reason = ERR_NOTSUPP; #endif - } else if (type == "bpf") { + } else if (type == "bpf") { #if defined(HAVE_HOSTNET_BPF) - CreateBPF(); + CreateBPF(); #else - item.Err("hostnet driver %s not compiled", type.c_str()); + reason = ERR_NOTSUPP; #endif - } else if (type == "afpacket") { + } else if (type == "afpacket") { #if defined(HAVE_HOSTNET_AFPACKET) - CreateAFPacket(); + CreateAFPacket(); #else - item.Err("hostnet driver %s not compiled", type.c_str()); + reason = ERR_NOTSUPP; #endif - } else if (type == "auto") { - // auto なら Tap -> { BPF, AFPacket } の順で試す。 - // - // Tap は事前の準備が面倒だが制約がない。 - // BPF, AFPacket (この2つは OS の違いだけでほぼ同じもの) は - // 準備に手間がかからないが自ホストと通信できない制約がある。 - // そのためどの環境でも Tap が使えれば Tap を、そうでなければ - // BPF, AFPacket を使う、という順にしておく。 - bool r; - - r = CreateTap(); - if (r == false) { - r = CreateAFPacket(); - } - if (r == false) { - r = CreateBPF(); - } + } else { + // 知らないドライバ種別 + reason = ERR_INVALID; + } + if ((bool)driver == false) { + // 指定のドライバが使えなかった場合、 + // o 起動時ならエラー終了する。 + // o 実行中なら none にフォールバックする。 + if (startup) { + switch (reason) { + case ERR_INVALID: + item.Err("Invalid driver name"); + break; + case ERR_CONFIG: + item.Err("Could not configure the driver"); + warnx("(See details with option -C -L%s=1)", key.c_str()); + break; + case ERR_NOTSUPP: + item.Err("Hostnet driver %s not compiled", type.c_str()); + break; + default: + assert(false); + } + return false; } else { - // 知らないドライバ種別 - item.Err(); - } - - if ((bool)driver == false) { - putmsg(1, "hostnet-fallback=%d", fallback ? 1 : 0); + // UI に通知してフォールバック。 + int n = GetId() - OBJ_HOSTNET0; + gMainApp.GetUIMessage()->Post(UIMessage::HOSTNET_FAILED, n); - if (fallback == false) { - // フォールバックしないならエラー終了 - errmsg = "No host network driver found"; - putmsg(1, "%s", errmsg.c_str()); - warnx("%s (See details with options -C -Lhostnet=1)", - errmsg.c_str()); - - return false; - } + CreateNone(); } } - - if ((bool)driver == false) { - CreateNone(); - } assert((bool)driver); // ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える… @@ -176,126 +284,166 @@ HostNetDevice::SelectDriver() return true; } -// None ドライバを生成する -bool +// None ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void HostNetDevice::CreateNone() { - driver.reset(new NetDriverNone(this)); + try { + driver.reset(new NetDriverNone(this)); + } catch (...) { } if ((bool)driver) { if (driver->InitDriver()) { // 成功 - return true; + return; } errmsg = driver->errmsg; driver.reset(); } - return false; } -// Tap ドライバを生成する -bool +// Slirp ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void +HostNetDevice::CreateSlirp(bool startup) +{ +#if defined(HAVE_HOSTNET_SLIRP) + try { + driver.reset(new NetDriverSlirp(this)); + } catch (...) { } + if ((bool)driver) { + if (driver->InitDriver(startup)) { + // 成功 + return; + } + errmsg = driver->errmsg; + driver.reset(); + } +#endif +} + +// Tap ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void HostNetDevice::CreateTap() { #if defined(HAVE_HOSTNET_TAP) - const ConfigItem& item = gConfig->Find("hostnet-tap-devpath"); + int n = GetId() - OBJ_HOSTNET0; + const std::string keyname = string_format("hostnet%d-tap-devpath", n); + const ConfigItem& item = gConfig->Find(keyname); const std::string& devpath = item.AsString(); - driver.reset(new NetDriverTap(this, devpath)); + try { + driver.reset(new NetDriverTap(this, devpath)); + } catch (...) { } if ((bool)driver) { if (driver->InitDriver()) { // 成功 - return true; + return; } errmsg = driver->errmsg; driver.reset(); } #endif - return false; } -// bpf ドライバを生成する -bool +// bpf ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void HostNetDevice::CreateBPF() { #if defined(HAVE_HOSTNET_BPF) - const ConfigItem& item = gConfig->Find("hostnet-bpf-ifname"); + int n = GetId() - OBJ_HOSTNET0; + const std::string keyname = string_format("hostnet%d-bpf-ifname", n); + const ConfigItem& item = gConfig->Find(keyname); const std::string& ifname = item.AsString(); - driver.reset(new NetDriverBPF(this, ifname)); + try { + driver.reset(new NetDriverBPF(this, ifname)); + } catch (...) { } if ((bool)driver) { if (driver->InitDriver()) { // 成功 - return true; + return; } errmsg = driver->errmsg; driver.reset(); } #endif - return false; } -// AFPacket ドライバを生成する -bool +// AFPacket ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void HostNetDevice::CreateAFPacket() { #if defined(HAVE_HOSTNET_AFPACKET) - const ConfigItem& item = gConfig->Find("hostnet-afpacket-ifname"); + int n = GetId() - OBJ_HOSTNET0; + const std::string keyname = string_format("hostnet%d-afpacket-ifname", n); + const ConfigItem& item = gConfig->Find(keyname); const std::string& ifname = item.AsString(); - driver.reset(new NetDriverAFPacket(this, ifname)); + try { + driver.reset(new NetDriverAFPacket(this, ifname)); + } catch (...) { } if ((bool)driver) { if (driver->InitDriver()) { // 成功 - return true; + return; } errmsg = driver->errmsg; driver.reset(); } #endif - return false; } // VM からの送信 (VM スレッドで呼ばれる) bool HostNetDevice::Tx(const NetPacket& packet) { + // 長さ 0 のパケットは破棄する。バグでもないと通常は起きない。 + // runt frame はどうするか? + if (packet.Length() < 1) { + stat.txzero_pkts++; + putlog(1, "Tx: drop empty packet"); + return true; + } + // 送信キューに入れて.. if (txq.Enqueue(packet) == false) { stat.txqfull_pkts++; - stat.txqfull_bytes += packet.length; + stat.txqfull_bytes += packet.Length(); putlog(2, "txq exhausted"); return false; } stat.tx_pkts++; - stat.tx_bytes += packet.length; + stat.tx_bytes += packet.Length(); // ざっくりピーク値 - stat.txq_peak = std::max((int)txq.Length(), stat.txq_peak); + stat.txq_peak = std::max((uint)txq.Length(), stat.txq_peak); - // パイプに通知 (値はダミー) - return WritePipe(0); + // パイプで通知。 + return WritePipe(PIPE_TX); } -// rxq への投入を行う場合は true +// rxq への投入を行う場合は true。 +// VM スレッドから呼ばれる。 void HostNetDevice::EnableRx(bool enable) { rx_enable = enable; -} - -// 自身の MAC アドレスを設定する。 -void -HostNetDevice::SetMyAddr(const macaddr_t& myaddr_) -{ - myaddr = myaddr_; -} -// プロミスキャスモードを設定する。 -void -HostNetDevice::SetPromisc(bool promisc_) -{ - promisc = promisc_; + // 受信を停止した時はキューに残っているのを破棄する。 + // (ゲスト再起動によるデバイスリセット時とか) + if (rx_enable == false) { + // 統計情報に足すためだけに一旦取り出す。 + NetPacket drop; + while (rxq.Dequeue(&drop)) { + stat.rxdisable_pkts++; + stat.rxdisable_bytes += drop.Length(); + } + } } // パケットをキューから取り出す。 @@ -307,9 +455,30 @@ HostNetDevice::Rx(NetPacket *dst) // キューが空 return false; } + uint length = dst->Length(); stat.rx_pkts++; - stat.rx_bytes += dst->length; - putlog(2, "Recv %d bytes", dst->length); + stat.rx_bytes += length; + if (__predict_false(loglevel >= 2)) { + putlogn("Recv %u bytes", length); + if (loglevel >= 4) { + std::string buf; + for (uint i = 0; i < length; i++) { + if (i % 16 == 0) { + buf += string_format("%04x:", i); + } + buf += string_format(" %02x", dst->Peek(i)); + if (i % 16 == 7) { + buf += " "; + } else if (i % 16 == 15) { + putlogn("%s", buf.c_str()); + buf = ""; + } + } + if (buf.empty() == false) { + putlogn("%s", buf.c_str()); + } + } + } return true; } @@ -322,6 +491,7 @@ HostNetDevice::Read() int queued = 0; int left = 0; + RWLockGuard_Read guard(driverlock); assert((bool)driver); do { @@ -335,7 +505,7 @@ HostNetDevice::Read() break; } stat.rxqfull_pkts++; - stat.rxqfull_bytes += discard.length; + stat.rxqfull_bytes += discard.Length(); continue; } @@ -351,117 +521,142 @@ HostNetDevice::Read() NetPacket& packet = *p; stat.read_pkts++; - stat.read_bytes += packet.length; + stat.read_bytes += packet.Length(); if (rx_enable == false) { rxq.CancelWrite(); stat.rxdisable_pkts++; - stat.rxdisable_bytes += packet.length; + stat.rxdisable_bytes += packet.Length(); continue; } // イーサネットフレームより長いパケットはゲスト OS が期待していない。 // ここでドロップしてみる。 // これによりゲストでの dropping chained buffer が出なくなる。 - if (packet.length > 1518) { + if (packet.Length() > 1518) { rxq.CancelWrite(); stat.rxjumbo_pkts++; - stat.rxjumbo_bytes += packet.length; + stat.rxjumbo_bytes += packet.Length(); continue; } // ホストカーネルから直接着信したパケット(というかフレームか)は // 60バイトパディングされないまま読み出せるので、ここでパディングする。 - while (packet.length < 60) { - packet.Append(0x00); + while (packet.Length() < 60) { + packet.Resize(60); } - // パケットフィルタリング - if (__predict_true(promisc == false)) { - bool filter = false; - if (__predict_false(packet[0] & 1)) { - // マルチキャスト - // XXX いまのところ全部通す -#if 0 - // LANCE では LADRF レジスタ、NE2000 では MAR0-7 レジスタで - // データシートを見る限りはロジックは同じものに見える... - - // 宛先アドレスの CRC32 を計算する - uint32 crc = ~crc32(~0U, &packet[0], 6); - // crc 上位 6bit をとりだして検査ビット位置とする - uint bit = 1 << (crc >> 26); - // LADRF,MAR の検査ビット位置が 1 ならば受信、0 ならば破棄 - if ((multicast_filter & bit) == 0) { - filter = true; - } -#endif - } else { - // ユニキャスト - if (memcmp(&packet[0], &myaddr[0], 6) != 0) { - filter = true; - } - } - if (filter) { - stat.rxfilter_pkts++; - stat.rxfilter_bytes += packet.length; - rxq.CancelWrite(); - continue; + // デバイスがこのアドレス宛のフレームを受け取るか。 + // 受け取らないと分かっているフレームは VM へのキューに入れる前に + // 落としておきたい。 + MacAddr dstaddr(packet.Data()); + auto res = ihwaddrfilter->HWAddrFilter(dstaddr); + if (res != 0) { + switch (res) { + case IHWAddrFilter::HPF_DROP_UNICAST: + stat.rxnotme_pkts++; + stat.rxnotme_bytes += packet.Length(); + break; + case IHWAddrFilter::HPF_DROP_MULTICAST: + stat.rxmcast_pkts++; + stat.rxmcast_bytes += packet.Length(); + break; + default: + assertmsg(false, "corrupted res=%u", res); } + rxq.CancelWrite(); + continue; } // CRC XXX 計算すること - for (int i = 0; i < 4; i++) { - packet.Append(0x00); - } + packet.Resize(packet.Length() + 4); rxq.EndWrite(); queued++; } while (left > 0); // ざっくりピーク値 - stat.rxq_peak = std::max((int)rxq.Length(), stat.rxq_peak); + stat.rxq_peak = std::max((uint)rxq.Length(), stat.rxq_peak); return queued; } // 外部への書き出し(パケットを送信) void -HostNetDevice::Write(uint32 data) +HostNetDevice::Write() { const NetPacket *p; + RWLockGuard_Read guard(driverlock); assert((bool)driver); // 送信キューから取り出す while ((p = txq.BeginRead()) != NULL) { const NetPacket& packet = *p; - putlog(2, "Send %d bytes", packet.length); + uint length = packet.Length(); + if (__predict_false(loglevel >= 2)) { + putlogn("Send %u bytes", length); + if (loglevel >= 4) { + std::string buf; + for (uint i = 0; i < length; i++) { + if (i % 16 == 0) { + buf += string_format("%04x:", i); + } + buf += string_format(" %02x", packet.Peek(i)); + if (i % 16 == 7) { + buf += " "; + } else if (i % 16 == 15) { + putlogn("%s", buf.c_str()); + buf = ""; + } + } + if (buf.empty() == false) { + putlogn("%s", buf.c_str()); + } + } + } stat.write_pkts++; - stat.write_bytes += packet.length; + stat.write_bytes += length; - driver->Write(packet.data(), packet.length); + driver->Write(packet.Data(), length); txq.EndRead(); } } // ドライバ名を返す -const std::string -HostNetDevice::GetDriverName() const +const std::string& +HostNetDevice::GetDriverName() { + RWLockGuard_Read guard(driverlock); assert((bool)driver); return driver->GetDriverName(); } +// 統計情報に1パケット分加算する。 +// これだけ SLIRP 裏スレッドから呼ばれるため。 +// 現状書き込むのが SLIRP スレッドだけなのでロックとかはしていない。 +void +HostNetDevice::CountTXUnsupp(size_t bytes) +{ + stat.txunsupp_pkts++; + stat.txunsupp_bytes += bytes; +} + // モニタ void -HostNetDevice::MonitorUpdate(Monitor *, TextScreen& screen) +HostNetDevice::MonitorScreen(Monitor *, TextScreen& screen) { screen.Clear(); - screen.Print(0, 0, "HostNet Driver: %s", driver->GetDriverName()); - // 次の2行はドライバ依存情報 - driver->MonitorUpdateMD(screen); + screen.Print(0, 0, "Device(Port) : %s", GetPortName().c_str()); + { + RWLockGuard_Read guard(driverlock); + screen.Print(0, 1, "HostNet Driver: %s", + driver->GetDriverName().c_str()); + // 次の2行はドライバ依存情報 + driver->MonitorScreenMD(screen, 2); + } // 01234567890123456 // Write to host @@ -470,7 +665,7 @@ HostNetDevice::MonitorUpdate(Monitor *, // Drop:Jumbo Frame // Drop:Addr Filter - int y = 4; + int y = 5; screen.Print(0, y++, "%-17s%13s %17s", "", "Packets", "Bytes"); screen.Print(0, y++, "%-17s%13s %17s", "VM sends", format_number(stat.tx_pkts).c_str(), @@ -478,6 +673,12 @@ HostNetDevice::MonitorUpdate(Monitor *, screen.Print(0, y++, "%-17s%13s %17s", "Write to host", format_number(stat.write_pkts).c_str(), format_number(stat.write_bytes).c_str()); + screen.Print(0, y++, "%-17s%13s %17s", "Drop:Empty Frame", + format_number(stat.txzero_pkts).c_str(), + "0"); + screen.Print(0, y++, "%-17s%13s %17s", "Drop:Unsupported", + format_number(stat.txunsupp_pkts).c_str(), + format_number(stat.txunsupp_bytes).c_str()); screen.Print(0, y++, "%-17s%13s %17s", "Drop:TxQ Full", format_number(stat.txqfull_pkts).c_str(), format_number(stat.txqfull_bytes).c_str()); @@ -499,14 +700,847 @@ HostNetDevice::MonitorUpdate(Monitor *, screen.Print(0, y++, "%-17s%13s %17s", "Drop:RxQ Full", format_number(stat.rxqfull_pkts).c_str(), format_number(stat.rxqfull_bytes).c_str()); - screen.Print(0, y++, "%-17s%13s %17s", "Drop:Addr Filter", - format_number(stat.rxfilter_pkts).c_str(), - format_number(stat.rxfilter_bytes).c_str()); + screen.Print(0, y++, "%-17s%13s %17s", "Drop:Unicast", + format_number(stat.rxnotme_pkts).c_str(), + format_number(stat.rxnotme_bytes).c_str()); + screen.Print(0, y++, "%-17s%13s %17s", "Drop:Multicast", + format_number(stat.rxmcast_pkts).c_str(), + format_number(stat.rxmcast_bytes).c_str()); y++; screen.Print(5, y++, "Capacity Peak"); - screen.Print(0, y++, "TxQ %4d/%4d %4d", - (int)txq.Length(), (int)txq.Capacity(), stat.txq_peak); - screen.Print(0, y++, "RxQ %4d/%4d %4d", - (int)rxq.Length(), (int)rxq.Capacity(), stat.rxq_peak); + screen.Print(0, y++, "TxQ %4u/%4u %4u", + (uint)txq.Length(), (uint)txq.Capacity(), stat.txq_peak); + screen.Print(0, y++, "RxQ %4u/%4u %4u", + (uint)rxq.Length(), (uint)rxq.Capacity(), stat.rxq_peak); +} + +// 16進ダンプに整形して、文字列の配列(改行を含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpHex(const void *src, size_t srclen) +{ + std::vector lines; + std::string buf; + + for (size_t i = 0; i < srclen; i++) { + buf += string_format(" %02x", ((const uint8 *)src)[i]); + if (i % 16 == 7) { + buf += ' '; + } + if (i % 16 == 15) { + lines.emplace_back(buf); + buf.clear(); + } + } + if (buf.empty() == false) { + lines.emplace_back(buf); + } + return lines; +} + +// イーサネットフレームを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpFrame(const void *src, size_t srclen) +{ + std::vector lines; + struct eth_header { + uint8 dst[6]; + uint8 src[6]; + uint16 type; + } __packed; + + const eth_header& eh = *(const eth_header *)src; + uint16 type = ntohs(eh.type); + std::string ethtype; + switch (type) { + case 0x0800: ethtype = "IPv4"; break; + case 0x0806: ethtype = "ARP"; break; + case 0x86dd: ethtype = "IPv6"; break; + default: + ethtype = string_format("%04x", type); + break; + } + lines.emplace_back(string_format("Eth: %02x:%02x:%02x:%02x:%02x:%02x -> " + "%02x:%02x:%02x:%02x:%02x:%02x (%s)", + eh.src[0], eh.src[1], eh.src[2], eh.src[3], eh.src[4], eh.src[5], + eh.dst[0], eh.dst[1], eh.dst[2], eh.dst[3], eh.dst[4], eh.dst[5], + ethtype.c_str())); + + // ペイロードは次の行につなげる。 + const uint8 *payload = (const uint8 *)src + sizeof(eth_header); + size_t payloadlen = srclen - sizeof(eth_header); + std::vector lines2; + switch (type) { + case 0x0800: + lines2 = DumpIPv4(payload, payloadlen); + break; + case 0x0806: + lines2 = DumpARP(payload, payloadlen); + break; + case 0x86dd: + lines2 = DumpIPv6(payload, payloadlen); + break; + default: + break; + } + if (lines2.empty() == false) { + for (auto& buf : lines2) { + lines.emplace_back(buf); + } + } + + return lines; +} + +// ARP パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpARP(const void *src, size_t srclen) +{ + std::vector lines; + struct arp_header { + uint16 hwtype; + uint16 prototype; + uint8 hwsize; + uint8 protosize; + enum : uint16 { + ARP_REQUEST = 1, + ARP_REPLY = 2, + } opcode; + uint8 dstmac[6]; + uint8 dstip[4]; + uint8 srcmac[6]; + uint8 srcip[4]; + } __packed; + + const arp_header& a = *(const arp_header *)src; + uint16 opcode = ntohs(a.opcode); + std::string opstr; + switch (opcode) { + case arp_header::ARP_REQUEST: + lines.emplace_back(string_format( + " ARP: Who has %u.%u.%u.%u? Tell %u.%u.%u.%u", + a.dstip[0], a.dstip[1], a.dstip[2], a.dstip[3], + a.srcip[0], a.srcip[1], a.srcip[2], a.srcip[3])); + break; + case arp_header::ARP_REPLY: + lines.emplace_back(string_format( + " ARP: Reply %u.%u.%u.%u is at %02x:%02x:%02x:%02x:%02x:%02x", + a.dstip[0], a.dstip[1], a.dstip[2], a.dstip[3], + a.dstmac[0], a.dstmac[1], a.dstmac[2], + a.dstmac[3], a.dstmac[4], a.dstmac[5])); + break; + default: + lines.emplace_back(string_format(" ARP: unknown op 0x%04x", opcode)); + break; + } + + return lines; +} + +// IPv4 パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpIPv4(const void *src, size_t srclen) +{ + std::vector lines; + struct ipv4_header { + uint8 ver_len; + uint8 tos; + uint16 total_len; + uint16 id; + uint16 fragment; + uint8 ttl; + uint8 proto; + uint16 cksum; + uint8 src[4]; + uint8 dst[4]; + } __packed; + + const ipv4_header& ip = *(const ipv4_header *)src; + const uint8 *payload = (const uint8 *)src + sizeof(ipv4_header); + size_t payloadlen = srclen - sizeof(ipv4_header); + + char srcname[INET_ADDRSTRLEN]; + char dstname[INET_ADDRSTRLEN]; + srcname[0] = '\0'; + dstname[0] = '\0'; + inet_ntop(AF_INET, &ip.src[0], srcname, (socklen_t)sizeof(srcname)); + inet_ntop(AF_INET, &ip.dst[0], dstname, (socklen_t)sizeof(dstname)); + + switch (ip.proto) { + case 1: + lines = DumpICMPv4(srcname, dstname, payload, payloadlen); + break; + case 6: + lines = DumpTCPv4(srcname, dstname, payload, payloadlen); + break; + case 17: + lines = DumpUDPv4(srcname, dstname, payload, payloadlen); + break; + default: + { + std::string protoname; + const struct protoent *pent = getprotobynumber(ip.proto); + if (pent) { + protoname = string_format("(%s)", pent->p_name); + } + lines.emplace_back(string_format(" IPv4 %s -> %s 0x%02x%s", + srcname, dstname, ip.proto, protoname.c_str())); + break; + } + } + + return lines; +} + +// ICMP(v4) パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpICMPv4(const char *srcname, const char *dstname, + const void *src, size_t srclen) +{ + std::vector lines; + struct icmp_header { + uint8 type; + uint8 code; + uint16 cksum; + } __packed; + + const icmp_header& icmp = *(const icmp_header *)src; + size_t payloadlen = srclen - sizeof(icmp_header); + + std::string msg = string_format(" ICMP %s -> %s ", srcname, dstname); + uint type = icmp.type; + uint code = icmp.code; + + switch (type) { + case 0: // ICMP_ECHOREPLY + msg += string_format("Echo Reply %zu bytes", payloadlen); + break; + + case 3: // ICMP_UNREACH + { + static const char * const reasons[] = { + "Destination Network Unreachable", // 0 + "Destination Host Unreachable", // 1 + "Destination Protocol Unreachable", // 2 + "Destination Port Unreachable", // 3 + "Fragment needed and DF set", // 4 + "Source Route Failed", // 5 + "Destination Network Unknown", // 6 + "Destination Host Unknown", // 7 + "Source Host Isolated", // 8 + "Communication with Destination Network is prohibited", // 9 + "Communication with Destination Host is prohibited", // 10 + "Bad ToS for Destination Network", // 11 + "Bad ToS for Destination Host", // 12 + "Communication is Administratively prohibited", // 13 + "Host Precedence Violation", // 14 + "Precedence cutoff", // 15 + }; + if (code < countof(reasons)) { + msg += reasons[code]; + } else { + msg += string_format("Network Unreachable: code=%02x", code); + } + break; + } + + case 5: // ICMP_REDIRECT + msg += string_format("Redirect code=%x", code); + break; + + case 8: // ICMP_ECHO + msg += string_format("Echo Request %zu bytes", payloadlen); + break; + + default: + msg += string_format("Type=0x%02x Code=0x%02x", type, code); + break; + } + lines.emplace_back(msg); + + return lines; +} + +// TCPv4 パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpTCPv4(const char *srcname, const char *dstname, + const void *src, size_t srclen) +{ + std::vector lines; + struct tcp_header { + uint16 sport; + uint16 dport; + uint32 seq; + uint32 ack; + uint8 off; + uint8 flags; + uint16 window; + uint16 cksum; + uint16 urp; + } __packed; + + const tcp_header& tcp = *(const tcp_header *)src; + size_t payloadlen = srclen - sizeof(tcp_header); + + std::string flagstr; + if (tcp.flags != 0) { + flagstr = ' '; + if ((tcp.flags & 0x01)) flagstr += ",FIN"; + if ((tcp.flags & 0x02)) flagstr += ",SYN"; + if ((tcp.flags & 0x04)) flagstr += ",RST"; + if ((tcp.flags & 0x08)) flagstr += ",PUSH"; + if ((tcp.flags & 0x10)) flagstr += ",ACK"; + if ((tcp.flags & 0x20)) flagstr += ",URG"; + if ((tcp.flags & 0x40)) flagstr += ",ECE"; + if ((tcp.flags & 0x80)) flagstr += ",CWR"; + flagstr[1] = '<'; + flagstr += '>'; + } + std::string sserv = GetServByPort(tcp.sport, "tcp"); + std::string dserv = GetServByPort(tcp.dport, "tcp"); + lines.emplace_back(string_format(" TCP %s:%u%s -> %s:%u%s%s %zu bytes", + srcname, ntohs(tcp.sport), sserv.c_str(), + dstname, ntohs(tcp.dport), dserv.c_str(), + flagstr.c_str(), payloadlen)); + + return lines; +} + +// UDPv4 パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpUDPv4(const char *srcname, const char *dstname, + const void *src, size_t srclen) +{ + std::vector lines; + struct udp_header { + uint16 sport; + uint16 dport; + uint16 len; + uint16 cksum; + } __packed; + + const udp_header& udp = *(const udp_header *)src; + const uint8 *payload = (const uint8 *)src + sizeof(udp_header); + size_t payloadlen = srclen - sizeof(udp_header); + + std::string sserv = GetServByPort(udp.sport, "udp"); + std::string dserv = GetServByPort(udp.dport, "udp"); + int sport = ntohs(udp.sport); + int dport = ntohs(udp.dport); + lines.emplace_back(string_format(" UDP %s:%u%s -> %s:%u%s %zu bytes", + srcname, sport, sserv.c_str(), + dstname, dport, dserv.c_str(), + payloadlen)); + + if (dport == 67 || dport == 68) { + auto lines2 = DumpDHCP(payload, payloadlen); + for (auto& buf : lines2) { + lines.emplace_back(buf); + } + } + + return lines; +} + +// BOOTP/DHCP パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpDHCP(const void *src, size_t srclen) +{ + std::vector lines; + struct bootp_msg { + uint8 op; // Message Operation Code + uint8 htype; // Hardware Type + uint8 hlen; // Hardware Address Length + uint8 hops; + uint32 xid; // Transaction ID + uint16 secs; // Seconds Elapsed (払い出し期間) + uint16 flags; + uint32 ciaddr; // Client IP Address (クライアントが使用) + uint32 yiaddr; // Your IP Address (払い出し IP) + uint32 siaddr; // Server IP Address (BOOTP) + uint32 giaddr; // Gateway IP Address + uint8 chaddr[16]; // Client Hardware Address + uint8 sname[64]; // Server Name (BOOTP) + uint8 file[128]; // Boot Filename (BOOTP) + // ここから options + } __packed; + + const bootp_msg& bootp = *(const bootp_msg *)src; + + std::string msg = " DHCP "; // IP,UDP の次のインデント + if (bootp.op == 1) { + msg += "Request"; + } else if (bootp.op == 2) { + msg += "Reply"; + } else { + msg += string_format("OP=0x%02x?", bootp.op); + } + + if (bootp.htype != 1) { + msg += string_format(" htype=0x%02x?", bootp.htype); + } + if (bootp.hlen != 6) { + msg += string_format(" hlen=0x%02x?", bootp.hlen); + } + if (bootp.ciaddr != 0) { + char ci[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &bootp.ciaddr, ci, (socklen_t)sizeof(ci)); + msg += " Client="; + msg += ci; + } + if (bootp.yiaddr != 0) { + char yi[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &bootp.yiaddr, yi, (socklen_t)sizeof(yi)); + msg += " YourIP="; + msg += yi; + } + lines.emplace_back(msg); + + const uint8 *options = (const uint8 *)src + sizeof(bootp_msg); + size_t optionslen = srclen - sizeof(bootp_msg); + // オプションの先頭4バイトはマジック。 + if (optionslen >= 4 && memcmp(options, "\x63\x82\x53\x63", 4) == 0) { + // 以降は Code-Length-Value 形式。 + const uint8 *s = options + 4; + optionslen -= 4; + while (s - options < optionslen) { + uint8 code = *s++; + if (code == 255) { // End + break; + } + if (code == 0) { // Pad + continue; + } + static const char * const optnames[] = { + "", // 0 + "Subnet Mask", // 1 + "Time Offset", // 2 + "Router", // 3 + "Time Server", // 4 + "IEN116 Name Server", // 5 + "DNS Server", // 6 + "Log Server", // 7 + "Quotes Server", // 8 + "LPR Server", // 9 + "Impress Server", // 10 + "RLP Server", // 11 + "Hostname", // 12 + "Boot File Size", // 13 + "Merit Dump File", // 14 + "Domain Name", // 15 + "Swap Server", // 16 + "Root Path", // 17 + "Extension File", // 18 + "Forward Enable", // 19 + "Source Route Enable", // 20 + "Policy Filter", // 21 + "Max DG Assembly", // 22 + "Default IP TTL", // 23 + "MTU Timeout", // 24 + "MTU Plateau", // 25 + "MTU Interface", // 26 + "MTU Subnet", // 27 + "Broadcast Address", // 28 + "Mask Discovery", // 29 + "Mask Supplier", // 30 + "Router Discovery", // 31 + "Router Request", // 32 + "Static Route", // 33 + "Trailers", // 34 + "ARP Timeout", // 35 + "Ethernet", // 36 + "Default TCP TTL", // 37 + "Keepalive Time", // 38 + "Keepalive Data", // 39 + "NIS Domain", // 40 + "NIS Servers", // 41 + "NTP Servers", // 42 + "Vendor Specific", // 43 + "NETBIOS Name Server", // 44 + "NETBIOS Dist Server", // 45 + "NETBIOS Node Type", // 46 + "NETBIOS Scope", // 47 + "X Window Font", // 48 + "X Window Manager", // 49 + "Requested Address", // 50 + "Lease Time", // 51 + "Overload", // 52 + "DHCP Message Type", // 53 + "DHCP Server Id", // 54 + "Parameter List", // 55 + "DHCP Message", // 56 + "DHCP Max Msg Size", // 57 + "Renewal Time", // 58 + "Rebinding Time", // 59 + "Class Id", // 60 + "Client Id", // 61 + "Netware/IP Domain", // 62 + "Netware/IP Option", // 63 + "NIS Domain Name", // 64 + "NIS Server Addr", // 65 + "TFTP Server Name", // 66 + "Bootfile Name", // 67 + "Home Agent Addrs", // 68 + "SMTP Server", // 69 + "POP3 Server", // 70 + "NNTP Server", // 71 + "WWW Server", // 72 + "Finger Server", // 73 + "IRC Server", // 74 + "StreetTalk Server", // 75 + "STDA Server", // 76 + "User Class", // 77 + "Directory Agent", // 78 + "Service Scope", // 79 + "Naming Authority", // 80 + "Client FQDN", // 81 + "Agent Circuit ID", // 82 + "Agent Remote ID", // 83 + "Agent Subnet Mask", // 84 + "NDS Servers", // 85 + "NDS Tree Name", // 86 + "NDS Context", // 87 + "TimeZone", // 88 + "FQDN", // 89 + "Authentication", // 90 + "Vines TCP/IP", // 91 + "Server Selection", // 92 + "Client System", // 93 + "Client NDI", // 94 + "LDAP", // 95 + "IPv6 Transitions", // 96 + "UUID/GUID", // 97 + "User-Auth", // 98 + "", // 99 + "Printer Name", // 100 + "MDHCP", // 101 + "", // 102 + "", // 103 + "", // 104 + "", // 105 + "", // 106 + "", // 107 + "Swap Path", // 108 + "", // 109 + "IPX Compatability", // 110 + "", // 111 + "Netinfo Address", // 112 + "Netinfo Tag", // 113 + "URL", // 114 + "DHCP Failover", // 115 + "DHCP AutoConfig", // 116 + "Name Service Search", // 117 + "Subnet Selection", // 118 + }; + std::string opt = " "; + if (code < countof(optnames) && optnames[code][0] != '\0') { + opt += optnames[code]; + } else { + opt += '?'; + } + opt += string_format("(%d): ", code); + size_t len = *s++; + switch (code) { + case 1: // Subnet Mask + case 50: // Requested Address + case 54: // DHCP Server Id + // 単一アドレス + opt += DumpDHCPAddrList(s, 1); + break; + + case 3: // Router + case 6: // DNS Server + // アドレスリスト + opt += DumpDHCPAddrList(s, len / 4); + break; + + case 12: // Hostname + case 15: // Domain Name + { + // 文字列 (終端文字なし) + std::vector buf(len + 1); + memcpy(buf.data(), s, len); + opt += buf.data(); + break; + } + + case 51: // Lease Time + { + uint32 sec = + (s[0] << 24) + | (s[1] << 16) + | (s[2] << 8) + | (s[3]); + opt += string_format("%d sec", sec); + break; + } + + case 53: // DHCP Message Type + { + static const char * const msgtypes[] = { + "0", + "DHCP DISCOVER", + "DHCP OFFER", + "DHCP REQUEST", + "DHCP DECLINE", + "DHCP ACK", + "DHCP NAK", + "DHCP RELEASE", + "DHCP INFORM", + }; + int type = s[0]; + if (type < countof(msgtypes)) { + opt += msgtypes[type]; + } else { + opt += string_format("0x%02x", type); + } + break; + } + + default: + // 簡易ダンプしとくか。 + for (size_t i = 0; i < len; i++) { + opt += string_format("%02x", s[i]); + if (i % 4 == 3) { + opt += ' '; + } + } + break; + } + lines.emplace_back(opt); + s += len; + } + } + + return lines; +} + +// s から num 個の IPv4 アドレスリストを文字列にして返す。 +// DumpDHCP() の下請け。 +/*static*/ std::string +HostNetDevice::DumpDHCPAddrList(const uint8 *s, size_t num) +{ + std::string buf; + + for (size_t i = 0; i < num; i++) { + char addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, s, addr, (socklen_t)sizeof(addr)); + if (i != 0) { + buf += ' '; + } + buf += addr; + } + + return buf; +} + +// IPv6 パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpIPv6(const void *src, size_t srclen) +{ + std::vector lines; + struct ipv6_header { + uint8 ver_cls; + uint8 cls_flow; + uint16 flowlabel; + uint16 payload_len; + uint8 nexthdr; + uint8 hoplimit; + uint8 src[16]; + uint8 dst[16]; + } __packed; + + const ipv6_header& ip6 = *(const ipv6_header *)src; + const uint8 *payload = (const uint8 *)src + sizeof(ipv6_header); + size_t payloadlen = srclen - sizeof(ipv6_header); + + char srcname[INET6_ADDRSTRLEN]; + char dstname[INET6_ADDRSTRLEN]; + srcname[0] = '\0'; + dstname[0] = '\0'; + inet_ntop(AF_INET6, &ip6.src[0], srcname, (socklen_t)sizeof(srcname)); + inet_ntop(AF_INET6, &ip6.dst[0], dstname, (socklen_t)sizeof(dstname)); + + switch (ip6.nexthdr) { + case 58: + lines = DumpICMPv6(srcname, dstname, payload, payloadlen); + break; + default: + { + std::string protoname; + const struct protoent *pent = getprotobynumber(ip6.nexthdr); + if (pent) { + protoname = string_format("(%s)", pent->p_name); + } + lines.emplace_back(string_format(" IPv6 %s -> %s 0x%02x%s", + srcname, dstname, ip6.nexthdr, protoname.c_str())); + break; + } + } + + return lines; +} + +// ICMPv6 パケットを整形して、文字列の配列(改行は含まない)を返す。 +/*static*/ std::vector +HostNetDevice::DumpICMPv6(const char *srcname, const char *dstname, + const void *src, size_t srclen) +{ + std::vector lines; + // フォーマット自体は ICMPv4 と同じ。 + struct icmp_header { + uint8 type; + uint8 code; + uint16 cksum; + } __packed; + + const icmp_header& icmp6 = *(const icmp_header *)src; + + std::string msg = string_format(" ICMPv6 %s -> %s ", srcname, dstname); + uint type = icmp6.type; + uint code = icmp6.code; + + switch (type) { + case 1: + static const char * const reasons[] = { + "No route to destination", // 0 + "Administratively prohibited", // 1 + "Beyond scope of source address", // 2 + "Address unreachable", // 3 + "Port unreachable", // 4 + "Source address failed ingress/egress policy", // 5 + "Reject route to destination", // 6 + "Error in source routing header", // 7 + }; + if (code < countof(reasons)) { + msg += reasons[code]; + } else { + msg += string_format("Destination Unreachable: code=%02x", code); + } + break; + + case 2: + msg += "Packet too big"; + break; + case 3: + msg += "Time exceeded"; + break; + case 4: + msg += "Parameter Problem"; + break; + case 128: + msg += "Echo Request"; + break; + case 129: + msg += "Echo Reply"; + break; + case 130: + msg += "Multicast Listener Query"; + break; + case 131: + msg += "Multicast Listener Report"; + break; + case 132: + msg += "Multicast Listener Done"; + break; + case 133: + msg += "NDP Router Solicitation"; + break; + case 134: + msg += "NDP Router Advertisement"; + break; + case 135: + msg += "NDP Neighbor Solicitation"; + break; + case 136: + msg += "NDP Neighbor Advertisement"; + break; + case 137: + msg += "NDP Redirect"; + break; + case 138: + msg += "Router Renumber"; + break; + case 139: + msg += "ICMP Node Information Query"; + break; + case 140: + msg += "ICMP Node Information Reply"; + break; + default: + msg += string_format("Type=0x%02x Code=0x%02x", type, code); + break; + } + lines.emplace_back(msg); + + return lines; +} + +// ポート番号に対する名前を括弧をつけて返す。表示用。 +// 名前が引けなければ空文字列を返す。 +/*static*/ std::string +HostNetDevice::GetServByPort(uint16 port_be, const char *protoname) +{ + std::string name; + + // getservbyport() の引数はネットワークバイトオーダ…。 + // かつ、戻り値は内部のスタティックな領域を指している可能性がある。 + const struct servent *serv = getservbyport(port_be, protoname); + if (serv) { + name = string_format("(%s)", serv->s_name); + } + return name; +} + + +// +// NetPacket +// + +// このパケットを src から srclen バイトの内容に置き換える。 +void +NetPacket::Assign(const void *src, uint srclen) +{ + Resize(srclen); + uint copylen = std::min(srclen, (uint)buf.size()); + memcpy(buf.data(), src, copylen); +} + +// パケット長を変更する。 +void +NetPacket::Resize(uint newlength) +{ + uint cliplength = newlength; + + if (__predict_false(cliplength > buf.size())) { + cliplength = buf.size(); + } + + // 新たに増えたところは 0 で埋める。 + if (cliplength > length) { + memset(Data() + length, 0, cliplength - length); + } + + // 長さは buf.size() を超えることを許容する。 + length = newlength; +} + +// パケットに1バイト追加する。 +void +NetPacket::Append(uint8 data) +{ + if (__predict_true(length < buf.size())) { + buf[length] = data; + } + length++; +} + + +// +// IHWAddrFilter +// + +// IHWAddrFilter の デストラクタ +IHWAddrFilter::~IHWAddrFilter() +{ }