Port the 3 root network self-tests to tagged Catch2 TEST_CASEs
persistent-netns, tap-relay, and dns-resolver (formerly hand-rolled bool-returning functions in self_test.cpp, called unconditionally by the old ad hoc run_self_tests()) move to tests/integration/test_root_networking.cpp as TEST_CASEs tagged [integration][root][net] -- SKIP() (not a whole-suite skip) when not root, or when dnsmasq isn't installed for the DNS one, so e.g. -t -- "[unit]" on a rootless machine is unaffected. Every assertion uses CHECK, not REQUIRE: these tests manage real host-side namespaces, bridges, and tap devices that must not leak just because an earlier assertion failed, so execution always falls through to the same unconditional cleanup at the end (guarded only by simple pid/bool checks to skip meaningless dependent steps). Real bug found running the tap-relay test under Catch2, not assumed: Catch2 installs its own fatal-signal handler around a running TEST_CASE, which create_tap_relay()'s own forked relay child inherits -- so the relay's ordinary shutdown SIGTERM (sent by stop_tap_relay()) got caught by that *inherited* handler in the child instead of terminating it via the default disposition the relay's own design relies on, producing a spurious "FAILED ... due to a fatal error condition: SIGTERM" report interleaved into the real output (confirmed cosmetic only -- exit code and assertion count were correct either way, just confusing). Fixed by resetting SIGTERM to SIG_DFL for the narrow window around the create_tap_relay() call and restoring it right after -- only the disposition at fork time is inherited, so nothing about how long the relay then keeps running matters. No production code changed for this; it's purely an artifact of forking network primitives from within a Catch2-instrumented process. meson.build: the new tests/integration/*.cpp sources are only added to slocker-lite's own source list when enable_tests is true (mirroring config.h's ENABLE_TESTS runtime guard, added last commit), and src/ is added to the target's own include_directories so test sources under tests/ can #include project headers the same way src/*.cpp already does. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
+11
-2
@@ -16,6 +16,15 @@ conf_data.set10('ENABLE_TESTS', get_option('enable_tests'))
|
||||
|
||||
configure_file(output : 'config.h', configuration : conf_data)
|
||||
|
||||
# TEST_CASE-containing sources -- only buildable/linkable when catch2_dep is
|
||||
# actually present, so kept out of the executable's sources entirely (not
|
||||
# just "compiled but unused") when enable_tests is off, matching config.h's
|
||||
# own ENABLE_TESTS-guarded runtime message in self_test.cpp.
|
||||
test_sources = []
|
||||
if get_option('enable_tests')
|
||||
test_sources = ['tests/integration/test_root_networking.cpp']
|
||||
endif
|
||||
|
||||
slocker_lite = executable('slocker-lite',
|
||||
['src/main.cpp', 'src/cli_args.cpp', 'src/commands.cpp', 'src/self_test.cpp',
|
||||
'src/process.cpp', 'src/oci_image.cpp', 'src/containers_storage.cpp',
|
||||
@@ -24,8 +33,8 @@ slocker_lite = executable('slocker-lite',
|
||||
'src/sandbox_process.cpp', 'src/session_cgroup.cpp', 'src/kill_session.cpp',
|
||||
'src/network_subnet.cpp', 'src/persistent_netns.cpp', 'src/network_bridge.cpp',
|
||||
'src/network_join.cpp', 'src/port_forward.cpp', 'src/network_tap_relay.cpp',
|
||||
'src/network_dns.cpp'],
|
||||
include_directories : include_directories('.'),
|
||||
'src/network_dns.cpp'] + test_sources,
|
||||
include_directories : include_directories('.', 'src'),
|
||||
dependencies : [fmt_dep, catch2_dep, yaml_dep, archive_dep, json_dep, spdlog_dep],
|
||||
install : true)
|
||||
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
// 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.
|
||||
|
||||
// [integration][root][net] tests exercising real host-side network state:
|
||||
// persistent network namespaces, the tap+relay veth substitute, and the
|
||||
// per-session DNS resolver. Ported from the old hand-rolled self_test.cpp
|
||||
// (see git history) into proper Catch2 TEST_CASEs -- every assertion uses
|
||||
// CHECK (not REQUIRE) specifically so a failure partway through still lets
|
||||
// the rest of the function reach its own cleanup at the end, since these
|
||||
// tests manage real OS-level resources (namespaces, bridges, tap devices,
|
||||
// forked processes) that must not leak just because an earlier assertion
|
||||
// failed. Simple `if`/pid guards skip dependent steps once a prerequisite
|
||||
// is known to have failed, rather than a REQUIRE-triggered abort that
|
||||
// would skip cleanup entirely.
|
||||
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <string>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "config_file.h"
|
||||
#include "network_dns.h"
|
||||
#include "network_tap_relay.h"
|
||||
#include "persistent_netns.h"
|
||||
#include "process.h"
|
||||
#include "sandbox_process.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Bounded (1s, 20ms interval) poll for `child_pid`'s own net namespace to
|
||||
// actually differ from ours -- fork() returning to the parent doesn't mean
|
||||
// the child has reached its own unshare(CLONE_NEWNET) call yet, the same
|
||||
// race network_join.cpp's own wait_for_isolated_net_namespace() guards
|
||||
// against for a real session's namespace.
|
||||
bool wait_for_net_namespace_isolated(pid_t child_pid) {
|
||||
for (int elapsed_ms = 0; elapsed_ms <= 1000; elapsed_ms += 20) {
|
||||
if (namespace_isolated(getpid(), child_pid, "net")) {
|
||||
return true;
|
||||
}
|
||||
struct timespec ts {
|
||||
0, 20L * 1000000L
|
||||
};
|
||||
while (nanosleep(&ts, &ts) != 0 && errno == EINTR) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("persistent network namespace create/verify/remove", "[integration][root][net]") {
|
||||
if (geteuid() != 0) {
|
||||
SKIP("requires root");
|
||||
}
|
||||
|
||||
constexpr std::string_view test_netns_name = "selftest";
|
||||
|
||||
// Clean up a leftover from a previous interrupted run, if any --
|
||||
// create_persistent_netns() refuses to overwrite an existing live
|
||||
// namespace.
|
||||
if (persistent_netns_exists(test_netns_name)) {
|
||||
remove_persistent_netns(test_netns_name);
|
||||
}
|
||||
|
||||
bool created = create_persistent_netns(test_netns_name);
|
||||
CHECK(created);
|
||||
|
||||
if (created) {
|
||||
// Checked from this (parent) process, after the child that
|
||||
// actually did the unshare()/bind-mount has already exited --
|
||||
// this is exactly what confirms the namespace outlives its
|
||||
// creating process, the whole point of the bind-mount technique.
|
||||
CHECK(persistent_netns_exists(test_netns_name));
|
||||
CHECK(remove_persistent_netns(test_netns_name));
|
||||
CHECK_FALSE(persistent_netns_exists(test_netns_name));
|
||||
}
|
||||
}
|
||||
|
||||
// Exercises network_tap_relay.h's create/verify/teardown cycle end to end:
|
||||
// a throwaway bridge stands in for a real network's bridge, inside a
|
||||
// throwaway persistent namespace (persistent_netns.h) standing in for a
|
||||
// real network's own dedicated one -- create_tap_relay() always enters
|
||||
// that namespace first (network_bridge.cpp's wrap_for_network() reaches a
|
||||
// real network's bridge the same way, both kinds), so this test needs one
|
||||
// too, even though network_bridge.h's own provisioning isn't otherwise
|
||||
// exercised here (a tap device only cares that *some* bridge interface
|
||||
// exists to attach to). A throwaway network namespace (kept alive by a
|
||||
// child blocked in pause()) stands in for a real -r/--run session's
|
||||
// isolated net namespace.
|
||||
TEST_CASE("tap-relay create/attach/teardown", "[integration][root][net]") {
|
||||
if (geteuid() != 0) {
|
||||
SKIP("requires root");
|
||||
}
|
||||
|
||||
const std::string test_network_name = "selftest-tap-relay";
|
||||
const std::string test_bridge = "slkselftest0";
|
||||
const std::string host_tap = "thselftest0";
|
||||
const std::string container_if = "ethselftest";
|
||||
|
||||
if (persistent_netns_exists(test_network_name)) {
|
||||
remove_persistent_netns(test_network_name);
|
||||
}
|
||||
bool netns_created = create_persistent_netns(test_network_name);
|
||||
CHECK(netns_created);
|
||||
|
||||
std::vector<std::string> netns_wrap = {
|
||||
"nsenter", fmt::format("--net={}", persistent_netns_path(test_network_name).string()), "--"};
|
||||
auto in_netns = [&netns_wrap](std::vector<std::string> argv) {
|
||||
std::vector<std::string> wrapped = netns_wrap;
|
||||
wrapped.insert(wrapped.end(), argv.begin(), argv.end());
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
bool bridge_ready = netns_created &&
|
||||
run_process(in_netns({"ip", "link", "add", test_bridge, "type", "bridge"})).exit_code == 0 &&
|
||||
run_process(in_netns({"ip", "link", "set", test_bridge, "up"})).exit_code == 0;
|
||||
CHECK(bridge_ready);
|
||||
|
||||
// A throwaway, otherwise-empty network namespace standing in for a
|
||||
// real session's own -- kept alive only by this child blocking in
|
||||
// pause() until signaled, mirroring how bwrap's own sandboxed child
|
||||
// keeps a real session's namespace alive for as long as it runs.
|
||||
pid_t container_pid = fork();
|
||||
CHECK(container_pid >= 0);
|
||||
if (container_pid == 0) {
|
||||
if (unshare(CLONE_NEWNET) != 0) {
|
||||
_exit(1);
|
||||
}
|
||||
pause();
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
bool isolated = container_pid > 0 && wait_for_net_namespace_isolated(container_pid);
|
||||
if (container_pid > 0) {
|
||||
CHECK(isolated);
|
||||
}
|
||||
|
||||
std::optional<TapRelayHandle> relay;
|
||||
if (bridge_ready && isolated) {
|
||||
NetworkEntry network{test_network_name, NetworkKind::extern_, "", false, "", true};
|
||||
// create_tap_relay() forks a relay child that relies on SIGTERM's
|
||||
// *default* disposition to terminate cleanly once stop_tap_relay()
|
||||
// (below) signals it -- network_tap_relay.cpp's own doc comment on
|
||||
// the relay loop is explicit that no SIGTERM handler is installed
|
||||
// there, by design. Real bug found running this under Catch2, not
|
||||
// assumed: Catch2 installs its own fatal-signal handler around a
|
||||
// running TEST_CASE, which the forked child inherits -- so its own
|
||||
// ordinary shutdown signal gets caught by that inherited handler
|
||||
// *in the child* instead of terminating it, producing a spurious
|
||||
// "FAILED ... due to a fatal error condition: SIGTERM" report
|
||||
// interleaved into this process's own Catch2 output (confirmed:
|
||||
// the actual test result/exit code were unaffected either way --
|
||||
// just confusing, misleading terminal output). Only the
|
||||
// disposition *at fork time* is inherited, so resetting SIGTERM to
|
||||
// SIG_DFL just around this call (and restoring right after) is
|
||||
// enough; nothing about how long the relay child then keeps
|
||||
// running matters.
|
||||
auto previous_sigterm = signal(SIGTERM, SIG_DFL);
|
||||
relay = create_tap_relay(network, test_bridge, host_tap, container_pid, container_if);
|
||||
signal(SIGTERM, previous_sigterm);
|
||||
CHECK(relay.has_value());
|
||||
}
|
||||
|
||||
if (relay) {
|
||||
auto host_check = run_process(in_netns({"ip", "link", "show", host_tap}));
|
||||
CHECK(host_check.exit_code == 0);
|
||||
CHECK(host_check.stdout_output.find("master " + test_bridge) != std::string::npos);
|
||||
|
||||
auto container_check = run_process({"nsenter", fmt::format("--net=/proc/{}/ns/net", container_pid), "--",
|
||||
"ip", "link", "show", container_if});
|
||||
CHECK(container_check.exit_code == 0);
|
||||
|
||||
stop_tap_relay(*relay);
|
||||
|
||||
CHECK(run_process(in_netns({"ip", "link", "show", host_tap})).exit_code != 0);
|
||||
// The container-side device is expected to survive the relay
|
||||
// stopping -- it's persistent (create_persistent_tap(),
|
||||
// network_tap_relay.cpp) and lives inside the container's own
|
||||
// network namespace, which stopping the relay doesn't touch. It
|
||||
// only disappears once that namespace itself is destroyed, below.
|
||||
CHECK(run_process({"nsenter", fmt::format("--net=/proc/{}/ns/net", container_pid), "--", "ip", "link",
|
||||
"show", container_if})
|
||||
.exit_code == 0);
|
||||
}
|
||||
|
||||
if (container_pid > 0) {
|
||||
kill(container_pid, SIGKILL);
|
||||
int status = 0;
|
||||
waitpid(container_pid, &status, 0);
|
||||
}
|
||||
if (bridge_ready) {
|
||||
run_process(in_netns({"ip", "link", "del", test_bridge}));
|
||||
}
|
||||
if (netns_created) {
|
||||
remove_persistent_netns(test_network_name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Builds a minimal DNS query (one question, A record, class IN) for `name`,
|
||||
// wire format -- just enough to exercise the resolver's own answer path,
|
||||
// not a general-purpose DNS client.
|
||||
std::vector<uint8_t> build_dns_a_query(const std::string& name, uint16_t id) {
|
||||
std::vector<uint8_t> packet;
|
||||
auto push16 = [&](uint16_t v) {
|
||||
packet.push_back(static_cast<uint8_t>(v >> 8));
|
||||
packet.push_back(static_cast<uint8_t>(v & 0xff));
|
||||
};
|
||||
push16(id);
|
||||
push16(0x0100); // standard query, recursion desired
|
||||
push16(1); // qdcount
|
||||
push16(0);
|
||||
push16(0);
|
||||
push16(0); // ancount/nscount/arcount
|
||||
|
||||
size_t start = 0;
|
||||
while (start <= name.size()) {
|
||||
size_t dot = name.find('.', start);
|
||||
std::string label = name.substr(start, dot == std::string::npos ? std::string::npos : dot - start);
|
||||
packet.push_back(static_cast<uint8_t>(label.size()));
|
||||
packet.insert(packet.end(), label.begin(), label.end());
|
||||
if (dot == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = dot + 1;
|
||||
}
|
||||
packet.push_back(0);
|
||||
push16(1); // qtype A
|
||||
push16(1); // qclass IN
|
||||
return packet;
|
||||
}
|
||||
|
||||
// Sends build_dns_a_query(name) to 127.0.0.1:53 from *inside* `ns_pid`'s own
|
||||
// network namespace (a plain setns() here, same technique
|
||||
// network_tap_relay.cpp's own enter_namespace() uses, done in a throwaway
|
||||
// forked child so this process's own namespace is untouched either way) and
|
||||
// parses the first answer's 4-byte A-record rdata back out. Returns false on
|
||||
// any failure (including NXDOMAIN/no answer) -- out_ip is only meaningful
|
||||
// when this returns true.
|
||||
bool query_dns_a_record(pid_t ns_pid, const std::string& name, std::string& out_ip) {
|
||||
int report_pipe[2];
|
||||
if (pipe(report_pipe) != 0) {
|
||||
return false;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
close(report_pipe[0]);
|
||||
close(report_pipe[1]);
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
close(report_pipe[0]);
|
||||
int ns_fd = open(fmt::format("/proc/{}/ns/net", ns_pid).c_str(), O_RDONLY);
|
||||
if (ns_fd < 0 || setns(ns_fd, CLONE_NEWNET) != 0) {
|
||||
_exit(1);
|
||||
}
|
||||
close(ns_fd);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
_exit(1);
|
||||
}
|
||||
struct timeval tv {
|
||||
2, 0
|
||||
};
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
struct sockaddr_in addr {};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(53);
|
||||
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
||||
|
||||
auto query = build_dns_a_query(name, 0x1234);
|
||||
if (sendto(sock, query.data(), query.size(), 0, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) <
|
||||
0) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
uint8_t buf[512];
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
ssize_t n = recvfrom(sock, buf, sizeof(buf), 0, reinterpret_cast<struct sockaddr*>(&addr), &addrlen);
|
||||
if (n < 12) {
|
||||
_exit(1);
|
||||
}
|
||||
uint16_t rcode = static_cast<uint16_t>(buf[3] & 0x0f);
|
||||
uint16_t ancount = static_cast<uint16_t>((buf[6] << 8) | buf[7]);
|
||||
if (ancount == 0) {
|
||||
// Exit code carries the RCODE (offset by 10 to stay clear of
|
||||
// the other exit codes above) -- e.g. 13 = NXDOMAIN,
|
||||
// 15 = REFUSED (the latter is exactly what caught this
|
||||
// project's own dnsmasq privilege-drop bug during development,
|
||||
// see start_dns_resolver()'s own --user=root/--group=root
|
||||
// comment).
|
||||
_exit(10 + rcode);
|
||||
}
|
||||
|
||||
// Skip the 12-byte header, then the question (the exact same
|
||||
// qname this process just sent, plus 4 bytes qtype/qclass).
|
||||
size_t pos = 12;
|
||||
while (pos < static_cast<size_t>(n) && buf[pos] != 0) {
|
||||
pos += static_cast<size_t>(buf[pos]) + 1;
|
||||
}
|
||||
pos += 1 + 4;
|
||||
|
||||
// The answer's own name field: a compression pointer (top two
|
||||
// bits set) is 2 bytes; a literal label sequence is read the same
|
||||
// way as the question's own qname above.
|
||||
if (pos < static_cast<size_t>(n) && (buf[pos] & 0xc0) == 0xc0) {
|
||||
pos += 2;
|
||||
} else {
|
||||
while (pos < static_cast<size_t>(n) && buf[pos] != 0) {
|
||||
pos += static_cast<size_t>(buf[pos]) + 1;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
pos += 2 + 2 + 4; // type, class, ttl
|
||||
if (pos + 2 > static_cast<size_t>(n)) {
|
||||
_exit(1);
|
||||
}
|
||||
uint16_t rdlength = static_cast<uint16_t>((buf[pos] << 8) | buf[pos + 1]);
|
||||
pos += 2;
|
||||
if (rdlength != 4 || pos + 4 > static_cast<size_t>(n)) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
char ip_str[32];
|
||||
std::snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u", buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]);
|
||||
ssize_t unused = write(report_pipe[1], ip_str, std::strlen(ip_str));
|
||||
(void)unused;
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
close(report_pipe[1]);
|
||||
char buf[64] = {0};
|
||||
ssize_t n = read(report_pipe[0], buf, sizeof(buf) - 1);
|
||||
close(report_pipe[0]);
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
if (n <= 0) {
|
||||
return false;
|
||||
}
|
||||
out_ip.assign(buf, static_cast<size_t>(n));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Exercises network_dns.h's create/answer/teardown cycle end to end: a
|
||||
// throwaway network namespace (same fork+unshare(CLONE_NEWNET)+pause()
|
||||
// technique the tap-relay test above uses) stands in for a real -r/--run
|
||||
// session's own isolated namespace. A single dns-hosts record is written
|
||||
// by hand (record_dns_host()) for a fabricated `intern` network,
|
||||
// start_dns_resolver() is pointed at it, and query_dns_a_record() above
|
||||
// confirms the resolver actually answers with the recorded address -- real
|
||||
// UDP wire format, not just "the process started". Skipped if dnsmasq
|
||||
// isn't installed, same "best-effort dependency" policy the feature itself
|
||||
// has.
|
||||
TEST_CASE("dns-resolver create/answer/teardown", "[integration][root][net]") {
|
||||
if (geteuid() != 0) {
|
||||
SKIP("requires root");
|
||||
}
|
||||
if (!is_dnsmasq_available()) {
|
||||
SKIP("dnsmasq not found in PATH");
|
||||
}
|
||||
|
||||
const std::string test_network_name = "selftest-dns";
|
||||
const std::string test_hostname = "selftest-peer";
|
||||
const std::string test_ip = "10.99.99.2";
|
||||
|
||||
pid_t container_pid = fork();
|
||||
CHECK(container_pid >= 0);
|
||||
if (container_pid == 0) {
|
||||
if (unshare(CLONE_NEWNET) != 0) {
|
||||
_exit(1);
|
||||
}
|
||||
// Not automatically up in a fresh network namespace -- the
|
||||
// resolver needs to bind 127.0.0.1.
|
||||
run_process({"ip", "link", "set", "lo", "up"});
|
||||
pause();
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
bool isolated = container_pid > 0 && wait_for_net_namespace_isolated(container_pid);
|
||||
if (container_pid > 0) {
|
||||
CHECK(isolated);
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(dns_hosts_dir(test_network_name), ec);
|
||||
if (isolated) {
|
||||
record_dns_host(test_network_name, "selftest-peer-container", 1, test_ip, test_hostname);
|
||||
}
|
||||
|
||||
std::optional<DnsResolverHandle> resolver;
|
||||
if (isolated) {
|
||||
NetworkEntry test_network{test_network_name, NetworkKind::intern, "", false, "", true};
|
||||
JoinedNetwork joined_network{test_network, test_ip, std::nullopt};
|
||||
resolver = start_dns_resolver("selftest-dns-session", container_pid, container_pid, {joined_network});
|
||||
CHECK(resolver.has_value());
|
||||
}
|
||||
|
||||
if (resolver) {
|
||||
std::string answered_ip;
|
||||
bool answered = query_dns_a_record(container_pid, test_hostname, answered_ip);
|
||||
CHECK(answered);
|
||||
if (answered) {
|
||||
CHECK(answered_ip == test_ip);
|
||||
}
|
||||
|
||||
stop_dns_resolver(*resolver);
|
||||
}
|
||||
remove_dns_resolver_record("selftest-dns-session", container_pid);
|
||||
remove_dns_host_record(test_network_name, "selftest-peer-container", 1);
|
||||
std::filesystem::remove_all(dns_hosts_dir(test_network_name), ec);
|
||||
|
||||
if (container_pid > 0) {
|
||||
kill(container_pid, SIGKILL);
|
||||
int status = 0;
|
||||
waitpid(container_pid, &status, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user