CS144Lab/libsponge/util/address.cc
2021-09-21 17:11:37 -07:00

135 lines
4.9 KiB
C++

#include "address.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <netdb.h>
#include <stdexcept>
#include <system_error>
using namespace std;
//! Converts Raw to `sockaddr *`.
Address::Raw::operator sockaddr *() { return reinterpret_cast<sockaddr *>(&storage); }
//! Converts Raw to `const sockaddr *`.
Address::Raw::operator const sockaddr *() const { return reinterpret_cast<const sockaddr *>(&storage); }
//! \param[in] addr points to a raw socket address
//! \param[in] size is `addr`'s length
Address::Address(const sockaddr *addr, const size_t size) : _size(size) {
// make sure proposed sockaddr can fit
if (size > sizeof(_address.storage)) {
throw runtime_error("invalid sockaddr size");
}
memcpy(&_address.storage, addr, size);
}
//! Error category for getaddrinfo and getnameinfo failures.
class gai_error_category : public error_category {
public:
//! The name of the wrapped error
const char *name() const noexcept override { return "gai_error_category"; }
//! \brief An error message
//! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo)
//! or [getnameinfo(3)](\ref man3::getnameinfo)
string message(const int return_value) const noexcept override { return gai_strerror(return_value); }
};
//! \param[in] node is the hostname or dotted-quad address
//! \param[in] service is the service name or numeric string
//! \param[in] hints are criteria for resolving the supplied name
Address::Address(const string &node, const string &service, const addrinfo &hints) : _size() {
// prepare for the answer
addrinfo *resolved_address = nullptr;
// look up the name or names
const int gai_ret = getaddrinfo(node.c_str(), service.c_str(), &hints, &resolved_address);
if (gai_ret != 0) {
throw tagged_error(gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret);
}
// if success, should always have at least one entry
if (resolved_address == nullptr) {
throw runtime_error("getaddrinfo returned successfully but with no results");
}
// put resolved_address in a wrapper so it will get freed if we have to throw an exception
auto addrinfo_deleter = [](addrinfo *const x) { freeaddrinfo(x); };
unique_ptr<addrinfo, decltype(addrinfo_deleter)> wrapped_address(resolved_address, move(addrinfo_deleter));
// assign to our private members (making sure size fits)
*this = Address(wrapped_address->ai_addr, wrapped_address->ai_addrlen);
}
//! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo)
//! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo)
//! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref man3::getaddrinfo)
static inline addrinfo make_hints(const int ai_flags, const int ai_family) {
addrinfo hints{}; // value initialized to all zeros
hints.ai_flags = ai_flags;
hints.ai_family = ai_family;
return hints;
}
//! \param[in] hostname to resolve
//! \param[in] service name (from `/etc/services`, e.g., "http" is port 80)
Address::Address(const string &hostname, const string &service)
: Address(hostname, service, make_hints(AI_ALL, AF_INET)) {}
//! \param[in] ip address as a dotted quad ("1.1.1.1")
//! \param[in] port number
Address::Address(const string &ip, const uint16_t port)
// tell getaddrinfo that we don't want to resolve anything
: Address(ip, ::to_string(port), make_hints(AI_NUMERICHOST | AI_NUMERICSERV, AF_INET)) {}
// accessors
pair<string, uint16_t> Address::ip_port() const {
array<char, NI_MAXHOST> ip{};
array<char, NI_MAXSERV> port{};
const int gni_ret =
getnameinfo(_address, _size, ip.data(), ip.size(), port.data(), port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
if (gni_ret != 0) {
throw tagged_error(gai_error_category(), "getnameinfo", gni_ret);
}
return {ip.data(), stoi(port.data())};
}
string Address::to_string() const {
const auto ip_and_port = ip_port();
return ip_and_port.first + ":" + ::to_string(ip_and_port.second);
}
uint32_t Address::ipv4_numeric() const {
if (_address.storage.ss_family != AF_INET or _size != sizeof(sockaddr_in)) {
throw runtime_error("ipv4_numeric called on non-IPV4 address");
}
sockaddr_in ipv4_addr{};
memcpy(&ipv4_addr, &_address.storage, _size);
return be32toh(ipv4_addr.sin_addr.s_addr);
}
Address Address::from_ipv4_numeric(const uint32_t ip_address) {
sockaddr_in ipv4_addr{};
ipv4_addr.sin_family = AF_INET;
ipv4_addr.sin_addr.s_addr = htobe32(ip_address);
return {reinterpret_cast<sockaddr *>(&ipv4_addr), sizeof(ipv4_addr)};
}
// equality
bool Address::operator==(const Address &other) const {
if (_size != other._size) {
return false;
}
return 0 == memcmp(&_address, &other._address, _size);
}