mirror of https://github.com/grpc/grpc.git
[ObjC] dns service resolver for cf event engine (#33971)
re-submit of #33233 with refactored tests 1. split ios event engine tests to client tests (require oracle engine) and unit test 2. disable dns server setup in [dns_test.cc](https://github.com/grpc/grpc/blob/master/test/core/event_engine/test_suite/tests/dns_test.cc#L127) for ios test, this is what's caused the revert. 3. disable dns_test in cf_event_engine_test for MacOSpull/33994/merge
parent
c9fe64c409
commit
239a5fce2d
27 changed files with 724 additions and 14 deletions
@ -0,0 +1,229 @@ |
|||||||
|
// Copyright 2023 The gRPC Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifdef GPR_APPLE |
||||||
|
|
||||||
|
#include "absl/strings/str_format.h" |
||||||
|
|
||||||
|
#include "src/core/lib/address_utils/parse_address.h" |
||||||
|
#include "src/core/lib/event_engine/cf_engine/dns_service_resolver.h" |
||||||
|
#include "src/core/lib/event_engine/posix_engine/lockfree_event.h" |
||||||
|
#include "src/core/lib/event_engine/tcp_socket_utils.h" |
||||||
|
#include "src/core/lib/event_engine/trace.h" |
||||||
|
#include "src/core/lib/gprpp/host_port.h" |
||||||
|
|
||||||
|
namespace grpc_event_engine { |
||||||
|
namespace experimental { |
||||||
|
|
||||||
|
void DNSServiceResolverImpl::LookupHostname( |
||||||
|
EventEngine::DNSResolver::LookupHostnameCallback on_resolve, |
||||||
|
absl::string_view name, absl::string_view default_port) { |
||||||
|
GRPC_EVENT_ENGINE_DNS_TRACE( |
||||||
|
"DNSServiceResolverImpl::LookupHostname: name: %.*s, default_port: %.*s, " |
||||||
|
"this: %p", |
||||||
|
static_cast<int>(name.length()), name.data(), |
||||||
|
static_cast<int>(default_port.length()), default_port.data(), this); |
||||||
|
|
||||||
|
absl::string_view host; |
||||||
|
absl::string_view port_string; |
||||||
|
if (!grpc_core::SplitHostPort(name, &host, &port_string)) { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
status = absl::InvalidArgumentError( |
||||||
|
absl::StrCat("Unparseable name: ", name))]() mutable { |
||||||
|
on_resolve(status); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
GPR_ASSERT(!host.empty()); |
||||||
|
if (port_string.empty()) { |
||||||
|
if (default_port.empty()) { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
status = absl::InvalidArgumentError(absl::StrFormat( |
||||||
|
"No port in name %s or default_port argument", |
||||||
|
name))]() mutable { on_resolve(std::move(status)); }); |
||||||
|
return; |
||||||
|
} |
||||||
|
port_string = default_port; |
||||||
|
} |
||||||
|
|
||||||
|
int port = 0; |
||||||
|
if (port_string == "http") { |
||||||
|
port = 80; |
||||||
|
} else if (port_string == "https") { |
||||||
|
port = 443; |
||||||
|
} else if (!absl::SimpleAtoi(port_string, &port)) { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
status = absl::InvalidArgumentError(absl::StrCat( |
||||||
|
"Failed to parse port in name: ", name))]() mutable { |
||||||
|
on_resolve(std::move(status)); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(yijiem): Change this when refactoring code in
|
||||||
|
// src/core/lib/address_utils to use EventEngine::ResolvedAddress.
|
||||||
|
grpc_resolved_address addr; |
||||||
|
const std::string hostport = grpc_core::JoinHostPort(host, port); |
||||||
|
if (grpc_parse_ipv4_hostport(hostport.c_str(), &addr, |
||||||
|
/*log_errors=*/false) || |
||||||
|
grpc_parse_ipv6_hostport(hostport.c_str(), &addr, |
||||||
|
/*log_errors=*/false)) { |
||||||
|
// Early out if the target is an ipv4 or ipv6 literal, otherwise dns service
|
||||||
|
// responses with kDNSServiceErr_NoSuchRecord
|
||||||
|
std::vector<EventEngine::ResolvedAddress> result; |
||||||
|
result.emplace_back(reinterpret_cast<sockaddr*>(addr.addr), addr.len); |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
result = std::move(result)]() mutable { |
||||||
|
on_resolve(std::move(result)); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
DNSServiceRef sdRef; |
||||||
|
auto host_string = std::string{host}; |
||||||
|
auto error = DNSServiceGetAddrInfo( |
||||||
|
&sdRef, kDNSServiceFlagsTimeout | kDNSServiceFlagsReturnIntermediates, 0, |
||||||
|
kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, host_string.c_str(), |
||||||
|
&DNSServiceResolverImpl::ResolveCallback, this /* do not Ref */); |
||||||
|
|
||||||
|
if (error != kDNSServiceErr_NoError) { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
status = absl::UnknownError(absl::StrFormat( |
||||||
|
"DNSServiceGetAddrInfo failed with error:%d", |
||||||
|
error))]() mutable { on_resolve(std::move(status)); }); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
grpc_core::ReleasableMutexLock lock(&request_mu_); |
||||||
|
|
||||||
|
error = DNSServiceSetDispatchQueue(sdRef, queue_); |
||||||
|
if (error != kDNSServiceErr_NoError) { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve), |
||||||
|
status = absl::UnknownError(absl::StrFormat( |
||||||
|
"DNSServiceSetDispatchQueue failed with error:%d", |
||||||
|
error))]() mutable { on_resolve(std::move(status)); }); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
requests_.try_emplace( |
||||||
|
sdRef, DNSServiceRequest{ |
||||||
|
std::move(on_resolve), static_cast<uint16_t>(port), {}}); |
||||||
|
} |
||||||
|
|
||||||
|
/* static */ |
||||||
|
void DNSServiceResolverImpl::ResolveCallback( |
||||||
|
DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, |
||||||
|
DNSServiceErrorType errorCode, const char* hostname, |
||||||
|
const struct sockaddr* address, uint32_t ttl, void* context) { |
||||||
|
GRPC_EVENT_ENGINE_DNS_TRACE( |
||||||
|
"DNSServiceResolverImpl::ResolveCallback: sdRef: %p, flags: %x, " |
||||||
|
"interface: %d, errorCode: %d, hostname: %s, addressFamily: %d, ttl: " |
||||||
|
"%d, " |
||||||
|
"this: %p", |
||||||
|
sdRef, flags, interfaceIndex, errorCode, hostname, address->sa_family, |
||||||
|
ttl, context); |
||||||
|
|
||||||
|
// no need to increase refcount here, since ResolveCallback and Shutdown is
|
||||||
|
// called from the serial queue and it is guarenteed that it won't be called
|
||||||
|
// after the sdRef is deallocated
|
||||||
|
auto that = static_cast<DNSServiceResolverImpl*>(context); |
||||||
|
|
||||||
|
grpc_core::ReleasableMutexLock lock(&that->request_mu_); |
||||||
|
auto request_it = that->requests_.find(sdRef); |
||||||
|
GPR_ASSERT(request_it != that->requests_.end()); |
||||||
|
auto& request = request_it->second; |
||||||
|
|
||||||
|
if (errorCode != kDNSServiceErr_NoError && |
||||||
|
errorCode != kDNSServiceErr_NoSuchRecord) { |
||||||
|
request.on_resolve(absl::UnknownError(absl::StrFormat( |
||||||
|
"address lookup failed for %s: errorCode: %d", hostname, errorCode))); |
||||||
|
that->requests_.erase(request_it); |
||||||
|
DNSServiceRefDeallocate(sdRef); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// set received ipv4 or ipv6 response, even for kDNSServiceErr_NoSuchRecord to
|
||||||
|
// mark that the response for the stack is received, it is possible that the
|
||||||
|
// one stack receives some results and the other stack gets
|
||||||
|
// kDNSServiceErr_NoSuchRecord error.
|
||||||
|
if (address->sa_family == AF_INET) { |
||||||
|
request.has_ipv4_response = true; |
||||||
|
} else if (address->sa_family == AF_INET6) { |
||||||
|
request.has_ipv6_response = true; |
||||||
|
} |
||||||
|
|
||||||
|
// collect results if there is no error (not kDNSServiceErr_NoSuchRecord)
|
||||||
|
if (errorCode == kDNSServiceErr_NoError) { |
||||||
|
request.result.emplace_back(address, address->sa_len); |
||||||
|
auto& resolved_address = request.result.back(); |
||||||
|
if (address->sa_family == AF_INET) { |
||||||
|
(const_cast<sockaddr_in*>( |
||||||
|
reinterpret_cast<const sockaddr_in*>(resolved_address.address()))) |
||||||
|
->sin_port = htons(request.port); |
||||||
|
} else if (address->sa_family == AF_INET6) { |
||||||
|
(const_cast<sockaddr_in6*>( |
||||||
|
reinterpret_cast<const sockaddr_in6*>(resolved_address.address()))) |
||||||
|
->sin6_port = htons(request.port); |
||||||
|
} |
||||||
|
|
||||||
|
GRPC_EVENT_ENGINE_DNS_TRACE( |
||||||
|
"DNSServiceResolverImpl::ResolveCallback: " |
||||||
|
"sdRef: %p, hostname: %s, addressPort: %s, this: %p", |
||||||
|
sdRef, hostname, |
||||||
|
ResolvedAddressToString(resolved_address).value_or("ERROR").c_str(), |
||||||
|
context); |
||||||
|
} |
||||||
|
|
||||||
|
// received both ipv4 and ipv6 responses, and no more responses (e.g. multiple
|
||||||
|
// IP addresses for a domain name) are coming, finish `LookupHostname` resolve
|
||||||
|
// with the collected results.
|
||||||
|
if (!(flags & kDNSServiceFlagsMoreComing) && request.has_ipv4_response && |
||||||
|
request.has_ipv6_response) { |
||||||
|
if (request.result.empty()) { |
||||||
|
request.on_resolve(absl::NotFoundError(absl::StrFormat( |
||||||
|
"address lookup failed for %s: Domain name not found", hostname))); |
||||||
|
} else { |
||||||
|
request.on_resolve(std::move(request.result)); |
||||||
|
} |
||||||
|
that->requests_.erase(request_it); |
||||||
|
DNSServiceRefDeallocate(sdRef); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void DNSServiceResolverImpl::Shutdown() { |
||||||
|
dispatch_async_f(queue_, Ref().release(), [](void* thatPtr) { |
||||||
|
grpc_core::RefCountedPtr<DNSServiceResolverImpl> that{ |
||||||
|
static_cast<DNSServiceResolverImpl*>(thatPtr)}; |
||||||
|
grpc_core::MutexLock lock(&that->request_mu_); |
||||||
|
for (auto& kv : that->requests_) { |
||||||
|
auto& sdRef = kv.first; |
||||||
|
auto& request = kv.second; |
||||||
|
GRPC_EVENT_ENGINE_DNS_TRACE( |
||||||
|
"DNSServiceResolverImpl::Shutdown sdRef: %p, this: %p", sdRef, |
||||||
|
thatPtr); |
||||||
|
|
||||||
|
request.on_resolve( |
||||||
|
absl::CancelledError("DNSServiceResolverImpl::Shutdown")); |
||||||
|
DNSServiceRefDeallocate(static_cast<DNSServiceRef>(sdRef)); |
||||||
|
} |
||||||
|
that->requests_.clear(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
} // namespace grpc_event_engine
|
||||||
|
|
||||||
|
#endif // GPR_APPLE
|
@ -0,0 +1,117 @@ |
|||||||
|
// Copyright 2023 The gRPC Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef GRPC_SRC_CORE_LIB_EVENT_ENGINE_CF_ENGINE_DNS_SERVICE_RESOLVER_H |
||||||
|
#define GRPC_SRC_CORE_LIB_EVENT_ENGINE_CF_ENGINE_DNS_SERVICE_RESOLVER_H |
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifdef GPR_APPLE |
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h> |
||||||
|
#include <dns_sd.h> |
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h" |
||||||
|
|
||||||
|
#include <grpc/event_engine/event_engine.h> |
||||||
|
|
||||||
|
#include "src/core/lib/event_engine/cf_engine/cf_engine.h" |
||||||
|
#include "src/core/lib/gprpp/ref_counted.h" |
||||||
|
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||||
|
|
||||||
|
namespace grpc_event_engine { |
||||||
|
namespace experimental { |
||||||
|
|
||||||
|
class DNSServiceResolverImpl |
||||||
|
: public grpc_core::RefCounted<DNSServiceResolverImpl> { |
||||||
|
struct DNSServiceRequest { |
||||||
|
EventEngine::DNSResolver::LookupHostnameCallback on_resolve; |
||||||
|
uint16_t port; |
||||||
|
std::vector<EventEngine::ResolvedAddress> result; |
||||||
|
bool has_ipv4_response = false; |
||||||
|
bool has_ipv6_response = false; |
||||||
|
}; |
||||||
|
|
||||||
|
public: |
||||||
|
explicit DNSServiceResolverImpl(std::shared_ptr<CFEventEngine> engine) |
||||||
|
: engine_(std::move((engine))) {} |
||||||
|
~DNSServiceResolverImpl() override { |
||||||
|
GPR_ASSERT(requests_.empty()); |
||||||
|
dispatch_release(queue_); |
||||||
|
} |
||||||
|
|
||||||
|
void Shutdown(); |
||||||
|
|
||||||
|
void LookupHostname( |
||||||
|
EventEngine::DNSResolver::LookupHostnameCallback on_resolve, |
||||||
|
absl::string_view name, absl::string_view default_port); |
||||||
|
|
||||||
|
private: |
||||||
|
static void ResolveCallback(DNSServiceRef sdRef, DNSServiceFlags flags, |
||||||
|
uint32_t interfaceIndex, |
||||||
|
DNSServiceErrorType errorCode, |
||||||
|
const char* hostname, |
||||||
|
const struct sockaddr* address, uint32_t ttl, |
||||||
|
void* context); |
||||||
|
|
||||||
|
private: |
||||||
|
std::shared_ptr<CFEventEngine> engine_; |
||||||
|
// DNSServiceSetDispatchQueue requires a serial dispatch queue
|
||||||
|
dispatch_queue_t queue_ = |
||||||
|
dispatch_queue_create("dns_service_resolver", nullptr); |
||||||
|
grpc_core::Mutex request_mu_; |
||||||
|
absl::flat_hash_map<DNSServiceRef, DNSServiceRequest> requests_ |
||||||
|
ABSL_GUARDED_BY(request_mu_); |
||||||
|
}; |
||||||
|
|
||||||
|
class DNSServiceResolver : public EventEngine::DNSResolver { |
||||||
|
public: |
||||||
|
explicit DNSServiceResolver(std::shared_ptr<CFEventEngine> engine) |
||||||
|
: engine_(std::move(engine)), |
||||||
|
impl_(grpc_core::MakeRefCounted<DNSServiceResolverImpl>( |
||||||
|
std::move((engine_)))) {} |
||||||
|
|
||||||
|
~DNSServiceResolver() override { impl_->Shutdown(); } |
||||||
|
|
||||||
|
void LookupHostname( |
||||||
|
EventEngine::DNSResolver::LookupHostnameCallback on_resolve, |
||||||
|
absl::string_view name, absl::string_view default_port) override { |
||||||
|
impl_->LookupHostname(std::move(on_resolve), name, default_port); |
||||||
|
}; |
||||||
|
|
||||||
|
void LookupSRV(EventEngine::DNSResolver::LookupSRVCallback on_resolve, |
||||||
|
absl::string_view /* name */) override { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve)]() mutable { |
||||||
|
on_resolve(absl::UnimplementedError( |
||||||
|
"The DNS Service resolver does not support looking up SRV records")); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void LookupTXT(EventEngine::DNSResolver::LookupTXTCallback on_resolve, |
||||||
|
absl::string_view /* name */) override { |
||||||
|
engine_->Run([on_resolve = std::move(on_resolve)]() mutable { |
||||||
|
on_resolve(absl::UnimplementedError( |
||||||
|
"The DNS Service resolver does not support looking up TXT records")); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::shared_ptr<CFEventEngine> engine_; |
||||||
|
grpc_core::RefCountedPtr<DNSServiceResolverImpl> impl_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
} // namespace grpc_event_engine
|
||||||
|
|
||||||
|
#endif // GPR_APPLE
|
||||||
|
|
||||||
|
#endif // GRPC_SRC_CORE_LIB_EVENT_ENGINE_CF_ENGINE_DNS_SERVICE_RESOLVER_H
|
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* Copyright 2023 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#import <XCTest/XCTest.h> |
||||||
|
|
||||||
|
#include <grpc/grpc.h> |
||||||
|
|
||||||
|
#include "src/core/lib/event_engine/cf_engine/cf_engine.h" |
||||||
|
#include "test/core/event_engine/test_suite/event_engine_test_framework.h" |
||||||
|
#include "test/core/event_engine/test_suite/tests/dns_test.h" |
||||||
|
#include "test/core/event_engine/test_suite/tests/timer_test.h" |
||||||
|
#include "test/core/util/test_config.h" |
||||||
|
|
||||||
|
@interface EventEngineTimerTests : XCTestCase |
||||||
|
@end |
||||||
|
|
||||||
|
@implementation EventEngineTimerTests |
||||||
|
|
||||||
|
- (void)testAll { |
||||||
|
NSArray *arguments = [NSProcessInfo processInfo].arguments; |
||||||
|
int argc = (int)arguments.count; |
||||||
|
char **argv = static_cast<char **>(alloca((sizeof(char *) * (argc + 1)))); |
||||||
|
for (int index = 0; index < argc; index++) { |
||||||
|
argv[index] = const_cast<char *>([arguments[index] UTF8String]); |
||||||
|
} |
||||||
|
argv[argc] = NULL; |
||||||
|
|
||||||
|
testing::InitGoogleTest(&argc, (char **)argv); |
||||||
|
grpc::testing::TestEnvironment env(&argc, (char **)argv); |
||||||
|
|
||||||
|
auto factory = []() { |
||||||
|
return std::make_unique<grpc_event_engine::experimental::CFEventEngine>(); |
||||||
|
}; |
||||||
|
SetEventEngineFactories(factory, nullptr); |
||||||
|
grpc_event_engine::experimental::InitTimerTests(); |
||||||
|
grpc_event_engine::experimental::InitDNSTests(); |
||||||
|
// TODO(ctiller): EventEngine temporarily needs grpc to be initialized first |
||||||
|
// until we clear out the iomgr shutdown code. |
||||||
|
grpc_init(); |
||||||
|
int r = RUN_ALL_TESTS(); |
||||||
|
grpc_shutdown(); |
||||||
|
|
||||||
|
XCTAssertEqual(r, 0); |
||||||
|
} |
||||||
|
|
||||||
|
@end |
Loading…
Reference in new issue