Skip to content

Commit

Permalink
feat: add IPv6 support
Browse files Browse the repository at this point in the history
close: vesoft-inc#4955

Now it should support dual stack for some friends in community.
This roughly change is targeted to create a branch for their
quick testing on IPv6 only env(that doesnt come with a DNS)

Note intToIPv4 is not handled due to it's probabbly for metad
of NebulaGraph v1 only.
  • Loading branch information
wey-gu committed Nov 29, 2023
1 parent 54d18fb commit e1d7b00
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 57 deletions.
105 changes: 66 additions & 39 deletions src/common/network/NetworkUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>

#include "common/base/Base.h"
#include "common/fs/FileUtils.h"
Expand All @@ -29,59 +30,63 @@ std::string NetworkUtils::getHostname() {
return std::string(hn);
}

StatusOr<std::string> NetworkUtils::getIPv4FromDevice(const std::string& device) {
StatusOr<std::string> NetworkUtils::getIPFromDevice(const std::string& device) {
if (device == "any") {
return "0.0.0.0";
}
auto result = listDeviceAndIPv4s();
auto result = listDeviceAndIPs();
if (!result.ok()) {
return std::move(result).status();
}
auto iter = std::find_if(result.value().begin(),
result.value().end(),
[&](const auto& deviceAndIp) { return deviceAndIp.first == device; });
if (iter == result.value().end()) {
return Status::Error("No IPv4 address found for `%s'", device.c_str());
return Status::Error("No IP address found for `%s'", device.c_str());
}
return iter->second;
}

StatusOr<std::vector<std::string>> NetworkUtils::listIPv4s() {
auto result = listDeviceAndIPv4s();
StatusOr<std::vector<std::string>> NetworkUtils::listIPs() {
auto result = listDeviceAndIPs();
if (!result.ok()) {
return std::move(result).status();
}
auto getval = [](const auto& entry) { return entry.second; };
std::vector<std::string> ipv4s;
ipv4s.resize(result.value().size());
std::transform(result.value().begin(), result.value().end(), ipv4s.begin(), getval);
return ipv4s;
std::vector<std::string> ips;
ips.resize(result.value().size());
std::transform(result.value().begin(), result.value().end(), ips.begin(), getval);
return ips;
}

StatusOr<std::vector<std::pair<std::string, std::string>>> NetworkUtils::listDeviceAndIPv4s() {
StatusOr<std::vector<std::pair<std::string, std::string>>> NetworkUtils::listDeviceAndIPs() {
struct ifaddrs* iflist;
std::vector<std::pair<std::string, std::string>> dev2ipv4s;
std::vector<std::pair<std::string, std::string>> dev2ips;
if (::getifaddrs(&iflist) != 0) {
return Status::Error("%s", ::strerror(errno));
}
for (auto* ifa = iflist; ifa != nullptr; ifa = ifa->ifa_next) {
// Skip non-IPv4 devices
if (ifa->ifa_addr->sa_family != AF_INET) {
char ip[INET6_ADDRSTRLEN];
if (ifa->ifa_addr->sa_family == AF_INET) {
auto* addr = reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr);
::inet_ntop(AF_INET, &(addr->sin_addr), ip, INET_ADDRSTRLEN);
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
auto* addr = reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr);
::inet_ntop(AF_INET6, &(addr->sin6_addr), ip, INET6_ADDRSTRLEN);
} else {
continue;
}
auto* addr = reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr);
// inet_ntoa is thread safe but not re-entrant,
// we could use inet_ntop instead when we need support for IPv6
dev2ipv4s.emplace_back(ifa->ifa_name, ::inet_ntoa(addr->sin_addr));
dev2ips.emplace_back(ifa->ifa_name, ip);
}
::freeifaddrs(iflist);
if (dev2ipv4s.empty()) {
return Status::Error("No IPv4 devices found");
if (dev2ips.empty()) {
return Status::Error("No IP devices found");
}
return dev2ipv4s;
return dev2ips;
}

bool NetworkUtils::getDynamicPortRange(uint16_t& low, uint16_t& high) {
// Note: this function is capable of getting the dynamic port range even for IPv6
FILE* pipe = popen("cat /proc/sys/net/ipv4/ip_local_port_range", "r");
if (!pipe) {
LOG(ERROR) << "Failed to open /proc/sys/net/ipv4/ip_local_port_range: " << strerror(errno);
Expand Down Expand Up @@ -201,20 +206,22 @@ StatusOr<std::vector<HostAddr>> NetworkUtils::resolveHost(const std::string& hos
}

for (rp = res; rp != nullptr; rp = rp->ai_next) {
char ip[INET6_ADDRSTRLEN];
switch (rp->ai_family) {
case AF_INET:
case AF_INET: {
auto address = (reinterpret_cast<struct sockaddr_in*>(rp->ai_addr))->sin_addr.s_addr;
::inet_ntop(AF_INET, &address, ip, INET_ADDRSTRLEN);
break;
case AF_INET6:
VLOG(1) << "Currently does not support Ipv6 address";
continue;
}
case AF_INET6: {
auto address = (reinterpret_cast<struct sockaddr_in6*>(rp->ai_addr))->sin6_addr;
::inet_ntop(AF_INET6, &address, ip, INET6_ADDRSTRLEN);
break;
}
default:
continue;
}

auto address = (reinterpret_cast<struct sockaddr_in*>(rp->ai_addr))->sin_addr.s_addr;
// We need to match the integer byte order generated by ipv4ToInt,
// so we need to convert here.
addrs.emplace_back(intToIPv4(htonl(std::move(address))), port);
addrs.emplace_back(ip, port);
}

freeaddrinfo(res);
Expand Down Expand Up @@ -278,31 +285,46 @@ StatusOr<std::vector<HostAddr>> NetworkUtils::toHosts(const std::string& peersSt
hosts.reserve(peers.size());
for (auto& peerStr : peers) {
auto addrPort = folly::trimWhitespace(peerStr);
auto pos = addrPort.find(':');
if (pos == folly::StringPiece::npos) {
std::string addr;
int32_t port;

// Handle strings that may contain IPv6 addresses
auto lastColonPos = addrPort.rfind(':');
if (lastColonPos == folly::StringPiece::npos) {
return Status::Error("Bad peer format: %s", addrPort.start());
}

int32_t port;
try {
port = folly::to<int32_t>(addrPort.subpiece(pos + 1));
port = folly::to<int32_t>(addrPort.subpiece(lastColonPos + 1));
} catch (const std::exception& ex) {
return Status::Error("Bad port number, error: %s", ex.what());
}

auto addr = addrPort.subpiece(0, pos).toString();
addr = addrPort.subpiece(0, lastColonPos).toString();
// remove the square brackets if present in IPv6 addresses
if (!addr.empty() && addr.front() == '[' && addr.back() == ']') {
addr = addr.substr(1, addr.size() - 2);
}

hosts.emplace_back(std::move(addr), port);
}
return hosts;
}

std::string NetworkUtils::toHostsStr(const std::vector<HostAddr>& hosts) {
std::string hostsString = "";
std::string hostsString;
for (auto& host : hosts) {
if (!hostsString.empty()) {
hostsString.append(", ");
}
hostsString += folly::stringPrintf("%s:%d", host.host.c_str(), host.port);
// Whether the host is IPv6 address
if (host.host.find(':') != std::string::npos) {
// if IPv6, add square brackets
hostsString += folly::stringPrintf("[%s]:%d", host.host.c_str(), host.port);
} else {
// IPv4 or hostname
hostsString += folly::stringPrintf("%s:%d", host.host.c_str(), host.port);
}
}
return hostsString;
}
Expand All @@ -314,13 +336,18 @@ Status NetworkUtils::validateHostOrIp(const std::string& hostOrIp) {
const std::regex ipv4(
"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}"
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)");
if (std::regex_match(hostOrIp, ipv4)) {
const std::regex ipv6(
"([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|"
"(([0-9a-fA-F]{1,4}:){1,6}:)|"
"(:(([0-9a-fA-F]{1,4}:){1,6}))");
if (std::regex_match(hostOrIp, ipv4) || std::regex_match(hostOrIp, ipv6)) {
const std::regex loopbackOrAny(
"^127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1$|(0\\.){3}0");
"^127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1$|"
"([0:]+1)|((0\\.){3}0)");
if (std::regex_match(hostOrIp, loopbackOrAny)) {
return Status::OK();
}
auto ipsStatus = listIPv4s();
auto ipsStatus = listIPs();
NG_RETURN_IF_ERROR(ipsStatus);
const auto& ips = ipsStatus.value();
auto result = std::find(ips.begin(), ips.end(), hostOrIp);
Expand Down
12 changes: 6 additions & 6 deletions src/common/network/NetworkUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ class NetworkUtils final {

static std::string getHostname();

// Get the Ipv4 address bound to a specific net device.
// Get the IP address bound to a specific net device.
// If given "any", it returns "0.0.0.0".
static StatusOr<std::string> getIPv4FromDevice(const std::string& device);
// List out all Ipv4 addresses, including the loopback one.
static StatusOr<std::vector<std::string>> listIPv4s();
// List out all network devices and its corresponding Ipv4 address.
static StatusOr<std::vector<std::pair<std::string, std::string>>> listDeviceAndIPv4s();
static StatusOr<std::string> getIPFromDevice(const std::string& device);
// List out all IP addresses, including the loopback one.
static StatusOr<std::vector<std::string>> listIPs();
// List out all network devices and its corresponding IP address.
static StatusOr<std::vector<std::pair<std::string, std::string>>> listDeviceAndIPs();

// Get the local dynamic port range [low, high], only works for IPv4
static bool getDynamicPortRange(uint16_t& low, uint16_t& high);
Expand Down
52 changes: 40 additions & 12 deletions src/common/network/test/NetworkUtilsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,53 @@ TEST(NetworkUtils, getHostname) {
EXPECT_EQ(std::string(buffer), hostname);
}

TEST(NetworkUtils, getIPv4FromDevice) {
TEST(NetworkUtils, getIPFromDevice) {
{
auto result = NetworkUtils::getIPv4FromDevice("lo");
auto result = NetworkUtils::getIPFromDevice("lo");
ASSERT_TRUE(result.ok()) << result.status();
ASSERT_EQ("127.0.0.1", result.value());
ASSERT_TRUE(result.value() == "127.0.0.1" || result.value() == "::1");
}
{
auto result = NetworkUtils::getIPv4FromDevice("any");
auto result = NetworkUtils::getIPFromDevice("any");
ASSERT_TRUE(result.ok()) << result.status();
ASSERT_EQ("0.0.0.0", result.value());
}
{
auto result = NetworkUtils::getIPv4FromDevice("nonexistent");
auto result = NetworkUtils::getIPFromDevice("nonexistent");
ASSERT_FALSE(result.ok()) << result.status();
}
}

TEST(NetworkUtils, listIPv4s) {
auto result = NetworkUtils::listIPv4s();
TEST(NetworkUtils, listIPs) {
auto result = NetworkUtils::listIPs();
ASSERT_TRUE(result.ok()) << result.status();
ASSERT_FALSE(result.value().empty());
auto found = false;
auto foundIPv4 = false;
auto foundIPv6 = false;
for (auto& ip : result.value()) {
if (ip == "127.0.0.1") {
found = true;
foundIPv4 = true;
}
if (ip == "::1") {
foundIPv6 = true;
}
}
ASSERT_TRUE(found);
ASSERT_TRUE(foundIPv4);
ASSERT_TRUE(foundIPv6); // This may fail on some OS without IPv6 support
}

TEST(NetworkUtils, listDeviceAndIPv4s) {
auto result = NetworkUtils::listDeviceAndIPv4s();
TEST(NetworkUtils, listDeviceAndIPs) {
auto result = NetworkUtils::listDeviceAndIPs();
ASSERT_TRUE(result.ok()) << result.status();
ASSERT_FALSE(result.value().empty());
ASSERT_NE(result.value().end(),
std::find_if(result.value().begin(), result.value().end(), [](const auto& deviceAndIp) {
return deviceAndIp.first == "lo";
}));
ASSERT_NE(result.value().end(),
std::find_if(result.value().begin(), result.value().end(), [](const auto& deviceAndIp) {
return deviceAndIp.second == "::1";
}));
}

TEST(NetworkUtils, getDynamicPortRange) {
Expand All @@ -86,6 +95,9 @@ TEST(NetworkUtils, toHosts) {

s = NetworkUtils::toHosts("1.1.2.3:123, a.b.c.d:a23");
ASSERT_FALSE(s.ok());

s = NetworkUtils::toHosts("[::1]:1200, localhost:1200");
ASSERT_TRUE(s.ok());
}

TEST(NetworkUtils, ValidateHostOrIp) {
Expand Down Expand Up @@ -120,6 +132,22 @@ TEST(NetworkUtils, ValidateHostOrIp) {
hostOrIp = "NonvalidHostName";
result = NetworkUtils::validateHostOrIp(hostOrIp);
EXPECT_FALSE(result.ok());

hostOrIp = "lab.vesoft-inc.com";
result = NetworkUtils::validateHostOrIp(hostOrIp);
EXPECT_TRUE(result.ok());

hostOrIp = "::1";
result = NetworkUtils::validateHostOrIp(hostOrIp);
EXPECT_TRUE(result.ok());

hostOrIp = "2001:db8::1";
result = NetworkUtils::validateHostOrIp(hostOrIp);
EXPECT_TRUE(result.ok());

hostOrIp = "::g";
result = NetworkUtils::validateHostOrIp(hostOrIp);
EXPECT_FALSE(result.ok());
}

} // namespace network
Expand Down

0 comments on commit e1d7b00

Please sign in to comment.