File:  [Isaki's NoNo m68k/m88k emulator] / nono / host / hostnet.cpp
Revision 1.1.1.12 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:02 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// ネットワークのホストデバイス
//

// 送信時のフロー
//
// 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)    (HostNet0)      (HostNet0.***)
// 識別名 "lance"    "hostnet0"      "hostnet0"
//
// HostNet は、特に NetDriver 派生クラス選択時はフォールバックも含めて
// どれが選択されるか複雑なこともあるので、ログ表示名に自分のドライバ名
// も表示したい。

#include "hostnet.h"
#include "config.h"
#include "monitor.h"
#include "netdriver_none.h"
#if defined(HAVE_HOSTNET_AFPACKET)
#include "netdriver_afpacket.h"
#endif
#if defined(HAVE_HOSTNET_BPF)
#include "netdriver_bpf.h"
#endif
#if defined(HAVE_HOSTNET_SLIRP)
#include "netdriver_slirp.h"
#include <slirp/libslirp-version.h>
#endif
#if defined(HAVE_HOSTNET_TAP)
#include "netdriver_tap.h"
#endif
#include "uimessage.h"
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// コンパイル済みのドライバ名一覧を返す (MainApp から呼ばれる)
/*static*/ std::vector<std::string>
HostNetDevice::GetDrivers()
{
	std::vector<std::string> list;

#if defined(HAVE_HOSTNET_AFPACKET)
	list.emplace_back("afpacket");
#endif
#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

	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(Device *parent_, uint n,
		const std::string& portname_)
	: inherited(parent_, OBJ_HOSTNET(n), portname_)
{
	ihwaddrfilter = dynamic_cast<IHWAddrFilter *>(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
}

// デストラクタ
HostNetDevice::~HostNetDevice()
{
}

// ログレベル設定
void
HostNetDevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	RWLockGuard_Read guard(driverlock);
	if ((bool)driver) {
		driver->SetLogLevel(loglevel_);
	}
}

// 動的コンストラクションその2
bool
HostNetDevice::Create2()
{
	if (inherited::Create2() == false) {
		return false;
	}

	if (SelectDriver(true) == false) {
		return false;
	}

	return true;
}

// ドライバ(再)選択。
// エラーが起きた場合、
// 起動時なら、warn() 等で表示して false を返す(終了する)。
// 実行中なら、warn() 等で表示してもいいが、必ず None にフォールバックして
// true を返すこと。
bool
HostNetDevice::SelectDriver(bool startup)
{
	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();

	} 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();
#else
		reason = ERR_NOTSUPP;
#endif

	} else if (type == "bpf") {
#if defined(HAVE_HOSTNET_BPF)
		CreateBPF();
#else
		reason = ERR_NOTSUPP;
#endif

	} else if (type == "afpacket") {
#if defined(HAVE_HOSTNET_AFPACKET)
		CreateAFPacket();
#else
		reason = ERR_NOTSUPP;
#endif

	} 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 {
			// UI に通知してフォールバック。
			int n = GetId() - OBJ_HOSTNET0;
			gMainApp.GetUIMessage()->Post(UIMessage::HOSTNET_FAILED, n);

			CreateNone();
		}
	}
	assert((bool)driver);

	// ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える…
	putmsg(1, "selected host driver: %s",
		string_tolower(driver->GetDriverName()).c_str());

	return true;
}

// None ドライバを生成する。
// 戻り値はなく、成否は (bool)driver で判断する。
void
HostNetDevice::CreateNone()
{
	try {
		driver.reset(new NetDriverNone(this));
	} catch (...) { }
	if ((bool)driver) {
		if (driver->InitDriver()) {
			// 成功
			return;
		}
		errmsg = driver->errmsg;
		driver.reset();
	}
}

// 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)
	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();

	try {
		driver.reset(new NetDriverTap(this, devpath));
	} catch (...) { }
	if ((bool)driver) {
		if (driver->InitDriver()) {
			// 成功
			return;
		}
		errmsg = driver->errmsg;
		driver.reset();
	}
#endif
}

// bpf ドライバを生成する。
// 戻り値はなく、成否は (bool)driver で判断する。
void
HostNetDevice::CreateBPF()
{
#if defined(HAVE_HOSTNET_BPF)
	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();

	try {
		driver.reset(new NetDriverBPF(this, ifname));
	} catch (...) { }
	if ((bool)driver) {
		if (driver->InitDriver()) {
			// 成功
			return;
		}
		errmsg = driver->errmsg;
		driver.reset();
	}
#endif
}

// AFPacket ドライバを生成する。
// 戻り値はなく、成否は (bool)driver で判断する。
void
HostNetDevice::CreateAFPacket()
{
#if defined(HAVE_HOSTNET_AFPACKET)
	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();

	try {
		driver.reset(new NetDriverAFPacket(this, ifname));
	} catch (...) { }
	if ((bool)driver) {
		if (driver->InitDriver()) {
			// 成功
			return;
		}
		errmsg = driver->errmsg;
		driver.reset();
	}
#endif
}

// 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();
		putlog(2, "txq exhausted");
		return false;
	}

	stat.tx_pkts++;
	stat.tx_bytes += packet.Length();

	// ざっくりピーク値
	stat.txq_peak = std::max((uint)txq.Length(), stat.txq_peak);

	// パイプで通知。
	return WritePipe(PIPE_TX);
}

