#include "network.hpp" #include #include #include #include #include #include #include #include #include #include "spec.hpp" #include "util.hpp" namespace doos { NetMode Network::parse(const std::string& s) { if (s == "slirp") return NetMode::Slirp; if (s == "pasta") return NetMode::Pasta; if (s == "host") return NetMode::Host; if (s == "none") return NetMode::None; throw std::runtime_error("unknown network mode '" + s + "' (use slirp, pasta, host or none)"); } std::string Network::name(NetMode m) { switch (m) { case NetMode::Slirp: return "slirp"; case NetMode::Pasta: return "pasta"; case NetMode::Host: return "host"; case NetMode::None: return "none"; } return "?"; } std::string Network::resolvConf(NetMode m) { switch (m) { case NetMode::Slirp: return "nameserver " + std::string(spec::kSlirpDns) + "\n"; case NetMode::None: return ""; case NetMode::Pasta: case NetMode::Host: { // pasta forwards the namespace's loopback to the host and host mode shares it, so the host resolver // config is valid as is, including the 127.0.0.53 stub. std::ifstream in("/etc/resolv.conf"); return std::string(std::istreambuf_iterator(in), {}); } } return ""; } void SlirpSession::start(pid_t targetPid) { int ready[2]; if (::pipe2(ready, O_CLOEXEC) != 0) throw SysError("pipe"); Fd readEnd(ready[0]), writeEnd(ready[1]); pid_ = ::fork(); if (pid_ < 0) throw SysError("fork slirp4netns"); if (pid_ == 0) { ::prctl(PR_SET_PDEATHSIG, SIGKILL); int nul = ::open("/dev/null", O_WRONLY); if (nul >= 0) { ::dup2(nul, 1); ::dup2(nul, 2); } int wfd = ::dup(writeEnd.get()); // dup clears CLOEXEC std::string readyArg = "--ready-fd=" + std::to_string(wfd); std::string target = std::to_string(targetPid); std::string bin(spec::kSlirpBinary), tap(spec::kSlirpTapName); ::execlp(bin.c_str(), bin.c_str(), "--configure", "--mtu=65520", "--disable-host-loopback", readyArg.c_str(), target.c_str(), tap.c_str(), static_cast(nullptr)); ::dprintf(2, "doos: cannot exec %s: %s\n", bin.c_str(), std::strerror(errno)); ::_exit(127); } writeEnd.reset(); pollfd p{readEnd.get(), POLLIN, 0}; int rc = ::poll(&p, 1, spec::kNetReadyTimeoutMs); char c = 0; if (rc > 0 && ::read(readEnd.get(), &c, 1) == 1) return; stop(); throw std::runtime_error(rc == 0 ? "slirp4netns not ready in time" : "slirp4netns failed to start (is it installed and permitted?)"); } void SlirpSession::stop() { if (pid_ <= 0) return; ::kill(pid_, SIGTERM); ::waitpid(pid_, nullptr, 0); pid_ = -1; } } // namespace doos namespace doos { void PastaSession::start(pid_t targetPid) { pid_ = ::fork(); if (pid_ < 0) throw SysError("fork pasta"); if (pid_ == 0) { ::prctl(PR_SET_PDEATHSIG, SIGKILL); int nul = ::open("/dev/null", O_WRONLY); if (nul >= 0) { ::dup2(nul, 1); ::dup2(nul, 2); } std::string target = std::to_string(targetPid); ::execlp("pasta", "pasta", "--foreground", "--quiet", "--config-net", target.c_str(), static_cast(nullptr)); ::_exit(127); } // pasta has no ready fd: wait until the target namespace has an interface besides lo. std::string dev = "/proc/" + std::to_string(targetPid) + "/net/dev"; for (int waited = 0; waited < spec::kNetReadyTimeoutMs; waited += 20) { int st = 0; if (::waitpid(pid_, &st, WNOHANG) == pid_) { pid_ = -1; throw std::runtime_error("pasta exited early (is it installed and permitted?)"); } std::ifstream in(dev); int lines = 0; for (std::string line; std::getline(in, line);) ++lines; if (lines > 3) return; // two header lines + lo + at least one more interface ::poll(nullptr, 0, 20); } stop(); throw std::runtime_error("pasta not ready in time"); } void PastaSession::stop() { if (pid_ <= 0) return; // pasta drops to another uid after start, so we cannot signal it, and it leaves by itself shortly after its // namespace is gone. Reap it if it is already done; otherwise let init adopt it instead of blocking the exit. for (int waited = 0; waited < 20; waited += 5) { if (::waitpid(pid_, nullptr, WNOHANG) == pid_) break; ::poll(nullptr, 0, 5); } pid_ = -1; } } // namespace doos