Enable ipv6 endpoints support
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
This commit is contained in:
parent
b82efea7a9
commit
c47f5af6f2
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
|
os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
|
||||||
etcd: [v3.2.26, v3.3.11, v3.4.13, v3.5.7]
|
etcd: [v3.2.32, v3.3.27, v3.4.27, v3.5.9]
|
||||||
exclude:
|
exclude:
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
etcd: v3.2.26
|
etcd: v3.2.26
|
||||||
|
|
@ -243,6 +243,10 @@ jobs:
|
||||||
|
|
||||||
# tests without auth
|
# tests without auth
|
||||||
|
|
||||||
|
echo "Run the etcd resolver test ........................."
|
||||||
|
# there's no ipv6 on github CI runner
|
||||||
|
# ./build/bin/EtcdResolverTest
|
||||||
|
|
||||||
echo "Run the etcd sync test ........................."
|
echo "Run the etcd sync test ........................."
|
||||||
./build/bin/EtcdSyncTest
|
./build/bin/EtcdSyncTest
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,14 @@ Connecting to multiple endpoints is supported:
|
||||||
etcd::Client etcd("http://a.com:2379;http://b.com:2379;http://c.com:2379");
|
etcd::Client etcd("http://a.com:2379;http://b.com:2379;http://c.com:2379");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### IPv6
|
||||||
|
|
||||||
|
Connecting to IPv6 endpoints is supported:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
etcd::Client etcd("http://::1:2379");
|
||||||
|
```
|
||||||
|
|
||||||
Behind the screen, gRPC's load balancer is used and the round-robin strategy will
|
Behind the screen, gRPC's load balancer is used and the round-robin strategy will
|
||||||
be used by default.
|
be used by default.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,20 +99,20 @@ static std::string string_join(std::vector<std::string> const& srcs,
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dns_resolve(std::string const& target,
|
static bool dns_resolve(std::string const& target,
|
||||||
std::vector<std::string>& endpoints) {
|
std::vector<std::string>& endpoints, bool ipv4 = true) {
|
||||||
struct addrinfo hints = {}, *addrs;
|
|
||||||
hints.ai_family = AF_INET;
|
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
|
||||||
hints.ai_protocol = IPPROTO_TCP;
|
|
||||||
|
|
||||||
std::vector<std::string> target_parts;
|
std::vector<std::string> target_parts;
|
||||||
string_split(target_parts, target, ":");
|
{
|
||||||
if (target_parts.size() != 2) {
|
size_t rindex = target.rfind(':');
|
||||||
|
if (rindex == target.npos) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::cerr << "[warn] invalid URL: " << target << std::endl;
|
std::cerr << "[warn] invalid URL: " << target << ", expects 'host:port'"
|
||||||
|
<< std::endl;
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
target_parts.push_back(target.substr(0, rindex));
|
||||||
|
target_parts.push_back(target.substr(rindex + 1));
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
{
|
{
|
||||||
|
|
@ -132,22 +132,42 @@ static bool dns_resolve(std::string const& target,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct addrinfo hints = {}, *addrs;
|
||||||
|
hints.ai_family = ipv4 ? AF_INET : AF_INET6;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
int r = getaddrinfo(target_parts[0].c_str(), target_parts[1].c_str(), &hints,
|
int r = getaddrinfo(target_parts[0].c_str(), target_parts[1].c_str(), &hints,
|
||||||
&addrs);
|
&addrs);
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::cerr << "[warn] getaddrinfo() failed for endpoint " << target
|
std::cerr << "[warn] getaddrinfo() as " << (ipv4 ? "ipv4" : "ipv6")
|
||||||
<< " with error: " << r << std::endl;
|
<< " failed for endpoint " << target << " with error: " << r
|
||||||
|
<< ", " << strerror(errno) << std::endl;
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char host[16] = {'\0'};
|
char host[16] = {'\0'};
|
||||||
for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) {
|
for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) {
|
||||||
|
if (addr->ai_family != AF_INET && addr->ai_family != AF_INET6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
memset(host, '\0', sizeof(host));
|
memset(host, '\0', sizeof(host));
|
||||||
getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host), NULL, 0,
|
int r = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host),
|
||||||
NI_NUMERICHOST);
|
NULL, 0, NI_NUMERICHOST);
|
||||||
endpoints.emplace_back(std::string(host) + ":" + target_parts[1]);
|
if (r != 0) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::cerr << "[warn] getnameinfo() failed for endpoint " << target
|
||||||
|
<< " with error: " << r << ", " << strerror(errno) << std::endl;
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string host_string = host;
|
||||||
|
if (addr->ai_family == AF_INET6) {
|
||||||
|
host_string = "[" + host_string + "]";
|
||||||
|
}
|
||||||
|
endpoints.emplace_back(host_string + ":" + target_parts[1]);
|
||||||
}
|
}
|
||||||
freeaddrinfo(addrs);
|
freeaddrinfo(addrs);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -156,19 +176,28 @@ static bool dns_resolve(std::string const& target,
|
||||||
const std::string strip_and_resolve_addresses(std::string const& address) {
|
const std::string strip_and_resolve_addresses(std::string const& address) {
|
||||||
std::vector<std::string> addresses;
|
std::vector<std::string> addresses;
|
||||||
string_split(addresses, address, ",;");
|
string_split(addresses, address, ",;");
|
||||||
std::string stripped_address;
|
std::string stripped_v4_address, stripped_v6_address;
|
||||||
{
|
{
|
||||||
std::vector<std::string> stripped_addresses;
|
std::vector<std::string> stripped_v4_addresses, stripped_v6_addresses;
|
||||||
std::string substr("://");
|
std::string substr("://");
|
||||||
for (auto const& addr : addresses) {
|
for (auto const& addr : addresses) {
|
||||||
std::string::size_type idx = addr.find(substr);
|
std::string::size_type idx = addr.find(substr);
|
||||||
std::string target =
|
std::string target =
|
||||||
idx == std::string::npos ? addr : addr.substr(idx + substr.length());
|
idx == std::string::npos ? addr : addr.substr(idx + substr.length());
|
||||||
etcd::detail::dns_resolve(target, stripped_addresses);
|
etcd::detail::dns_resolve(target, stripped_v4_addresses, true);
|
||||||
|
etcd::detail::dns_resolve(target, stripped_v6_addresses, false);
|
||||||
}
|
}
|
||||||
stripped_address = string_join(stripped_addresses, ",");
|
stripped_v4_address = string_join(stripped_v4_addresses, ",");
|
||||||
|
stripped_v6_address = string_join(stripped_v6_addresses, ",");
|
||||||
}
|
}
|
||||||
return "ipv4:///" + stripped_address;
|
// prefer resolved ipv4 addresses
|
||||||
|
if (!stripped_v4_address.empty()) {
|
||||||
|
return "ipv4:///" + stripped_v4_address;
|
||||||
|
}
|
||||||
|
if (!stripped_v6_address.empty()) {
|
||||||
|
return "ipv6:///" + stripped_v6_address;
|
||||||
|
}
|
||||||
|
return std::string{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool authenticate(std::shared_ptr<grpc::Channel> const& channel,
|
bool authenticate(std::shared_ptr<grpc::Channel> const& channel,
|
||||||
|
|
@ -227,7 +256,10 @@ static std::shared_ptr<grpc::Channel> create_grpc_channel(
|
||||||
const grpc::ChannelArguments& grpc_args) {
|
const grpc::ChannelArguments& grpc_args) {
|
||||||
const std::string addresses =
|
const std::string addresses =
|
||||||
etcd::detail::strip_and_resolve_addresses(address);
|
etcd::detail::strip_and_resolve_addresses(address);
|
||||||
if (addresses.empty() || addresses == "ipv4:///") {
|
#ifndef NDEBUG
|
||||||
|
std::cerr << "[debug] resolved addresses: " << addresses << std::endl;
|
||||||
|
#endif
|
||||||
|
if (addresses.empty() || addresses == "ipv4:///" || addresses == "ipv6:///") {
|
||||||
// bypass grpc initialization to avoid noisy logs from grpc
|
// bypass grpc initialization to avoid noisy logs from grpc
|
||||||
return grpc::CreateChannelInternal(
|
return grpc::CreateChannelInternal(
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <catch.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "etcd/Client.hpp"
|
||||||
|
|
||||||
|
static const std::string etcd_v4_url =
|
||||||
|
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
|
||||||
|
static const std::string etcd_v6_url =
|
||||||
|
etcdv3::detail::resolve_etcd_endpoints("http://::1:2379");
|
||||||
|
|
||||||
|
TEST_CASE("test ipv4 connection") {
|
||||||
|
std::cout << "ipv4 endpoints: " << etcd_v4_url << std::endl;
|
||||||
|
etcd::Client etcd(etcd_v4_url);
|
||||||
|
REQUIRE(etcd.head().get().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("test ipv6 connection") {
|
||||||
|
std::cout << "ipv6 endpoints: " << etcd_v6_url << std::endl;
|
||||||
|
etcd::Client etcd(etcd_v6_url);
|
||||||
|
REQUIRE(etcd.head().get().is_ok());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue