|
|
nono 1.7.0
//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//
//
// ホストネットワークの usermode (SLIRP) ドライバ
//
//
// NetDriverSlirp SlirpThread
// (HostNet スレッド) : (裏スレッド)
//
// 送信時のフロー:
// NetDriverSlirp() は送信パケットをパイプ txfd (=txfd_w) に書き込む。
// SlirpThread 側で txfd_r に着信するので取り出して slirp_input() へ送る。
//
// NetDriverSlirp::Write() :
// |
// | txfd(=txfd_w) txfd_r
// +--------------->| pipe |------+ … パイプで裏スレッドに送信
// | |
// <-+ : v
// --- kevent @ ThreadRun()
// : |
// SlirpThread::Write()
// : |
// +-----> slirp_input()
//
// 受信時のフロー:
// SlirpThread 側のイベントループにて、管理する受信ディスクリプタに
// 着信があれば SlirpThread::Read() がそれを読み出してパイプ rxfd_w に
// 書き込む。
// このパイプの対向は HostNetDevice に AddOuter() で登録したものなので
// この書き込みに対する着信で HostDevice::Dispatch() が動き、
// その結果 NetDriverSlirp() が呼ばれる。
// NetDriverSlirp() はこのパイプからパケットを読み出して親に引き渡す。
//
// --- kevent @ ThreadRun()
// |
// SlirpThread::Read()
// |
// rxfd(=rxfd_r) rxfd_w|
// +---------------| pipe |<-----+
// | 着信通知 ‖
// v ‖
// --- kqueue ‖
// | ‖
// HostNetDevice::Read() ‖
// | ‖
// NetDriverSlirp::Read() ‖
// | ‖
// パイプから受信 <==++
// |
// <--+ VM 側へ
#include "netdriver_slirp.h"
#include "hostnet.h"
#include "scheduler.h"
#include <sys/socket.h>
#include <sys/uio.h>
#include <slirp/libslirp.h>
//
// NetDriver 側スレッド
//
// コンストラクタ
NetDriverSlirp::NetDriverSlirp(HostDevice *hostdev_)
: inherited(hostdev_, "usermode")
{
txd = -1;
rxd = -1;
}
// デストラクタ
NetDriverSlirp::~NetDriverSlirp()
{
Close();
}
// ログレベルを設定。
void
NetDriverSlirp::SetLogLevel(int loglevel_)
{
inherited::SetLogLevel(loglevel_);
// バックエンドにも伝達。
if ((bool)backend) {
backend->SetLogLevel(loglevel_);
}
}
// ドライバ初期化
bool
NetDriverSlirp::InitDriver(bool startup)
{
putmsg(1, "trying usermode...");
if (slirp_occupied) {
errmsg = "'usermode' is already in use by another hostnet-driver";
putmsg(1, "%s", errmsg.c_str());
return false;
}
try {
backend.reset(new SlirpThread(this));
} catch (...) { }
if ((bool)backend == false) {
errmsg = string_format("Failed to initialize SlirpThread at %s",
__method__);
putmsg(1, "%s", errmsg.c_str());
return false;
}
// ディスクリプタは裏スレッドが所有しているのでこちらでは解放しないこと。
const std::string key = hostdev->GetConfigKey();
if (backend->InitBackend(key, &rxd, &txd) == false) {
errmsg = backend->errmsg;
putmsg(1, "%s", errmsg.c_str());
return false;
}
// 読み込み側を Slirp からの受信端として親に登録。
if (hostdev->AddOuter(rxd) < 0) {
errmsg = string_format("AddOuter(rxd=%d): %s", rxd, strerror(errno));
putmsg(1, "%s", errmsg.c_str());
return false;
}
// 起動時は一斉に StartThread() されて backend のスレッドも起動するが、
// 再選択時はここで手動でスレッドを起動する必要がある。
if (startup == false) {
backend->StartThread();
}
slirp_occupied = true;
putmsg(1, "opened");
return true;
}
// クローズ
void
NetDriverSlirp::Close()
{
// rxd がすでに閉じられていたらとかはここでは関係なくて、
// この番号を kqueue から外す必要がある。(Linux)
if (rxd >= 0) {
hostdev->DelOuter(rxd);
}
// txd, rxd は裏スレッドの所有なのでこちらでは何もしない。
if ((bool)backend) {
backend->Terminate();
backend->Close();
backend.reset();
}
slirp_occupied = false;
}
// モニタ (ドライバ依存情報のみ)
void
NetDriverSlirp::MonitorScreenMD(TextScreen& screen, int y)
{
// なし
}
// パケットを送信する。
// と言っても裏スレッドに送り直すだけ。
void
NetDriverSlirp::Write(const void *buf, int buflen)
{
struct iovec iov[2];
ssize_t n;
// 先頭4バイトに後続パケット長を入れておく。
static_assert(sizeof(buflen) == sizeof(uint32), "");
iov[0].iov_base = &buflen;
iov[0].iov_len = sizeof(buflen);
iov[1].iov_base = const_cast<void *>(buf);
iov[1].iov_len = buflen;
n = writev(txd, &iov[0], countof(iov));
if (n < 0) {
putmsg(0, "writev(%zu+%d) failed: %s",
sizeof(buflen), buflen, strerror(errno));
return;
}
if (n < sizeof(buflen) + buflen) {
putmsg(0, "writev(%zu+%d): short(%zd)", sizeof(buflen), buflen, n);
return;
}
}
// パケットを受信する。
// これは rxd に着信があってから HostDevice::Dispatch() から呼ばれるほう。
int
NetDriverSlirp::Read(NetPacket *p)
{
std::vector<uint8> databuf;
ssize_t r;
ssize_t len;
ssize_t bufsize;
uint8 *buf;
uint32 datalen;
// 先頭4バイトに後続パケット長を入れてある。
buf = reinterpret_cast<uint8 *>(&datalen);
bufsize = sizeof(datalen);
for (len = 0; len < bufsize; ) {
r = read(rxd, buf + len, bufsize - len);
if (r < 0) {
putmsg(0, "read(len) failed: %s", strerror(errno));
goto nodata;
}
if (r == 0) {
putmsg(0, "read(len) failed: EOF?");
goto nodata;
}
len += r;
}
// データ本体。
databuf.resize(datalen);
len = 0;
for (auto datasize = databuf.size(); len < datasize; ) {
r = read(rxd, databuf.data() + len, datasize - len);
if (r < 0) {
putmsg(0, "read(data) failed: %s", strerror(errno));
goto nodata;
}
if (r == 0) {
putmsg(0, "read(data) failed: EOF?");
goto nodata;
}
len += r;
}
p->Assign(databuf.data(), len);
return 0;
nodata:
// 一旦全部読み捨てるくらいしか。
std::vector<uint8> dropbuf(1024);
for (;;) {
r = read(rxd, dropbuf.data(), dropbuf.size());
if (r <= 0) {
break;
}
}
return NODATA;
}
// usermode を使えるのは同時に1人だけに限定する。
/*static*/ bool NetDriverSlirp::slirp_occupied = false;
//
// 裏スレッド
//
// おそらく同じはずなので無駄な変換はしない。
#if SLIRP_POLL_IN == POLLIN && \
SLIRP_POLL_OUT == POLLOUT && \
SLIRP_POLL_PRI == POLLPRI && \
SLIRP_POLL_ERR == POLLERR && \
SLIRP_POLL_HUP == POLLHUP
#define POLL_EVENT_to_SLIRP_EVENT(x) (x)
#define SLIRP_EVENT_to_POLL_EVENT(x) (x)
#else
static int POLL_EVENT_to_SLIRP_EVENT(int pollevents)
{
int slevents = 0;
if ((pollevents & POLLIN)) slevents |= SLIRP_POLL_IN;
if ((pollevents & POLLOUT)) slevents |= SLIRP_POLL_OUT;
if ((pollevents & POLLPRI)) slevents |= SLIRP_POLL_PRI;
if ((pollevents & POLLERR)) slevents |= SLIRP_POLL_ERR;
if ((pollevents & POLLHUP)) slevents |= SLIRP_POLL_HUP;
return slevents;
}
static int SLIRP_EVENT_to_POLL_EVENT(int slevents)
{
int pollevents = 0;
if ((slevents & SLIRP_POLL_IN)) pollevents |= POLLIN;
if ((slevents & SLIRP_POLL_OUT)) pollevents |= POLLOUT;
if ((slevents & SLIRP_POLL_PRI)) pollevents |= POLLPRI;
if ((slevents & SLIRP_POLL_ERR)) pollevents |= POLLERR;
if ((slevents & SLIRP_POLL_HUP)) pollevents |= POLLHUP;
return pollevents;
}
#endif
// タイマーは TimerNewCB() コールバックで確保して返す。
// 確保した領域そのものはライブラリが管理し、TimerFreeCB() コールバックで
// 解放する。
// 一方 timerlist は有効なタイマーのリストなので、期限が来たらこのリスト
// からは削除してよい。
class SlirpTimer
{
public:
SlirpTimer(SlirpTimerCb func_, void *arg_)
{
func = func_;
arg = arg_;
}
SlirpTimerCb func {};
void *arg {};
int64 expire_msec {}; // 期限の(仮想?)絶対時刻 [msec]
int num {}; // デバッグ用のタイマー番号。
};
static ssize_t send_packet_cb(const void *, size_t, void *);
static void guest_error_cb(const char *msg, void *);
static int64 clock_get_ns_cb(void *);
static void *timer_new_cb(SlirpTimerCb cb, void *cb_arg, void *);
static void timer_free_cb(void *timer, void *);
static void timer_mod_cb(void *timer, int64 expire_msec, void *);
static void register_poll_fd_cb(int fd, void *);
static void unregister_poll_fd_cb(int fd, void *);
static void notify_cb(void *);
static int add_poll_cb(int fd, int events, void *);
static int get_revents_cb(int idx, void *);
static SlirpCb slirp_callbacks = {
.send_packet = send_packet_cb,
.guest_error = guest_error_cb,
.clock_get_ns = clock_get_ns_cb,
.timer_new = timer_new_cb,
.timer_free = timer_free_cb,
.timer_mod = timer_mod_cb,
.register_poll_fd = register_poll_fd_cb,
.unregister_poll_fd = unregister_poll_fd_cb,
.notify = notify_cb,
};
// コンストラクタ
SlirpThread::SlirpThread(NetDriverSlirp *parent_)
: inherited(OBJ_NONE)
{
parent = parent_;
hostnet = dynamic_cast<HostNetDevice *>(parent->GetHostDev());
SetName(parent->GetName());
SetThreadName("SlirpThread");
// コンストラクト後ただちにログ出力できるようここで一度追従しておく。
// 以降は親 (NetDriver側) の SetLogLevel() で変更する。
loglevel = parent->loglevel;
}
// デストラクタ
SlirpThread::~SlirpThread()
{
TerminateThread();
Close();
}
// 初期化。
// Device::Init() とは違い、NetDriver::InitDriver() から呼ばれる独自版。
// (表スレッドから見た裏スレッドへの) 受信側(RX)と送信側(TX)の
// ディスクリプタを書き戻す。
// 成功すれば true を返す。
// 失敗すれば (こちらのメンバ変数の) errmsg をセットして false を返す。
// これはスレッド起動前に (VM スレッドで) 呼ばれる。
bool
SlirpThread::InitBackend(const std::string& key, int *rxdp, int *txdp)
{
SlirpConfig config;
struct in_addr vnetwork;
struct in_addr vnetmask;
struct in6_addr vprefix_addr6;
uint vprefix_len;
int fds[2];
const ConfigItem& netitem = gConfig->Find(key + "-usermode-net");
if (ParseConfigNet(netitem, &vnetwork, &vnetmask) == false) {
return false;
}
const ConfigItem& net6item = gConfig->Find(key + "-usermode-net6");
if (ParseConfigNet6(net6item, &vprefix_addr6, &vprefix_len) == false) {
return false;
}
// hostnet-usermode-hostfwd=<entry1>[;<entry2>[;<entry3>...]]
const ConfigItem& fwditem = gConfig->Find(key + "-usermode-hostfwd");
auto entries = string_split(fwditem.AsString(), ';');
if (entries.size() == 1) {
std::string entry = string_trim(entries[0]);
if (ParseConfigHostFwdEntry(fwditem, entry, "") == false) {
fwdinfo.clear();
return false;
}
} else {
for (const auto& e : entries) {
std::string entry = string_trim(e);
// エントリが複数ある時はそのうちのどこで起きたか表示したい。
std::string where = " at \"" + entry + "\"";
if (ParseConfigHostFwdEntry(fwditem, entry, where) == false) {
fwdinfo.clear();
return false;
}
}
}
// Slirp を初期化。
memset(&config, 0, sizeof(config));
config.version = 4;
config.in_enabled = true;
config.vnetwork = vnetwork;
config.vnetmask = vnetmask;
config.vhost.s_addr = htonl(ntohl(vnetwork.s_addr) + 0x01);
config.vnameserver.s_addr = htonl(ntohl(vnetwork.s_addr) + 0x02);
config.vdhcp_start.s_addr = htonl(ntohl(vnetwork.s_addr) + 0x0a);
config.in6_enabled = true;
config.vprefix_addr6 = vprefix_addr6;
config.vprefix_len = vprefix_len;
config.vhost6 = vprefix_addr6;
config.vhost6.s6_addr[15] = 1;
config.vnameserver6 = vprefix_addr6;
config.vnameserver6.s6_addr[15] = 2;
gw6 = config.vhost6;
dns6 = config.vnameserver6;
sl = slirp_new(&config, &slirp_callbacks, this);
if (sl == NULL) {
errmsg = "slirp_new() failed";
return false;
}
for (const auto& fwd : fwdinfo) {
int r = slirp_add_hostfwd(sl, fwd.is_udp,
fwd.host.sin_addr, fwd.host.sin_port,
fwd.guest.sin_addr, fwd.guest.sin_port);
if (r != 0) {
char hname[INET_ADDRSTRLEN];
char gname[INET_ADDRSTRLEN];
hname[0] = '\0';
gname[0] = '\0';
inet_ntop(AF_INET, &fwd.host.sin_addr,
hname, (socklen_t)sizeof(hname));
inet_ntop(AF_INET, &fwd.guest.sin_addr,
gname, (socklen_t)sizeof(gname));
errmsg = string_format("slirp_add_hostfwd(%s,%s:%d,%s:%d) failed",
(fwd.is_udp ? "udp" : "tcp"),
hname, fwd.host.sin_port, gname, fwd.guest.sin_port);
goto abort;
}
}
// libslirp のバージョンをチェック。
{
const char *version_str = slirp_version_string();
uint major;
uint minor;
uint patch;
if (sscanf(version_str, "%u.%u.%u", &major, &minor, &patch) == 3) {
putmsg(1, "libslirp version %u.%u", major, minor);
ver = VER(major, minor, patch);
}
}
// 送信用のパイプを用意。
if (pipe(fds) < 0) {
errmsg = string_format("pipe(): %s", strerror(errno));
goto abort;
}
txd_r = fds[0];
txd_w = fds[1];
// 受信用のパイプを用意。
if (pipe(fds) < 0) {
errmsg = string_format("pipe(): %s", strerror(errno));
goto abort;
}
rxd_r = fds[0];
rxd_w = fds[1];
*rxdp = rxd_r;
*txdp = txd_w;
// モニタを設定。(登録自体は hostnet で行ってある)
monitor = gMonitorManager->Find(ID_MONITOR_SLIRP);
assert(monitor);
monitor->obj = this;
monitor->SetCallback(&SlirpThread::MonitorScreen);
monitor->SetSize(80, 40);
return true;
abort:
Close();
return false;
}
// hostnetN-usermode-net の引数 <addr>/<netmask> or <addr>/<netlen> を
// パースして *pnetwork, *pnetmask に格納する。
// 失敗したら errmsg をセットして false を返す。
bool
SlirpThread::ParseConfigNet(const ConfigItem& item,
struct in_addr *pnetwork, struct in_addr *pnetmask)
{
std::string net = item.AsString();
auto list = string_split(net, '/');
if (list.empty()) {
syntax_error:
errmsg = item.ErrMsg("Syntax is <addr>/<netmask>");
return false;
}
// アドレス。
auto addr = list[0];
if (addr.empty()) {
goto syntax_error;
}
if (inet_pton(AF_INET, addr.c_str(), pnetwork) < 1) {
errmsg = item.ErrMsg("Invalid network address");
return false;
}
// 区切りの '/'。
if (list.size() == 1) {
goto syntax_error;
}
// ここからネットマスク or ネットマスク長。
auto mask = list[1];
const char *p = mask.c_str();
// アドレスでも長さでも必ず1文字目は10進数値。
// 特に strtoul() は先頭のスペースを無視して成功してしまうので
// 先頭がスペースでないことはチェックする必要がある。
if (*p < '0' || *p > '9') {
goto syntax_error;
}
char *end;
errno = 0;
unsigned long netlen = strtoul(p, &end, 10);
if (*end == '\0') {
// 数値だけから成っていれば、ネットマスク長かも知れない。
if (errno == ERANGE) {
out_of_range:
errmsg = item.ErrMsg("netmask len is out of range");
return false;
} else if (errno != 0) {
errmsg = item.ErrMsg("Invalid netmask len: %s", strerror(errno));
return false;
}
// DHCP 配布アドレスまで入れるには最小でも /28 (16-2個) が必要。
if (netlen < 1 || netlen > 28) {
goto out_of_range;
}
pnetmask->s_addr = htonl(((1U << netlen) - 1) << (32 - netlen));
} else {
// 数値以外も含まれていれば、ネットマスクかも知れない。
if (inet_pton(AF_INET, p, pnetmask) < 1) {
errmsg = item.ErrMsg("Invalid netmask");
return false;
}
}
// これ以上続くのはエラー。
if (list.size() > 2) {
goto syntax_error;
}
return true;
}
// hostnetN-usermode-net6 の引数 <addr6>/<prefixlen> をパースして
// *paddr6, *plen に格納する。
// 失敗したら errmsg をセットして false を返す。
bool
SlirpThread::ParseConfigNet6(const ConfigItem& item,
struct in6_addr *paddr6, uint *plen)
{
std::string net6 = item.AsString();
auto list = string_split(net6, '/');
if (list.empty()) {
syntax_error:
errmsg = item.ErrMsg("Syntax is <v6addr>/<len>");
return false;
}
// アドレス。
auto addr = list[0];
if (addr.empty()) {
goto syntax_error;
}
if (inet_pton(AF_INET6, addr.c_str(), paddr6) < 1) {
errmsg = item.ErrMsg("Invalid network address");
return false;
}
// 区切りの '/'。
if (list.size() == 1) {
goto syntax_error;
}
// プレフィックス長。
auto prefixlen = list[1];
const char *p = prefixlen.c_str();
// strtoul() は先頭のスペースを無視して成功してしまうので
// 先頭がスペースでないことはチェックする必要がある。
if (*p < '0' || *p > '9') {
goto syntax_error;
}
char *end;
errno = 0;
unsigned long len = strtoul(p, &end, 10);
if (*end != '\0') {
goto syntax_error;
}
if (errno == ERANGE) {
out_of_range:
errmsg = item.ErrMsg("prefixlen is out of range");
return false;
} else if (errno != 0) {
errmsg = item.ErrMsg("Invalid netmask len: %s", strerror(errno));
return false;
}
// 適切な範囲がよく分からないのでとりあえず物理限界だけチェック。
if (len < 1 || len > 124) {
goto out_of_range;
}
*plen = len;
// これ以上続くのはエラー。
if (list.size() > 2) {
goto syntax_error;
}
return true;
}
// hostnetN-usermode-hostfwd の引数のエントリ1つ分をパースして fwdinfo に
// 追加する。失敗したら errmsg をセットして false を返す。
//
// 引数は <entry> を複数ならセミコロンで区切ったもの。
// ここに渡される一つの <entry> の書式は次の通り。
// <entry> := <proto>,[<hostaddr>:]<hostport>,[<guestaddr>:]<guestport>
bool
SlirpThread::ParseConfigHostFwdEntry(const ConfigItem& item,
const std::string& entry, const std::string& where)
{
int r;
// エントリが空なのは許容するか。
if (entry.empty()) {
return true;
}
auto val = string_split(entry, ',');
if (val.size() != 3) {
syntax_error:
errmsg = item.ErrMsg(
"Syntax is <proto>,[<hostaddr>:]<port>,[<guestaddr>]:<port>[;...]"
+ where);
return false;
}
FwdInfo fwd;
memset(&fwd, 0, sizeof(fwd));
// proto
auto proto = string_trim(val[0]);
if (strcasecmp(proto.c_str(), "tcp") == 0) {
fwd.is_udp = 0;
} else if (strcasecmp(proto.c_str(), "udp") == 0) {
fwd.is_udp = 1;
} else {
errmsg = item.ErrMsg("protocol must be \"tcp\" or \"udp\"" + where);
return false;
}
// host
r = ParseAddrPort(item, val[1], &fwd.host, "host", where);
if (r < 0) {
goto syntax_error;
} else if (r == 0) {
return false;
}
// guest
r = ParseAddrPort(item, val[2], &fwd.guest, "guest", where);
if (r < 0) {
goto syntax_error;
} else if (r == 0) {
return false;
}
fwdinfo.push_back(fwd);
return true;
}
// "[アドレス:]ポート" 形式の hostport_arg からアドレスとポートを取り出して
// sin に格納する。
// 戻り値は 1 が成功、
// 0 なら失敗で errno はセット済み、
// -1 なら失敗で呼び出し元で syntax_error へ分岐。
int
SlirpThread::ParseAddrPort(const ConfigItem& item,
const std::string& hostport_arg, struct sockaddr_in *sin,
const char *which, const std::string& where)
{
std::string hostport = string_trim(hostport_arg);
if (hostport.empty()) {
return -1;
}
// ':' で分離。
std::string port;
auto list = string_split(hostport, ':');
if (list.size() == 1) {
// ポートのみ。
port = list[0];
} else {
// list.size() == 0 はないので、2以上。
std::string host;
host = list[0];
port = list[1];
// アドレス。
if (host.empty()) {
return -1;
}
if (inet_pton(AF_INET, host.c_str(), &sin->sin_addr) < 1) {
errmsg = item.ErrMsg("Invalid %s address%s", which, where.c_str());
return 0;
}
}
// ポート。
const char *p = port.c_str();
if (*p < '0' || *p > '9') {
invalid_port:
errmsg = item.ErrMsg("Invalid %s port%s", which, where.c_str());
return 0;
}
char *end;
errno = 0;
unsigned long portnum = strtoul(p, &end, 10);
if (*end != '\0') {
goto invalid_port;
}
if (errno == ERANGE) {
out_of_range:
errmsg = item.ErrMsg("%s port is out of range%s", which, where.c_str());
return 0;
}
if (errno != 0) {
errmsg = item.ErrMsg("%s port: %s%s",
which, strerror(errno), where.c_str());
return 0;
}
if (portnum < 1 || portnum > 65535) {
goto out_of_range;
}
// これ以上続くのはエラー。
if (list.size() > 2) {
return -1;
}
sin->sin_port = portnum;
return 1;
}
void
SlirpThread::Close()
{
txd_r.Close();
txd_w.Close();
rxd_r.Close();
rxd_w.Close();
if (sl) {
for (const auto& fwd : fwdinfo) {
slirp_remove_hostfwd(sl, fwd.is_udp,
fwd.host.sin_addr, fwd.host.sin_port);
}
slirp_cleanup(sl);
sl = NULL;
}
}
// スレッドの終了を指示
void
SlirpThread::Terminate()
{
txd_r.Close();
txd_w.Close();
rxd_r.Close();
rxd_w.Close();
}
// スレッド
void
SlirpThread::ThreadRun()
{
SetThreadAffinityHint(AffinityClass::Light);
while (exit_requested == false) {
// タイマー処理。
int64 cur_msec = nsec_to_msec(ClockGetNsCB());
for (auto it = timerlist.begin(); it != timerlist.end(); ) {
SlirpTimer *timer = *it;
if (timer->expire_msec <= cur_msec) {
// 期限が来ていれば登録されているハンドラを実行。
// このハンドラ内から TimerModCB() が呼ばれて
// timerlist が書き換わる可能性があるので先に削除。
timerlist.erase(it);
timer->func(timer->arg);
// この場合 it の状態が保証されなくなるので、
// もう一度先頭からやり直す。
it = timerlist.begin();
} else {
it++;
}
}
// poll に入る前に内部のテーブル(文字列)を取得しておく。
UpdateInfo();
// 先頭を txd とする。
pollfds.clear();
AddPollCB(txd_r, SLIRP_POLL_IN);
// slirp ハンドルが持っているディスクリプタを pollfds[] にセット。
// コールバックで都度 AddPollCB() が呼ばれてそいつが追加している。
//
// slirp_pollfds_fill() の &timeout_ms 引数は、timeout_ms を
// 書き出す(out パラメータ)ではなく、現行の timeout_ms を必要に
// 応じて更新というか短くする(ref パラメータ)。つまり呼び出し前に
// 適切な値をセットしておく必要がある。値は必ず 1000 以下にしてくる
// ため、初期値も 1000 をセットするのがよさそう。
// ドキュメントをちゃんと書け。
uint32 timeout_ms = 1000;
slirp_pollfds_fill(sl, &timeout_ms, add_poll_cb, this);
int err = poll(pollfds.data(), pollfds.size(), timeout_ms);
if (err < 0) {
if (errno == EINTR) {
continue;
}
// XXX どうする?
putmsg(0, "poll failed: %s", strerror(errno));
break;
}
// txd に何か起きた。
if (pollfds[0].revents != 0) {
// txd のクローズはこのスレッドの終了通知。
if ((pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))) {
exit_requested = true;
continue;
}
// txd への着信は VM (-> HostNet スレッド) からのパケット送信。
if ((pollfds[0].revents & POLLIN)) {
Write();
}
}
// poll() 完了後に slirp ハンドルの持っている各ディスクリプタの
// revents (GetReventsCB() コールバックを通じて取得する) に応じた
// 処理をするようだ。
slirp_pollfds_poll(sl, (err < 0), get_revents_cb, this);
}
}
// slirp_pollfds_fill() から呼ばれるコールバック。
// fd, events をこっちの pollfds[] に追加し、追加したインデックスを返す。
int
SlirpThread::AddPollCB(int fd, int slevents)
{
struct pollfd pfd;
pfd.fd = fd;
pfd.events = SLIRP_EVENT_to_POLL_EVENT(slevents);
pfd.revents = 0;
int idx = pollfds.size();
pollfds.emplace_back(pfd);
return idx;
}
// slirp_pollfds_poll() から呼ばれるコールバック。
// pollfds[idx] の revents (SLRIP_POLL_*) を返す。
int
SlirpThread::GetReventsCB(int idx)
{
if (idx < 0 || idx >= pollfds.size()) {
return 0;
}
int slevents = POLL_EVENT_to_SLIRP_EVENT(pollfds[idx].revents);
return slevents;
}
// HostNet(NetDriver) スレッドから txd に着信したパケットを Slirp に送信する。
void
SlirpThread::Write()
{
uint32 len;
int r;
// 先頭4バイトに後続パケット長を入れてある。
r = read(txd_r, &len, sizeof(len));
if (r < 0) {
putmsg(0, "Write: read(len) failed: %s", strerror(errno));
return;
}
std::vector<uint8> buf(len);
r = read(txd_r, buf.data(), buf.size());
if (r < 0) {
putmsg(0, "Write: read(buf) failed: %s", strerror(errno));
return;
}
// 一部のフレームを libslirp に渡す前にこちらで処理する。
if (__predict_false(WriteHook(buf))) {
return;
}
if (__predict_false(loglevel >= 2)) {
putmsgn("Send to SLIRP:");
DumpFrame(buf.data(), buf.size());
}
slirp_input(sl, buf.data(), buf.size());
}
// 一部の frame を libslirp に渡す前にこちらで処理する。
// 処理した (libslirp に渡さない) 場合は true を返す。
// 処理しない (libslirp に渡す) 場合は false を返す。
//
// 現状、自分(ルータと DNS サーバ)以外への ICMPv6 Echo Request が該当。
bool
SlirpThread::WriteHook(const std::vector<uint8>& frame) const
{
const uint8 *payload;
size_t payloadlen;
// XXX libslirp-4.7.0 は、自身が管理するルータ・DNS サーバ以外の
// アドレスへの ICMPv6 Echo Request の送出に対する処理が未実装。
// それ自体はいいのだが、slirp_input() がそのパケットを受け取ると
// GLib の機構でコンソールにログを出してしまい、このログはこちらで制御
// 出来ない (しにくい) のにユーザに目立つ形で表示されるので大変困る。
// そこで、このパケットを事前にここで検出して、自分で処理というか
// ドロップして putmsg() で控えめにログを出すことにする。
// libslirp-4.8.0 で修正された。
if (ver >= VER(4, 8, 0)) {
return false;
}
// イーサネットフレームが IPv6。
if (__predict_false(frame.size() < 14)) {
return false;
}
if (frame[12] != 0x86 || frame[13] != 0xdd) {
return false;
}
struct ipv6_hdr {
uint8 ver_cls; // 上位4ビットが version
uint8 clf_flw;
uint16 flowlabel;
uint16 payloadlen;
uint8 nexthdr;
uint8 hoplimit;
uint8 srcip[16];
uint8 dstip[16];
} __packed;
payload = frame.data() + 14;
payloadlen = frame.size() - 14;
const ipv6_hdr& ipv6 = *(const ipv6_hdr *)payload;
if (__predict_false(payloadlen < sizeof(ipv6_hdr))) {
return false;
}
// 先に NextHdr をチェック。途中に別ヘッダが挟まるケースは諦める。
if (ipv6.nexthdr != 58) {
return false;
}
struct icmp_hdr {
uint8 type;
uint8 code;
uint16 cksum;
} __packed;
payload += sizeof(ipv6_hdr);
payloadlen -= sizeof(ipv6_hdr);
const icmp_hdr& icmp = *(const icmp_hdr *)payload;
if (__predict_false(payloadlen < sizeof(icmp_hdr))) {
return false;
}
if (icmp.type != 128) { // ICMPV6 Echo Request
return false;
}
// ICMPv6 Echo Reuqest は確定したのでここで宛先をチェック。
if (0) {
char srcname[100];
char dstname[100];
inet_ntop(AF_INET6, ipv6.srcip, srcname, (socklen_t)sizeof(srcname));
inet_ntop(AF_INET6, ipv6.dstip, dstname, (socklen_t)sizeof(dstname));
printf("ICMPv6 src=%s dst=%s\n", srcname, dstname);
}
// 宛先が自分のルータか DNS サーバなら処理可能なので何もしない。
if (memcmp(ipv6.dstip, &gw6, sizeof(gw6)) == 0) {
return false;
}
if (memcmp(ipv6.dstip, &dns6, sizeof(dns6)) == 0) {
return false;
}
// リンクローカルはここで作って比較。
struct in6_addr linklocal;
memset(&linklocal, 0, sizeof(linklocal));
linklocal.s6_addr[0] = 0xfe;
linklocal.s6_addr[1] = 0x80;
memcpy(&linklocal.s6_addr[8], &gw6.s6_addr[8], 8);
if (memcmp(ipv6.dstip, &linklocal, sizeof(linklocal)) == 0) {
return false;
}
memcpy(&linklocal.s6_addr[8], &dns6.s6_addr[8], 8);
if (memcmp(ipv6.dstip, &linklocal, sizeof(linklocal)) == 0) {
return false;
}
// ここから未実装ログが出る組み合わせ。
// 統計情報。
hostnet->CountTXUnsupp(frame.size());
return true;
}
// Slirp ライブラリがパケットを "送信した" 時に呼ばれるコールバック。
// なのでこちらから見れば HUB から NIC への着信。
ssize_t
SlirpThread::SendPacketCB(const void *src, size_t srclen)
{
if (__predict_false(loglevel >= 2)) {
putmsgn("Recv from SLIRP");
DumpFrame(src, srclen);
}
// 受信ディスクリプタ (の書き込み端) に書き込む。
// 先頭4バイトはホストバイトオーダーでの後続のパケット長。
uint32 lenbuf = srclen;
struct iovec iov[2];
iov[0].iov_base = &lenbuf;
iov[0].iov_len = sizeof(lenbuf);
iov[1].iov_base = const_cast<void *>(src);
iov[1].iov_len = srclen;
ssize_t r = writev(rxd_w, &iov[0], countof(iov));
if (r < 0) {
putmsg(0, "writev failed: %s", strerror(errno));
return -1;
}
if (r < sizeof(lenbuf) + srclen) {
putmsg(0, "writev failed: too short");
return -1;
}
return srclen;
}
// 現在の仮想時間を返すコールバック。
int64
SlirpThread::ClockGetNsCB() const
{
// XXX scheduler がセットされる前から呼ばれるのでとりあえず。
if (__predict_false(scheduler == NULL)) {
return 0;
}
uint64 time = scheduler->GetVirtTime();
return (int64)time;
}
// 新しいタイマー(イベント)を返すコールバック。
// 引数は時間が来たら呼ぶハンドラと引数。
void *
SlirpThread::TimerNewCB(SlirpTimerCb func, void *arg)
{
SlirpTimer *newtimer = new SlirpTimer(func, arg);
newtimer->num = latest_timer_num++;
return newtimer;
}
// タイマーを削除する。
void
SlirpThread::TimerFreeCB(SlirpTimer *timer)
{
// まだアクティブならそれも削除する。
timerlist.remove(timer);
delete timer;
}
// タイマーの期限を新しく設定する?。
void
SlirpThread::TimerModCB(SlirpTimer *timer, int64 expire_msec)
{
timer->expire_msec = expire_msec;
// リストから一旦削除してから、適切なところに挿入し直す。
timerlist.remove(timer);
if (timerlist.empty()) {
timerlist.push_back(timer);
} else {
for (auto it = timerlist.begin(); it != timerlist.end(); ++it) {
auto *t = *it;
if (t->expire_msec > timer->expire_msec) {
timerlist.insert(it, timer);
break;
}
}
}
}
// ゲスト (VM) から送信しようとしたパケットのエラーメッセージ。
// とりあえずログに出しておく。
void
SlirpThread::GuestErrorCB(const char *msg)
{
putmsg(1, "libslirp: %s", msg);
}
// Slirp の内部テーブルのこちら側のコピーを更新する。
// メインループ内から呼ばれる。
void
SlirpThread::UpdateInfo()
{
// 取得。
char *newconn = slirp_connection_info(sl);
char *newnbr = slirp_neighbor_info(sl);
// 更新が必要か。前回と同じなら今取得したほうを解放。
if (conninfo && strcmp(conninfo, newconn) == 0) {
free(newconn);
newconn = NULL;
}
if (nbrinfo && strcmp(nbrinfo, newnbr) == 0) {
free(newnbr);
newnbr = NULL;
}
// どちらも更新なしならここで終了。
if (newconn == NULL && newnbr == NULL) {
return;
}
// 更新。
char *oldconn = NULL;
char *oldnbr = NULL;
{
std::lock_guard<std::mutex> lock(info_mtx);
if (newconn) {
oldconn = conninfo;
conninfo = newconn;
}
if (newnbr) {
oldnbr = nbrinfo;
nbrinfo = newnbr;
}
}
free(oldconn);
free(oldnbr);
}
void
SlirpThread::MonitorScreen(Monitor *, TextScreen& screen)
{
std::string local_conn;
std::string local_nbr;
int y;
// スレッド越しにコピーを取る。
{
std::lock_guard<std::mutex> lock(info_mtx);
local_conn = std::string(conninfo);
local_nbr = std::string(nbrinfo);
}
// Connection State の UDP 欄は [0 sec] の次に一瞬だけ [42949?? sec]
// くらいになる。これは slirp_connection_info() が文字列を返してきた時点で
// こうなっているのでこっちの話ではないのだが、あまりに幼稚すぎて
// 自分のソフトでこれが表示されるのは恥ずかしいのでパッチしておく。
// UDP[4294967 sec]
static const char udphdr[] = "UDP[";
static const char patch[] = "expired] ";
for (char *s = &local_conn[0]; (s = strstr(s, udphdr)) != NULL; ) {
const char *end;
s += 4;
for (end = s; '0' <= *end && *end <= '9'; end++)
; // nop
if (__predict_false(end - s >= 7)) {
for (const char *p = patch; *s && *p; ) {
*s++ = *p++;
}
}
}
screen.Clear();
y = 0;
auto lines = string_split(local_conn, '\n');
for (auto& buf : lines) {
screen.Puts(0, y++, buf.c_str());
}
lines = string_split(local_nbr, '\n');
for (auto& buf : lines) {
screen.Puts(0, y++, buf.c_str());
}
// あふれたら諦める…。
}
// src から srclen バイトの16進ダンプをログに(無条件に)出力する。
void
SlirpThread::DumpHex(const void *src, size_t srclen) const
{
std::vector<std::string> lines = HostNetDevice::DumpHex(src, srclen);
for (auto& line : lines) {
putmsgn("%s", line.c_str());
}
}
// イーサネットフレームを整形してログに(無条件に)出力する。
void
SlirpThread::DumpFrame(const void *src, size_t srclen) const
{
std::vector<std::string> lines = HostNetDevice::DumpFrame(src, srclen);
for (auto& line : lines) {
putmsgn("%s", line.c_str());
}
}
// コールバックのトランポリン関数たち
static ssize_t
send_packet_cb(const void *buf, size_t buflen, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
return backend->SendPacketCB(buf, buflen);
}
static void
guest_error_cb(const char *msg, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
backend->GuestErrorCB(msg);
}
static int64
clock_get_ns_cb(void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
return backend->ClockGetNsCB();
}
static void *
timer_new_cb(SlirpTimerCb func, void *arg, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
return backend->TimerNewCB(func, arg);
}
static void
timer_free_cb(void *timer, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
auto *sltimer = reinterpret_cast<SlirpTimer *>(timer);
return backend->TimerFreeCB(sltimer);
}
static void
timer_mod_cb(void *timer, int64 expire_msec, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
auto *sltimer = reinterpret_cast<SlirpTimer *>(timer);
backend->TimerModCB(sltimer, expire_msec);
}
// おそらく不要で何もしなくていいのだが、コールバックを NULL に出来ないため。
static void
register_poll_fd_cb(int fd, void *)
{
}
// おそらく不要で何もしなくていいのだが、コールバックを NULL に出来ないため。
static void
unregister_poll_fd_cb(int fd, void *)
{
}
// シングルスレッド動作なので不要なはずだが、コールバックを NULL に出来ない。
static void
notify_cb(void *)
{
}
static int
add_poll_cb(int fd, int events, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
return backend->AddPollCB(fd, events);
}
static int
get_revents_cb(int idx, void *opaque)
{
auto *backend = reinterpret_cast<SlirpThread *>(opaque);
return backend->GetReventsCB(idx);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.