--- nono/host/netdriver_tap.cpp 2026/04/29 17:04:28 1.1 +++ nono/host/netdriver_tap.cpp 2026/04/29 17:04:51 1.1.1.4 @@ -1,10 +1,11 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "header.h" #include "netdriver_tap.h" +#include "autofd.h" #include "cvprompt.h" #include #include @@ -19,6 +20,8 @@ #if defined(__linux__) #include #include +#elif defined(__OpenBSD__) +#include #else #include #include @@ -26,16 +29,25 @@ #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(EthernetDevice *parent_, + std::mutex *mtx_, std::condition_variable *cv_, + const std::string& devpath_) + : inherited("BSD tap", parent_, mtx_, cv_) { -#if defined(__linux__) - devpath = "/dev/net/tun"; -#else - devpath = "/dev/tap"; -#endif + devpath = devpath_; } // デストラクタ @@ -48,68 +60,243 @@ NetDriverTap::~NetDriverTap() bool NetDriverTap::Init() { + bool r; + + // 空文字列もデフォルトと同じ "auto" にする。 + if (devpath.empty()) { + devpath = "auto"; + } + putmsg(1, "NetDriverTap: device \"%s\"", devpath.c_str()); + + // デバイス名や tap の作り方は OS ごとに異なる。 + // もっと言えば、なんか適当にオープンしてほしいデフォルト挙動も全部違う。 + // + // いずれの Open*() 関数も (必要なら) devpath に応じてデバイスをオープンし + // 成功すれば fd にデバイスのディスクリプタ、ifname に作成した + // インタフェース名をセットして true を返すこと。 + // 失敗すればデバイスをクローズしてエラーメッセージを表示し false を返す + // こと。その場合の ifname は不問。 +#if defined(__linux__) + // Linux はたぶん固定 + r = OpenLinux(); + +#elif defined(__OpenBSD__) + if (devpath == "auto") { + // 指定されてなければ、tapN を順に試す + r = OpenNumbered(); + if (r == false) { + warnx("NetDriverTap.OpenNumbered: %s", errmsg.c_str()); + } + } else { + // ご指名の場合 + r = OpenDirect(); + // warnx は出力済み + } + +#else + if (devpath == "auto") { + // 指定されてなければ、tap、tapN の順で試すか + r = OpenCloning(); + if (r == false) { + r = OpenNumbered(); + if (r == false) { + warnx("NetDriverTap.Open: %s", errmsg.c_str()); + } + } + } else { + // 指定されている場合 tap か tapN かで対応が違う + if (devpath == "/dev/tap") { + // tap ご指名の場合 + r = OpenCloning(); + if (r == false) { + warnx("NetDriverTap.OpenCloning: %s", errmsg.c_str()); + } + } else { + // それ以外でご指名の場合 + r = OpenDirect(); + // warnx は出力済み + } + } + +#endif + if (r == false) { + // エラーメッセージなどは各呼び出し先で処理済み + return false; + } + // ここで ifname に作成したインタフェース名が入っているはず + putmsg(1, "ifname: %s", ifname.c_str()); + + // オープンできたので受信スレッド起動。 + if (InitRecvThread() == false) { + // ifup 実行前なのでまだ ifdown 不要。 + CloseFd(); + return false; + } + + // 最後に ifup スクリプトがあれば実行 + if (!Run_ifup()) { + // どこまで実行できたか分からないので念のため ifdown も呼ぶ + Close(); + return false; + } + + return true; +} + +#if defined(__linux__) +// tap インタフェースを作成する。Linux の場合。 +// 他の Open*() とは混在しないので、ここで直接エラーメッセージを出力する。 +bool +NetDriverTap::OpenLinux() +{ struct ifreq ifr; int r; - // tap デバイスオープン + // 指定がなければデフォルトパス + if (devpath == "auto") { + devpath = "/dev/net/tun"; + } + + // デバイスオープン fd = open(devpath.c_str(), O_RDWR); - if (fd == -1) { - warn("open \"%s\" failed", devpath.c_str()); + if (fd < 0) { + warn("NetDriverTap.OpenLinux: %s", devpath.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) { + warn("NetDriverTap.OpenLinux: %s: TUNSETIFF", devpath.c_str()); if (errno == EPERM) { 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; + CloseFd(); + return false; } -#endif // 作成できたインタフェース名を取得 ifname = std::string(ifr.ifr_name); - // オープンできたので受信スレッド起動。 - // ifup 実行前なので、ここで失敗しても ifdown 不要。 - if (InitRecvThread() == false) { - goto abort; + return true; +} +#endif // __linux__ + +// tap インタフェースを作成する。BSD で直接指定の場合。 +// 他の Open*() とは混在しないので、ここで直接エラーメッセージを出力する。 +// devpath がデバイス名で "/dev/tap1" なら tap1 インタフェースが出来るはず? +bool +NetDriverTap::OpenDirect() +{ + // devpath は "/dev/tapNN" という書式のはずなので、tapNN のところを + // 取り出せばインタフェース名になるはず。それ以外の運用されたらシラン。 + auto pos = devpath.rfind("tap"); + if (pos == std::string::npos) { + warnx("NetDriverTap.OpenDirect: %s: device name must contain \"tap\"", + devpath.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) { + warn("NetDriverTap.OpenDirect: %s", devpath.c_str()); return false; } return true; +} + +// tap インタフェースを作成する。BSD で /dev/tapN を順に試す場合。 +// エラーの場合はログには直ちに出力して、warn() で表示するメッセージを +// errmsg にセットして返す。 +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; + } + // オープンできたら終了 + putmsg(1, "%s: %s: found", __func__, devpath.c_str()); + return true; + } - // ifdown を実行させないために fd = -1 にして帰る。 - // (まだ ifup を実行していないので) - abort: + // 見付からなかった + if (fd < 0) { + errmsg = "no tap devices available"; + putmsg(1, "%s: %s", __func__, errmsg.c_str()); + return false; + } + return false; +} + +// tap インタフェースを作成する。NetBSD で /dev/tap による cloning の場合。 +// エラーの場合はログには直ちに出力して、warn() で表示するメッセージを +// errmsg にセットして返す。 +bool +NetDriverTap::OpenCloning() +{ +#if defined(TAPGIFNAME) + struct ifreq ifr; + + // /dev/tap 以外のデバイスファイル名に /dev/tap の major/minor 番号を + // 割り当てることは合法だが、ここではデバイスファイル名でどの方式を + // 使うかを選択しているので /dev/tap という名前以外の選択肢はない。 + devpath = "/dev/tap"; + + // 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: TAPGIFNAME: %s", + devpath.c_str(), strerror(errno)); + putmsg(1, "%s: %s", __func__, errmsg.c_str()); + CloseFd(); + 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 +} + +// fd のクローズ +void +NetDriverTap::CloseFd() +{ close(fd); fd = -1; - return false; } // クローズ @@ -120,8 +307,7 @@ NetDriverTap::Close() if (IsOpen()) { Run_ifdown(); - close(fd); - fd = -1; + CloseFd(); } } @@ -155,20 +341,19 @@ void NetDriverTap::ThreadRun() { struct kevent kev; - int kq; + autofd kq; int r; kq = kqueue(); if (kq == -1) { - warn("kqueue failed"); + warn("kqueue"); 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) failed", fd); - close(kq); + warn("kevent_set(fd=%d)", fd); return; } @@ -183,7 +368,7 @@ NetDriverTap::ThreadRun() continue; } // XXX ? - warn("kevent_poll failed"); + warn("kevent_poll"); break; } @@ -195,7 +380,7 @@ NetDriverTap::ThreadRun() if (errno == EINTR) { continue; } - warn("read(fd=%d) failed", fd); + warn("read"); break; } if (n == 0) { // EOF