#include "address.hh" #include "util.hh" #include #include #include #include #include #include using namespace std; //! Converts Raw to `sockaddr *`. Address::Raw::operator sockaddr *() { return reinterpret_cast(&storage); } //! Converts Raw to `const sockaddr *`. Address::Raw::operator const sockaddr *() const { return reinterpret_cast(&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 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 Address::ip_port() const { array ip{}; array 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(&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); }