Skip to content

Commit a782b50

Browse files
authored
Fix legacy_proxy_filter_integration_test CI (#39613)
Commit Message: Fix legacy_proxy_filter_integration_test CI Additional Description: Mocking out the DNS operations should make these tests resilient against environmental differences. Unfortunately it's a hideous thing to do, made worse by numeric IPv6 address parsing also using the mockable function. There are two main issues being fixed by this: 1. in some environments, DNS lookups for e.g. `nonexistent.example.com` time out because the DNS server is unreachable. 2. in some environments, IPv6 isn't available, so the IPv6 versions of the tests fails. Risk Level: test-only (plus making a pair of API calls both use the production-only OsSysCall like they should have been all along) Testing: Yes it is. Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Raven Black <[email protected]>
1 parent d44c808 commit a782b50

File tree

3 files changed

+93
-17
lines changed

3 files changed

+93
-17
lines changed

source/common/network/utility.cc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,31 @@ StatusOr<sockaddr_in6> parseV6Address(const std::string& ip_address, uint16_t po
133133
// we consume only the first element (if the call succeeds).
134134
hints.ai_socktype = SOCK_DGRAM;
135135
hints.ai_protocol = IPPROTO_UDP;
136-
const Api::SysCallIntResult rc = Api::OsSysCallsSingleton::get().getaddrinfo(
137-
ip_address.c_str(), /*service=*/nullptr, &hints, &res);
136+
137+
// We want to use the interface of OsSysCalls for this for the
138+
// platform-independence, but we don't want to use the common
139+
// OsSysCallsSingleton.
140+
//
141+
// The problem with using OsSysCallsSingleton is that we likely
142+
// want to override getaddrinfo() for DNS lookups in tests, but
143+
// typically that override would resolve a name to e.g.
144+
// the address from resolveUrl("tcp://[::1]:80") - but resolveUrl
145+
// calls parseV6Address, which calls getaddrinfo(), so if we use
146+
// the mock *here* then mocking DNS causes infinite recursion.
147+
//
148+
// We don't ever need to mock *this* getaddrinfo() call, because
149+
// it's only used to parse numeric IP addresses, per `ai_flags`,
150+
// so it should be deterministic resolution; there's no need to
151+
// mock it to test failure cases.
152+
static Api::OsSysCallsImpl os_sys_calls;
153+
154+
const Api::SysCallIntResult rc =
155+
os_sys_calls.getaddrinfo(ip_address.c_str(), /*service=*/nullptr, &hints, &res);
138156
if (rc.return_value_ != 0) {
139157
return absl::FailedPreconditionError(fmt::format("getaddrinfo error: {}", rc.return_value_));
140158
}
141159
sockaddr_in6 sa6 = *reinterpret_cast<sockaddr_in6*>(res->ai_addr);
142-
freeaddrinfo(res);
160+
os_sys_calls.freeaddrinfo(res);
143161
sa6.sin6_port = htons(port);
144162
return sa6;
145163
}

test/extensions/filters/http/dynamic_forward_proxy/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ envoy_extension_cc_test(
8787
"//source/extensions/network/dns_resolver/getaddrinfo:config",
8888
"//test/integration:http_integration_lib",
8989
"//test/integration/filters:stream_info_to_headers_filter_lib",
90+
"//test/test_common:threadsafe_singleton_injector_lib",
9091
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
9192
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
9293
"@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",
@@ -119,6 +120,7 @@ envoy_extension_cc_test(
119120
"//source/extensions/network/dns_resolver/getaddrinfo:config",
120121
"//test/integration:http_integration_lib",
121122
"//test/integration/filters:stream_info_to_headers_filter_lib",
123+
"//test/test_common:threadsafe_singleton_injector_lib",
122124
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
123125
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
124126
"@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",

test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,72 @@
1212
#include "test/integration/http_integration.h"
1313
#include "test/integration/ssl_utility.h"
1414
#include "test/test_common/registry.h"
15+
#include "test/test_common/threadsafe_singleton_injector.h"
1516

1617
using testing::HasSubstr;
1718

1819
namespace Envoy {
1920
namespace {
2021

22+
class OsSysCallsWithMockedDns : public Api::OsSysCallsImpl {
23+
public:
24+
static addrinfo* makeAddrInfo(const Network::Address::InstanceConstSharedPtr& addr) {
25+
addrinfo* ai = reinterpret_cast<addrinfo*>(malloc(sizeof(addrinfo)));
26+
memset(ai, 0, sizeof(addrinfo));
27+
ai->ai_protocol = IPPROTO_TCP;
28+
ai->ai_socktype = SOCK_STREAM;
29+
if (addr->ip()->ipv4() != nullptr) {
30+
ai->ai_family = AF_INET;
31+
} else {
32+
ai->ai_family = AF_INET6;
33+
}
34+
sockaddr_storage* storage =
35+
reinterpret_cast<sockaddr_storage*>(malloc(sizeof(sockaddr_storage)));
36+
ai->ai_addr = reinterpret_cast<sockaddr*>(storage);
37+
memcpy(ai->ai_addr, addr->sockAddr(), addr->sockAddrLen());
38+
ai->ai_addrlen = addr->sockAddrLen();
39+
return ai;
40+
}
41+
42+
Api::SysCallIntResult getaddrinfo(const char* node, const char* /*service*/,
43+
const addrinfo* /*hints*/, addrinfo** res) override {
44+
*res = nullptr;
45+
if (absl::string_view{"localhost"} == node) {
46+
if (ip_version_ == Network::Address::IpVersion::v6) {
47+
*res = makeAddrInfo(Network::Utility::getIpv6LoopbackAddress());
48+
} else {
49+
*res = makeAddrInfo(Network::Utility::getCanonicalIpv4LoopbackAddress());
50+
}
51+
return {0, 0};
52+
}
53+
if (nonexisting_addresses_.find(node) != nonexisting_addresses_.end()) {
54+
return {EAI_NONAME, 0};
55+
}
56+
std::cerr << "Mock DNS does not have entry for: " << node << std::endl;
57+
return {-1, 128};
58+
}
59+
void freeaddrinfo(addrinfo* ai) override {
60+
while (ai != nullptr) {
61+
addrinfo* p = ai;
62+
ai = ai->ai_next;
63+
free(p->ai_addr);
64+
free(p);
65+
}
66+
}
67+
68+
void setIpVersion(Network::Address::IpVersion version) { ip_version_ = version; }
69+
70+
Network::Address::IpVersion ip_version_ = Network::Address::IpVersion::v4;
71+
72+
absl::flat_hash_set<absl::string_view> nonexisting_addresses_ = {"doesnotexist.example.com",
73+
"itdoesnotexist"};
74+
};
75+
2176
class ProxyFilterIntegrationTest : public testing::TestWithParam<Network::Address::IpVersion>,
2277
public HttpIntegrationTest {
2378
public:
2479
ProxyFilterIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {
80+
mock_os_sys_calls_.setIpVersion(GetParam());
2581
upstream_tls_ = true;
2682
filename_ = TestEnvironment::temporaryPath("dns_cache.txt");
2783
::unlink(filename_.c_str());
@@ -36,6 +92,14 @@ class ProxyFilterIntegrationTest : public testing::TestWithParam<Network::Addres
3692
setUpstreamProtocol(Http::CodecType::HTTP1);
3793
}
3894

95+
void TearDown() override {
96+
// Shut down the server and upstreams before os_calls_ goes out of scope to avoid syscalls
97+
// during its removal racing with the unlatching of the mocks.
98+
test_server_.reset();
99+
cleanupUpstreamAndDownstream();
100+
fake_upstreams_.clear();
101+
}
102+
39103
void initialize() override { initializeWithArgs(); }
40104

41105
void initializeWithArgs(uint64_t max_hosts = 1024, uint32_t max_pending_requests = 1024,
@@ -349,6 +413,8 @@ name: envoy.clusters.dynamic_forward_proxy
349413
}
350414
}
351415

416+
OsSysCallsWithMockedDns mock_os_sys_calls_;
417+
TestThreadsafeSingletonInjector<Api::OsSysCallsImpl> os_calls_{&mock_os_sys_calls_};
352418
bool upstream_tls_{};
353419
bool low_stream_limits_{};
354420
std::string upstream_cert_name_{"upstreamlocalhost"};
@@ -422,9 +488,8 @@ TEST_P(ProxyFilterIntegrationTest, MultiPortTest) {
422488
requestWithBodyTest();
423489

424490
// Create a second upstream, and send a request there.
425-
// The second upstream is autonomous where the first was not so we'll only get a 200-ok if we hit
426-
// the new port.
427-
// this regression tests https://github.com/envoyproxy/envoy/issues/27331
491+
// The second upstream is autonomous where the first was not so we'll only get a 200-ok if we
492+
// hit the new port. this regression tests https://github.com/envoyproxy/envoy/issues/27331
428493
autonomous_upstream_ = true;
429494
createUpstream(Network::Test::getCanonicalLoopbackAddress(version_), upstreamConfig());
430495
default_request_headers_.setHost(
@@ -436,16 +501,6 @@ TEST_P(ProxyFilterIntegrationTest, MultiPortTest) {
436501

437502
// Do a sanity check using the getaddrinfo() resolver.
438503
TEST_P(ProxyFilterIntegrationTest, RequestWithBodyGetAddrInfoResolver) {
439-
// getaddrinfo() does not reliably return v6 addresses depending on the environment. For now
440-
// just run this on v4 which is most likely to succeed. In v6 only environments this test won't
441-
// run at all but should still be covered in public CI.
442-
if (GetParam() != Network::Address::IpVersion::v4) {
443-
return;
444-
}
445-
446-
// See https://github.com/envoyproxy/envoy/issues/28504.
447-
DISABLE_UNDER_WINDOWS;
448-
449504
requestWithBodyTest(R"EOF(
450505
typed_dns_resolver_config:
451506
name: envoy.network.dns_resolver.getaddrinfo
@@ -877,7 +932,8 @@ TEST_P(ProxyFilterIntegrationTest, UpstreamCleartext) {
877932
checkSimpleRequestSuccess(0, 0, response.get());
878933
}
879934

880-
// Regression test a bug where the host header was used for cache lookups rather than host:port key
935+
// Regression test a bug where the host header was used for cache lookups rather than host:port
936+
// key
881937
TEST_P(ProxyFilterIntegrationTest, CacheSansPort) {
882938
useAccessLog("%RESPONSE_CODE_DETAILS%");
883939
initializeWithArgs();

0 commit comments

Comments
 (0)