From 65c6d8fc0368d37ccf63490159648036686ccda9 Mon Sep 17 00:00:00 2001 From: Viorel Munteanu Date: Sat, 5 Sep 2026 11:42:06 +0000 Subject: [PATCH] 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. --- .../test_network_join_scenarios.cpp | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/integration/test_network_join_scenarios.cpp b/tests/integration/test_network_join_scenarios.cpp index b74562b..aeaec0b 100644 --- a/tests/integration/test_network_join_scenarios.cpp +++ b/tests/integration/test_network_join_scenarios.cpp @@ -45,6 +45,7 @@ #include "commands.h" #include "config_file.h" #include "fixtures.h" +#include "network_dns.h" #include "network_subnet.h" namespace { @@ -245,6 +246,23 @@ std::string wait_for_eth0_then(const std::string& 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 // foreground, returning its captured stdout -- the same dispatch_command() // 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); } + +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); +}