Skip to content

Commit

Permalink
Enable ipv6 endpoints support
Browse files Browse the repository at this point in the history
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
  • Loading branch information
sighingnow committed Dec 20, 2023
1 parent b82efea commit f4081e0
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 28 deletions.
41 changes: 34 additions & 7 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -237,12 +237,21 @@ jobs:
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
/usr/local/bin/etcd \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster default=http://127.0.0.1:2380 &
sleep 5
# 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
Expand Down Expand Up @@ -288,7 +297,12 @@ jobs:
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
/usr/local/bin/etcd \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster default=http://127.0.0.1:2380 &
sleep 5
Expand All @@ -315,7 +329,12 @@ jobs:
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
/usr/local/bin/etcd \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster default=http://127.0.0.1:2380 &
sleep 5
Expand All @@ -336,7 +355,12 @@ jobs:
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
/usr/local/bin/etcd \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster default=http://127.0.0.1:2380 &
sleep 5
Expand Down Expand Up @@ -389,8 +413,11 @@ jobs:
--key-file security-config/private/etcd0.example.com.key \
--client-cert-auth \
--trusted-ca-file security-config/certs/ca.crt \
--advertise-client-urls=https://127.0.0.1:2379 \
--listen-client-urls=https://127.0.0.1:2379 &
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://127.0.0.1:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-peer-urls http://127.0.0.1:2380 \
--initial-cluster default=http://127.0.0.1:2380 &
sleep 5
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
74 changes: 53 additions & 21 deletions src/SyncClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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, ",");
}
// 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 "ipv4:///" + stripped_address;
return std::string{};
}

bool authenticate(std::shared_ptr<grpc::Channel> const& channel,
Expand Down Expand Up @@ -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(
"",
Expand Down
23 changes: 23 additions & 0 deletions tst/EtcdResolverTest.cpp
Original file line number Diff line number Diff line change
@@ -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());
}

0 comments on commit f4081e0

Please sign in to comment.