--- nono/host/netdriver_tap.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/host/netdriver_tap.cpp 2026/04/29 17:05:11 1.1.1.6 @@ -1,24 +1,24 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt +// + +// +// ホストネットワークの tap ドライバ // -#include "header.h" #include "netdriver_tap.h" -#include "cvprompt.h" +#include "autofd.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 +#elif defined(__OpenBSD__) +#include #else #include #include @@ -26,16 +26,26 @@ #include #endif +// Linux +// /dev/net/tun をオープンし、ioctl(TUNSETIFF) を発行すると tap +// インタフェースが生えて、そのインタフェース名が返ってくる。 +// +// OpenBSD +// /dev/tapN をオープンする。インタフェース名は対応する tapN のはず。 +// +// NetBSD (dynamic) +// /dev/tap をオープンすると tap インタフェースが生えるので +// ioctl(TAPGIFNAME) でインタフェース名を取得する。 +// /dev/tapN をオープンする方式も使える。 + // コンストラクタ -NetDriverTap::NetDriverTap(EthernetDevice *p, - std::mutex *m, std::condition_variable *c) - : inherited("BSD tap", p, m, c) +NetDriverTap::NetDriverTap(HostDevice *hostdev_, const std::string& devpath_) + : inherited(hostdev_, "Tap") { -#if defined(__linux__) - devpath = "/dev/net/tun"; -#else - devpath = "/dev/tap"; -#endif + devpath = devpath_; + if (devpath.empty()) { + devpath = "auto"; + } } // デストラクタ @@ -44,184 +54,300 @@ NetDriverTap::~NetDriverTap() Close(); } -// 初期化 & オープン +// ドライバ初期化 +bool +NetDriverTap::InitDriver() +{ + 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::Init() +NetDriverTap::OpenLinux() { struct ifreq ifr; int r; - // tap デバイスオープン + // 指定がなければデフォルトパス + 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 == -1) { - warn("open \"%s\" failed", devpath.c_str()); + 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 defined(__linux__) - // Linux なら、open() したデバイスに対して tap デバイス作成を指示、 - // その応答にインタフェース名が入っている。 + // インタフェース作成。これには CAP_NET_ADMIN 権限が必要 ifr.ifr_flags = IFF_TAP | IFF_NO_PI; // tap として使う strlcpy(ifr.ifr_name, "tap%d", sizeof(ifr.ifr_name)); - // インタフェース作成。これには CAP_NET_ADMIN 権限が必要 r = ioctl(fd, TUNSETIFF, (void *)&ifr); - if (r == -1) { - warn("Open: TUNSETIFF failed"); + 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 execute file.\n" + " ** Set CAP_NET_ADMIN capability to the nono executable file." + "\n" " ** ex) sudo setcap cap_net_admin=ep path/to/%s\n", getprogname()); } - goto abort; - } -#else - // NetBSD なら、open() でインタフェースは作成されているので、 - // オープンされたこのディスクリプタのインタフェース名を取得。 - r = ioctl(fd, TAPGIFNAME, &ifr); - if (r == -1) { - warn("Open: TAPGIFNAME failed"); - goto abort; + fd.Close(); + return false; } -#endif // 作成できたインタフェース名を取得 ifname = std::string(ifr.ifr_name); - // オープンできたので受信スレッド起動。 - // ifup 実行前なので、ここで失敗しても ifdown 不要。 - if (InitRecvThread() == false) { - goto abort; + 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); - // 最後に ifup スクリプトがあれば実行 - if (!Run_ifup()) { - // どこまで実行できたか分からないので念のため ifdown も呼ぶ - Close(); + // 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; - - // ifdown を実行させないために fd = -1 にして帰る。 - // (まだ ifup を実行していないので) - abort: - close(fd); - fd = -1; - return false; } -// クローズ -// (private) -void -NetDriverTap::Close() +// tap インタフェースを作成する。BSD で /dev/tapN を順に試す場合。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 +bool +NetDriverTap::OpenNumbered() { - if (IsOpen()) { - Run_ifdown(); + // 最大値は適当 + 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; + } - close(fd); - fd = -1; + // 見付からなかった + if (fd < 0) { + // ここでは何も表示しなくてもだいたい分かる + errmsg = "no tap devices available"; + return false; } + return true; } -// パケットを送信する +// tap インタフェースを作成する。NetBSD で /dev/tap による cloning の場合。 +// エラーの場合は putmsg ログを出力し、errmsg をセットして false を返す。 bool -NetDriverTap::SendPacket(const std::vector& data) +NetDriverTap::OpenCloning() { - int n; - - assert(IsOpen()); +#if defined(TAPGIFNAME) + struct ifreq ifr; - putlog(2, "SendPacket %zd bytes", data.size()); - tx_pkts++; - tx_bytes += data.size(); + // /dev/tap 以外のデバイスファイル名に /dev/tap の major/minor 番号を + // 割り当てることは合法だが、ここではデバイスファイル名でどの方式を + // 使うかを選択しているので /dev/tap という名前以外の選択肢はない。 + devpath = "/dev/tap"; + putmsg(1, "%s: use fixed devpath: %s", __func__, devpath.c_str()); - n = write(fd, data.data(), data.size()); - if (n == -1) { - warn("SendPacket: write failed: fd=%d datalen=%zu", - fd, data.size()); + // 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; } - if (n < (int)data.size()) { + // オープンされたこのディスクリプタのインタフェース名を取得。 + 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::ThreadRun() +NetDriverTap::Close() { - struct kevent kev; - int kq; - int r; - - kq = kqueue(); - if (kq == -1) { - warn("kqueue failed"); - return; - } + if (fd.Valid()) { + Run_ifdown(); - 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) failed", fd); - close(kq); - return; + hostdev->DelOuter(fd); + fd.Close(); } +} - for (;;) { - uint8 buf[1600]; - int n; - - // 何か起きるまで待つ - r = kevent_poll(kq, &kev, 1, NULL); - if (r == -1) { - if (errno == EINTR) { - continue; - } - // XXX ? - warn("kevent_poll failed"); - break; - } - - // 自動的にデバイスに着信があったことになるはず - assert(r > 0); - - n = read(fd, buf, sizeof(buf)); - if (n == -1) { - if (errno == EINTR) { - continue; - } - warn("read(fd=%d) failed", fd); - break; - } - if (n == 0) { // EOF - return; - } +// モニタ (ドライバ依存情報のみ) +void +NetDriverTap::MonitorUpdateMD(TextScreen& screen) +{ + screen.Print(0, 1, "Device : %s", devpath.c_str()); + screen.Print(0, 2, "Interface: %s", ifname.c_str()); +} - // カーネルが tap(4) に直接書き込んだパケット(というかフレームか)は - // 60バイトパディングされないまま読み出せるので、ここでパディングする。 - if (n < 60) { - memset(buf + n, 0, 60 - n); - n = 60; - } +// パケットを送信する +void +NetDriverTap::Write(const void *buf, int buflen) +{ + ssize_t n; - // CRC XXX 計算すること - memset(buf + n, 0, 4); - n += 4; + n = write(fd, buf, buflen); + if (n < 0) { + putmsg(0, "write: %s", strerror(errno)); + return; + } + if (n < buflen) { + putmsg(0, "write: short"); + return; + } +} - // 受信処理に引き渡す - // ただしデバッガプロンプトで停止してる状態なら 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; }