|
|
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;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.