db3a9d82c7
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.
123 lines
4.1 KiB
C++
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;
|
|
}
|