// rxq への投入を行う場合は true。
// VM スレッドから呼ばれる。
void
HostNetDevice::EnableRx(bool enable)
{
	rx_enable = enable;

	// 受信を停止した時はキューに残っているのを破棄する。
	// (ゲスト再起動によるデバイスリセット時とか)
	if (rx_enable == false) {
		// 統計情報に足すためだけに一旦取り出す。
		NetPacket drop;
		while (rxq.Dequeue(&drop)) {
			stat.rxdisable_pkts++;
			stat.rxdisable_bytes += drop.Length();
		}
	}
}

// パケットをキューから取り出す。
// VM 側から呼ばれる。
bool
HostNetDevice::Rx(NetPacket *dst)
{
	if (rxq.Dequeue(dst) == false) {
		// キューが空
		return false;
	}
	uint length = dst->Length();
	stat.rx_pkts++;
	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;
}

// 外部からの読み込み (パケットを受信)。
// 戻り値はキューに投入したパケット数。
int
HostNetDevice::Read()
{
	int queued = 0;
	int left = 0;

	RWLockGuard_Read guard(driverlock);
	assert((bool)driver);

	do {
		NetPacket *p;

		p = rxq.BeginWrite();
		if (p == NULL) {
			NetPacket discard;
			left = driver->Read(&discard);
			if (left < 0) {
				break;
			}
			stat.rxqfull_pkts++;
			stat.rxqfull_bytes += discard.Length();
			continue;
		}

		// driver->Read() は VM に投入すべきパケットがなければ負数を返す。
		// パケットがあれば p に書き戻して、driver 側のバッファに残っている
		// パケット数 (といっても 0 か 1以上) を返してくる。
		left = driver->Read(p);
		if (left < 0) {
			rxq.CancelWrite();
			break;
		}

		NetPacket& packet = *p;

		stat.read_pkts++;
		stat.read_bytes += packet.Length();

		if (rx_enable == false) {
			rxq.CancelWrite();
			stat.rxdisable_pkts++;
			stat.rxdisable_bytes += packet.Length();
			continue;
		}

		// イーサネットフレームより長いパケットはゲスト OS が期待していない。
		// ここでドロップしてみる。
		// これによりゲストでの dropping chained buffer が出なくなる。
		if (packet.Length() > 1518) {
			rxq.CancelWrite();
			stat.rxjumbo_pkts++;
			stat.rxjumbo_bytes += packet.Length();
			continue;
		}

		// ホストカーネルから直接着信したパケット(というかフレームか)は
		// 60バイトパディングされないまま読み出せるので、ここでパディングする。
		while (packet.Length() < 60) {
			packet.Resize(60);
		}

		// デバイスがこのアドレス宛のフレームを受け取るか。
		// 受け取らないと分かっているフレームは 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 計算すること
		packet.Resize(packet.Length() + 4);

		rxq.EndWrite();
		queued++;
	} while (left > 0);

	// ざっくりピーク値
	stat.rxq_peak = std::max((uint)rxq.Length(), stat.rxq_peak);

	return queued;
}

// 外部への書き出し(パケットを送信)
void
HostNetDevice::Write()
{
	const NetPacket *p;

	RWLockGuard_Read guard(driverlock);
	assert((bool)driver);

	// 送信キューから取り出す
	while ((p = txq.BeginRead()) != NULL) {
		const NetPacket& packet = *p;

		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 += length;

		driver->Write(packet.Data(), length);
		txq.EndRead();
	}
}

// ドライバ名を返す
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::MonitorScreen(Monitor *, TextScreen& screen)
{
	screen.Clear();

	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
	// Drop:TxQ Full
	// Drop:Rx Disabled
	// Drop:Jumbo Frame
	// Drop:Addr Filter

	int y = 5;
	screen.Print(0, y++, "%-17s%13s %17s", "<Tx>", "Packets", "Bytes");
	screen.Print(0, y++, "%-17s%13s %17s", "VM sends",
		format_number(stat.tx_pkts).c_str(),
		format_number(stat.tx_bytes).c_str());
	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());
	y++;

	screen.Print(0, y++, "%-17s%13s %17s", "<Rx>", "Packets", "Bytes");
	screen.Print(0, y++, "%-17s%13s %17s", "Read from host",
		format_number(stat.read_pkts).c_str(),
		format_number(stat.read_bytes).c_str());
	screen.Print(0, y++, "%-17s%13s %17s", "VM receives",
		format_number(stat.rx_pkts).c_str(),
		format_number(stat.rx_bytes).c_str());
	screen.Print(0, y++, "%-17s%13s %17s", "Drop:Rx Disabled",
		format_number(stat.rxdisable_pkts).c_str(),
		format_number(stat.rxdisable_bytes).c_str());
	screen.Print(0, y++, "%-17s%13s %17s", "Drop:Jumbo Frame",
		format_number(stat.rxjumbo_pkts).c_str(),
		format_number(stat.rxjumbo_bytes).c_str());
	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: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 %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<std::string>
HostNetDevice::DumpHex(const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpFrame(const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string> 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<std::string>
HostNetDevice::DumpARP(const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpIPv4(const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpICMPv4(const char *srcname, const char *dstname,
	const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpTCPv4(const char *srcname, const char *dstname,
	const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpUDPv4(const char *srcname, const char *dstname,
	const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpDHCP(const void *src, size_t srclen)
{
	std::vector<std::string> 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<char> 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<std::string>
HostNetDevice::DumpIPv6(const void *src, size_t srclen)
{
	std::vector<std::string> 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<std::string>
HostNetDevice::DumpICMPv6(const char *srcname, const char *dstname,
	const void *src, size_t srclen)
{
	std::vector<std::string> 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()
{
}

unix.superglobalmegacorp.com

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