cdk/foundation/socket_detail.cc (849 lines of code) (raw):
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0, as
* published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* Without limiting anything contained in the foregoing, this file,
* which is part of Connector/C++, is also subject to the
* Universal FOSS Exception, version 1.0, a copy of which can be found at
* https://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "socket_detail.h"
#include <mysql/cdk/foundation/error.h>
#include <mysql/cdk/foundation/connection_tcpip.h>
PUSH_SYS_WARNINGS_CDK
#include "openssl/ssl.h"
#include <cstdio>
#include <limits>
#include <chrono>
#include <sstream>
#include <mutex>
#include <thread>
#include <forward_list>
#include <map>
#include <functional>
#include <iostream>
#ifndef _WIN32
#include <arpa/inet.h>
#include <signal.h>
#include <sys/un.h>
#include <poll.h>
#include <resolv.h>
#include <arpa/nameser.h>
#else
#include <windns.h>
#pragma comment(lib,"Dnsapi")
#endif
POP_SYS_WARNINGS_CDK
using namespace std::chrono;
namespace cdk {
namespace foundation {
namespace connection {
namespace detail {
#ifdef _WIN32
/*
error_category_winsock class
============================
Used for handling Winsock errors.
*/
const error_category& winsock_error_category();
class error_category_winsock : public error_category_base
{
error_category_winsock() {}
const char* name() const NOEXCEPT { return "winsock"; }
std::string message(int) const;
DIAGNOSTIC_PUSH_CDK
#ifdef _MSC_VER
// 4702 = unreachable code
DISABLE_WARNING_CDK(4702)
#endif // _MSC_VER
error_condition do_default_error_condition(int code) const
{
switch (code)
{
case WSAEACCES: return errc::permission_denied;
case WSAEADDRINUSE: return errc::address_in_use;
case WSAEADDRNOTAVAIL: return errc::address_not_available;
case WSAEAFNOSUPPORT: return errc::address_family_not_supported;
case WSAEALREADY: return errc::connection_already_in_progress;
case WSAEBADF: return errc::bad_file_descriptor;
case WSAECONNABORTED: return errc::connection_aborted;
case WSAECONNREFUSED: return errc::connection_refused;
case WSAECONNRESET: return errc::connection_reset;
case WSAEDESTADDRREQ: return errc::destination_address_required;
case WSAEFAULT: return errc::bad_address;
case WSAEHOSTUNREACH: return errc::host_unreachable;
case WSAEINPROGRESS: return errc::operation_in_progress;
case WSAEINTR: return errc::interrupted;
case WSAEINVAL: return errc::invalid_argument;
case WSAEISCONN: return errc::already_connected;
case WSAEMFILE: return errc::too_many_files_open;
case WSAEMSGSIZE: return errc::message_size;
case WSAENAMETOOLONG: return errc::filename_too_long;
case WSAENETDOWN: return errc::network_down;
case WSAENETRESET: return errc::network_reset;
case WSAENETUNREACH: return errc::network_unreachable;
case WSAENOBUFS: return errc::no_buffer_space;
case WSAENOPROTOOPT: return errc::no_protocol_option;
case WSAENOTCONN: return errc::not_connected;
case WSAENOTSOCK: return errc::not_a_socket;
case WSAEOPNOTSUPP: return errc::operation_not_supported;
case WSAEPROTONOSUPPORT: return errc::protocol_not_supported;
case WSAEPROTOTYPE: return errc::wrong_protocol_type;
case WSAETIMEDOUT: return errc::timed_out;
case WSAEWOULDBLOCK: return errc::operation_would_block;
default:
throw_error(code, winsock_error_category());
return errc::no_error; // suppress copile warnings
}
}
DIAGNOSTIC_POP_CDK
bool do_equivalent(int code, const error_condition &ec) const
{
try
{
return ec == default_error_condition(code);
}
catch (...)
{
return false;
}
}
friend const error_category& winsock_error_category();
};
std::string error_category_winsock::message(int code) const
{
std::string message;
LPSTR buffer = NULL;
// Note: on windows error codes are unsigned
assert(code > 0);
DWORD result = ::FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, static_cast<DWORD>(code),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&buffer, 0, NULL
);
if (result == 0)
message = "Unknown Winsock error";
else
message = buffer;
::LocalFree(buffer);
return message;
}
const error_category& winsock_error_category()
{
static const error_category_winsock instance;
return instance;
}
#else // _WIN32
const int SOCKET_ERROR = -1;
#endif // _WIN32
/*
error_category_resolve class
============================
Used for handling errors returned by network name resolution routines
related to `getaddrinfo` (see
<http://pubs.opengroup.org/onlinepubs/009695399/functions/getaddrinfo.html>)
*/
class error_category_resolve : public error_category_base
{
error_category_resolve() {}
const char* name() const NOEXCEPT { return "resolve"; }
std::string message(int code) const;
DIAGNOSTIC_PUSH_CDK
#ifdef _MSC_VER
// 4702 = unreachable code
DISABLE_WARNING_CDK(4702)
#endif // _MSC_VER
error_condition do_default_error_condition(int code) const
{
switch (code)
{
case EAI_AGAIN: return errc::resource_unavailable_try_again;
case EAI_BADFLAGS: return errc::invalid_argument;
case EAI_FAIL: return errc::address_not_available;
case EAI_FAMILY: return errc::address_family_not_supported;
case EAI_MEMORY: return errc::not_enough_memory;
// Note: On Windows EAI_NODATA == EAI_NONAME
#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
case EAI_NODATA:
#endif
case EAI_NONAME: return errc::address_not_available;
case EAI_SERVICE:
//The service passed was not recognized for the specified socket type.
return errc::invalid_argument;
case EAI_SOCKTYPE:
//The intended socket type was not recognized.
return errc::not_a_socket;
#ifdef EAI_OVERFLOW
case EAI_OVERFLOW:
//An argument buffer overflowed.
return errc::value_too_large;
#endif
#ifdef EAI_SYSTEM
case EAI_SYSTEM:
//A system error occurred; the error code can be found in errno.
return posix_error_category().default_error_condition(errno);
#endif
default:
throw_error(code, error_category_resolve());
return errc::no_error; // suppress compile warnings
}
}
DIAGNOSTIC_POP_CDK
bool do_equivalent(int code, const error_condition &ec) const
{
try
{
return ec == default_error_condition(code);
}
catch (...)
{
return false;
}
}
friend const error_category& resolve_error_category();
};
std::string error_category_resolve::message(int code) const
{
return gai_strerror(code);
}
const error_category& resolve_error_category()
{
static const error_category_resolve instance;
return instance;
}
/**
Throws thread specific socket error.
*/
static void throw_socket_error()
{
#ifdef _WIN32
int error = WSAGetLastError();
if (error)
throw_error(error, winsock_error_category());
#else
throw_system_error();
#endif
}
/**
Checks socket's state for errors. If an error is encountered, the appropriate
exception is thrown.
*/
static void check_socket_error(Socket socket)
{
int error = 0;
socklen_t error_length = sizeof(error);
if (::getsockopt(socket, SOL_SOCKET, SO_ERROR, (char *)&error, &error_length) != 0)
throw_socket_error();
if (error)
#ifdef _WIN32
throw_error(error, winsock_error_category());
#else
// Note: this is not very clear in POSIX docs, but the error code returned by
// getsockopt(.. SO_ERROR ..) should be interpreted like errno value.
// For example IBM docs for SO_ERROR specify:
// "Return any pending errors in the socket. The value returned corresponds
// to the standard error codes defined in <errno.h>"
throw_error(error, posix_error_category());
#endif
}
void set_nonblocking(Socket socket, bool nonblocking)
{
#ifdef _WIN32
u_long set_nonblocking = nonblocking ? 1ul : 0ul;
if (::ioctlsocket(socket, FIONBIO, &set_nonblocking) == SOCKET_ERROR)
throw_socket_error();
#else
int flags = ::fcntl(socket, F_GETFL, 0);
if (flags >= 0)
{
if (nonblocking)
flags |= O_NONBLOCK;
else
flags &= ~O_NONBLOCK;
if (::fcntl(socket, F_SETFL, flags) != 0)
throw_socket_error();
}
else
{
throw_socket_error();
}
#endif
}
#if defined WITH_SSL && OPENSSL_VERSION_NUMBER < 0x10100000L
//Not needed after 1.1
static std::mutex* m_openssl_mutex = nullptr;
void thread_setup()
{
m_openssl_mutex = new std::mutex[CRYPTO_num_locks()];
}
void thread_cleanup()
{
delete[] m_openssl_mutex;
}
static void locking_function(
int mode, int n, const char* /*file*/, int /*line*/
)
{
if(mode & CRYPTO_LOCK)
{
m_openssl_mutex[n].lock();
}
else if(mode & CRYPTO_UNLOCK)
{
m_openssl_mutex[n].unlock();
}
}
static void id_function(CRYPTO_THREADID *id)
{
CRYPTO_THREADID_set_numeric(
id,
static_cast<unsigned long>(
std::hash<std::thread::id>()(std::this_thread::get_id())
)
);
}
#endif
void initialize_socket_system()
{
#ifdef _WIN32
WSADATA wsa_data;
WORD version_requested = MAKEWORD(2, 2);
if (::WSAStartup(version_requested, &wsa_data) != 0)
throw_error("Winsock initialization failed.");
#endif
#ifdef WITH_SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
# if OPENSSL_VERSION_NUMBER < 0x10100000L
thread_setup();
CRYPTO_set_locking_callback(locking_function);
CRYPTO_THREADID_set_callback(id_function);
# endif
#endif
#ifndef WIN32
//ignore SIGPIPE signal when sending data with connection closed by server
signal(SIGPIPE, SIG_IGN);
#endif
}
void uninitialize_socket_system()
{
#ifdef _WIN32
if (::WSACleanup() != 0)
throw_socket_error();
#endif
#ifdef WITH_SSL
# if OPENSSL_VERSION_NUMBER < 0x10100000L
thread_cleanup();
# endif
#endif
}
Socket socket(bool nonblocking, addrinfo* hints)
{
Socket socket = NULL_SOCKET;
if (hints)
socket = ::socket(hints->ai_family, hints->ai_socktype, hints->ai_protocol);
else
socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket != NULL_SOCKET)
{
int reuse_addr = 1;
if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse_addr, sizeof(reuse_addr)) != 0)
throw_socket_error();
try
{
set_nonblocking(socket, nonblocking);
}
catch (...)
{
close(socket);
throw;
}
}
else
{
throw_socket_error();
throw_error("Failed to create socket.");
}
return socket;
}
#ifndef _WIN32
Socket unix_socket(bool nonblocking)
{
Socket socket = NULL_SOCKET;
socket = ::socket(AF_UNIX, SOCK_STREAM, 0);
if (socket != NULL_SOCKET)
{
int reuse_addr = 1;
if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse_addr, sizeof(reuse_addr)) != 0)
throw_socket_error();
try
{
set_nonblocking(socket, nonblocking);
}
catch (...)
{
close(socket);
throw;
}
}
else
{
throw_socket_error();
}
return socket;
}
#endif //_WIN32
void close(Socket socket)
{
if (socket == NULL_SOCKET)
return;
#ifdef _WIN32
if (::closesocket(socket) != 0)
#else
if (::close(socket) != 0)
#endif
{
throw_socket_error();
}
}
void shutdown(Socket socket, Shutdown_mode mode)
{
#ifdef _WIN32
const int SHUT_RD = SD_RECEIVE;
const int SHUT_WR = SD_SEND;
const int SHUT_RDWR = SD_BOTH;
#endif
int sys_mode;
switch(mode)
{
case SHUTDOWN_MODE_READ:
sys_mode = SHUT_RD;
break;
case SHUTDOWN_MODE_WRITE:
sys_mode = SHUT_WR;
break;
case SHUTDOWN_MODE_BOTH:
sys_mode = SHUT_RDWR;
break;
default:
THROW("Invalid socket shutdown mode.");
}
if (::shutdown(socket, sys_mode) != 0)
throw_socket_error();
}
addrinfo* addrinfo_from_string(const char* host_name, unsigned short port)
{
addrinfo* result = NULL;
addrinfo hints = {};
in6_addr addr = {};
hints.ai_flags = AI_NUMERICSERV;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (inet_pton(AF_INET, host_name, &addr) == 1)
{
hints.ai_family = AF_INET;
hints.ai_flags |= AI_NUMERICHOST;
}
else
{
if (inet_pton(AF_INET6, host_name, &addr) == 1)
{
hints.ai_family = AF_INET6;
hints.ai_flags |= AI_NUMERICHOST;
}
}
// cast to unsigned because, unsigned short would use
// std::to_string(int)
int rc = getaddrinfo(host_name,
std::to_string(static_cast<unsigned>(port)).c_str(),
&hints, &result);
#ifdef EAI_SYSTEM
if (EAI_SYSTEM == rc && errno)
throw_posix_error();
#endif
if (rc != 0)
throw_error(rc, resolve_error_category());
if (!result)
throw_error(std::string("Invalid host name: ") + host_name);
return result;
}
DIAGNOSTIC_PUSH_CDK
#ifdef _MSC_VER
// 4189 = local variable is initialized but not referenced
DISABLE_WARNING_CDK(4189)
#endif
Socket connect(const char *host_name, unsigned short port,
uint64_t timeout_usec)
{
Socket socket = NULL_SOCKET;
addrinfo* host_list = NULL;
auto deadline = system_clock::now() + microseconds(timeout_usec);
// Resolve host name.
// TODO: Configurable number of attempts
int attempts = 2;
while (!host_list)
{
attempts--;
try
{
/*
The DNS async resolution is not supported on all platforms.
Therefore, we will do the blocking call and measure the time
*/
host_list = detail::addrinfo_from_string(host_name, port);
if (timeout_usec > 0 && system_clock::now() >= deadline)
{
throw Connect_timeout_error(timeout_usec / 1000);
}
}
catch (Error& e)
{
if(e != errc::resource_unavailable_try_again || attempts <= 0)
throw;
}
}
struct AddrInfoGuard
{
addrinfo* list;
~AddrInfoGuard() NOEXCEPT { freeaddrinfo(list); }
}
guard = { host_list };
// Connect to host.
int connect_result = SOCKET_ERROR;
addrinfo* host = host_list;
while (connect_result != 0 && host)
{
try
{
socket = detail::socket(true, host);
connect_result = ::connect(socket, host->ai_addr, static_cast<int>(host->ai_addrlen));
if (connect_result != 0)
{
#ifdef _WIN32
if (connect_result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
#else
if (connect_result == SOCKET_ERROR && errno == EINPROGRESS)
#endif
{
int select_result = 0;
do{
auto timeout = duration_cast<microseconds>(
deadline - system_clock::now()
).count();
select_result = poll_one(
socket, POLL_MODE_CONNECT, true,
0 == timeout_usec ? 0 : timeout > 0 ? timeout : 1
);
// Note: if poll_one() returns 0 then, according to POSIX specs:
// A value of 0 indicates that the call timed out and no file descriptors have been selected
// Due to a bug on WSApool, it may return 0 even if no timeout occur..
// So we will check if timeout occurs and try again if not
} while ((select_result == 0) &&
((timeout_usec == 0) ||
(std::chrono::system_clock::now() < deadline)
)
);
if ((timeout_usec > 0) &&
(std::chrono::system_clock::now() >= deadline))
{
// Throw the error in milliseconds, which we did not adjust.
// Otherwise the user will be confused why the timeout
// in the error message is smaller than defined
// (original timeout minus DNS resolution time)
throw Connect_timeout_error(timeout_usec / 1000);
}
if (select_result < 0)
throw_socket_error();
else
check_socket_error(socket);
connect_result = 0;
}
else
{
throw_socket_error();
}
}
}
catch (Connect_timeout_error&)
{
close(socket);
throw;
}
catch (...)
{
close(socket);
host = host->ai_next;
if (!host)
throw;
}
}
return socket;
}
DIAGNOSTIC_POP_CDK
#ifndef _WIN32
Socket connect(const char *path, uint64_t timeout_usec)
{
Socket socket = NULL_SOCKET;
auto deadline = system_clock::now() + microseconds(timeout_usec);
// Connect to host.
int connect_result = SOCKET_ERROR;
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1);
try
{
socket = detail::unix_socket(true);
connect_result = ::connect(socket,
(struct sockaddr*)(&addr),
sizeof(addr));
if (connect_result != 0)
{
if (connect_result == SOCKET_ERROR && errno == EINPROGRESS)
{
int select_result = poll_one(socket, POLL_MODE_CONNECT, true,
timeout_usec);
if (select_result == 0 && (timeout_usec > 0) &&
(system_clock::now() >= deadline))
{
// We probably hit the timeout
throw Connect_timeout_error(timeout_usec / 1000);
}
else if (select_result < 0)
throw_socket_error();
else
check_socket_error(socket);
connect_result = 0;
}
else
{
throw_socket_error();
}
}
}
catch (...)
{
close(socket);
rethrow_error();
}
return socket;
}
#endif //_WIN32
Socket listen_and_accept(unsigned short port)
{
Socket client = NULL_SOCKET;
Socket acceptor = detail::socket(true);
try
{
sockaddr_in serv_addr = {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
if (::bind(acceptor, (sockaddr *)&serv_addr, sizeof(serv_addr)) < 0 ||
::listen(acceptor, 1) == SOCKET_ERROR)
{
throw_socket_error();
}
int select_result = poll_one(acceptor, POLL_MODE_CONNECT, true);
if (select_result > 0)
{
sockaddr_in cli_addr = {};
socklen_t cli_addr_length = sizeof(cli_addr);
client = ::accept(acceptor, (sockaddr *)&cli_addr, &cli_addr_length);
if (client == NULL_SOCKET)
throw_socket_error();
}
else if (select_result == 0)
{
check_socket_error(acceptor);
}
else
{
throw_socket_error();
}
detail::close(acceptor);
}
catch (...)
{
detail::close(acceptor);
throw;
}
return client;
}
int poll_one(Socket socket, Poll_mode mode, bool wait,
uint64_t timeout_usec)
{
DIAGNOSTIC_PUSH_CDK
#ifdef _WIN32
// 4548 = expression has no effect
// This warning is generated by FD_SET
DISABLE_WARNING_CDK(4548)
#endif
struct pollfd fds = {};
fds.fd = socket;
switch(mode)
{
case POLL_MODE_CONNECT:
fds.events = POLLIN | POLLOUT;
break;
case POLL_MODE_READ:
fds.events = POLLIN;
break;
case POLL_MODE_WRITE:
fds.events = POLLOUT;
break;
}
DIAGNOSTIC_POP_CDK
//milliseconds
int timeout =
!wait ? 0
: timeout_usec > 0 ? static_cast<int>((1000+timeout_usec) / 1000) : -1;
#ifdef _WIN32
int result = ::WSAPoll(&fds, 1, timeout);
#else
int result = ::poll(&fds, 1, timeout);
#endif
if (fds.revents & (POLLERR | POLLHUP | POLLNVAL))
{
check_socket_error(socket);
}
return result;
}
size_t bytes_available(Socket socket)
{
unsigned long bytes_available;
#ifdef _WIN32
if (::ioctlsocket(socket, FIONREAD, &bytes_available) != 0)
#else
if (::ioctl(socket, FIONREAD, &bytes_available) == -1)
#endif
{
throw_socket_error();
}
return bytes_available;
}
void recv(Socket socket, byte *buffer, size_t buffer_size)
{
// TODO: Investigate if more efficient implementation is possible with ::recv() and MSG_WAITALL flag.
if (buffer_size == 0)
return;
size_t bytes_received = 0;
/*
Note: In presence of timeouts recv_some() can return 0 which would lead
to an infinite loop here! See also bug#37278716.
A solution would be to throw error if timeout was hit?
*/
while (bytes_received != buffer_size)
bytes_received += recv_some(socket, buffer + bytes_received, buffer_size - bytes_received, true);
}
void send(Socket socket, const byte *buffer, size_t buffer_size)
{
if (buffer_size == 0)
return;
size_t bytes_sent = 0;
while (bytes_sent != buffer_size)
bytes_sent += send_some(socket, buffer + bytes_sent, buffer_size - bytes_sent, true);
}
size_t recv_some(Socket socket, byte *buffer, size_t buffer_size, bool wait)
{
if (buffer_size == 0)
return 0;
/*
TODO: buffer size checks - throw error if passed buffer is bigger than
some reasonable limit.
*/
assert(buffer_size > 0);
assert(buffer_size < (size_t)std::numeric_limits<int>::max());
size_t bytes_received = 0;
int select_result = poll_one(socket, POLL_MODE_READ, wait);
if (select_result > 0)
{
int recv_result = ::recv(socket, reinterpret_cast<char *>(buffer),
static_cast<int>(buffer_size), 0);
if (recv_result == 0)
{
throw connection::Error_eos();
}
else if (recv_result == SOCKET_ERROR)
{
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
#else
if (errno == EAGAIN || errno == EWOULDBLOCK)
#endif
{
bytes_received = 0;
}
else
{
throw_socket_error();
}
}
else
{
assert(recv_result > 0);
bytes_received = static_cast<size_t>(recv_result);
}
}
else if (select_result == 0)
{
return 0;
}
else
{
throw_socket_error();
}
return bytes_received;
}
size_t send_some(Socket socket, const byte *buffer, size_t buffer_size, bool wait)
{
if (buffer_size == 0)
return 0;
/*
TODO: buffer size checks - throw error if passed buffer is bigger than
some reasonable limit.
*/
assert(buffer_size > 0);
assert(buffer_size < (size_t)std::numeric_limits<int>::max());
size_t bytes_sent = 0;
int select_result = poll_one(socket, POLL_MODE_WRITE, wait);
if (select_result > 0)
{
int send_result = ::send(socket, reinterpret_cast<const char *>(buffer),
static_cast<int>(buffer_size), 0);
if (send_result == SOCKET_ERROR)
{
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
#else
if (errno == EAGAIN || errno == EWOULDBLOCK)
#endif
{
bytes_sent = 0;
}
else
{
throw_socket_error();
}
}
else
{
assert(send_result >= 0);
bytes_sent = static_cast<size_t>(send_result);
}
}
else if (select_result == 0)
{
return 0;
}
else
{
throw_socket_error();
}
return bytes_sent;
}
std::string get_local_hostname()
{
char buf[1024] = {0};
if (gethostname(buf, sizeof(buf)) < 0) {
throw_socket_error();
}
return buf;
}
#ifdef _WIN32
std::forward_list<Srv_host_detail> srv_list(const std::string &hostname)
{
DNS_STATUS status; //Return value of DnsQuery_A() function.
PDNS_RECORD pDnsRecord =nullptr; //Pointer to DNS_RECORD structure.
using Srv_list = std::forward_list<Srv_host_detail>;
Srv_list srv;
Srv_list::const_iterator srv_it = srv.before_begin();
status = DnsQuery(hostname.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, nullptr, &pDnsRecord, nullptr);
if (!status)
{
PDNS_RECORD pRecord = pDnsRecord;
while (pRecord)
{
if (pRecord->wType == DNS_TYPE_SRV)
{
srv_it = srv.emplace_after(srv_it,
Srv_host_detail
{
pRecord->Data.Srv.wPriority,
pRecord->Data.Srv.wWeight,
pRecord->Data.Srv.wPort,
pRecord->Data.Srv.pNameTarget
}
);
}
pRecord = pRecord->pNext;
}
DnsRecordListFree(pDnsRecord, DnsFreeRecordListDeep);
}
return srv;
}
#else
std::forward_list<Srv_host_detail> srv_list(const std::string &hostname)
{
struct __res_state state {};
res_ninit(&state);
using Srv_list = std::forward_list<Srv_host_detail>;
Srv_list srv;
Srv_list::const_iterator srv_it = srv.before_begin();
unsigned char query_buffer[NS_PACKETSZ];
//let get
int res = res_nsearch(&state, hostname.c_str(), ns_c_in, ns_t_srv, query_buffer, sizeof (query_buffer) );
if (res >= 0)
{
ns_msg msg;
char name_buffer[NS_MAXDNAME];
Srv_host_detail host_data;
ns_initparse(query_buffer, res, &msg);
auto process = [&msg, &name_buffer, &host_data, &srv, &srv_it](const ns_rr &rr) -> void
{
const unsigned char* srv_data = ns_rr_rdata(rr);
//Each NS_GET16 call moves srv_data to next value
NS_GET16(host_data.prio, srv_data);
NS_GET16(host_data.weight, srv_data);
NS_GET16(host_data.port, srv_data);
dn_expand(ns_msg_base(msg), ns_msg_end(msg),
srv_data, name_buffer, sizeof(name_buffer));
host_data.name = name_buffer;
srv_it = srv.emplace_after(
srv_it,
std::move(host_data));
};
for(int x= 0; x < ns_msg_count(msg, ns_s_an); x++)
{
ns_rr rr;
ns_parserr(&msg, ns_s_an, x, &rr);
process(rr);
}
}
res_nclose(&state);
return srv;
}
#endif
}}}} // cdk::foundation::connection::detail