File:  [Isaki's NoNo m68k/m88k emulator] / nono / host / netdriver_tap.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
//

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

#include "netdriver_tap.h"
#include "autofd.h"
#include "hostnet.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#if defined(__linux__)
#include <linux/if.h>
#include <linux/if_tun.h>
#elif defined(__OpenBSD__)
#include <net/if_tun.h>
#else
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_tap.h>
#endif

// Linux
//	/dev/net/tun をオープンし、ioctl(TUNSETIFF) を発行すると tap
//	インタフェースが生えて、そのインタフェース名が返ってくる。
//
// OpenBSD
//	/dev/tapN をオープンする。インタフェース名は対応する tapN のはず。
//
// NetBSD (dynamic)
//	/dev/tap をオープンすると tap インタフェースが生えるので
//	ioctl(TAPGIFNAME) でインタフェース名を取得する。
//	/dev/tapN をオープンする方式も使える。

// コンストラクタ
NetDriverTap::NetDriverTap(HostDevice *hostdev_, const std::string& devpath_)
	: inherited(hostdev_, "tap")
{
	devpath = devpath_;
	if (devpath.empty()) {
		devpath = "auto";
	}
}

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

// ドライバ初期化
bool
NetDriverTap::InitDriver(bool startup)
{
	bool r;

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

	// デバイス名や tap の作り方は OS ごとに異なる。
	// もっと言えば、なんか適当にオープンしてほしいデフォルト挙動も全部違う。
	//
	// いずれの Open*() 関数も (必要なら) devpath に応じてデバイスをオープンし
	// 成功すれば fd にデバイスのディスクリプタ、ifname に作成した
	// インタフェース名をセットして true を返すこと。
	// 失敗すればデバイスをクローズして putmsg() を出力し false を返すこと。
	// warnx() での出力は行わない(この後他ドライバで成功するかも知れないので)。
	// その場合の ifname は不問。
#if defined(__linux__)
	// Linux はたぶん固定
	r = OpenLinux();
	if (r == false) {
		// 単独試行なのでここで終了
		return false;
	}

#elif defined(__OpenBSD__)
	if (devpath == "auto") {
		// 指定されてなければ、tapN を順に試す
		r = OpenNumbered();
		// FALLTHROUGH
	} else {
		// ご指名の場合
		r = OpenDirect();
		if (r == false) {
			// 単独試行なのでここで終了
			return false;
		}
	}

#else
	if (devpath == "auto") {
		// 指定されてなければ、tap、tapN の順で試すか
		r = OpenCloning();
		if (r == false) {
			r = OpenNumbered();
		}
		// FALLTHROUGH
	} else {
		// 指定されている場合 tap か tapN かで対応が違う
		if (devpath == "/dev/tap") {
			// tap ご指名の場合
			r = OpenCloning();
		} else {
			// それ以外でご指名の場合
			r = OpenDirect();
		}
		if (r == false) {
			// 単独試行なのでここで終了
			return false;
		}
	}

#endif
	if (r == false) {
		// 複数試した結果見付からない時だけこのメッセージを出したい
		errmsg = "no tap devices available";
		putmsg(1, "%s", errmsg.c_str());
		return false;
	}
	// ここで ifname に作成したインタフェース名が入っているはず
	putmsg(1, "opened %s for %s", devpath.c_str(), ifname.c_str());

	// 最後に ifup スクリプトがあれば実行
	if (!Run_ifup()) {
		// どこまで実行できたか分からないので念のため ifdown も呼ぶ
		Close();
		return false;
	}

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

	return true;
}

#if defined(__linux__)
// tap インタフェースを作成する。Linux の場合。
// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。
bool
NetDriverTap::OpenLinux()
{
	struct ifreq ifr;
	int r;

	// 指定がなければデフォルトパス
	if (devpath == "auto") {
		devpath = "/dev/net/tun";
		putmsg(1, "auto-selected devpath: \"%s\"", devpath.c_str());
	}

	// デバイスオープン
	fd = open(devpath.c_str(), O_RDWR);
	if (fd < 0) {
		errmsg = string_format("%s: %s", devpath.c_str(), strerror(errno));
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		return false;
	}

	// インタフェース作成。これには CAP_NET_ADMIN 権限が必要
	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;	// tap として使う
	strlcpy(ifr.ifr_name, "tap%d", sizeof(ifr.ifr_name));
	r = ioctl(fd, TUNSETIFF, (void *)&ifr);
	if (r < 0) {
		errmsg = string_format("%s: %s: %s",
			devpath.c_str(), "TUNSETIFF", strerror(errno));
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		if (errno == EPERM) {
			// Linux では setcap(8) が必要。分かりづらいのでメッセージを出すが
			// その例示より先に errmsg を表示したい。
			warnx("%s", errmsg.c_str());
			errmsg.clear();

			fprintf(stderr,
				" ** Set CAP_NET_ADMIN capability to the nono executable file."
				"\n"
				" **  ex) sudo setcap cap_net_admin=ep path/to/%s\n",
				getprogname());
		}
		fd.Close();
		return false;
	}

	// 作成できたインタフェース名を取得
	ifname = std::string(ifr.ifr_name);

	return true;
}
#endif // __linux__

// tap インタフェースを作成する。BSD で直接指定の場合。
// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。
// devpath がデバイス名で "/dev/tap1" なら tap1 インタフェースが出来るはず?
bool
NetDriverTap::OpenDirect()
{
	// devpath は "/dev/tapNN" という書式のはずなので、tapNN のところを
	// 取り出せばインタフェース名になるはず。それ以外の運用されたらシラン。
	auto pos = devpath.rfind("tap");
	if (pos == std::string::npos) {
		errmsg = string_format("%s: devpath must contain \"tap\"",
			devpath.c_str());
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		return false;
	}
	ifname = devpath.substr(pos);

	// tapN デバイスオープン
	fd = open(devpath.c_str(), O_RDWR);
	if (fd < 0) {
		errmsg = string_format("%s: %s", devpath.c_str(), strerror(errno));
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		return false;
	}

	return true;
}

// tap インタフェースを作成する。BSD で /dev/tapN を順に試す場合。
// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。
bool
NetDriverTap::OpenNumbered()
{
	// 最大値は適当
	for (int i = 0; i < 10; i++) {
		ifname = string_format("tap%d", i);
		devpath = "/dev/" + ifname;

		fd = open(devpath.c_str(), O_RDWR);
		if (fd < 0) {
			// ファイルがないのを表示したら結構うるさいので除く
			if (errno != ENOENT) {
				putmsg(1, "%s: %s: %s",
					__func__, devpath.c_str(), strerror(errno));
			}
			continue;
		}
		// オープンできたら終了
		return true;
	}

	// 見付からなかった
	if (fd < 0) {
		// ここでは何も表示しなくてもだいたい分かる
		errmsg = "no tap devices available";
		return false;
	}
	return true;
}

// tap インタフェースを作成する。NetBSD で /dev/tap による cloning の場合。
// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。
bool
NetDriverTap::OpenCloning()
{
#if defined(TAPGIFNAME)
	struct ifreq ifr;

	// /dev/tap 以外のデバイスファイル名に /dev/tap の major/minor 番号を
	// 割り当てることは合法だが、ここではデバイスファイル名でどの方式を
	// 使うかを選択しているので /dev/tap という名前以外の選択肢はない。
	devpath = "/dev/tap";
	putmsg(1, "%s: use fixed devpath: %s", __func__, devpath.c_str());

	// tap デバイスオープン
	fd = open(devpath.c_str(), O_RDWR);
	if (fd < 0) {
		errmsg = string_format("%s: %s", devpath.c_str(), strerror(errno));
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		return false;
	}

	// オープンされたこのディスクリプタのインタフェース名を取得。
	memset(&ifr, 0, sizeof(ifr));
	if (ioctl(fd, TAPGIFNAME, &ifr) < 0) {
		errmsg = string_format("%s: %s: %s",
			devpath.c_str(), "TAPGIFNAME", strerror(errno));
		putmsg(1, "%s: %s", __func__, errmsg.c_str());
		fd.Close();
		return false;
	}

	// 作成できたインタフェース名を取得
	ifname = std::string(ifr.ifr_name);

	return true;
#else
	errmsg = "TAPGIFNAME not compiled(?)";
	putmsg(1, "%s: %s", __func__, errmsg.c_str());
	return false;
#endif
}

// クローズ
// (private)
void
NetDriverTap::Close()
{
	if (fd.Valid()) {
		Run_ifdown();

		hostdev->DelOuter(fd);
		fd.Close();
	}
}

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

// パケットを送信する
void
NetDriverTap::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
NetDriverTap::Read(NetPacket *p)
{
	uint8 buf[1600];
	int n;

	n = read(fd, buf, sizeof(buf));
	if (n < 0) {
		putmsg(0, "read: %s", strerror(errno));
		return NODATA;
	}
	p->Assign(buf, n);
	return 0;
}

unix.superglobalmegacorp.com

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