aboutsummaryrefslogtreecommitdiff
path: root/src/net/sys/address.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/sys/address.cc')
-rw-r--r--src/net/sys/address.cc207
1 files changed, 207 insertions, 0 deletions
diff --git a/src/net/sys/address.cc b/src/net/sys/address.cc
new file mode 100644
index 0000000..48f22ac
--- /dev/null
+++ b/src/net/sys/address.cc
@@ -0,0 +1,207 @@
+/**
+ * ---------------------------------------------------------------------------
+ * @notice This file was adapted from the Minnow networking framework,
+ * originally developed for Stanford University's CS144 course.
+ * It has been incorporated into the Ricochet project as foundational
+ * system utility code.
+ * ---------------------------------------------------------------------------
+ */
+
+#include "ricochet/net/sys/address.hh"
+
+#include "ricochet/net/sys/exception.hh"
+
+#include <array>
+#include <cstring>
+#include <linux/if_packet.h>
+#include <memory>
+#include <netdb.h>
+#include <stdexcept>
+#include <sys/socket.h>
+#include <system_error>
+
+using namespace std;
+
+//! Converts Raw to `sockaddr *`.
+Address::Raw::operator sockaddr*()
+{
+ return reinterpret_cast<sockaddr*>( &storage ); // NOLINT(*-reinterpret-cast)
+}
+
+//! Converts Raw to `const sockaddr *`.
+Address::Raw::operator const sockaddr*() const
+{
+ return reinterpret_cast<const sockaddr*>( &storage ); // NOLINT(*-reinterpret-cast)
+}
+
+//! \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)
+namespace {
+inline addrinfo make_hints( int ai_flags, int ai_family ) // NOLINT(*-swappable-parameters)
+{
+ addrinfo hints {}; // value initialized to all zeros
+ hints.ai_flags = ai_flags;
+ hints.ai_family = ai_family;
+ return hints;
+}
+} // namespace
+
+//! \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
+{
+ if ( address_.storage.ss_family != AF_INET and address_.storage.ss_family != AF_INET6 ) {
+ throw runtime_error( "Address::ip_port() called on non-Internet address" );
+ }
+
+ array<char, NI_MAXHOST> ip {};
+ array<char, NI_MAXSERV> port {};
+
+ const int gni_ret = getnameinfo( static_cast<const sockaddr*>( 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
+{
+ if ( address_.storage.ss_family == AF_INET or address_.storage.ss_family == AF_INET6 ) {
+ const auto ip_and_port = ip_port();
+ return ip_and_port.first + ":" + ::to_string( ip_and_port.second );
+ }
+
+ return "(non-Internet address)";
+}
+
+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 ) }; // NOLINT(*-reinterpret-cast)
+}
+
+// equality
+bool Address::operator==( const Address& other ) const
+{
+ if ( size_ != other.size_ ) {
+ return false;
+ }
+
+ return 0 == memcmp( &address_, &other.address_, size_ );
+}
+
+// address families that correspond to each sockaddr type
+template<typename sockaddr_type>
+constexpr int sockaddr_family = -1;
+
+template<>
+constexpr int sockaddr_family<sockaddr_in> = AF_INET;
+
+template<>
+constexpr int sockaddr_family<sockaddr_in6> = AF_INET6;
+
+template<>
+constexpr int sockaddr_family<sockaddr_ll> = AF_PACKET;
+
+// safely cast the address to its underlying sockaddr type
+template<typename sockaddr_type>
+const sockaddr_type* Address::as() const
+{
+ const sockaddr* raw { address_ };
+ if ( sizeof( sockaddr_type ) < size() or raw->sa_family != sockaddr_family<sockaddr_type> ) {
+ throw runtime_error( "Address::as() conversion failure" );
+ }
+
+ return reinterpret_cast<const sockaddr_type*>( raw ); // NOLINT(*-reinterpret-cast)
+}
+
+template const sockaddr_in* Address::as<sockaddr_in>() const;
+template const sockaddr_in6* Address::as<sockaddr_in6>() const;
+template const sockaddr_ll* Address::as<sockaddr_ll>() const;