Unverified Commit 984e2dc5 authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #22

Add function to resolve local addresses and interface names
parents f582df13 fd2f5137
...@@ -27,9 +27,19 @@ namespace caf { ...@@ -27,9 +27,19 @@ namespace caf {
namespace net { namespace net {
namespace ip { namespace ip {
/// Returns all IP addresses of to `host` (if any). /// Returns all IP addresses of `host` (if any).
std::vector<ip_address> resolve(string_view host); std::vector<ip_address> resolve(string_view host);
/// Returns all IP addresses of `host` (if any).
std::vector<ip_address> resolve(ip_address host);
/// Returns the IP addresses for a local endpoint, which is either an address,
/// an interface name, or the string "localhost".
std::vector<ip_address> local_addresses(string_view host);
/// Returns the IP addresses for a local endpoint address.
std::vector<ip_address> local_addresses(ip_address host);
/// Returns the hostname of this device. /// Returns the hostname of this device.
std::string hostname(); std::string hostname();
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
* http://www.boost.org/LICENSE_1_0.txt. * * http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/ ******************************************************************************/
#include "caf/net/ip.hpp"
#include <cstddef> #include <cstddef>
#include <string> #include <string>
#include <utility> #include <utility>
...@@ -25,7 +27,10 @@ ...@@ -25,7 +27,10 @@
#include "caf/detail/socket_sys_includes.hpp" #include "caf/detail/socket_sys_includes.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/ip_address.hpp" #include "caf/ip_address.hpp"
#include "caf/ip_subnet.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/logger.hpp" #include "caf/logger.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
#ifdef CAF_WINDOWS #ifdef CAF_WINDOWS
...@@ -56,7 +61,8 @@ namespace ip { ...@@ -56,7 +61,8 @@ namespace ip {
namespace { namespace {
// Dummy port to resolve empty string with getaddrinfo. // Dummy port to resolve empty string with getaddrinfo.
constexpr auto dummy_port = "42"; constexpr string_view dummy_port = "42";
constexpr string_view localhost = "localhost";
void* fetch_in_addr(int family, sockaddr* addr) { void* fetch_in_addr(int family, sockaddr* addr) {
if (family == AF_INET) if (family == AF_INET)
...@@ -75,6 +81,86 @@ int fetch_addr_str(char (&buf)[INET6_ADDRSTRLEN], sockaddr* addr) { ...@@ -75,6 +81,86 @@ int fetch_addr_str(char (&buf)[INET6_ADDRSTRLEN], sockaddr* addr) {
: AF_UNSPEC; : AF_UNSPEC;
} }
#ifdef CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
using adapters_ptr = std::unique_ptr<IP_ADAPTER_ADDRESSES, void (*)(void*)>;
ULONG len = 0;
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr,
&len)
!= ERROR_BUFFER_OVERFLOW) {
CAF_LOG_ERROR("failed to get adapter addresses buffer length");
return;
}
auto adapters = adapters_ptr{reinterpret_cast<IP_ADAPTER_ADDRESSES*>(
::malloc(len)),
free};
if (!adapters) {
CAF_LOG_ERROR("malloc failed");
return;
}
// TODO: The Microsoft WIN32 API example propopses to try three times, other
// examples online just perform the call once. If we notice the call to be
// unreliable, we might adapt that behavior.
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr,
adapters.get(), &len)
!= ERROR_SUCCESS) {
CAF_LOG_ERROR("failed to get adapter addresses");
return;
}
char ip_buf[INET6_ADDRSTRLEN];
char name_buf[HOST_NAME_MAX];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto* it = adapters.get(); it != nullptr; it = it->Next) {
memset(name_buf, 0, HOST_NAME_MAX);
WideCharToMultiByte(CP_ACP, 0, it->FriendlyName, wcslen(it->FriendlyName),
name_buf, HOST_NAME_MAX, nullptr, nullptr);
std::string name{name_buf};
for (auto* addr = it->FirstUnicastAddress; addr != nullptr;
addr = addr->Next) {
memset(ip_buf, 0, INET6_ADDRSTRLEN);
getnameinfo(addr->Address.lpSockaddr, addr->Address.iSockaddrLength,
ip_buf, sizeof(ip_buf), nullptr, 0, NI_NUMERICHOST);
ip_address ip;
if (!is_link_local && starts_with(ip_buf, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << ip_buf);
continue;
} else if (auto err = parse(ip_buf, ip))
continue;
f(name_buf, ip);
}
}
}
#else // CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
ifaddrs* tmp = nullptr;
if (getifaddrs(&tmp) != 0)
return;
std::unique_ptr<ifaddrs, decltype(freeifaddrs)*> addrs{tmp, freeifaddrs};
char buffer[INET6_ADDRSTRLEN];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto i = addrs.get(); i != nullptr; i = i->ifa_next) {
auto family = fetch_addr_str(buffer, i->ifa_addr);
if (family != AF_UNSPEC) {
ip_address ip;
if (!is_link_local && starts_with(buffer, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << buffer);
continue;
} else if (auto err = parse(buffer, ip)) {
CAF_LOG_ERROR("could not parse into ip address " << buffer);
continue;
}
f({i->ifa_name, strlen(i->ifa_name)}, ip);
}
}
}
#endif // CAF_WINDOWS
} // namespace } // namespace
std::vector<ip_address> resolve(string_view host) { std::vector<ip_address> resolve(string_view host) {
...@@ -87,7 +173,7 @@ std::vector<ip_address> resolve(string_view host) { ...@@ -87,7 +173,7 @@ std::vector<ip_address> resolve(string_view host) {
addrinfo* tmp = nullptr; addrinfo* tmp = nullptr;
std::string host_str{host.begin(), host.end()}; std::string host_str{host.begin(), host.end()};
if (getaddrinfo(host.empty() ? nullptr : host_str.c_str(), if (getaddrinfo(host.empty() ? nullptr : host_str.c_str(),
host.empty() ? dummy_port : nullptr, &hint, &tmp) host.empty() ? dummy_port.data() : nullptr, &hint, &tmp)
!= 0) != 0)
return {}; return {};
std::unique_ptr<addrinfo, decltype(freeaddrinfo)*> addrs{tmp, freeaddrinfo}; std::unique_ptr<addrinfo, decltype(freeaddrinfo)*> addrs{tmp, freeaddrinfo};
...@@ -111,6 +197,53 @@ std::vector<ip_address> resolve(string_view host) { ...@@ -111,6 +197,53 @@ std::vector<ip_address> resolve(string_view host) {
return results; return results;
} }
std::vector<ip_address> resolve(ip_address host) {
return resolve(to_string(host));
}
std::vector<ip_address> local_addresses(string_view host) {
ip_address host_ip;
std::vector<ip_address> results;
if (host.empty()) {
for_each_adapter(
[&](string_view, ip_address ip) { results.push_back(ip); });
} else if (host == localhost) {
auto v6_local = ip_address{{0}, {0x1}};
auto v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
for_each_adapter([&](string_view, ip_address ip) {
if (ip == v4_local || ip == v6_local)
results.push_back(ip);
});
} else if (auto err = parse(host, host_ip)) {
for_each_adapter([&](string_view iface, ip_address ip) {
if (iface == host)
results.push_back(ip);
});
} else {
return local_addresses(host_ip);
}
return results;
}
std::vector<ip_address> local_addresses(ip_address host) {
static auto v6_any = ip_address{{0}, {0}};
static auto v4_any = ip_address{make_ipv4_address(0, 0, 0, 0)};
if (host == v4_any || host == v6_any)
return resolve("");
auto link_local = ip_address({0xfe, 0x8, 0x0, 0x0}, {0x0, 0x0, 0x0, 0x0});
auto ll_prefix = ip_subnet(link_local, 10);
// Unless explicitly specified we are going to skip link-local addresses.
auto is_link_local = ll_prefix.contains(host);
std::vector<ip_address> results;
for_each_adapter(
[&](string_view, ip_address ip) {
if (host == ip)
results.push_back(ip);
},
is_link_local);
return results;
}
std::string hostname() { std::string hostname() {
char buf[HOST_NAME_MAX + 1]; char buf[HOST_NAME_MAX + 1];
buf[HOST_NAME_MAX] = '\0'; buf[HOST_NAME_MAX] = '\0';
......
...@@ -30,28 +30,54 @@ ...@@ -30,28 +30,54 @@
using namespace caf; using namespace caf;
using namespace caf::net; using namespace caf::net;
CAF_TEST_FIXTURE_SCOPE(ip_tests, host_fixture) namespace {
struct fixture : host_fixture {
fixture() : v6_local{{0}, {0x1}} {
v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
v4_any_addr = ip_address{make_ipv4_address(0, 0, 0, 0)};
}
bool contains(ip_address x) {
return std::count(addrs.begin(), addrs.end(), x) > 0;
}
ip_address v4_any_addr;
ip_address v6_any_addr;
ip_address v4_local;
ip_address v6_local;
std::vector<ip_address> addrs;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(ip_tests, fixture)
CAF_TEST(resolve localhost) { CAF_TEST(resolve localhost) {
ip_address v4_local{make_ipv4_address(127, 0, 0, 1)}; addrs = ip::resolve("localhost");
ip_address v6_local{{0}, {0x1}};
auto addrs = ip::resolve("localhost");
CAF_CHECK(!addrs.empty()); CAF_CHECK(!addrs.empty());
auto contains = [&](ip_address x) {
return std::count(addrs.begin(), addrs.end(), x) > 0;
};
CAF_CHECK(contains(v4_local) || contains(v6_local)); CAF_CHECK(contains(v4_local) || contains(v6_local));
} }
CAF_TEST(resolve any) { CAF_TEST(resolve any) {
ip_address v4_any{make_ipv4_address(0, 0, 0, 0)}; addrs = ip::resolve("");
ip_address v6_any{{0}, {0}};
auto addrs = ip::resolve("");
CAF_CHECK(!addrs.empty()); CAF_CHECK(!addrs.empty());
auto contains = [&](ip_address x) { CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
return std::count(addrs.begin(), addrs.end(), x) > 0; }
};
CAF_CHECK(contains(v4_any) || contains(v6_any)); CAF_TEST(local addresses localhost) {
addrs = ip::local_addresses("localhost");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_local) || contains(v6_local));
}
CAF_TEST(local addresses any) {
addrs = ip::local_addresses("0.0.0.0");
auto tmp = ip::local_addresses("::");
addrs.insert(addrs.end(), tmp.begin(), tmp.end());
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment