mirror of https://github.com/c-ares/c-ares.git
On Linux we can potentially use user and UTS namespaces to run a test in a pseudo-container with: - arbitrary filesystem (e.g. /etc/resolv.conf, /etc/nsswitch.conf, /etc/hosts) - arbitrary hostname/domainname. Include a first pass at the framework code to allow this, along with a first test case that uses the container.pull/34/head
parent
439eb45cc0
commit
5dc450a4b3
8 changed files with 392 additions and 28 deletions
@ -0,0 +1,54 @@ |
||||
# -*- Autoconf -*- |
||||
|
||||
# SYNOPSIS |
||||
# |
||||
# AX_CHECK_USER_NAMESPACE |
||||
# |
||||
# DESCRIPTION |
||||
# |
||||
# This macro checks whether the local system supports Linux user namespaces. |
||||
# If so, it calls AC_DEFINE(HAVE_USER_NAMESPACE). |
||||
|
||||
AC_DEFUN([AX_CHECK_USER_NAMESPACE],[dnl |
||||
AC_CACHE_CHECK([whether user namespaces are supported], |
||||
ax_cv_user_namespace,[ |
||||
AC_LANG_PUSH([C]) |
||||
AC_RUN_IFELSE([AC_LANG_SOURCE([[ |
||||
#define _GNU_SOURCE |
||||
#include <fcntl.h> |
||||
#include <sched.h> |
||||
#include <signal.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/types.h> |
||||
#include <sys/wait.h> |
||||
|
||||
int userfn(void *d) { |
||||
usleep(100000); /* synchronize by sleep */ |
||||
return (getuid() != 0); |
||||
} |
||||
char userst[1024*1024]; |
||||
int main() { |
||||
char buffer[1024]; |
||||
int rc, status, fd; |
||||
pid_t child = clone(userfn, userst + 1024*1024, CLONE_NEWUSER|SIGCHLD, 0); |
||||
if (child < 0) return 1; |
||||
|
||||
sprintf(buffer, "/proc/%d/uid_map", child); |
||||
fd = open(buffer, O_CREAT|O_WRONLY|O_TRUNC, 0755); |
||||
sprintf(buffer, "0 %d 1\n", getuid()); |
||||
write(fd, buffer, strlen(buffer)); |
||||
close(fd); |
||||
|
||||
rc = waitpid(child, &status, 0); |
||||
if (rc <= 0) return 1; |
||||
if (!WIFEXITED(status)) return 1; |
||||
return WEXITSTATUS(status); |
||||
} |
||||
]])],[ax_cv_user_namespace=yes], [ax_cv_user_namespace=no]) |
||||
AC_LANG_POP([C]) |
||||
]) |
||||
if test "$ax_cv_user_namespace" = yes; then |
||||
AC_DEFINE([HAVE_USER_NAMESPACE],[1],[Whether user namespaces are available]) |
||||
fi |
||||
]) # AX_CHECK_USER_NAMESPACE |
@ -0,0 +1,76 @@ |
||||
# -*- Autoconf -*- |
||||
|
||||
# SYNOPSIS |
||||
# |
||||
# AX_CHECK_UTS_NAMESPACE |
||||
# |
||||
# DESCRIPTION |
||||
# |
||||
# This macro checks whether the local system supports Linux UTS namespaces. |
||||
# Also requires user namespaces to be available, so that non-root users |
||||
# can enter the namespace. |
||||
# If so, it calls AC_DEFINE(HAVE_UTS_NAMESPACE). |
||||
|
||||
AC_DEFUN([AX_CHECK_UTS_NAMESPACE],[dnl |
||||
AC_CACHE_CHECK([whether UTS namespaces are supported], |
||||
ax_cv_uts_namespace,[ |
||||
AC_LANG_PUSH([C]) |
||||
AC_RUN_IFELSE([AC_LANG_SOURCE([[ |
||||
#define _GNU_SOURCE |
||||
#include <sched.h> |
||||
#include <signal.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <fcntl.h> |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
#include <sys/wait.h> |
||||
|
||||
int utsfn(void *d) { |
||||
char buffer[1024]; |
||||
const char *name = "autoconftest"; |
||||
int rc = sethostname(name, strlen(name)); |
||||
if (rc != 0) return 1; |
||||
gethostname(buffer, 1024); |
||||
return (strcmp(buffer, name) != 0); |
||||
} |
||||
|
||||
char st2[1024*1024]; |
||||
int fn(void *d) { |
||||
pid_t child; |
||||
int rc, status; |
||||
usleep(100000); /* synchronize by sleep */ |
||||
if (getuid() != 0) return 1; |
||||
child = clone(utsfn, st2 + 1024*1024, CLONE_NEWUTS|SIGCHLD, 0); |
||||
if (child < 0) return 1; |
||||
rc = waitpid(child, &status, 0); |
||||
if (rc <= 0) return 1; |
||||
if (!WIFEXITED(status)) return 1; |
||||
return WEXITSTATUS(status); |
||||
} |
||||
char st[1024*1024]; |
||||
int main() { |
||||
char buffer[1024]; |
||||
int rc, status, fd; |
||||
pid_t child = clone(fn, st + 1024*1024, CLONE_NEWUSER|SIGCHLD, 0); |
||||
if (child < 0) return 1; |
||||
|
||||
sprintf(buffer, "/proc/%d/uid_map", child); |
||||
fd = open(buffer, O_CREAT|O_WRONLY|O_TRUNC, 0755); |
||||
sprintf(buffer, "0 %d 1\n", getuid()); |
||||
write(fd, buffer, strlen(buffer)); |
||||
close(fd); |
||||
|
||||
rc = waitpid(child, &status, 0); |
||||
if (rc <= 0) return 1; |
||||
if (!WIFEXITED(status)) return 1; |
||||
return WEXITSTATUS(status); |
||||
} |
||||
]]) |
||||
],[ax_cv_uts_namespace=yes], [ax_cv_uts_namespace=no]) |
||||
AC_LANG_POP([C]) |
||||
]) |
||||
if test "$ax_cv_uts_namespace" = yes; then |
||||
AC_DEFINE([HAVE_UTS_NAMESPACE],[1],[Whether UTS namespaces are available]) |
||||
fi |
||||
]) # AX_CHECK_UTS_NAMESPACE |
@ -0,0 +1,141 @@ |
||||
#include "ares-test.h" |
||||
|
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <fcntl.h> |
||||
|
||||
#include <iostream> |
||||
#include <functional> |
||||
#include <string> |
||||
#include <sstream> |
||||
#include <vector> |
||||
|
||||
#ifdef HAVE_CONTAINER |
||||
|
||||
namespace ares { |
||||
namespace test { |
||||
|
||||
namespace { |
||||
|
||||
struct ContainerInfo { |
||||
std::string dirname_; |
||||
std::string hostname_; |
||||
std::string domainname_; |
||||
VoidToIntFn fn_; |
||||
}; |
||||
|
||||
int EnterContainer(void *data) { |
||||
ContainerInfo *container = (ContainerInfo*)data; |
||||
|
||||
if (verbose) { |
||||
std::cerr << "Running function in container {chroot='" |
||||
<< container->dirname_ << "', hostname='" << container->hostname_ |
||||
<< "', domainname='" << container->domainname_ << "'}" |
||||
<< std::endl; |
||||
} |
||||
|
||||
// Ensure we are apparently root before continuing.
|
||||
int count = 10; |
||||
while (getuid() != 0 && count > 0) { |
||||
usleep(100000); |
||||
count--; |
||||
} |
||||
if (getuid() != 0) { |
||||
std::cerr << "Child in user namespace has uid " << getuid() << std::endl; |
||||
return -1; |
||||
} |
||||
// Move into the specified directory.
|
||||
if (chdir(container->dirname_.c_str()) != 0) { |
||||
std::cerr << "Failed to chdir('" << container->dirname_ |
||||
<< "'), errno=" << errno << std::endl; |
||||
return -1; |
||||
} |
||||
// And make it the new root directory;
|
||||
char buffer[PATH_MAX + 1]; |
||||
if (getcwd(buffer, PATH_MAX) == NULL) { |
||||
std::cerr << "failed to retrieve cwd, errno=" << errno << std::endl; |
||||
return -1; |
||||
} |
||||
buffer[PATH_MAX] = '\0'; |
||||
if (chroot(buffer) != 0) { |
||||
std::cerr << "chroot('" << buffer << "') failed, errno=" << errno << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
// Set host/domainnames if specified
|
||||
if (!container->hostname_.empty()) { |
||||
if (sethostname(container->hostname_.c_str(), |
||||
container->hostname_.size()) != 0) { |
||||
std::cerr << "Failed to sethostname('" << container->hostname_ |
||||
<< "'), errno=" << errno << std::endl; |
||||
return -1; |
||||
} |
||||
} |
||||
if (!container->domainname_.empty()) { |
||||
if (setdomainname(container->domainname_.c_str(), |
||||
container->domainname_.size()) != 0) { |
||||
std::cerr << "Failed to setdomainname('" << container->domainname_ |
||||
<< "'), errno=" << errno << std::endl; |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
return container->fn_(); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
// Run a function while:
|
||||
// - chroot()ed into a particular directory
|
||||
// - having a specified hostname/domainname
|
||||
|
||||
int RunInContainer(const std::string& dirname, const std::string& hostname, |
||||
const std::string& domainname, VoidToIntFn fn) { |
||||
const int stack_size = 1024 * 1024; |
||||
std::vector<byte> stack(stack_size, 0); |
||||
ContainerInfo container = {dirname, hostname, domainname, fn}; |
||||
|
||||
// Start a child process in a new user and UTS namespace
|
||||
pid_t child = clone(EnterContainer, stack.data() + stack_size, |
||||
CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD, (void *)&container); |
||||
if (child < 0) { |
||||
std::cerr << "Failed to clone()" << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
// Build the UID map that makes us look like root inside the namespace.
|
||||
std::stringstream mapfiless; |
||||
mapfiless << "/proc/" << child << "/uid_map"; |
||||
std::string mapfile = mapfiless.str(); |
||||
int fd = open(mapfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0644); |
||||
if (fd < 0) { |
||||
std::cerr << "Failed to create '" << mapfile << "'" << std::endl; |
||||
return -1; |
||||
} |
||||
std::stringstream contentss; |
||||
contentss << "0 " << getuid() << " 1" << std::endl; |
||||
std::string content = contentss.str(); |
||||
int rc = write(fd, content.c_str(), content.size()); |
||||
if (rc != (int)content.size()) { |
||||
std::cerr << "Failed to write uid map to '" << mapfile << "'" << std::endl; |
||||
} |
||||
close(fd); |
||||
|
||||
// Wait for the child process and retrieve its status.
|
||||
int status; |
||||
waitpid(child, &status, 0); |
||||
if (rc <= 0) { |
||||
std::cerr << "Failed to waitpid(" << child << ")" << std::endl; |
||||
return -1; |
||||
} |
||||
if (!WIFEXITED(status)) { |
||||
std::cerr << "Child " << child << " did not exit normally" << std::endl; |
||||
return -1; |
||||
} |
||||
return status; |
||||
} |
||||
|
||||
} // namespace test
|
||||
} // namespace ares
|
||||
|
||||
#endif |
Loading…
Reference in new issue