Files
slocker-lite/src/persistent_netns.cpp
T
ceamac db3a9d82c7 Add persistent network namespace primitives
Commit 2/6 of the network isolation feature (docs/networking-design.md).
persistent_netns.{h,cpp}: create/verify/remove a network namespace kept
alive with no process in it, the way `ip netns add` does (fork a child,
unshare(CLONE_NEWNET), bind-mount its /proc/self/ns/net onto a
persistent path, exit -- the bind mount keeps it alive). Root-only
(CAP_SYS_ADMIN for the bind mount), best-effort like this project's
other host-state primitives. Not wired into -n/--network yet.

xdg_state_dir() (pid_file.cpp) moved out of its anonymous namespace so
this file can reuse the same $XDG_STATE_HOME resolution rather than a
second, drifting copy.

-t/--test now exercises the create/verify/remove cycle (skipped with a
message, not a failure, when not root) -- confirmed working via doas.
2026-08-30 12:43:12 +00:00

123 lines
4.1 KiB
C++

// Copyright (C) 2026 Viorel Munteanu
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "persistent_netns.h"
#include <sched.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <system_error>
#include <fcntl.h>
#include <spdlog/spdlog.h>
#include "pid_file.h"
std::filesystem::path persistent_netns_path(std::string_view name) {
return xdg_state_dir() / "netns" / sanitize_for_filename(name);
}
bool persistent_netns_exists(std::string_view name) {
auto path = persistent_netns_path(name);
struct stat file_stat {};
struct stat parent_stat {};
if (stat(path.c_str(), &file_stat) != 0 || stat(path.parent_path().c_str(), &parent_stat) != 0) {
return false;
}
return file_stat.st_dev != parent_stat.st_dev;
}
bool create_persistent_netns(std::string_view name) {
if (persistent_netns_exists(name)) {
spdlog::error("persistent network namespace '{}' already exists", name);
return false;
}
auto path = persistent_netns_path(name);
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
if (ec) {
spdlog::warn("failed to create directory {}: {}", path.parent_path().string(), ec.message());
return false;
}
// A plain regular file for the bind mount to land on -- created (or, if a
// stale never-mounted leftover from a previous failed attempt, simply
// reused) before forking, so the child has a stable target to bind onto.
int fd = open(path.c_str(), O_CREAT | O_RDONLY | O_CLOEXEC, 0644);
if (fd < 0) {
spdlog::warn("failed to create {}: {}", path.string(), strerror(errno));
return false;
}
close(fd);
pid_t pid = fork();
if (pid < 0) {
spdlog::warn("failed to fork while creating persistent network namespace '{}': {}", name, strerror(errno));
return false;
}
if (pid == 0) {
// unshare(2) affects only the calling process -- doing this in a
// forked child, never the caller itself, means slocker-lite's own
// network namespace is never touched by creating one of these.
if (unshare(CLONE_NEWNET) != 0) {
_exit(1);
}
// The bind mount is what actually keeps the namespace alive after
// this child exits -- exactly the technique `ip netns add` itself
// uses (bind-mounting /proc/self/ns/net onto a persistent path).
if (mount("/proc/self/ns/net", path.c_str(), nullptr, MS_BIND, nullptr) != 0) {
_exit(1);
}
_exit(0);
}
int status = 0;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
spdlog::warn(
"failed to create persistent network namespace '{}' (unshare/bind-mount failed -- requires root)",
name);
return false;
}
return true;
}
bool remove_persistent_netns(std::string_view name) {
auto path = persistent_netns_path(name);
if (umount2(path.c_str(), 0) != 0) {
spdlog::warn("failed to unmount persistent network namespace '{}' at {}: {}", name, path.string(),
strerror(errno));
return false;
}
std::error_code ec;
std::filesystem::remove(path, ec);
if (ec) {
spdlog::warn("failed to remove {}: {}", path.string(), ec.message());
return false;
}
return true;
}