lokinet/llarp/linux/netns.cpp
Stephen Shelton 273270916e
The Great Wall of Blame
This commit reflects changes to clang-format rules. Unfortunately,
these rule changes create a massive change to the codebase, which
causes an apparent rewrite of git history.

Git blame's --ignore-rev flag can be used to ignore this commit when
attempting to `git blame` some code.
2020-04-07 12:38:56 -06:00

320 lines
8.3 KiB
C++

#if defined(RPI) || defined(ANDROID) || NETNS == 0
#else
#include <asm/types.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/capability.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <linux/netns.hpp>
#include <util/logger.hpp>
#ifndef MS_REC
#define MS_REC (16384)
#endif
#include <util/fs.hpp>
namespace llarp
{
namespace GNULinux
{
static const char netns_rundir[] = "/var/run/netns";
static const char netns_etcdir[] = "/etc/netns";
static bool
GetCGroups2MountPoint(fs::path& cgroups2_mount)
{
std::string mountpoint;
std::ifstream inf;
inf.open("/proc/mounts");
if (!inf.is_open())
{
llarp::LogError("failed to open /proc/mounts");
return false;
}
std::string line;
while (std::getline(inf, line))
{
std::string part;
std::stringstream parts;
parts.str(line);
// discard
std::getline(parts, part);
// mount point
std::getline(parts, part);
mountpoint = part;
// type
std::getline(parts, part);
if (part == "cgroup2")
{
// found cgroup2 mountpoint
cgroups2_mount = mountpoint;
return true;
}
}
llarp::LogError("cannot find cgroups2 in /proc/mounts");
return false;
}
static bool
GetNetNS(std::string& netns)
{
auto nfd = open("/proc/self/ns/net", O_RDONLY);
if (nfd < 0)
{
llarp::LogError("Failed to get our own netns, could not open /proc/self/ns/net");
return false;
}
struct stat netst;
if (::fstat(nfd, &netst) < 0)
{
close(nfd);
llarp::LogError("stat of netns failed: ", strerror(errno));
return false;
}
close(nfd);
fs::path run_dir = netns_rundir;
bool foundIt = false;
// find corrosponding file for netns
llarp::util::IterDir(run_dir, [&](const fs::path& f) -> bool {
struct stat fst;
if (::stat(f.string().c_str(), &fst) >= 0)
{
if (fst.st_dev == netst.st_dev && fst.st_ino == netst.st_ino)
{
// found it
foundIt = true;
netns = f.filename().string();
// break iteration
return false;
}
}
// continue iteration
return true;
});
return foundIt;
}
static bool
GetVRFPath(std::string& path)
{
char p[256] = {0};
snprintf(p, sizeof(p), "/proc/%d/cgroup", getpid());
std::ifstream inf;
inf.open(p);
if (!inf.is_open())
{
llarp::LogError("could not open '", p, "': ", strerror(errno));
return false;
}
path = "";
std::string line;
while (std::getline(inf, line))
{
auto pos = line.find("::/");
if (pos != std::string::npos)
{
line = line.substr(pos + 2);
pos = line.find("/vrf");
if (pos != std::string::npos)
{
path = line.substr(pos);
if (path == "/")
path = "";
}
break;
}
}
return true;
}
static bool
ResetVRF()
{
fs::path cgroups2_mount;
if (!GetCGroups2MountPoint(cgroups2_mount))
{
llarp::LogError("could not find cgroup2 mount point, is it mounted?");
return false;
}
std::string netns;
if (!GetNetNS(netns))
{
llarp::LogError("could not get our netns: ", strerror(errno));
return false;
}
std::string vrfpath;
if (!GetVRFPath(vrfpath))
{
llarp::LogError("could not determine vrf cgroup path: ", strerror(errno));
return false;
}
fs::path cgroup_path = cgroups2_mount / vrfpath / netns / "vrf" / "default";
std::error_code ec;
if (!fs::exists(cgroup_path, ec))
{
if (!fs::create_directories(cgroup_path, ec))
{
llarp::LogError("could not create '", cgroup_path.string(), "': ", ec);
return false;
}
}
else if (ec)
{
llarp::LogError("Could not check '", cgroup_path.string(), "': ", ec);
return false;
}
cgroup_path /= "cgroup.procs";
auto fd = open(cgroup_path.string().c_str(), O_RDWR | O_APPEND);
if (fd < 0)
{
llarp::LogError("could not open '", cgroup_path.string(), "': ", strerror(errno));
return false;
}
bool success = true;
std::string pid = std::to_string(getpid());
if (write(fd, pid.c_str(), pid.size()) < 0)
{
llarp::LogError("failed to join cgroup");
success = false;
}
close(fd);
return success;
}
/// bind network namespace paths into /etc/
static bool
BindNetworkNS(const char* name)
{
fs::path etc_dir = netns_etcdir;
etc_dir /= name;
std::error_code ec;
if (!fs::exists(etc_dir, ec))
{
errno = 0;
llarp::LogInfo(etc_dir, " does not exist, skipping");
return true;
}
bool didFail = false;
llarp::util::IterDir(etc_dir, [&](const fs::path& f) -> bool {
if (fs::is_regular_file(f))
{
fs::path netns_path = "/etc";
netns_path /= f.filename();
if (mount(f.string().c_str(), netns_path.string().c_str(), "none", MS_BIND, nullptr) < 0)
{
llarp::LogError(
"failed to bind '",
f.string(),
"' to '",
netns_path.string(),
"': ",
strerror(errno));
didFail = true;
}
}
// continue iteration
return true;
});
return !didFail;
}
static void
DropCap()
{
if (getuid() != 0 && geteuid() != 0)
{
cap_t capabilities;
cap_value_t net_admin = CAP_NET_ADMIN;
cap_flag_t inheritable = CAP_INHERITABLE;
cap_flag_value_t is_set;
capabilities = cap_get_proc();
if (!capabilities)
exit(EXIT_FAILURE);
if (cap_get_flag(capabilities, net_admin, inheritable, &is_set) != 0)
exit(EXIT_FAILURE);
if (is_set == CAP_CLEAR)
{
if (cap_clear(capabilities) != 0)
exit(EXIT_FAILURE);
if (cap_set_proc(capabilities) != 0)
exit(EXIT_FAILURE);
}
cap_free(capabilities);
}
}
bool
NetNSSwitch(const char* name)
{
fs::path netns_path = netns_rundir;
netns_path /= name;
auto nsfd = open(netns_path.string().c_str(), O_RDONLY | O_CLOEXEC);
if (nsfd < 0)
{
llarp::LogError("Failed to open network namespace '", name, "': ", strerror(errno));
return false;
}
if (setns(nsfd, CLONE_NEWNET) < 0)
{
llarp::LogError("Failed to enter network namespace '", name, "': ", strerror(errno));
close(nsfd);
return false;
}
close(nsfd);
if (unshare(CLONE_NEWNS) < 0)
{
llarp::LogError("unshare failed: ", strerror(errno));
return false;
}
// dont let any mount points prop back to parent
// iproute2 source does this
if (mount("", "/", "none", MS_SLAVE | MS_REC, nullptr))
{
llarp::LogError("mount --make-rslave failed: ", strerror(errno));
return false;
}
unsigned long mountflags = 0;
// ensaure /sys not mounted
if (umount2("/sys", MNT_DETACH) < 0)
{
struct statvfs fsstat;
if (statvfs("/sys", &fsstat) == 0)
{
if (fsstat.f_flag & ST_RDONLY)
mountflags = MS_RDONLY;
}
}
// mount sysfs for our namespace
if (mount(name, "/sys", "sysfs", mountflags, nullptr) < 0)
{
llarp::LogError("failed to mount sysfs: ", strerror(errno));
return false;
}
if (!BindNetworkNS(name))
{
llarp::LogError("failed to bind namespace directories");
return false;
}
if (!ResetVRF())
{
llarp::LogError("failed to reset vrf");
return false;
}
DropCap();
return true;
}
} // namespace GNULinux
} // namespace llarp
#endif