--- nono/host/netdriver_tap.cpp 2026/04/29 17:04:51 1.1.1.4 +++ nono/host/netdriver_tap.cpp 2026/04/29 17:05:47 1.1.1.8 @@ -4,19 +4,16 @@ // Licensed under nono-license.txt // +// +// ホストネットワークの tap ドライバ +// + #include "netdriver_tap.h" #include "autofd.h" -#include "cvprompt.h" +#include "hostnet.h" #include -#include #include #include -#if defined(HAVE_SYS_EVENT_H) -#include -#endif -#if defined(HAVE_KQUEUE_SYS_EVENT_H) -#include -#endif #if defined(__linux__) #include #include @@ -42,12 +39,13 @@ // /dev/tapN をオープンする方式も使える。 // コンストラクタ -NetDriverTap::NetDriverTap(EthernetDevice *parent_, - std::mutex *mtx_, std::condition_variable *cv_, - const std::string& devpath_) - : inherited("BSD tap", parent_, mtx_, cv_) +NetDriverTap::NetDriverTap(HostDevice *hostdev_, const std::string& devpath_) + : inherited(hostdev_, "tap") { devpath = devpath_; + if (devpath.empty()) { + devpath = "auto"; + } } // デストラクタ @@ -56,17 +54,14 @@ NetDriverTap::~NetDriverTap() Close(); } -// 初期化 & オープン +// ドライバ初期化 bool -NetDriverTap::Init() +NetDriverTap::InitDriver(bool startup) { bool r; - // 空文字列もデフォルトと同じ "auto" にする。 - if (devpath.empty()) { - devpath = "auto"; - } - putmsg(1, "NetDriverTap: device \"%s\"", devpath.c_str()); + putmsg(1, "trying tap..."); + putmsg(1, "argument: devpath=\"%s\"", devpath.c_str()); // デバイス名や tap の作り方は OS ごとに異なる。 // もっと言えば、なんか適当にオープンしてほしいデフォルト挙動も全部違う。 @@ -74,23 +69,29 @@ NetDriverTap::Init() // いずれの Open*() 関数も (必要なら) devpath に応じてデバイスをオープンし // 成功すれば fd にデバイスのディスクリプタ、ifname に作成した // インタフェース名をセットして true を返すこと。 - // 失敗すればデバイスをクローズしてエラーメッセージを表示し false を返す - // こと。その場合の ifname は不問。 + // 失敗すればデバイスをクローズして putmsg() を出力し false を返すこと。 + // warnx() での出力は行わない(この後他ドライバで成功するかも知れないので)。 + // その場合の ifname は不問。 #if defined(__linux__) // Linux はたぶん固定 r = OpenLinux(); + if (r == false) { + // 単独試行なのでここで終了 + return false; + } #elif defined(__OpenBSD__) if (devpath == "auto") { // 指定されてなければ、tapN を順に試す r = OpenNumbered(); - if (r == false) { - warnx("NetDriverTap.OpenNumbered: %s", errmsg.c_str()); - } + // FALLTHROUGH } else { // ご指名の場合 r = OpenDirect(); - // warnx は出力済み + if (r == false) { + // 単独試行なのでここで終了 + return false; + } } #else @@ -99,39 +100,32 @@ NetDriverTap::Init() r = OpenCloning(); if (r == false) { r = OpenNumbered(); - if (r == false) { - warnx("NetDriverTap.Open: %s", errmsg.c_str()); - } } + // FALLTHROUGH } else { // 指定されている場合 tap か tapN かで対応が違う if (devpath == "/dev/tap") { // tap ご指名の場合 r = OpenCloning(); - if (r == false) { - warnx("NetDriverTap.OpenCloning: %s", errmsg.c_str()); - } } else { // それ以外でご指名の場合 r = OpenDirect(); - // warnx は出力済み + } + 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, "ifname: %s", ifname.c_str()); - - // オープンできたので受信スレッド起動。 - if (InitRecvThread() == false) { - // ifup 実行前なのでまだ ifdown 不要。 - CloseFd(); - return false; - } + putmsg(1, "opened %s for %s", devpath.c_str(), ifname.c_str()); // 最後に ifup スクリプトがあれば実行 if (!Run_ifup()) { @@ -140,12 +134,17 @@ NetDriverTap::Init() 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 の場合。 -// 他の Open*() とは混在しないので、ここで直接エラーメッセージを出力する。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 bool NetDriverTap::OpenLinux() { @@ -155,12 +154,14 @@ NetDriverTap::OpenLinux() // 指定がなければデフォルトパス 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) { - warn("NetDriverTap.OpenLinux: %s", devpath.c_str()); + errmsg = string_format("%s: %s", devpath.c_str(), strerror(errno)); + putmsg(1, "%s: %s", __func__, errmsg.c_str()); return false; } @@ -169,15 +170,22 @@ NetDriverTap::OpenLinux() strlcpy(ifr.ifr_name, "tap%d", sizeof(ifr.ifr_name)); r = ioctl(fd, TUNSETIFF, (void *)&ifr); if (r < 0) { - warn("NetDriverTap.OpenLinux: %s: TUNSETIFF", devpath.c_str()); + 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()); } - CloseFd(); + fd.Close(); return false; } @@ -189,7 +197,7 @@ NetDriverTap::OpenLinux() #endif // __linux__ // tap インタフェースを作成する。BSD で直接指定の場合。 -// 他の Open*() とは混在しないので、ここで直接エラーメッセージを出力する。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 // devpath がデバイス名で "/dev/tap1" なら tap1 インタフェースが出来るはず? bool NetDriverTap::OpenDirect() @@ -198,8 +206,9 @@ NetDriverTap::OpenDirect() // 取り出せばインタフェース名になるはず。それ以外の運用されたらシラン。 auto pos = devpath.rfind("tap"); if (pos == std::string::npos) { - warnx("NetDriverTap.OpenDirect: %s: device name must contain \"tap\"", + 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); @@ -207,7 +216,8 @@ NetDriverTap::OpenDirect() // tapN デバイスオープン fd = open(devpath.c_str(), O_RDWR); if (fd < 0) { - warn("NetDriverTap.OpenDirect: %s", devpath.c_str()); + errmsg = string_format("%s: %s", devpath.c_str(), strerror(errno)); + putmsg(1, "%s: %s", __func__, errmsg.c_str()); return false; } @@ -215,8 +225,7 @@ NetDriverTap::OpenDirect() } // tap インタフェースを作成する。BSD で /dev/tapN を順に試す場合。 -// エラーの場合はログには直ちに出力して、warn() で表示するメッセージを -// errmsg にセットして返す。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 bool NetDriverTap::OpenNumbered() { @@ -235,22 +244,20 @@ NetDriverTap::OpenNumbered() continue; } // オープンできたら終了 - putmsg(1, "%s: %s: found", __func__, devpath.c_str()); return true; } // 見付からなかった if (fd < 0) { + // ここでは何も表示しなくてもだいたい分かる errmsg = "no tap devices available"; - putmsg(1, "%s: %s", __func__, errmsg.c_str()); return false; } - return false; + return true; } // tap インタフェースを作成する。NetBSD で /dev/tap による cloning の場合。 -// エラーの場合はログには直ちに出力して、warn() で表示するメッセージを -// errmsg にセットして返す。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 bool NetDriverTap::OpenCloning() { @@ -261,6 +268,7 @@ NetDriverTap::OpenCloning() // 割り当てることは合法だが、ここではデバイスファイル名でどの方式を // 使うかを選択しているので /dev/tap という名前以外の選択肢はない。 devpath = "/dev/tap"; + putmsg(1, "%s: use fixed devpath: %s", __func__, devpath.c_str()); // tap デバイスオープン fd = open(devpath.c_str(), O_RDWR); @@ -273,10 +281,10 @@ NetDriverTap::OpenCloning() // オープンされたこのディスクリプタのインタフェース名を取得。 memset(&ifr, 0, sizeof(ifr)); if (ioctl(fd, TAPGIFNAME, &ifr) < 0) { - errmsg = string_format("%s: TAPGIFNAME: %s", - devpath.c_str(), strerror(errno)); + errmsg = string_format("%s: %s: %s", + devpath.c_str(), "TAPGIFNAME", strerror(errno)); putmsg(1, "%s: %s", __func__, errmsg.c_str()); - CloseFd(); + fd.Close(); return false; } @@ -291,122 +299,55 @@ NetDriverTap::OpenCloning() #endif } -// fd のクローズ -void -NetDriverTap::CloseFd() -{ - close(fd); - fd = -1; -} - // クローズ // (private) void NetDriverTap::Close() { - if (IsOpen()) { + if (fd.Valid()) { Run_ifdown(); - CloseFd(); + hostdev->DelOuter(fd); + fd.Close(); } } -// パケットを送信する -bool -NetDriverTap::SendPacket(const std::vector& data) +// モニタ (ドライバ依存情報のみ) +void +NetDriverTap::MonitorUpdateMD(TextScreen& screen, int y) { - int n; - - assert(IsOpen()); - - putlog(2, "SendPacket %zd bytes", data.size()); - tx_pkts++; - tx_bytes += data.size(); - - n = write(fd, data.data(), data.size()); - if (n == -1) { - warn("SendPacket: write failed: fd=%d datalen=%zu", - fd, data.size()); - return false; - } - - if (n < (int)data.size()) { - return false; - } - return true; + screen.Print(0, y++, "Device : %s", devpath.c_str()); + screen.Print(0, y++, "Interface: %s", ifname.c_str()); } -// 受信スレッド +// パケットを送信する void -NetDriverTap::ThreadRun() +NetDriverTap::Write(const void *buf, int buflen) { - struct kevent kev; - autofd kq; - int r; + ssize_t n; - kq = kqueue(); - if (kq == -1) { - warn("kqueue"); + n = write(fd, buf, buflen); + if (n < 0) { + putmsg(0, "write: %s", strerror(errno)); return; } - - EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, (kevent_udata_t)0); - r = kevent_set(kq, &kev, 1); - if (r == -1) { - warn("kevent_set(fd=%d)", fd); + if (n < buflen) { + putmsg(0, "write: short"); return; } +} - for (;;) { - uint8 buf[1600]; - int n; - - // 何か起きるまで待つ - r = kevent_poll(kq, &kev, 1, NULL); - if (r == -1) { - if (errno == EINTR) { - continue; - } - // XXX ? - warn("kevent_poll"); - break; - } - - // 自動的にデバイスに着信があったことになるはず - assert(r > 0); - - n = read(fd, buf, sizeof(buf)); - if (n == -1) { - if (errno == EINTR) { - continue; - } - warn("read"); - break; - } - if (n == 0) { // EOF - return; - } - - // カーネルが tap(4) に直接書き込んだパケット(というかフレームか)は - // 60バイトパディングされないまま読み出せるので、ここでパディングする。 - if (n < 60) { - memset(buf + n, 0, 60 - n); - n = 60; - } - - // CRC XXX 計算すること - memset(buf + n, 0, 4); - n += 4; - - // 受信処理に引き渡す - // ただしデバッガプロンプトで停止してる状態なら VM に干渉しない。 - if (parent->RecvEnabled && gCVPrompt->IsPrompt() == false) { - putlog(2, "RecvPacket %d bytes", n); - rx_pkts++; - rx_bytes += n; +// パケットを受信する +int +NetDriverTap::Read(NetPacket *p) +{ + int n; - qvector recvbuf(buf, buf + n); - parent->RecvPacket(recvbuf); - } + n = read(fd, p->data(), p->size()); + if (n < 0) { + putmsg(0, "read: %s", strerror(errno)); + return NODATA; } + p->length = n; + return 0; }