Enable ipv6 endpoints support

Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
This commit is contained in:
Tao He 2023-12-20 16:18:25 +08:00
parent b82efea7a9
commit c47f5af6f2
4 changed files with 89 additions and 22 deletions

View File

@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
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:
- os: ubuntu-20.04
etcd: v3.2.26
@ -243,6 +243,10 @@ jobs:
# 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 ........................."
./build/bin/EtcdSyncTest

View File

@ -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");
```
### 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
be used by default.

View File

@ -99,19 +99,19 @@ static std::string string_join(std::vector<std::string> const& srcs,
}
static bool dns_resolve(std::string const& target,
std::vector<std::string>& endpoints) {
struct addrinfo hints = {}, *addrs;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
std::vector<std::string>& endpoints, bool ipv4 = true) {
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
std::cerr << "[warn] invalid URL: " << target << std::endl;
std::cerr << "[warn] invalid URL: " << target << ", expects 'host:port'"
<< std::endl;
#endif
return false;
return false;
}
target_parts.push_back(target.substr(0, rindex));
target_parts.push_back(target.substr(rindex + 1));
}
#if defined(_WIN32)
@ -132,22 +132,42 @@ static bool dns_resolve(std::string const& target,
}
#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,
&addrs);
if (r != 0) {
#ifndef NDEBUG
std::cerr << "[warn] getaddrinfo() failed for endpoint " << target
<< " with error: " << r << std::endl;
std::cerr << "[warn] getaddrinfo() as " << (ipv4 ? "ipv4" : "ipv6")
<< " failed for endpoint " << target << " with error: " << r
<< ", " << strerror(errno) << std::endl;
#endif
return false;
}
char host[16] = {'\0'};
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));
getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host), NULL, 0,
NI_NUMERICHOST);
endpoints.emplace_back(std::string(host) + ":" + target_parts[1]);
int r = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host),
NULL, 0, NI_NUMERICHOST);
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);
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) {
std::vector<std::string> addresses;
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("://");
for (auto const& addr : addresses) {
std::string::size_type idx = addr.find(substr);
std::string target =
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,
@ -227,7 +256,10 @@ static std::shared_ptr<grpc::Channel> create_grpc_channel(
const grpc::ChannelArguments& grpc_args) {
const std::string addresses =
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
return grpc::CreateChannelInternal(
"",

23
tst/EtcdResolverTest.cpp Normal file
View File

@ -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());
}