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