Skip to content

Instantly share code, notes, and snippets.

@aprehot
Created March 6, 2021 22:56
Show Gist options
  • Save aprehot/86c20b91a64a745650269b28aeec194c to your computer and use it in GitHub Desktop.
Save aprehot/86c20b91a64a745650269b28aeec194c to your computer and use it in GitHub Desktop.
/// XXX: Currently only used for testing due to issue with name lookup
/// Class that populates a sockaddress_storage with information related to the hostname, service, family and socktype.
/// Specifically avoids 0.0.0.0 address for compliance with STIGs
/// in Centos7, getaddrinfo doesnt work correctly with hostnames specified within /etc/hosts
class AddressInfo {
public:
AddressInfo() = delete;
~AddressInfo() {
if (this->ressave != nullptr) {
free(this->ressave);
this->ressave = nullptr;
}
};
enum TransportType : int {
TCP = SOCK_STREAM,
UDP = SOCK_DGRAM
};
enum IpDomain : int {
IPV4 = PF_INET,
IPV6 = PF_INET6,
ANY = PF_UNSPEC
};
AddressInfo(std::string const &hName,
short portNum) :
hostname(hName),
port(portNum) {
if (!isValidPortNumber(port)) {
LOG(ERROR) << "Supplied port was invalid";
}
this->getAddressFromHostname();
}
AddressInfo(std::string const &hName,
short portNum,
int proto,
int ipDomain) :
hostname(hName),
port(portNum),
protocol(TransportType(proto)),
domain(IpDomain(ipDomain)) {
if (ipDomain == IpDomain::IPV6) {
this->isIpv6 = true;
}
if (!isValidPortNumber(port)) {
LOG(ERROR) << "Supplied port was invalid";
}
this->getAddressFromHostname();
}
/// check for if 0.0.0.0 is returned from current address
bool checkForMeta() {
std::string addrStr(INET6_ADDRSTRLEN, '\0');
std::variant<sockaddr_in6*, sockaddr_in*> sockInfo;
if (this->isIpv6) {
sockInfo = reinterpret_cast<sockaddr_in6 *>(res->ai_addr);
} else {
sockInfo = reinterpret_cast<sockaddr_in *>(res->ai_addr);
}
std::visit([&](auto &&sock) {
using T = std::decay_t<decltype(sock)>;
if constexpr (std::is_same_v<T, sockaddr_in6*>) {
inet_ntop(AF_INET6, &sock->sin6_addr, addrStr.data(), addrStr.size());
} else {
inet_ntop(AF_INET, &sock->sin_addr, addrStr.data(), addrStr.size());
}
}, sockInfo);
if (addrStr == META_ADDRESS) {
LOG(INFO) << "Skipping address " << addrStr;
res = res->ai_next;
return true;
} else {
LOG(INFO) << "Got address: " << addrStr;
}
return false;
}
/// this is so that a valid address can be identified based on the supplied hostname parameter
bool getValidAddress() {
int yes{};
ressave = res;
std::string addrStr(INET6_ADDRSTRLEN, '\0');
while (res) {
sockFd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &(yes = 1), sizeof(yes)) < 0) {
LOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
// return false;
}
if (!(sockFd < 0)) {
this->isIpv6 = res->ai_family == AF_INET6; /// set ipv6 here or in ctor
if (this->checkForMeta()) continue; /// skip if 0.0.0.0
int err = bind(sockFd, res->ai_addr, res->ai_addrlen);
std::stringstream ss;
ss << "getaddrinfo error:: [" << gai_strerror(err) << "]";
LOG(ERROR) << ss.str();
if (err == 0) {
LOG(INFO) << "Address located was " << addrStr;
close(sockFd);
sockFd = Socket::INVALID_SOCKET;
memcpy(&this->sockStorage, res->ai_addr, sizeof(this->sockStorage));
if (this->ressave != nullptr) {
freeaddrinfo(ressave);
this->ressave = nullptr;
}
return true;
}
close(sockFd);
sockFd = Socket::INVALID_SOCKET;
}
res = res->ai_next;
}
if (this->ressave != nullptr) {
freeaddrinfo(ressave);
this->ressave = nullptr;
}
return false;
}
/// Fetch ipv4 or ipv6 address info from the supplied hostname
void getAddressFromHostname() {
this->hints = {};
memset(&hints, 0, sizeof(hints));
/// TODO: change these back to class members
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = 0;
hints.ai_protocol = 0;
hints.ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG);
int err{};
const char * portNum = std::to_string(port).c_str();
err = getaddrinfo(hostname.c_str(), port == 0 ? nullptr : portNum, &hints, &res);
if (err != 0) {
std::stringstream ss;
ss << "getaddrinfo error:: [" << gai_strerror(err) << "]";
auto lookupErr = ss.str().c_str();
throw lookup_error(lookupErr);
}
// addrinfo *reslocal;
// /// Get the local wildcard address to bind to (i.e. "::")
// int wildcardAddrFd = socket(AF_INET6, SOCK_DGRAM, 0);
// sockaddr_in6 addr = {};
// addr.sin6_addr = in6addr_any;
// addrinfo * inAddrAny = reinterpret_cast<addrinfo *>(&addr);
// if (0 != (bind(wildcardAddrFd, inAddrAny->ai_addr, inAddrAny->ai_addrlen))) {
// std::stringstream ss;
// ss << "bind error:: could not bind to ip address=[::]";
// auto lookupErr = ss.str().c_str();
// throw lookup_error(lookupErr);
// }
// getaddrinfo(nullptr, nullptr, &hints, &reslocal);
if (!this->getValidAddress()) {
std::stringstream ss;
ss << "get_addr error:: could not find ip address=["
<< hostname << "] port=[" << std::to_string(port) << "]";
auto lookupErr = ss.str().c_str();
throw lookup_error(lookupErr);
}
// freeaddrinfo(reslocal);
}
sockaddr_storage getAddressInfo() const {
return sockStorage;
}
private:
int sockFd{ Socket::INVALID_SOCKET };
addrinfo hints = {};
addrinfo *res{ nullptr };
addrinfo *ressave{ nullptr };
static inline constexpr std::string_view META_ADDRESS{ "0.0.0.0" };
bool isIpv6{ false };
std::string hostname;
short port;
TransportType protocol{ TransportType::TCP };
IpDomain domain{ IpDomain::ANY };
sockaddr_storage sockStorage = {};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment