--- nono/host/hostnet.cpp 2026/04/29 17:05:26 1.1.1.4 +++ nono/host/hostnet.cpp 2026/04/29 17:05:39 1.1.1.7 @@ -8,6 +8,51 @@ // ネットワークのホストデバイス // +// 送信時のフロー +// +// 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 // | | | @@ -22,6 +67,7 @@ #include "hostnet.h" #include "config.h" +#include "monitor.h" #include "netdriver_none.h" #if defined(HAVE_HOSTNET_AFPACKET) #include "netdriver_afpacket.h" @@ -29,10 +75,16 @@ #if defined(HAVE_HOSTNET_BPF) #include "netdriver_bpf.h" #endif +#if defined(HAVE_HOSTNET_SLIRP) +#include "netdriver_slirp.h" +#endif #if defined(HAVE_HOSTNET_TAP) #include "netdriver_tap.h" #endif -#include +#include +#include +#include +#include // コンパイル済みのドライバ名一覧を返す (MainApp から呼ばれる) /*static*/ std::vector @@ -46,6 +98,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 @@ -55,12 +110,15 @@ HostNetDevice::GetDrivers() // コンストラクタ -HostNetDevice::HostNetDevice(Device *parent_, int n) +HostNetDevice::HostNetDevice(Device *parent_, uint n) : inherited(parent_, OBJ_HOSTNET(n)) { - monitor.func = ToMonitorCallback(&HostNetDevice::MonitorUpdate); - monitor.SetSize(48, 22); - monitor.Regist(ID_MONITOR_HOSTNET(n)); + ihwaddrfilter = dynamic_cast(parent); + assert(ihwaddrfilter); + + monitor = gMonitorManager->Regist(ID_MONITOR_HOSTNET(n), this); + monitor->func = ToMonitorCallback(&HostNetDevice::MonitorUpdate); + monitor->SetSize(48, 24); } // デストラクタ @@ -79,11 +137,11 @@ HostNetDevice::SetLogLevel(int loglevel_ } } -// 初期化 +// 動的コンストラクションその2 bool -HostNetDevice::Init() +HostNetDevice::Create2() { - if (inherited::Init() == false) { + if (inherited::Create2() == false) { return false; } @@ -102,13 +160,29 @@ HostNetDevice::SelectDriver() const std::string key_driver = string_format("hostnet%d-driver", n); const std::string key_fallback = string_format("hostnet%d-fallback", n); const ConfigItem& item = gConfig->Find(key_driver); - const std::string& type = item.AsString(); + std::string type = item.AsString(); bool fallback = gConfig->Find(key_fallback).AsInt(); driver.reset(); + // auto は usermode か none と同義にする。 + if (type == "auto") { +#if defined(HAVE_HOSTNET_SLIRP) + type = "usermode"; +#else + type = "none"; +#endif + } + if (type != "none") { - if (type == "tap") { + if (type == "usermode") { +#if defined(HAVE_HOSTNET_SLIRP) + CreateSlirp(); +#else + item.Err("Hostnet driver %s not compiled", type.c_str()); +#endif + + } else if (type == "tap") { #if defined(HAVE_HOSTNET_TAP) CreateTap(); #else @@ -129,24 +203,6 @@ HostNetDevice::SelectDriver() item.Err("Hostnet driver %s not compiled", type.c_str()); #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 { // 知らないドライバ種別 item.Err(); @@ -195,6 +251,24 @@ HostNetDevice::CreateNone() return false; } +// Slirp ドライバを生成する +bool +HostNetDevice::CreateSlirp() +{ +#if defined(HAVE_HOSTNET_SLIRP) + driver.reset(new NetDriverSlirp(this)); + if ((bool)driver) { + if (driver->InitDriver()) { + // 成功 + return true; + } + errmsg = driver->errmsg; + driver.reset(); + } +#endif + return false; +} + // Tap ドライバを生成する bool HostNetDevice::CreateTap() @@ -268,6 +342,14 @@ HostNetDevice::CreateAFPacket() 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++; @@ -280,38 +362,29 @@ HostNetDevice::Tx(const NetPacket& packe 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); } -// 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::SetMCastFilter(uint64 filter_) -{ - multicast_filter = filter_; -} -// プロミスキャスモードを設定する。 -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; + } + } } // パケットをキューから取り出す。 @@ -326,10 +399,10 @@ HostNetDevice::Rx(NetPacket *dst) stat.rx_pkts++; stat.rx_bytes += dst->length; if (__predict_false(loglevel >= 2)) { - putlogn("Recv %d bytes", dst->length); - if (loglevel >= 3) { + putlogn("Recv %u bytes", dst->length); + if (loglevel >= 4) { std::string buf; - for (int i = 0; i < dst->length; i++) { + for (uint i = 0; i < dst->length; i++) { if (i % 16 == 0) { buf += string_format("%04x:", i); } @@ -412,51 +485,26 @@ HostNetDevice::Read() packet.Append(0x00); } - // パケットフィルタリング - if (__predict_true(promisc == false)) { - static std::array bcast { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff - }; - if (memcmp(&packet[0], &bcast[0], bcast.size()) == 0) { - // ブロードキャストは素通し。 - } else if (__predict_false(packet[0] & 1)) { - // マルチキャスト。 - // LANCE では LADRF レジスタ、NE2000 では MAR0-7 レジスタで - // データシートを見る限りはロジックは同じもののようだ。 - - // 宛先アドレスの CRC32 を計算する。 - // イーサネットの CRC は zlib の crc32() と同じもの。 - ulong crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, &packet[0], 6); - - // crc の上位6ビットをとりだして検査ビット位置とする(=pos)。 - // multicast_filter の pos の位置のビットが 1 なら受信、 - // 0 なら破棄。 - // multicast_filter は64ビットで LSB が0番目、MSB が63番目 - // としたもの (NIC 側でこの通りに並べ替えて渡してある)。 - uint pos = (crc >> 26) & 0x3f; - uint64 bit = 1U << pos; - if ((multicast_filter & bit) == 0) { - putlog(2, "mcast %02x:%02x:%02x:%02x:%02x:%02x pos=%d drop", - packet[0], packet[1], packet[2], - packet[3], packet[4], packet[5], pos); - stat.rxmcast_pkts++; - stat.rxmcast_bytes += packet.length; - rxq.CancelWrite(); - continue; - } - putlog(2, "mcast %02x:%02x:%02x:%02x:%02x:%02x pos=%d pass", - packet[0], packet[1], packet[2], - packet[3], packet[4], packet[5], pos); - } else { - // ユニキャスト - if (memcmp(&packet[0], &myaddr[0], 6) != 0) { - stat.rxnotme_pkts++; - stat.rxnotme_bytes += packet.length; - rxq.CancelWrite(); - continue; - } + // デバイスがこのアドレス宛のフレームを受け取るか。 + // 受け取らないと分かっているフレームは VM へのキューに入れる前に + // 落としておきたい。 + MacAddr dstaddr(&packet[0]); + 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 計算すること @@ -469,7 +517,7 @@ HostNetDevice::Read() } 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; } @@ -487,10 +535,10 @@ HostNetDevice::Write(uint32 data) const NetPacket& packet = *p; if (__predict_false(loglevel >= 2)) { - putlogn("Send %d bytes", packet.length); - if (loglevel >= 3) { + putlogn("Send %u bytes", packet.length); + if (loglevel >= 4) { std::string buf; - for (int i = 0; i < packet.length; i++) { + for (uint i = 0; i < packet.length; i++) { if (i % 16 == 0) { buf += string_format("%04x:", i); } @@ -523,6 +571,16 @@ HostNetDevice::GetDriverName() const 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) @@ -549,6 +607,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()); @@ -579,8 +643,791 @@ HostNetDevice::MonitorUpdate(Monitor *, 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; +} + + +// IHWAddrFilter の デストラクタ +IHWAddrFilter::~IHWAddrFilter() +{ }