--- nono/host/netdriver_slirp.cpp 2026/04/29 17:05:43 1.1.1.2 +++ nono/host/netdriver_slirp.cpp 2026/04/29 17:05:51 1.1.1.4 @@ -69,7 +69,7 @@ // コンストラクタ NetDriverSlirp::NetDriverSlirp(HostDevice *hostdev_) - : inherited(hostdev_, "Usermode") + : inherited(hostdev_, "usermode") { txd = -1; rxd = -1; @@ -95,12 +95,13 @@ NetDriverSlirp::SetLogLevel(int loglevel // ドライバ初期化 bool -NetDriverSlirp::InitDriver() +NetDriverSlirp::InitDriver(bool startup) { putmsg(1, "trying usermode..."); if (slirp_occupied) { - putmsg(1, "Already configured on the other interface"); + errmsg = "'usermode' is already in use by another hostnet-driver"; + putmsg(1, "%s", errmsg.c_str()); return false; } @@ -108,23 +109,33 @@ NetDriverSlirp::InitDriver() backend.reset(new SlirpThread(this)); } catch (...) { } if ((bool)backend == false) { - warnx("Failed to initialize SlirpThread at %s", __method__); + 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) { - backend.reset(); + errmsg = backend->errmsg; + putmsg(1, "%s", errmsg.c_str()); return false; } // 読み込み側を Slirp からの受信端として親に登録。 if (hostdev->AddOuter(rxd) < 0) { - putmsg(0, "AddOuter(rxd=%d): %s", rxd, strerror(errno)); + 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; @@ -136,13 +147,16 @@ NetDriverSlirp::Close() { // rxd がすでに閉じられていたらとかはここでは関係なくて、 // この番号を kqueue から外す必要がある。(Linux) - hostdev->DelOuter(rxd); + if (rxd >= 0) { + hostdev->DelOuter(rxd); + } // txd, rxd は裏スレッドの所有なのでこちらでは何もしない。 if ((bool)backend) { backend->Terminate(); backend->Close(); + backend.reset(); } slirp_occupied = false; } @@ -291,25 +305,26 @@ SlirpThread::SlirpThread(NetDriverSlirp parent = parent_; hostnet = dynamic_cast(parent->GetHostDev()); SetName(parent->GetName()); + SetThreadName("SlirpThread"); // コンストラクト後ただちにログ出力できるようここで一度追従しておく。 // 以降は親 (NetDriver側) の SetLogLevel() で変更する。 loglevel = parent->loglevel; - - // オレオレパフォーマンス測定用アドレス (see vm/virtio_net.cpp) - inet_pton(AF_INET6, "fe80::db8:db8:9090", &perfaddr6); } // デストラクタ 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) @@ -321,132 +336,35 @@ SlirpThread::InitBackend(const std::stri uint vprefix_len; int fds[2]; - // hostnetN-usermode-net=/ - { - const ConfigItem& netitem = gConfig->Find(key + "-usermode-net"); - std::string net = netitem.AsString(); - const char *s = strchr(net.c_str(), '/'); - if (s == NULL) { - netitem.Err(); - return false; - } - const char *p = s + 1; - auto plen = strlen(p); - if (plen < 3) { - // strtoul() は先頭のスペースを無視して進むので - // 先頭がスペースでないことはチェックする必要がある。 - if (*p < '0' || *p > '9') { - netitem.Err(); - return false; - } - char *end; - errno = 0; - unsigned long netlen = strtoul(p, &end, 10); - if (*end != '\0' || errno != 0) { - netitem.Err("Invalid netmask len"); - return false; - } - // DHCP 配布アドレスまで入れるには最小でも /28 (16-2個) が必要。 - if (netlen < 1 || netlen > 28) { - netitem.Err("netmask len is out of range"); - return false; - } - vnetmask.s_addr = htonl(((1U << netlen) - 1) << (32 - netlen)); - } else { - int r = inet_pton(AF_INET, p, &vnetmask); - if (r < 1) { - netitem.Err("Invalid netmask"); - return false; - } - } - - net.resize(s - net.c_str()); - int r = inet_pton(AF_INET, net.c_str(), &vnetwork); - if (r < 1) { - netitem.Err("Invalid network address"); - return false; - } + const ConfigItem& netitem = gConfig->Find(key + "-usermode-net"); + if (ParseConfigNet(netitem, &vnetwork, &vnetmask) == false) { + return false; } - // hostnetN-usermode-net6=/ - { - const ConfigItem& net6item = gConfig->Find(key + "-usermode-net6"); - std::string net6 = net6item.AsString(); - const char *s = strchr(net6.c_str(), '/'); - if (s == NULL) { - net6item.Err(); - return false; - } - const char *p = s + 1; - // strtoul() は先頭のスペースを無視して進むので - // 先頭がスペースでないことはチェックする必要がある。 - if (*p < '0' || *p > '9') { - net6item.Err(); - return false; - } - char *end; - errno = 0; - unsigned long netlen = strtoul(p, &end, 10); - if (*end != '\0' || errno != 0) { - net6item.Err("Invalid prefix len"); - return false; - } - // 適切な範囲がよく分からないのでとりあえず物理限界だけチェック。 - if (netlen < 1 || netlen > 125) { - net6item.Err("prefix len iis out of range"); - return false; - } - vprefix_len = netlen; - - net6.resize(s - net6.c_str()); - int r = inet_pton(AF_INET6, net6.c_str(), &vprefix_addr6); - if (r < 1) { - net6item.Err("Invalid network address"); - return false; - } + const ConfigItem& net6item = gConfig->Find(key + "-usermode-net6"); + if (ParseConfigNet6(net6item, &vprefix_addr6, &vprefix_len) == false) { + return false; } - // hostnetN-usermode-hostfwd の書式は - // := ,[:],[:] - // で、 が複数ならセミコロンで区切る。 + // hostnet-usermode-hostfwd=[;[;...]] const ConfigItem& fwditem = gConfig->Find(key + "-usermode-hostfwd"); auto entries = string_split(fwditem.AsString(), ';'); - for (auto entry : entries) { - FwdInfo fwd; - std::string msg; - - entry = string_trim(entry); - auto val = string_split(entry, ','); - if (val.size() != 3) { - fwditem.Err("syntax error at \"%s\"", entry.c_str()); + if (entries.size() == 1) { + std::string entry = string_trim(entries[0]); + if (ParseConfigHostFwdEntry(fwditem, entry, "") == false) { + fwdinfo.clear(); return false; } - - // proto - auto proto_str = string_trim(val[0]); - if (strcasecmp(proto_str.c_str(), "tcp") == 0) { - fwd.is_udp = 0; - } else if (strcasecmp(proto_str.c_str(), "udp") == 0) { - fwd.is_udp = 1; - } else { - fwditem.Err("protocol must be \"tcp\" or \"udp\" at \"%s\"", - entry.c_str()); - return false; - } - - // [:] - if (ParseAddr(val[1], "host", &fwd.host, msg) == false) { - fwditem.Err("%s at \"%s\"", msg.c_str(), entry.c_str()); - return false; - } - - // [:] - if (ParseAddr(val[2], "guest", &fwd.guest, msg) == false) { - fwditem.Err("%s at \"%s\"", msg.c_str(), entry.c_str()); - 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; + } } - - fwdinfo.push_back(fwd); } // Slirp を初期化。 @@ -470,7 +388,7 @@ SlirpThread::InitBackend(const std::stri sl = slirp_new(&config, &slirp_callbacks, this); if (sl == NULL) { - putmsg(0, "slirp_new() failed"); + errmsg = "slirp_new() failed"; return false; } @@ -487,35 +405,37 @@ SlirpThread::InitBackend(const std::stri hname, (socklen_t)sizeof(hname)); inet_ntop(AF_INET, &fwd.guest.sin_addr, gname, (socklen_t)sizeof(gname)); - putmsg(0, "slirp_add_hostfwd(%s,%s,%d,%s,%d) failed", + 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); - return false; + 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); + { + 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) { - putmsg(0, "pipe() failed"); - return false; + errmsg = string_format("pipe(): %s", strerror(errno)); + goto abort; } txd_r = fds[0]; txd_w = fds[1]; // 受信用のパイプを用意。 if (pipe(fds) < 0) { - putmsg(0, "pipe() failed"); - return false; + errmsg = string_format("pipe(): %s", strerror(errno)); + goto abort; } rxd_r = fds[0]; rxd_w = fds[1]; @@ -523,56 +443,298 @@ SlirpThread::InitBackend(const std::stri *rxdp = rxd_r; *txdp = txd_w; - // 成功したらモニタを登録。 - monitor = gMonitorManager->Regist(ID_MONITOR_SLIRP, this); + // モニタを設定。(登録自体は hostnet で行ってある) + monitor = gMonitorManager->Find(ID_MONITOR_SLIRP); + assert(monitor); + monitor->obj = this; monitor->func = ToMonitorCallback(&SlirpThread::MonitorUpdate); monitor->SetSize(80, 40); return true; + + abort: + Close(); + return false; } -// "[アドレス:]ポート" から addr, port を取り出す。 -// エラーなら errmsg にエラーメッセージを格納して false を返す。 +// hostnetN-usermode-net の引数 / or / を +// パースして *pnetwork, *pnetmask に格納する。 +// 失敗したら errmsg をセットして false を返す。 bool -SlirpThread::ParseAddr(std::string hostport, const char *msg, - struct sockaddr_in *sin, std::string& errmsg) const +SlirpThread::ParseConfigNet(const ConfigItem& item, + struct in_addr *pnetwork, struct in_addr *pnetmask) { - hostport = string_trim(hostport); + std::string net = item.AsString(); - // ':' で分離。':' 自体がなければポートのみ。 - std::string serv; - if (strchr(hostport.c_str(), ':')) { - auto h = string_split(hostport, ':'); - const std::string& name = h[0]; - serv = h[1]; + auto list = string_split(net, '/'); + if (list.empty()) { + syntax_error: + errmsg = item.ErrMsg("Syntax is /"); + return false; + } - // アドレス。 - int r = inet_pton(AF_INET, name.c_str(), &sin->sin_addr); - if (r < 0) { - errmsg = string_format("\"%s\": inet_pton failed", name.c_str()); + // アドレス。 + 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; } - if (r == 0) { - errmsg = string_format("\"%s\": %s address not parsable", - name.c_str(), msg); + 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 の引数 / をパースして +// *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 /"); + 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 を返す。 +// +// 引数は を複数ならセミコロンで区切ったもの。 +// ここに渡される一つの の書式は次の通り。 +// := ,[:],[:] +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 ,[:],[]:[;...]" + + 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 { - sin->sin_addr.s_addr = 0; - serv = hostport; + errmsg = item.ErrMsg("protocol must be \"tcp\" or \"udp\"" + where); + return false; } - // ポート。 - int port = atoi(serv.c_str()); - if (port < 1 || port > 65535) { - errmsg = string_format("%d: invalid %s port", port, msg); + // host + r = ParseAddrPort(item, val[1], &fwd.host, "host", where); + if (r < 0) { + goto syntax_error; + } else if (r == 0) { return false; } - sin->sin_port = port; + + // 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() { @@ -837,14 +999,6 @@ SlirpThread::WriteHook(const std::vector // 統計情報。 hostnet->CountTXUnsupp(frame.size()); - // 宛先が fe80::db8:db8:9090 なのはオレオレパフォーマンス測定用だし、 - // この宛先はドキュメント用に予約されたもので送出すべきでないので、 - // これに限り黙って握りつぶす。 - // それ以外は同じメッセージを出しておく。 - if (memcmp(ipv6.dstip, &perfaddr6, sizeof(perfaddr6)) != 0) { - putmsg(1, "libslirp: external icmpv6 not supported yet"); - } - return true; } @@ -968,7 +1122,7 @@ SlirpThread::UpdateInfo() char *oldconn = NULL; char *oldnbr = NULL; { - std::unique_lock lock(info_mtx); + std::lock_guard lock(info_mtx); if (newconn) { oldconn = conninfo; @@ -993,20 +1147,28 @@ SlirpThread::MonitorUpdate(Monitor *, Te // スレッド越しにコピーを取る。 { - std::unique_lock lock(info_mtx); + std::lock_guard lock(info_mtx); local_conn = std::string(conninfo); local_nbr = std::string(nbrinfo); } - // Connection State の UDP 欄は [0 sec] の次に一瞬だけ [4294966 sec] に - // なる。これは slirp_connection_info() が文字列を返してきた時点で + // Connection State の UDP 欄は [0 sec] の次に一瞬だけ [42949?? sec] + // くらいになる。これは slirp_connection_info() が文字列を返してきた時点で // こうなっているのでこっちの話ではないのだが、あまりに幼稚すぎて // 自分のソフトでこれが表示されるのは恥ずかしいのでパッチしておく。 - static const char ovfsec[] = "UDP[4294966 sec]"; - static const char patch[] = "UDP[expired] "; - for (char *s = &local_conn[0]; (s = strstr(s, ovfsec)) != NULL; ) { - memcpy(s, patch, strlen(patch)); - s += strlen(patch); + // 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();