mirror of https://github.com/grpc/grpc.git
Add resolver for binder transport URI scheme (#27529)
This commit is part of the effort to create binder channel with GRPC_CLIENT_CHANNEL type. The resolver will be used during name resolution, and the result will later be used to identify the corresponding endpoint binder in SubchannelConnector. Besides the unit test, this change is tested with other changes locally end to end on real device.pull/27788/head
parent
f72f778565
commit
34fdb542b5
19 changed files with 459 additions and 0 deletions
@ -0,0 +1,9 @@ |
||||
Support for resolving the scheme used by binder transport implementation. |
||||
|
||||
The URI's authority is required to be empty. |
||||
|
||||
The path is used as the identifiers of endpoint binder objects and the length |
||||
limit of the identifier is the same as unix socket length limit. |
||||
|
||||
The length limit of the path should at least be 100 characters long. This is |
||||
guaranteed by `static_assert` in the implementation. |
@ -0,0 +1,139 @@ |
||||
// Copyright 2021 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> |
||||
|
||||
#include "src/core/lib/iomgr/port.h" |
||||
|
||||
#ifdef GRPC_HAVE_UNIX_SOCKET |
||||
|
||||
#include <sys/un.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/resolver_registry.h" |
||||
#include "src/core/ext/filters/client_channel/server_address.h" |
||||
#include "src/core/lib/address_utils/parse_address.h" |
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace { |
||||
|
||||
class BinderResolver : public Resolver { |
||||
public: |
||||
BinderResolver(ServerAddressList addresses, ResolverArgs args) |
||||
: result_handler_(std::move(args.result_handler)), |
||||
addresses_(std::move(addresses)), |
||||
channel_args_(grpc_channel_args_copy(args.args)) {} |
||||
|
||||
~BinderResolver() override { grpc_channel_args_destroy(channel_args_); }; |
||||
|
||||
void StartLocked() override { |
||||
Result result; |
||||
result.addresses = std::move(addresses_); |
||||
result.args = channel_args_; |
||||
channel_args_ = nullptr; |
||||
result_handler_->ReturnResult(std::move(result)); |
||||
} |
||||
|
||||
void ShutdownLocked() override {} |
||||
|
||||
private: |
||||
std::unique_ptr<ResultHandler> result_handler_; |
||||
ServerAddressList addresses_; |
||||
const grpc_channel_args* channel_args_ = nullptr; |
||||
}; |
||||
|
||||
class BinderResolverFactory : public ResolverFactory { |
||||
public: |
||||
bool IsValidUri(const URI& uri) const override { |
||||
return ParseUri(uri, nullptr); |
||||
} |
||||
|
||||
OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override { |
||||
ServerAddressList addresses; |
||||
if (!ParseUri(args.uri, &addresses)) return nullptr; |
||||
return MakeOrphanable<BinderResolver>(std::move(addresses), |
||||
std::move(args)); |
||||
} |
||||
|
||||
const char* scheme() const override { return "binder"; } |
||||
|
||||
private: |
||||
static grpc_error_handle BinderAddrPopulate( |
||||
absl::string_view path, grpc_resolved_address* resolved_addr) { |
||||
path = absl::StripPrefix(path, "/"); |
||||
if (path.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING("path is empty"); |
||||
} |
||||
// Store parsed path in a unix socket so it can be reinterpreted as
|
||||
// sockaddr. An invalid address family (AF_MAX) is set to make sure it won't
|
||||
// be accidentally used.
|
||||
memset(resolved_addr, 0, sizeof(*resolved_addr)); |
||||
struct sockaddr_un* un = |
||||
reinterpret_cast<struct sockaddr_un*>(resolved_addr->addr); |
||||
un->sun_family = AF_MAX; |
||||
static_assert(sizeof(un->sun_path) >= 101, |
||||
"unix socket path size is unexpectedly short"); |
||||
if (path.size() + 1 > sizeof(un->sun_path)) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat(path, " is too long to be handled")); |
||||
} |
||||
// `un` has already be set to zero, no need to append null after the string
|
||||
memcpy(un->sun_path, path.data(), path.size()); |
||||
resolved_addr->len = |
||||
static_cast<socklen_t>(sizeof(un->sun_family) + path.size() + 1); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
static bool ParseUri(const URI& uri, ServerAddressList* addresses) { |
||||
grpc_resolved_address addr; |
||||
{ |
||||
if (!uri.authority().empty()) { |
||||
gpr_log(GPR_ERROR, "authority is not supported in binder scheme"); |
||||
return false; |
||||
} |
||||
grpc_error_handle error = BinderAddrPopulate(uri.path(), &addr); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
gpr_log(GPR_ERROR, "%s", grpc_error_std_string(error).c_str()); |
||||
GRPC_ERROR_UNREF(error); |
||||
return false; |
||||
} |
||||
} |
||||
if (addresses != nullptr) { |
||||
addresses->emplace_back(addr, nullptr /* args */); |
||||
} |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
} // namespace
|
||||
} // namespace grpc_core
|
||||
|
||||
void grpc_resolver_binder_init() { |
||||
grpc_core::ResolverRegistry::Builder::RegisterResolverFactory( |
||||
absl::make_unique<grpc_core::BinderResolverFactory>()); |
||||
} |
||||
|
||||
void grpc_resolver_binder_shutdown() {} |
||||
|
||||
#else |
||||
|
||||
void grpc_resolver_binder_init() {} |
||||
|
||||
void grpc_resolver_binder_shutdown() {} |
||||
|
||||
#endif |
@ -0,0 +1,181 @@ |
||||
// Copyright 2021 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 <gtest/gtest.h> |
||||
|
||||
#include "src/core/lib/iomgr/port.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
#ifdef GRPC_HAVE_UNIX_SOCKET |
||||
|
||||
#include <sys/un.h> |
||||
|
||||
#include <cstring> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/resolver_registry.h" |
||||
#include "src/core/lib/channel/channel_args.h" |
||||
|
||||
// Registers the factory with `grpc_core::ResolverRegistry`. Defined in
|
||||
// binder_resolver.cc
|
||||
void grpc_resolver_binder_init(void); |
||||
|
||||
namespace { |
||||
|
||||
class BinderResolverTest : public ::testing::Test { |
||||
public: |
||||
BinderResolverTest() { |
||||
factory_ = grpc_core::ResolverRegistry::LookupResolverFactory("binder"); |
||||
} |
||||
~BinderResolverTest() override {} |
||||
static void SetUpTestSuite() { |
||||
grpc_init(); |
||||
if (grpc_core::ResolverRegistry::LookupResolverFactory("binder") == |
||||
nullptr) { |
||||
// Binder resolver will only be registered on platforms that support
|
||||
// binder transport. If it is not registered on current platform, we
|
||||
// manually register it here for testing purpose.
|
||||
grpc_resolver_binder_init(); |
||||
ASSERT_TRUE(grpc_core::ResolverRegistry::LookupResolverFactory("binder")); |
||||
} |
||||
} |
||||
static void TearDownTestSuite() { grpc_shutdown(); } |
||||
|
||||
void SetUp() override { ASSERT_TRUE(factory_); } |
||||
|
||||
class ResultHandler : public grpc_core::Resolver::ResultHandler { |
||||
public: |
||||
ResultHandler() = default; |
||||
|
||||
explicit ResultHandler(const std::string& expected_binder_id) |
||||
: expect_result_(true), expected_binder_id_(expected_binder_id) {} |
||||
|
||||
void ReturnResult(grpc_core::Resolver::Result result) override { |
||||
EXPECT_TRUE(expect_result_); |
||||
ASSERT_TRUE(result.addresses.size() == 1); |
||||
grpc_core::ServerAddress addr = result.addresses[0]; |
||||
const struct sockaddr_un* un = |
||||
reinterpret_cast<const struct sockaddr_un*>(addr.address().addr); |
||||
EXPECT_EQ(addr.address().len, |
||||
sizeof(un->sun_family) + expected_binder_id_.length() + 1); |
||||
EXPECT_EQ(un->sun_family, AF_MAX); |
||||
EXPECT_EQ(un->sun_path, expected_binder_id_); |
||||
} |
||||
|
||||
void ReturnError(grpc_error_handle error) override { |
||||
GRPC_ERROR_UNREF(error); |
||||
} |
||||
|
||||
private: |
||||
// Whether we expect ReturnResult function to be invoked
|
||||
bool expect_result_ = false; |
||||
|
||||
std::string expected_binder_id_; |
||||
}; |
||||
|
||||
void TestSucceeds(const char* string, const std::string& expected_path) { |
||||
gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string, |
||||
factory_->scheme()); |
||||
grpc_core::ExecCtx exec_ctx; |
||||
absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(string); |
||||
ASSERT_TRUE(uri.ok()) << uri.status().ToString(); |
||||
grpc_core::ResolverArgs args; |
||||
args.uri = std::move(*uri); |
||||
args.result_handler = |
||||
absl::make_unique<BinderResolverTest::ResultHandler>(expected_path); |
||||
grpc_core::OrphanablePtr<grpc_core::Resolver> resolver = |
||||
factory_->CreateResolver(std::move(args)); |
||||
ASSERT_TRUE(resolver != nullptr); |
||||
resolver->StartLocked(); |
||||
} |
||||
|
||||
void TestFails(const char* string) { |
||||
gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string, |
||||
factory_->scheme()); |
||||
grpc_core::ExecCtx exec_ctx; |
||||
absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(string); |
||||
ASSERT_TRUE(uri.ok()) << uri.status().ToString(); |
||||
grpc_core::ResolverArgs args; |
||||
args.uri = std::move(*uri); |
||||
args.result_handler = |
||||
absl::make_unique<BinderResolverTest::ResultHandler>(); |
||||
grpc_core::OrphanablePtr<grpc_core::Resolver> resolver = |
||||
factory_->CreateResolver(std::move(args)); |
||||
EXPECT_TRUE(resolver == nullptr); |
||||
} |
||||
|
||||
private: |
||||
grpc_core::ResolverFactory* factory_; |
||||
}; |
||||
|
||||
} // namespace
|
||||
|
||||
// Authority is not allowed
|
||||
TEST_F(BinderResolverTest, AuthorityPresents) { |
||||
TestFails("binder://example"); |
||||
TestFails("binder://google.com"); |
||||
TestFails("binder://google.com/test"); |
||||
} |
||||
|
||||
// Path cannot be empty
|
||||
TEST_F(BinderResolverTest, EmptyPath) { |
||||
TestFails("binder:"); |
||||
TestFails("binder:/"); |
||||
TestFails("binder://"); |
||||
} |
||||
|
||||
TEST_F(BinderResolverTest, PathLength) { |
||||
// Note that we have a static assert in binder_resolver.cc that checks
|
||||
// sizeof(sockaddr_un::sun_path) is greater than 100
|
||||
|
||||
// 100 character path should be fine
|
||||
TestSucceeds(("binder:l" + std::string(98, 'o') + "g").c_str(), |
||||
"l" + std::string(98, 'o') + "g"); |
||||
|
||||
// 200 character path most likely will fail
|
||||
TestFails(("binder:l" + std::string(198, 'o') + "g").c_str()); |
||||
} |
||||
|
||||
TEST_F(BinderResolverTest, SlashPrefixes) { |
||||
TestSucceeds("binder:///test", "test"); |
||||
TestSucceeds("binder:////test", "/test"); |
||||
} |
||||
|
||||
TEST_F(BinderResolverTest, ValidCases) { |
||||
TestSucceeds("binder:[[", "[["); |
||||
TestSucceeds("binder:google!com", "google!com"); |
||||
TestSucceeds("binder:test/", "test/"); |
||||
TestSucceeds("binder:test:", "test:"); |
||||
|
||||
TestSucceeds("binder:e", "e"); |
||||
TestSucceeds("binder:example", "example"); |
||||
TestSucceeds("binder:google.com", "google.com"); |
||||
TestSucceeds("binder:~", "~"); |
||||
TestSucceeds("binder:12345", "12345"); |
||||
TestSucceeds( |
||||
"binder:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._" |
||||
"~", |
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"); |
||||
} |
||||
|
||||
#endif |
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue