File:  [Isaki's NoNo m68k/m88k emulator] / nono / host / netdriver_bpf.cpp
Revision 1.1.1.9 (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
//

//
// ホストネットワークの BPF ドライバ
//

#include "netdriver_bpf.h"
#include "autofd.h"
#include "hostnet.h"
#include <fcntl.h>
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>

// コンストラクタ
NetDriverBPF::NetDriverBPF(HostDevice *hostdev_, const std::string& ifname_)
	: inherited(hostdev_, "bpf")
{
	ifname = ifname_;
	if (ifname.empty()) {
		ifname = "auto";
	}
}

// デストラクタ
NetDriverBPF::~NetDriverBPF()
{
	Close();
}

// ドライバ初期化
bool
NetDriverBPF::InitDriver(bool startup)
{
	struct ifreq ifr;
	struct bpf_program prog;
	u_int val;
	u_int bpfbuflen;
	struct bpf_insn insn[] = {
		// すべてのパケットを全長受信
		BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
	};

	putmsg(1, "trying bpf...");
	putmsg(1, "argument: ifname=\"%s\"", ifname.c_str());

	if (ifname == "auto") {
		// インタフェース指定がなければそれっぽいのを探すか
		ifname = FindInterface();
		if (ifname.empty()) {
			errmsg = "auto could not find a suitable interface";
			goto abort;
		}
		putmsg(1, "auto-selected ifname: \"%s\"", ifname.c_str());
	}

	// 最大値は適当
	fd = -1;
	for (int i = -1; i < 10; i++) {
		// i == -1 なら /dev/bpf、0..10 は /dev/bpfN。
		// まず /dev/bpf を cloning デバイスだと思って開いてみる。
		// そうでなければ従来どおり 0 から順番に試してみる。
		if (i == -1) {
			devpath = "/dev/bpf";
		} else {
			devpath = string_format("/dev/bpf%d", i);
		}

		fd = open(devpath.c_str(), O_RDWR);
		if (fd < 0) {
			// ファイルがないのを表示したら結構うるさいので除く
			if (errno != ENOENT) {
				putmsg(1, "open %s: %s", devpath.c_str(), strerror(errno));
			}
			continue;
		}
		// 成功したら抜ける
		break;
	}
	if (fd < 0) {
		errmsg = "no bpf devices available";
		goto abort;
	}

	// インタフェースにバインド
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname.c_str(), sizeof(ifr.ifr_name));
	if (ioctl(fd, BIOCSETIF, &ifr) == -1) {
		errmsg = string_format("%s(%s): BIOCSETIF: %s",
			devpath.c_str(), ifname.c_str(), strerror(errno));
		goto abort;
	}

	if (ioctl(fd, BIOCPROMISC, (void *)0) == -1) {
		errmsg = string_format("%s(%s): %s: %s",
			devpath.c_str(), ifname.c_str(), "BIOCPROMISC", strerror(errno));
		goto abort;
	}

	val = 1;
	if (ioctl(fd, BIOCIMMEDIATE, &val) == -1) {
		errmsg = string_format("%s(%s): %s: %s",
			devpath.c_str(), ifname.c_str(), "BIOCIMMEDIATE", strerror(errno));
		goto abort;
	}

	if (ioctl(fd, BIOCSHDRCMPLT, &val) == -1) {
		errmsg = string_format("%s(%s): %s: %s",
			devpath.c_str(), ifname.c_str(), "BIOCSHDRCMPLT", strerror(errno));
		goto abort;
	}

	// フィルタをセット
	prog.bf_len = countof(insn);
	prog.bf_insns = insn;
	if (ioctl(fd, BIOCSETF, &prog) == -1) {
		errmsg = string_format("%s(%s): %s: %s",
			devpath.c_str(), ifname.c_str(), "BIOCSETF", strerror(errno));
		goto abort;
	}

	// バッファサイズを取得
	if (ioctl(fd, BIOCGBLEN, &bpfbuflen) == -1) {
		errmsg = string_format("%s(%s): %s: %s",
			devpath.c_str(), ifname.c_str(), "BIOCGBLEN", strerror(errno));
		goto abort;
	}

	// 受信用バッファを確保
	bpfbuf.resize(bpfbuflen);

	putmsg(1, "opened %s for %s", devpath.c_str(), ifname.c_str());
	putmsg(1, "warning: communicating with the localhost is not possible");

	// bpf では ifup スクリプトは実行しない

	if (hostdev->AddOuter(fd) < 0) {
		putmsg(0, "AddOuter(fd=%d): %s", (int)fd, strerror(errno));
		return false;
	}

	return true;

 abort:
	putmsg(1, "%s", errmsg.c_str());
	Close();
	return false;
}

// クローズ
// (private)
void
NetDriverBPF::Close()
{
	if (fd.Valid()) {
		hostdev->DelOuter(fd);
		fd.Close();
	}
}

// モニタ (ドライバ依存情報のみ)
void
NetDriverBPF::MonitorScreenMD(TextScreen& screen, int y)
{
	screen.Print(0, y++, "Device   : %s", devpath.c_str());
	screen.Print(0, y++, "Interface: %s", ifname.c_str());
}

// パケットを送信する
void
NetDriverBPF::Write(const void *buf, int buflen)
{
	ssize_t n;

	n = write(fd, buf, buflen);
	if (n < 0) {
		putmsg(0, "write(%d): %s", buflen, strerror(errno));
		return;
	}
	if (n < buflen) {
		putmsg(0, "write(%d): short(%zd)", buflen, n);
		return;
	}
}

// パケットを受信する
int
NetDriverBPF::Read(NetPacket *p)
{
	uint8 *buf = bpfbuf.data();

	if (bpfptr == NULL) {
		bpflen = read(fd, bpfbuf.data(), bpfbuf.size());
		if (bpflen < 0) {
			putmsg(0, "read: %s", strerror(errno));
			return NODATA;
		}

		bpfptr = buf;
	}

	// BPF ヘッダを除いた部分が 1 パケット
	struct bpf_hdr *bh = (struct bpf_hdr *)bpfptr;
	p->Assign(bpfptr + bh->bh_hdrlen, bh->bh_caplen);

	// 次のフレームの先頭を指す
	int framelen = BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
	bpfptr += framelen;

	if (bpfptr < buf + bpflen) {
		// 次がある
		return 1;
	} else {
		// 次はない
		bpfptr = NULL;
		return 0;
	}
}

// 最初に見付かった使えそうなインタフェース名を返す。
// 物理インタフェースが明らかに1つしかないところで、
// インタフェース名の指定を省略できるようにするため。
std::string
NetDriverBPF::FindInterface() const
{
	char name[IFNAMSIZ];
	struct ifaddrs *ifa_list, *ifa;
	struct sockaddr_dl *dl;

	if (getifaddrs(&ifa_list) == -1) {
		return "";
	}

	name[0] = '\0';
	for (ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
		dl = (struct sockaddr_dl *)ifa->ifa_addr;
		if ((ifa->ifa_flags & IFF_UP) != 0 && dl->sdl_type == IFT_ETHER) {
			strlcpy(name, ifa->ifa_name, sizeof(name));
			break;
		}
	}
	freeifaddrs(ifa_list);

	return std::string(name);
}

unix.superglobalmegacorp.com

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