Add DNS hostname resolution ping tests (veth + tap+relay variants)

Two peers on the same intern network, one started with --hostname
peer-a, resolve and ping each other by name via the per-session dnsmasq
resolver (network_dns.cpp). Skips cleanly when dnsmasq isn't available.

wait_for_hostname_then() retries the whole ping-by-name probe (not just
an eth0 existence check) until it succeeds or the bound is hit, since
this races against two independent things starting concurrently with
the sandboxed command: the interface coming up, and the per-session
resolver picking up the peer's own hosts record -- same underlying
join_networks() timing limitation the earlier wait_for_eth0_then() fix
was for. Verified end-to-end as root, both variants.
This commit is contained in:
2026-09-05 11:42:06 +00:00
parent 4b264d3df3
commit 65c6d8fc03
@@ -45,6 +45,7 @@
#include "commands.h" #include "commands.h"
#include "config_file.h" #include "config_file.h"
#include "fixtures.h" #include "fixtures.h"
#include "network_dns.h"
#include "network_subnet.h" #include "network_subnet.h"
namespace { namespace {
@@ -245,6 +246,23 @@ std::string wait_for_eth0_then(const std::string& command_after_eth0) {
command_after_eth0); command_after_eth0);
} }
// Like wait_for_eth0_then(), but for a command that resolves `hostname`
// before using it: retries the whole ping-by-name probe (not just an eth0
// existence check) until it succeeds or the bound is hit, then runs the
// real, assertable command. A single probe covers two independent races
// against the same command starting concurrently with its own join
// (network_join.{h,cpp}'s own already-documented limitation): the
// interface itself not being up yet, and the per-session dnsmasq resolver
// (network_dns.cpp's start_dns_resolver(), started from the same
// on_bwrap_pid_known callback as the join itself) not having started, or
// not yet having picked up the peer's own hosts record, yet.
std::string wait_for_hostname_then(const std::string& hostname, const std::string& command_after) {
return fmt::format(
"echo BEGIN-TEST-OUTPUT; for i in $(seq 1 20); do ping -c 1 -W 1 {0} >/dev/null 2>&1 && break; "
"sleep 0.5; done; {1}; echo END-TEST-OUTPUT",
hostname, command_after);
}
// Runs `command` inside a fresh container joined to `networks`, in the // Runs `command` inside a fresh container joined to `networks`, in the
// foreground, returning its captured stdout -- the same dispatch_command() // foreground, returning its captured stdout -- the same dispatch_command()
// path BackgroundPeer's own forked child uses, just synchronous. // path BackgroundPeer's own forked child uses, just synchronous.
@@ -519,3 +537,68 @@ TEST_CASE("network join: two intern peers can ping each other by IP (tap+relay)"
} }
CHECK(found_success); CHECK(found_success);
} }
TEST_CASE("network join: two peers can resolve and ping each other by hostname (veth)", "[integration][root][net]") {
if (geteuid() != 0) {
SKIP("requires root");
}
if (!is_dnsmasq_available()) {
SKIP("dnsmasq not available");
}
auto image = find_busybox_fixture();
if (!image) {
SKIP("no busybox fixture (images/busybox.tar) -- see tests/setup-tests.py");
}
ScratchXdgDirs scratch;
TestNetwork network("selftest-dns-ping-veth", NetworkKind::intern, /*veth=*/true);
REQUIRE(network.created());
BackgroundPeer peer_a(*image, {network.name()}, std::string("peer-a"), 20);
REQUIRE(peer_a.ready());
auto output = run_networked(
*image, {network.name()},
{"sh", "-c", wait_for_hostname_then("peer-a", "ping -c 2 -W 2 peer-a; echo RESULT=$?")});
auto lines = extract_marked_lines(output);
bool found_success = false;
for (const auto& line : lines) {
if (line == "RESULT=0") {
found_success = true;
}
}
CHECK(found_success);
}
TEST_CASE("network join: two peers can resolve and ping each other by hostname (tap+relay)",
"[integration][root][net]") {
if (geteuid() != 0) {
SKIP("requires root");
}
if (!is_dnsmasq_available()) {
SKIP("dnsmasq not available");
}
auto image = find_busybox_fixture();
if (!image) {
SKIP("no busybox fixture (images/busybox.tar) -- see tests/setup-tests.py");
}
ScratchXdgDirs scratch;
TestNetwork network("selftest-dns-ping-tap", NetworkKind::intern, /*veth=*/false);
REQUIRE(network.created());
BackgroundPeer peer_a(*image, {network.name()}, std::string("peer-a"), 20);
REQUIRE(peer_a.ready());
auto output = run_networked(
*image, {network.name()},
{"sh", "-c", wait_for_hostname_then("peer-a", "ping -c 2 -W 2 peer-a; echo RESULT=$?")});
auto lines = extract_marked_lines(output);
bool found_success = false;
for (const auto& line : lines) {
if (line == "RESULT=0") {
found_success = true;
}
}
CHECK(found_success);
}