diff --git a/core/include/join/protocol.hpp b/core/include/join/protocol.hpp index c8f1dbdf..57fe2053 100644 --- a/core/include/join/protocol.hpp +++ b/core/include/join/protocol.hpp @@ -53,6 +53,11 @@ namespace join template class BasicTlsAcceptor; + template + class BasicDatagramResolver; + template + class BasicTlsResolver; + template class BasicHttpClient; template @@ -671,6 +676,204 @@ namespace join return !(a == b); } + /** + * @brief DNS over UDP protocol class. + */ + class Dns + { + public: + using Endpoint = BasicInternetEndpoint; + using Socket = BasicDatagramSocket; + using Resolver = BasicDatagramResolver; + + /** + * @brief construct the DNS protocol instance. + * @param family IP address family. + */ + constexpr Dns (int family = AF_INET) noexcept + : _family (family) + { + } + + /** + * @brief get protocol suitable for IPv4 address family. + * @return an IPv4 address family suitable protocol. + */ + static inline Dns& v4 () noexcept + { + static Dns dnsv4 (AF_INET); + return dnsv4; + } + + /** + * @brief get protocol suitable for IPv6 address family. + * @return an IPv6 address family suitable protocol. + */ + static inline Dns& v6 () noexcept + { + static Dns dnsv6 (AF_INET6); + return dnsv6; + } + + /** + * @brief get the protocol IP address family. + * @return the protocol IP address family. + */ + constexpr int family () const noexcept + { + return _family; + } + + /** + * @brief get the protocol communication semantic. + * @return the protocol communication semantic. + */ + constexpr int type () const noexcept + { + return SOCK_DGRAM; + } + + /** + * @brief get the protocol type. + * @return the protocol type. + */ + constexpr int protocol () const noexcept + { + return IPPROTO_UDP; + } + + /// default DNS port. + static constexpr uint16_t defaultPort = 53; + + /// maximum DNS message size. + static constexpr size_t maxMsgSize = 8192; + + private: + /// IP address family. + int _family; + }; + + /** + * @brief check if equals. + * @param a protocol to check. + * @param b protocol to check. + * @return true if equals. + */ + constexpr bool operator== (const Dns& a, const Dns& b) noexcept + { + return a.family () == b.family (); + } + + /** + * @brief check if not equals. + * @param a protocol to check. + * @param b protocol to check. + * @return true if not equals. + */ + constexpr bool operator!= (const Dns& a, const Dns& b) noexcept + { + return !(a == b); + } + + /** + * @brief DNS over TLS protocol class. + */ + class Dot + { + public: + using Endpoint = BasicInternetEndpoint; + using Socket = BasicTlsSocket; + using Resolver = BasicTlsResolver; + + /** + * @brief construct the DoT protocol instance. + * @param family IP address family. + */ + constexpr Dot (int family = AF_INET) noexcept + : _family (family) + { + } + + /** + * @brief get protocol suitable for IPv4 address family. + * @return an IPv4 address family suitable protocol. + */ + static inline Dot& v4 () noexcept + { + static Dot dotv4 (AF_INET); + return dotv4; + } + + /** + * @brief get protocol suitable for IPv6 address family. + * @return an IPv6 address family suitable protocol. + */ + static inline Dot& v6 () noexcept + { + static Dot dotv6 (AF_INET6); + return dotv6; + } + + /** + * @brief get the protocol IP address family. + * @return the protocol IP address family. + */ + constexpr int family () const noexcept + { + return _family; + } + + /** + * @brief get the protocol communication semantic. + * @return the protocol communication semantic. + */ + constexpr int type () const noexcept + { + return SOCK_STREAM; + } + + /** + * @brief get the protocol type. + * @return the protocol type. + */ + constexpr int protocol () const noexcept + { + return IPPROTO_TCP; + } + + /// default DoT port. + static constexpr uint16_t defaultPort = 853; + + /// maximum DoT message size. + static constexpr size_t maxMsgSize = 16384; + + private: + /// IP address family. + int _family; + }; + + /** + * @brief check if equals. + * @param a protocol to check. + * @param b protocol to check. + * @return true if equals. + */ + constexpr bool operator== (const Dot& a, const Dot& b) noexcept + { + return a.family () == b.family (); + } + + /** + * @brief check if not equals. + * @param a protocol to check. + * @param b protocol to check. + * @return true if not equals. + */ + constexpr bool operator!= (const Dot& a, const Dot& b) noexcept + { + return !(a == b); + } + /** * @brief HTTP protocol class. */ diff --git a/core/include/join/socket.hpp b/core/include/join/socket.hpp index c0307f18..69dc5cbe 100644 --- a/core/include/join/socket.hpp +++ b/core/include/join/socket.hpp @@ -469,7 +469,7 @@ namespace join * @brief determine the local endpoint associated with this socket. * @return local endpoint. */ - Endpoint localEndpoint () const + Endpoint localEndpoint () const noexcept { struct sockaddr_storage sa; socklen_t sa_len = sizeof (struct sockaddr_storage); @@ -590,7 +590,7 @@ namespace join handle.events |= POLLOUT; } - int nset = (handle.fd > -1) ? ::poll (&handle, 1, timeout) : -1; + int nset = (handle.fd > -1) ? ::poll (&handle, 1, timeout == 0 ? -1 : timeout) : -1; if (nset != 1) { if (nset == -1) @@ -1053,7 +1053,7 @@ namespace join * @brief determine the remote endpoint associated with this socket. * @return remote endpoint. */ - const Endpoint& remoteEndpoint () const + const Endpoint& remoteEndpoint () const noexcept { return this->_remote; } @@ -1109,7 +1109,7 @@ namespace join * @brief returns the Time-To-Live value. * @return The Time-To-Live value. */ - int ttl () const + int ttl () const noexcept { return this->_ttl; } @@ -1691,16 +1691,19 @@ namespace join * @param endpoint endpoint to connect to. * @return 0 on success, -1 on failure. */ - int connectEncrypted (const Endpoint& endpoint) + virtual int connectEncrypted (const Endpoint& endpoint) { - if (BasicStreamSocket::connect (endpoint) == -1) + if (this->connect (endpoint) == -1) { return -1; } if (this->startEncryption () == -1) { - this->close (); + if (lastError != Errc::TemporaryError) + { + this->close (); + } return -1; } @@ -1763,7 +1766,7 @@ namespace join * @param timeout timeout in milliseconds (0: infinite). * return true on success, false otherwise. */ - bool waitEncrypted (int timeout = 0) + virtual bool waitEncrypted (int timeout = 0) { if (this->encrypted () == false) { @@ -2144,7 +2147,7 @@ namespace join * @param verify Enable peer verification if set to true, false otherwise. * @param depth The maximum certificate verification depth (default: no limit). */ - void setVerify (bool verify, int depth = -1) + void setVerify (bool verify, int depth = -1) noexcept { if (verify == true) { @@ -2191,6 +2194,32 @@ namespace join return 0; } + /** + * @brief set the ALPN protocols list. + * @param protocols list of protocol names (ex. {"h2", "http/1.1"}). + * @return 0 on success, -1 on failure. + */ + int setAlpnProtocols (const std::vector& protocols) + { + std::vector wire; + wire.reserve (256); + + for (auto const& proto : protocols) + { + wire.push_back (static_cast (proto.size ())); + wire.insert (wire.end (), proto.begin (), proto.end ()); + } + + if (SSL_CTX_set_alpn_protos (this->_tlsContext.get (), wire.data (), + static_cast (wire.size ())) != 0) + { + lastError = make_error_code (Errc::InvalidParam); + return -1; + } + + return 0; + } + protected: /** * @brief TLS state. diff --git a/core/tests/protocol_test.cpp b/core/tests/protocol_test.cpp index b7237b9a..550fc006 100644 --- a/core/tests/protocol_test.cpp +++ b/core/tests/protocol_test.cpp @@ -35,6 +35,8 @@ using join::Udp; using join::Icmp; using join::Tcp; using join::Tls; +using join::Dns; +using join::Dot; using join::Http; using join::Https; using join::Smtp; @@ -61,6 +63,12 @@ TEST (Protocol, family) ASSERT_EQ (Tls ().family (), AF_INET); ASSERT_EQ (Tls::v6 ().family (), AF_INET6); ASSERT_EQ (Tls::v4 ().family (), AF_INET); + ASSERT_EQ (Dns ().family (), AF_INET); + ASSERT_EQ (Dns::v6 ().family (), AF_INET6); + ASSERT_EQ (Dns::v4 ().family (), AF_INET); + ASSERT_EQ (Dot ().family (), AF_INET); + ASSERT_EQ (Dot::v6 ().family (), AF_INET6); + ASSERT_EQ (Dot::v4 ().family (), AF_INET); ASSERT_EQ (Http ().family (), AF_INET); ASSERT_EQ (Http::v6 ().family (), AF_INET6); ASSERT_EQ (Http::v4 ().family (), AF_INET); @@ -88,6 +96,8 @@ TEST (Protocol, type) ASSERT_EQ (Icmp ().type (), SOCK_RAW); ASSERT_EQ (Tcp ().type (), SOCK_STREAM); ASSERT_EQ (Tls ().type (), SOCK_STREAM); + ASSERT_EQ (Dns ().type (), SOCK_DGRAM); + ASSERT_EQ (Dot ().type (), SOCK_STREAM); ASSERT_EQ (Http ().type (), SOCK_STREAM); ASSERT_EQ (Https ().type (), SOCK_STREAM); ASSERT_EQ (Smtp ().type (), SOCK_STREAM); @@ -108,6 +118,8 @@ TEST (Protocol, protocol) ASSERT_EQ (Icmp::v4 ().protocol (), IPPROTO_ICMP); ASSERT_EQ (Tcp ().protocol (), IPPROTO_TCP); ASSERT_EQ (Tls ().protocol (), IPPROTO_TCP); + ASSERT_EQ (Dns ().protocol (), IPPROTO_UDP); + ASSERT_EQ (Dot ().protocol (), IPPROTO_TCP); ASSERT_EQ (Http ().protocol (), IPPROTO_TCP); ASSERT_EQ (Https ().protocol (), IPPROTO_TCP); ASSERT_EQ (Smtp ().protocol (), IPPROTO_TCP); @@ -142,6 +154,16 @@ TEST (Protocol, equal) ASSERT_EQ (Tls::v6 (), Tls::v6 ()); ASSERT_NE (Tls::v6 (), Tls::v4 ()); + ASSERT_EQ (Dns::v4 (), Dns::v4 ()); + ASSERT_NE (Dns::v4 (), Dns::v6 ()); + ASSERT_EQ (Dns::v6 (), Dns::v6 ()); + ASSERT_NE (Dns::v6 (), Dns::v4 ()); + + ASSERT_EQ (Dot::v4 (), Dot::v4 ()); + ASSERT_NE (Dot::v4 (), Dot::v6 ()); + ASSERT_EQ (Dot::v6 (), Dot::v6 ()); + ASSERT_NE (Dot::v6 (), Dot::v4 ()); + ASSERT_EQ (Http::v4 (), Http::v4 ()); ASSERT_NE (Http::v4 (), Http::v6 ()); ASSERT_EQ (Http::v6 (), Http::v6 ()); diff --git a/core/tests/tlssocket_test.cpp b/core/tests/tlssocket_test.cpp index 603e39c6..ab5ea393 100644 --- a/core/tests/tlssocket_test.cpp +++ b/core/tests/tlssocket_test.cpp @@ -1035,6 +1035,23 @@ TEST_F (TlsSocket, setCipher_1_3) tlsSocket.close (); } +/** + * @brief Test setAlpnProtocols method. + */ +TEST_F (TlsSocket, setAlpnProtocols) +{ + Tls::Socket tlsSocket (Tls::Socket::Blocking); + + ASSERT_EQ (tlsSocket.setAlpnProtocols ({""}), -1); + ASSERT_EQ (join::lastError, Errc::InvalidParam); + ASSERT_EQ (tlsSocket.setAlpnProtocols ({"http/1.1", "h2"}), 0) << join::lastError.message (); + ASSERT_EQ (tlsSocket.connectEncrypted ({_hostv4, _port}), 0) << join::lastError.message (); + ASSERT_EQ (tlsSocket.setAlpnProtocols ({"http/1.1", "h2"}), 0) << join::lastError.message (); + ASSERT_EQ (tlsSocket.disconnect (), 0) << join::lastError.message (); + ASSERT_EQ (tlsSocket.setAlpnProtocols ({"http/1.1", "h2"}), 0) << join::lastError.message (); + tlsSocket.close (); +} + /** * @brief Test is lower method. */ diff --git a/fabric/CMakeLists.txt b/fabric/CMakeLists.txt index 35ef98ed..e75e2b50 100644 --- a/fabric/CMakeLists.txt +++ b/fabric/CMakeLists.txt @@ -1,9 +1,8 @@ cmake_minimum_required(VERSION 3.22.1) set(PUBLIC_HEADERS - include/join/dnsbase.hpp + include/join/dnsmessage.hpp include/join/resolver.hpp - include/join/dns.hpp include/join/netlinkmanager.hpp include/join/neighbor.hpp include/join/neighbormanager.hpp diff --git a/fabric/include/join/dns.hpp b/fabric/include/join/dns.hpp deleted file mode 100644 index 2aaf4337..00000000 --- a/fabric/include/join/dns.hpp +++ /dev/null @@ -1,163 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2026 Mathieu Rabine - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef JOIN_FABRIC_DNS_HPP -#define JOIN_FABRIC_DNS_HPP - -// libjoin. -#include -#include - -namespace join -{ - /** - * @brief standard DNS transport - */ - class Dns - { - public: - using Socket = Udp::Socket; - using Endpoint = Udp::Endpoint; - using Resolver = BasicDnsResolver; - - /** - * @brief default constructor. - */ - Dns () noexcept = default; - - /** - * @brief destroy instance. - */ - ~Dns () noexcept - { - close (); - } - - /** - * @brief create the DNS over UDP transport. - * @param handler event handler to register to the reactor. - * @param interface network interface to bind the socket to. - * @param server remote DNS server address. - * @param port remote DNS server port (default: 53). - * @param reactor reactor instance. - * @return 0 on success, -1 on failure. - */ - int create (EventHandler* handler, const std::string& interface, const IpAddress& server, - uint16_t port = defaultPort, Reactor* reactor = nullptr) - { - _reactor = reactor ? reactor : ReactorThread::reactor (); - - if ((_socket.bind ({IpAddress (server.family ()), 0}) == -1) || (_socket.bindToDevice (interface) == -1)) - { - _socket.close (); - return -1; - } - - if (_socket.connect ({server, port}) == -1) - { - _socket.close (); - return -1; - } - - return _reactor->addHandler (_socket.handle (), handler); - } - - /** - * @brief close the DNS over UDP transport. - */ - void close () noexcept - { - if (_reactor && _socket.handle () != -1) - { - _reactor->delHandler (_socket.handle ()); - } - - _socket.close (); - } - - /** - * @brief read DNS message from UDP stream. - * @param buffer destination buffer. - * @param maxSize maximum number of bytes to read. - * @return number of bytes read, or -1 on error. - */ - int read (uint8_t* buffer, size_t maxSize) - { - return _socket.read (reinterpret_cast (buffer), maxSize); - } - - /** - * @brief write DNS message to UDP stream. - * @param buffer source buffer. - * @param size number of bytes to write. - * @return number of bytes written, -1 on error. - */ - int write (const uint8_t* buffer, size_t size) noexcept - { - return _socket.write (reinterpret_cast (buffer), size); - } - - /** - * @brief get the IP address family. - * @return the IP address family. - */ - int family () const - { - return _socket.family (); - } - - /** - * @brief determine the local endpoint associated with this transport. - * @return local endpoint. - */ - Endpoint localEndpoint () const - { - return _socket.localEndpoint (); - } - - /** - * @brief determine the remote endpoint associated with this transport. - * @return remote endpoint. - */ - Endpoint remoteEndpoint () const - { - return _socket.remoteEndpoint (); - } - - /// default port. - static constexpr uint16_t defaultPort = 53; - - /// max message size. - static constexpr size_t maxMsgSize = 8192; - - private: - /// socket. - Socket _socket; - - /// event loop reactor. - Reactor* _reactor = nullptr; - }; -} - -#endif diff --git a/fabric/include/join/dnsbase.hpp b/fabric/include/join/dnsmessage.hpp similarity index 68% rename from fabric/include/join/dnsbase.hpp rename to fabric/include/join/dnsmessage.hpp index 0cbbf055..10bd45e1 100644 --- a/fabric/include/join/dnsbase.hpp +++ b/fabric/include/join/dnsmessage.hpp @@ -22,17 +22,18 @@ * SOFTWARE. */ -#ifndef JOIN_FABRIC_DNSBASE_HPP -#define JOIN_FABRIC_DNSBASE_HPP +#ifndef JOIN_FABRIC_DNSMESSAGE_HPP +#define JOIN_FABRIC_DNSMESSAGE_HPP // libjoin. #include -#include +#include +#include // C++. #include +#include #include -#include #include #include @@ -41,10 +42,7 @@ namespace join { - /// forward declarations. - class Dns; - - /// list of alias. + /// list of aliases. using AliasList = std::unordered_set; /// list of name servers. @@ -73,8 +71,8 @@ namespace join std::string name; /**< canonical, server or mail exchanger name. */ uint16_t priority = 0; /**< SRV priority. */ uint16_t weight = 0; /**< SRV weight. */ - uint16_t port = 0; /**< SRV port (ex: 8009 pour Nest). */ - std::vector txts; /**< TXT records (ex: "fn=Salon"). */ + uint16_t port = 0; /**< SRV port. */ + std::vector txts; /**< TXT records. */ std::string mail; /**< server mail. */ uint32_t serial = 0; /**< serial number. */ uint32_t refresh = 0; /**< refresh interval. */ @@ -91,9 +89,9 @@ namespace join { uint16_t id = 0; /**< transaction ID. */ uint16_t flags = 0; /**< transaction flags. */ - IpAddress src; /**< source IP address.*/ - IpAddress dest; /**< destination IP address.*/ - uint16_t port = 0; /**< port.*/ + IpAddress src; /**< source IP address. */ + IpAddress dest; /**< destination IP address. */ + uint16_t port = 0; /**< port. */ std::vector questions; /**< question records. */ std::vector answers; /**< answer records. */ std::vector authorities; /**< authority records. */ @@ -101,15 +99,11 @@ namespace join }; /** - * @brief basic domain name resolution class. + * @brief DNS message codec. */ - template - class BasicDns : public EventHandler + class DnsMessage { public: - using Socket = typename TransportPolicy::Socket; - using Endpoint = typename Socket::Endpoint; - /** * @brief DNS record types. */ @@ -136,84 +130,41 @@ namespace join }; /** - * @brief create the DNS base instance. + * @brief create the DnsMessage instance. */ - explicit BasicDns () - : _buffer (std::make_unique (TransportPolicy::maxMsgSize)) - { - } + DnsMessage () noexcept = default; /** * @brief copy constructor. * @param other other object to copy. */ - BasicDns (const BasicDns& other) = delete; + DnsMessage (const DnsMessage& other) = delete; /** * @brief copy assignment operator. * @param other other object to copy. * @return a reference to the current object. */ - BasicDns& operator= (const BasicDns& other) = delete; + DnsMessage& operator= (const DnsMessage& other) = delete; /** * @brief move constructor. * @param other other object to move. */ - BasicDns (BasicDns&& other) = delete; + DnsMessage (DnsMessage&& other) = delete; /** * @brief move assignment operator. * @param other other object to move. * @return a reference to the current object. */ - BasicDns& operator= (BasicDns&& other) = delete; + DnsMessage& operator= (DnsMessage&& other) = delete; /** * @brief destroy instance. */ - virtual ~BasicDns () noexcept = default; - - /** - * @brief get record type name. - * @param recordType record type. - * @return record type name. - */ - static std::string typeName (uint16_t recordType) - { - switch (recordType) - { - OUT_ENUM (A); - OUT_ENUM (NS); - OUT_ENUM (CNAME); - OUT_ENUM (SOA); - OUT_ENUM (PTR); - OUT_ENUM (MX); - OUT_ENUM (TXT); - OUT_ENUM (AAAA); - OUT_ENUM (SRV); - OUT_ENUM (ANY); - } - - return "UNKNOWN"; - } - - /** - * @brief get record class name. - * @param recordClass record class. - * @return record class name. - */ - static std::string className (uint16_t recordClass) - { - switch (recordClass & 0x7FFF) - { - OUT_ENUM (IN); - } - - return "UNKNOWN"; - } + ~DnsMessage () noexcept = default; - protected: /** * @brief serialize a DNS packet into a byte stream. * @param packet DNS packet to serialize. @@ -242,23 +193,35 @@ namespace join for (auto const& question : packet.questions) { - encodeQuestion (question, data); + if (encodeQuestion (question, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } - // for (auto const& answer : packet.answers) - // { - // encodeResource (answer, data); - // } + for (auto const& answer : packet.answers) + { + if (encodeResource (answer, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } + } - // for (auto const& authority : packet.authorities) - // { - // encodeResource (authority, data); - // } + for (auto const& authority : packet.authorities) + { + if (encodeResource (authority, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } + } - // for (auto const& additional : packet.additionals) - // { - // encodeResource (additional, data); - // } + for (auto const& additional : packet.additionals) + { + if (encodeResource (additional, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } + } return 0; } @@ -297,7 +260,10 @@ namespace join for (uint16_t i = 0; i < qcount; ++i) { QuestionRecord question; - decodeQuestion (question, data); + if (decodeQuestion (question, data) == -1) + { + return -1; + } packet.questions.emplace_back (std::move (question)); } @@ -305,7 +271,10 @@ namespace join for (uint16_t i = 0; i < ancount; ++i) { ResourceRecord answer; - decodeResource (answer, data); + if (decodeResource (answer, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } packet.answers.emplace_back (std::move (answer)); } @@ -313,7 +282,10 @@ namespace join for (uint16_t i = 0; i < nscount; ++i) { ResourceRecord authority; - decodeResource (authority, data); + if (decodeResource (authority, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } packet.authorities.emplace_back (std::move (authority)); } @@ -321,13 +293,81 @@ namespace join for (uint16_t i = 0; i < arcount; ++i) { ResourceRecord additional; - decodeResource (additional, data); + if (decodeResource (additional, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } packet.additionals.emplace_back (std::move (additional)); } return 0; } + /** + * @brief convert DNS error to system error code. + * @param error DNS error number. + * @return system error. + */ + static std::error_code decodeError (uint16_t error) noexcept + { + switch (error) + { + case 0: + return {}; + case 1: + case 4: + return make_error_code (Errc::InvalidParam); + case 2: + return make_error_code (Errc::OperationFailed); + case 3: + return make_error_code (Errc::NotFound); + case 5: + return make_error_code (Errc::PermissionDenied); + default: + return make_error_code (Errc::UnknownError); + } + } + + /** + * @brief get record type name. + * @param recordType record type. + * @return record type name. + */ + static std::string typeName (uint16_t recordType) + { + switch (recordType) + { + OUT_ENUM (A); + OUT_ENUM (NS); + OUT_ENUM (CNAME); + OUT_ENUM (SOA); + OUT_ENUM (PTR); + OUT_ENUM (MX); + OUT_ENUM (TXT); + OUT_ENUM (AAAA); + OUT_ENUM (SRV); + OUT_ENUM (ANY); + } + + return "UNKNOWN"; + } + + /** + * @brief get record class name. + * @param recordClass record class. + * @return record class name. + */ + static std::string className (uint16_t recordClass) + { + switch (recordClass & 0x7FFF) + { + OUT_ENUM (IN); + } + + return "UNKNOWN"; + } + + private: /** * @brief encode a DNS name into a byte stream. * @param name DNS name to encode. @@ -360,7 +400,7 @@ namespace join { if (depth > 10) { - return -1; // LCOV_EXCL_LINE: requires malicious DNS packet. + return -1; } for (;;) @@ -375,7 +415,10 @@ namespace join { pos = data.tellg (); data.seekg (offset & 0x3FFF); - decodeName (name, data, depth + 1); + if (decodeName (name, data, depth + 1) == -1) + { + return -1; + } data.seekg (pos); break; } @@ -410,17 +453,20 @@ namespace join * @param data output stream. * @return 0 on success, -1 on error. */ - // int encodeMail (const std::string& mail, std::stringstream& data) const - // { - // std::string encodedMail = mail; - // size_t atPos = encodedMail.find ('@'); - // if (atPos != std::string::npos) - // { - // encodedMail.replace (atPos, 1, "."); - // } - // encodeName (encodedMail, data); - // return 0; - // } + int encodeMail (const std::string& mail, std::stringstream& data) const + { + std::string encodedMail = mail; + size_t atPos = encodedMail.find ('@'); + + if (atPos != std::string::npos) + { + encodedMail.replace (atPos, 1, "."); + } + + encodeName (encodedMail, data); + + return 0; + } /** * @brief decode a mail address from a byte stream. @@ -430,12 +476,17 @@ namespace join */ int decodeMail (std::string& mail, std::stringstream& data) const { - decodeName (mail, data); + if (decodeName (mail, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } + auto pos = mail.find ('.'); if (pos != std::string::npos) { mail[pos] = '@'; } + return 0; } @@ -466,7 +517,10 @@ namespace join */ int decodeQuestion (QuestionRecord& question, std::stringstream& data) const { - decodeName (question.host, data); + if (decodeName (question.host, data) == -1) + { + return -1; + } data.read (reinterpret_cast (&question.type), sizeof (question.type)); question.type = ntohs (question.type); @@ -483,105 +537,104 @@ namespace join * @param data output stream. * @return 0 on success, -1 on error. */ - // int encodeResource (const ResourceRecord& resource, std::stringstream& data) const - // { - // encodeName (resource.host, data); - - // uint16_t type = htons (resource.type); - // data.write (reinterpret_cast (&type), sizeof (type)); - - // uint16_t dnsclass = htons (resource.dnsclass); - // data.write (reinterpret_cast (&dnsclass), sizeof (dnsclass)); - - // uint32_t ttl = htonl (resource.ttl); - // data.write (reinterpret_cast (&ttl), sizeof (ttl)); - - // uint16_t dataLen = 0; - // auto dataLenPos = data.tellp (); - // data.write (reinterpret_cast (&dataLen), sizeof (dataLen)); - - // auto dataBegPos = data.tellp (); - - // if (resource.type == RecordType::A) - // { - // data.write (reinterpret_cast (resource.addr.addr ()), sizeof (in_addr)); - // } - // else if (resource.type == RecordType::AAAA) - // { - // data.write (reinterpret_cast (resource.addr.addr ()), sizeof (in6_addr)); - // } - // else if (resource.type == RecordType::NS) - // { - // encodeName (resource.name, data); - // } - // else if (resource.type == RecordType::CNAME) - // { - // encodeName (resource.name, data); - // } - // else if (resource.type == RecordType::PTR) - // { - // encodeName (resource.name, data); - // } - // else if (resource.type == RecordType::MX) - // { - // uint16_t mxpref = htons (resource.mxpref); - // data.write (reinterpret_cast (&mxpref), sizeof (mxpref)); - - // encodeName (resource.name, data); - // } - // else if (resource.type == RecordType::SOA) - // { - // encodeName (resource.name, data); - // encodeMail (resource.mail, data); - - // uint32_t serial = htonl (resource.serial); - // data.write (reinterpret_cast (&serial), sizeof (serial)); - - // uint32_t refresh = htonl (resource.refresh); - // data.write (reinterpret_cast (&refresh), sizeof (refresh)); - - // uint32_t retry = htonl (resource.retry); - // data.write (reinterpret_cast (&retry), sizeof (retry)); - - // uint32_t expire = htonl (resource.expire); - // data.write (reinterpret_cast (&expire), sizeof (expire)); - - // uint32_t minimum = htonl (resource.minimum); - // data.write (reinterpret_cast (&minimum), sizeof (minimum)); - // } - // else if (resource.type == RecordType::TXT) - // { - // for (const auto& txt : resource.txts) - // { - // uint8_t size = static_cast (txt.size ()); - // data.write (reinterpret_cast (&size), sizeof (size)); - // data.write (txt.data (), size); - // } - // } - // else if (resource.type == RecordType::SRV) - // { - // uint16_t priority = htons (resource.priority); - // data.write (reinterpret_cast (&priority), sizeof (priority)); - - // uint16_t weight = htons (resource.weight); - // data.write (reinterpret_cast (&weight), sizeof (weight)); - - // uint16_t port = htons (resource.port); - // data.write (reinterpret_cast (&port), sizeof (port)); - - // encodeName (resource.name, data); - // } - - // auto dataEndPos = data.tellp (); - // dataLen = static_cast (dataEndPos - dataBegPos); - - // data.seekp (dataLenPos); - // dataLen = htons (dataLen); - // data.write (reinterpret_cast (&dataLen), sizeof (dataLen)); - // data.seekp (dataEndPos); - - // return 0; - // } + int encodeResource (const ResourceRecord& resource, std::stringstream& data) const + { + encodeName (resource.host, data); + + uint16_t type = htons (resource.type); + data.write (reinterpret_cast (&type), sizeof (type)); + + uint16_t dnsclass = htons (resource.dnsclass); + data.write (reinterpret_cast (&dnsclass), sizeof (dnsclass)); + + uint32_t ttl = htonl (resource.ttl); + data.write (reinterpret_cast (&ttl), sizeof (ttl)); + + uint16_t dataLen = 0; + auto dataLenPos = data.tellp (); + data.write (reinterpret_cast (&dataLen), sizeof (dataLen)); + + auto dataBegPos = data.tellp (); + + if (resource.type == RecordType::A) + { + data.write (reinterpret_cast (resource.addr.addr ()), sizeof (in_addr)); + } + else if (resource.type == RecordType::AAAA) + { + data.write (reinterpret_cast (resource.addr.addr ()), sizeof (in6_addr)); + } + else if (resource.type == RecordType::NS) + { + encodeName (resource.name, data); + } + else if (resource.type == RecordType::CNAME) + { + encodeName (resource.name, data); + } + else if (resource.type == RecordType::PTR) + { + encodeName (resource.name, data); + } + else if (resource.type == RecordType::MX) + { + uint16_t mxpref = htons (resource.mxpref); + data.write (reinterpret_cast (&mxpref), sizeof (mxpref)); + encodeName (resource.name, data); + } + else if (resource.type == RecordType::SOA) + { + encodeName (resource.name, data); + encodeMail (resource.mail, data); + + uint32_t serial = htonl (resource.serial); + data.write (reinterpret_cast (&serial), sizeof (serial)); + + uint32_t refresh = htonl (resource.refresh); + data.write (reinterpret_cast (&refresh), sizeof (refresh)); + + uint32_t retry = htonl (resource.retry); + data.write (reinterpret_cast (&retry), sizeof (retry)); + + uint32_t expire = htonl (resource.expire); + data.write (reinterpret_cast (&expire), sizeof (expire)); + + uint32_t minimum = htonl (resource.minimum); + data.write (reinterpret_cast (&minimum), sizeof (minimum)); + } + else if (resource.type == RecordType::TXT) + { + for (auto const& txt : resource.txts) + { + uint8_t size = static_cast (txt.size ()); + data.write (reinterpret_cast (&size), sizeof (size)); + data.write (txt.data (), size); + } + } + else if (resource.type == RecordType::SRV) + { + uint16_t priority = htons (resource.priority); + data.write (reinterpret_cast (&priority), sizeof (priority)); + + uint16_t weight = htons (resource.weight); + data.write (reinterpret_cast (&weight), sizeof (weight)); + + uint16_t port = htons (resource.port); + data.write (reinterpret_cast (&port), sizeof (port)); + + encodeName (resource.name, data); + } + + auto dataEndPos = data.tellp (); + dataLen = static_cast (dataEndPos - dataBegPos); + + data.seekp (dataLenPos); + dataLen = htons (dataLen); + data.write (reinterpret_cast (&dataLen), sizeof (dataLen)); + data.seekp (dataEndPos); + + return 0; + } /** * @brief decode a resource record from a byte stream. @@ -591,7 +644,10 @@ namespace join */ int decodeResource (ResourceRecord& resource, std::stringstream& data) const { - decodeName (resource.host, data); + if (decodeName (resource.host, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } data.read (reinterpret_cast (&resource.type), sizeof (resource.type)); resource.type = ntohs (resource.type); @@ -622,26 +678,42 @@ namespace join } else if (resource.type == RecordType::NS) { - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } else if (resource.type == RecordType::CNAME) { - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } else if (resource.type == RecordType::PTR) { - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } else if (resource.type == RecordType::MX) { data.read (reinterpret_cast (&resource.mxpref), sizeof (resource.mxpref)); resource.mxpref = ntohs (resource.mxpref); - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } else if (resource.type == RecordType::SOA) { - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } + decodeMail (resource.mail, data); data.read (reinterpret_cast (&resource.serial), sizeof (resource.serial)); @@ -661,14 +733,21 @@ namespace join } else if (resource.type == RecordType::TXT) { - while (data.tellg () - dataBegPos < dataLen) + while (data.tellg () != -1 && (data.tellg () - dataBegPos < dataLen)) { uint8_t size = 0; - data.read (reinterpret_cast (&size), sizeof (size)); + if (!data.read (reinterpret_cast (&size), sizeof (size))) + { + return -1; // LCOV_EXCL_LINE + } std::string txt; txt.resize (size); - data.read (&txt[0], size); + if (!data.read (&txt[0], size)) + { + return -1; // LCOV_EXCL_LINE + } + resource.txts.emplace_back (std::move (txt)); } } @@ -683,7 +762,10 @@ namespace join data.read (reinterpret_cast (&resource.port), sizeof (resource.port)); resource.port = ntohs (resource.port); - decodeName (resource.name, data); + if (decodeName (resource.name, data) == -1) + { + return -1; // LCOV_EXCL_LINE + } } else { @@ -692,37 +774,6 @@ namespace join return 0; } - - /** - * @brief convert DNS error to system error code. - * @param error DNS error number. - * @return system error. - */ - static std::error_code decodeError (uint16_t error) noexcept - { - switch (error) - { - case 0: - return {}; - case 1: - case 4: - return make_error_code (Errc::InvalidParam); - case 2: - return make_error_code (Errc::OperationFailed); - case 3: - return make_error_code (Errc::NotFound); - case 5: - return make_error_code (Errc::PermissionDenied); - default: - return make_error_code (Errc::UnknownError); - } - } - - /// reception buffer. - std::unique_ptr _buffer; - - /// transport policy. - TransportPolicy _transport; }; } diff --git a/fabric/include/join/resolver.hpp b/fabric/include/join/resolver.hpp index 93492c57..cedfd3d1 100644 --- a/fabric/include/join/resolver.hpp +++ b/fabric/include/join/resolver.hpp @@ -26,11 +26,14 @@ #define JOIN_FABRIC_RESOLVER_HPP // libjoin. +#include #include -#include +#include // C++. +#include #include +#include // C. #include @@ -41,24 +44,25 @@ namespace join { /** - * @brief . + * @brief basic DNS resolver over datagram socket. */ - template - class BasicDnsResolver : public BasicDns + template + class BasicDatagramResolver : public Protocol::Socket { public: - using Endpoint = typename BasicDns::Endpoint; - using RecordType = typename BasicDns::RecordType; - using RecordClass = typename BasicDns::RecordClass; + using Socket = typename Protocol::Socket; + using Endpoint = typename Protocol::Endpoint; + using State = typename Socket::State; /** - * @brief create the resolver instance binded to the given interface. - * @param interface interface to use. - * @param args additional arguments (see. transport policies ::create). + * @brief construct the resolver instance. + * @param server remote DNS server hostname or IP address. + * @param port remote DNS server port. + * @param reactor reactor instance. */ - template - explicit BasicDnsResolver (const std::string& interface = "", Args&&... args) - : BasicDns () + explicit BasicDatagramResolver (const std::string& server = {}, uint16_t port = Protocol::defaultPort, + Reactor* reactor = nullptr) + : Socket () #ifdef DEBUG , _onSuccess (defaultOnSuccess) , _onFailure (defaultOnFailure) @@ -66,73 +70,86 @@ namespace join , _onSuccess (nullptr) , _onFailure (nullptr) #endif + , _server (server) + , _port (port) + , _reactor (reactor ? reactor : ReactorThread::reactor ()) + , _buffer (std::make_unique (Protocol::maxMsgSize)) { - if (this->_transport.create (this, interface, std::forward (args)...) == -1) - { - throw std::system_error (lastError); - } } /** - * @brief create instance by copy. + * @brief copy constructor. + * @param other other object to copy. */ - BasicDnsResolver (const BasicDnsResolver&) = delete; + BasicDatagramResolver (const BasicDatagramResolver& other) = delete; /** - * @brief create instance by move. + * @brief copy assignment operator. + * @param other other object to copy. + * @return a reference to the current object. */ - BasicDnsResolver& operator= (const BasicDnsResolver&) = delete; + BasicDatagramResolver& operator= (const BasicDatagramResolver& other) = delete; /** - * @brief assign instance by copy. + * @brief move constructor. + * @param other other object to move. */ - BasicDnsResolver (BasicDnsResolver&&) = delete; + BasicDatagramResolver (BasicDatagramResolver&& other) = delete; /** - * @brief assign instance by move. + * @brief move assignment operator. + * @param other other object to move. + * @return a reference to the current object. */ - BasicDnsResolver& operator= (BasicDnsResolver&&) = delete; + BasicDatagramResolver& operator= (BasicDatagramResolver&& other) = delete; /** * @brief destroy instance. */ - virtual ~BasicDnsResolver () noexcept + virtual ~BasicDatagramResolver () noexcept = default; + + /** + * @brief make a connection to the given endpoint. + * @param endpoint endpoint to connect to. + * @return 0 on success, -1 on failure. + */ + virtual int connect (const Endpoint& endpoint) override { - this->_transport.close (); + if (Socket::connect (endpoint) == -1) + { + return -1; + } + + _server = endpoint.hostname (); + if (_server.empty ()) + { + _server = endpoint.ip ().toString (); + } + _port = endpoint.port (); + + this->_reactor->addHandler (this->handle (), this); + + return 0; } /** - * @brief get IP address of the currently configured name servers. - * @return a list of configured name servers. + * @brief shutdown the connection. + * @return 0 on success, -1 on failure. */ - static IpAddressList nameServers () + virtual int disconnect () override { - IpAddressList addressList; + this->_reactor->delHandler (this->_handle); - struct __res_state res; - if (res_ninit (&res) == 0) + if (Socket::disconnect () == -1) { - for (int i = 0; i < res.nscount; ++i) - { - if (res.nsaddr_list[i].sin_family == AF_INET) - { - addressList.emplace_back (&res.nsaddr_list[i].sin_addr, sizeof (struct in_addr)); - } - // LCOV_EXCL_START: requires specific host IPv6 configuration. - else if (res._u._ext.nsaddrs[i] != nullptr && res._u._ext.nsaddrs[i]->sin6_family == AF_INET6) - { - addressList.emplace_back (&res._u._ext.nsaddrs[i]->sin6_addr, sizeof (struct in6_addr)); - } - // LCOV_EXCL_STOP - } - res_nclose (&res); + return -1; } - return addressList; + return 0; } /** - * @brief resolve host name and return all IP address found. + * @brief resolve host name and return all IP addresses found. * @param host host name to resolve. * @param family address family. * @param timeout timeout in milliseconds (default: 5000). @@ -152,13 +169,11 @@ namespace join QuestionRecord question; question.host = host; - RecordType expected = (family == AF_INET6) ? RecordType::AAAA : RecordType::A; - question.type = expected; - question.dnsclass = RecordClass::IN; - + question.type = (family == AF_INET6) ? DnsMessage::RecordType::AAAA : DnsMessage::RecordType::A; + question.dnsclass = DnsMessage::RecordClass::IN; packet.questions.push_back (question); - if (this->query (packet, timeout) == -1) + if (query (packet, timeout) == -1) { return {}; } @@ -167,7 +182,7 @@ namespace join for (auto const& answer : packet.answers) { - if (!answer.addr.isWildcard () && (answer.type == expected)) + if (!answer.addr.isWildcard () && (answer.type == question.type)) { addresses.push_back (answer.addr); } @@ -187,7 +202,7 @@ namespace join for (auto const& server : nameServers ()) { IpAddressList addresses = - BasicDnsResolver ("", server).resolveAllAddress (host, family); + BasicDatagramResolver (server.toString ()).resolveAllAddress (host, family); if (!addresses.empty ()) { return addresses; @@ -198,7 +213,7 @@ namespace join } /** - * @brief resolve host name and return all IP address found. + * @brief resolve host name and return all IP addresses found. * @param host host name to resolve. * @param timeout timeout in milliseconds (default: 5000). * @return the resolved IP address list. @@ -226,7 +241,7 @@ namespace join { for (auto const& server : nameServers ()) { - IpAddressList addresses = BasicDnsResolver ("", server).resolveAllAddress (host); + IpAddressList addresses = BasicDatagramResolver (server.toString ()).resolveAllAddress (host); if (!addresses.empty ()) { return addresses; @@ -321,12 +336,11 @@ namespace join QuestionRecord question; question.host = address.toArpa (); - question.type = RecordType::PTR; - question.dnsclass = RecordClass::IN; - + question.type = DnsMessage::RecordType::PTR; + question.dnsclass = DnsMessage::RecordClass::IN; packet.questions.push_back (question); - if (this->query (packet, timeout) == -1) + if (query (packet, timeout) == -1) { return {}; } @@ -335,7 +349,7 @@ namespace join for (auto const& answer : packet.answers) { - if (!answer.name.empty () && (answer.type == RecordType::PTR)) + if (!answer.name.empty () && (answer.type == DnsMessage::RecordType::PTR)) { aliases.insert (answer.name); } @@ -353,7 +367,7 @@ namespace join { for (auto const& server : nameServers ()) { - AliasList aliases = BasicDnsResolver ("", server).resolveAllName (address); + AliasList aliases = BasicDatagramResolver (server.toString ()).resolveAllName (address); if (!aliases.empty ()) { return aliases; @@ -414,11 +428,11 @@ namespace join QuestionRecord question; question.host = host; - question.type = RecordType::NS; - question.dnsclass = RecordClass::IN; + question.type = DnsMessage::RecordType::NS; + question.dnsclass = DnsMessage::RecordClass::IN; packet.questions.push_back (question); - if (this->query (packet, timeout) == -1) + if (query (packet, timeout) == -1) { return {}; } @@ -427,7 +441,7 @@ namespace join for (auto const& answer : packet.answers) { - if (!answer.name.empty () && (answer.type == RecordType::NS)) + if (!answer.name.empty () && (answer.type == DnsMessage::RecordType::NS)) { servers.insert (answer.name); } @@ -445,7 +459,7 @@ namespace join { for (auto const& server : nameServers ()) { - ServerList servers = BasicDnsResolver ("", server).resolveAllNameServer (host); + ServerList servers = BasicDatagramResolver (server.toString ()).resolveAllNameServer (host); if (!servers.empty ()) { return servers; @@ -507,18 +521,18 @@ namespace join QuestionRecord question; question.host = host; - question.type = RecordType::SOA; - question.dnsclass = RecordClass::IN; + question.type = DnsMessage::RecordType::SOA; + question.dnsclass = DnsMessage::RecordClass::IN; packet.questions.push_back (question); - if (this->query (packet, timeout) == -1) + if (query (packet, timeout) == -1) { return {}; } for (auto const& answer : packet.answers) { - if (!answer.name.empty () && (answer.type == RecordType::SOA)) + if (!answer.name.empty () && (answer.type == DnsMessage::RecordType::SOA)) { return answer.name; } @@ -536,7 +550,7 @@ namespace join { for (auto const& server : nameServers ()) { - std::string authority = BasicDnsResolver ("", server).resolveAuthority (host); + std::string authority = BasicDatagramResolver (server.toString ()).resolveAuthority (host); if (!authority.empty ()) { return authority; @@ -566,20 +580,19 @@ namespace join QuestionRecord question; question.host = host; - question.type = RecordType::MX; - question.dnsclass = RecordClass::IN; + question.type = DnsMessage::RecordType::MX; + question.dnsclass = DnsMessage::RecordClass::IN; packet.questions.push_back (question); - if (this->query (packet, timeout) == -1) + if (query (packet, timeout) == -1) { return {}; } ExchangerList exchangers; - for (auto const& answer : packet.answers) { - if (!answer.name.empty () && (answer.type == RecordType::MX)) + if (!answer.name.empty () && (answer.type == DnsMessage::RecordType::MX)) { exchangers.insert (answer.name); } @@ -598,7 +611,7 @@ namespace join for (auto const& server : nameServers ()) { ExchangerList exchangers = - BasicDnsResolver ("", server).resolveAllMailExchanger (host); + BasicDatagramResolver (server.toString ()).resolveAllMailExchanger (host); if (!exchangers.empty ()) { return exchangers; @@ -640,6 +653,36 @@ namespace join return {}; } + /** + * @brief get IP address of the currently configured name servers. + * @return a list of configured name servers. + */ + static IpAddressList nameServers () noexcept + { + IpAddressList addressList; + + struct __res_state res; + if (res_ninit (&res) == 0) + { + for (int i = 0; i < res.nscount; ++i) + { + if (res.nsaddr_list[i].sin_family == AF_INET) + { + addressList.emplace_back (&res.nsaddr_list[i].sin_addr, sizeof (struct in_addr)); + } + // LCOV_EXCL_START: requires specific host IPv6 configuration. + else if (res._u._ext.nsaddrs[i] != nullptr && res._u._ext.nsaddrs[i]->sin6_family == AF_INET6) + { + addressList.emplace_back (&res._u._ext.nsaddrs[i]->sin6_addr, sizeof (struct in6_addr)); + } + // LCOV_EXCL_STOP + } + res_nclose (&res); + } + + return addressList; + } + /** * @brief resolve service name. * @param service service name to resolve (ex. "http", "ftp", "ssh" etc...). @@ -668,7 +711,39 @@ namespace join /// callback called when a lookup sequence failed. DnsNotify _onFailure; - private: + protected: + /** + * @brief check if client must reconnect. + * @return true if reconnection is required. + */ + bool needReconnection () noexcept + { + return !this->connected (); + } + + /** + * @brief reconnect to the remote DNS server. + * @param endpoint endpoint to connect to. + * @param timeout timeout in milliseconds. + * @return 0 on success, -1 on failure. + */ + virtual int reconnect (const Endpoint& endpoint, [[maybe_unused]] std::chrono::milliseconds timeout) + { + if (this->disconnect () == -1) + { + this->close (); + return -1; + } + + if (this->connect (endpoint) == -1) + { + this->close (); + return -1; + } + + return 0; + } + /** * @brief serialize and send a DNS query, waiting for a response. * @param packet DNS packet to send, filled with the response on success. @@ -677,12 +752,41 @@ namespace join */ int query (DnsPacket& packet, std::chrono::milliseconds timeout) { - packet.src = this->_transport.localEndpoint ().ip (); - packet.dest = this->_transport.remoteEndpoint ().ip (); - packet.port = this->_transport.remoteEndpoint ().port (); + if (this->_remote.ip ().isWildcard ()) + { + IpAddress ip = IpAddress::isIpAddress (_server) ? IpAddress (_server) + : Dns::Resolver::lookupAddress (_server, AF_INET); + + if (ip.isWildcard ()) + { + lastError = make_error_code (Errc::InvalidParam); + notify (_onFailure, packet); + return -1; + } + + this->_remote.ip (ip); + this->_remote.port (_port); + } + + packet.dest = this->_remote.ip (); + packet.port = this->_remote.port (); + + if (this->needReconnection ()) + { + Endpoint endpoint{packet.dest, packet.port}; + endpoint.hostname (_server); + + if (this->reconnect (endpoint, timeout) == -1) + { + notify (_onFailure, packet); + return -1; + } + } + + packet.src = this->localEndpoint ().ip (); std::stringstream data; - if (this->serialize (packet, data) == -1) + if (_message.serialize (packet, data) == -1) { lastError = make_error_code (Errc::InvalidParam); notify (_onFailure, packet); @@ -690,7 +794,7 @@ namespace join } std::string buffer = data.str (); - if (buffer.size () > TransportPolicy::maxMsgSize) + if (buffer.size () > Protocol::maxMsgSize) { lastError = make_error_code (Errc::MessageTooLong); notify (_onFailure, packet); @@ -707,7 +811,7 @@ namespace join return -1; } - if (this->_transport.write (reinterpret_cast (buffer.data ()), buffer.size ()) == -1) + if (this->write (buffer.data (), buffer.size ()) == -1) { _pending.erase (inserted.first); notify (_onFailure, packet); @@ -744,17 +848,17 @@ namespace join */ void onReceive ([[maybe_unused]] int fd) override final { - int size = this->_transport.read (this->_buffer.get (), TransportPolicy::maxMsgSize); - if (size >= 12) + int size = this->read (_buffer.get (), Protocol::maxMsgSize); + if (size >= int (_headerSize)) { std::stringstream data; - data.rdbuf ()->pubsetbuf (reinterpret_cast (this->_buffer.get ()), size); + data.rdbuf ()->pubsetbuf (_buffer.get (), size); DnsPacket packet; - this->deserialize (packet, data); - packet.src = this->_transport.localEndpoint ().ip (); - packet.dest = this->_transport.remoteEndpoint ().ip (); - packet.port = this->_transport.remoteEndpoint ().port (); + _message.deserialize (packet, data); + packet.src = this->localEndpoint ().ip (); + packet.dest = this->remoteEndpoint ().ip (); + packet.port = this->remoteEndpoint ().port (); if (packet.flags & 0x8000) { @@ -764,7 +868,7 @@ namespace join if (it != _pending.end ()) { it->second->packet = packet; - it->second->ec = this->decodeError (packet.flags & 0x000F); + it->second->ec = DnsMessage::decodeError (packet.flags & 0x000F); if ((packet.flags & 0x0200) && it->second->ec == std::error_code{}) { it->second->ec = make_error_code (Errc::MessageTooLong); @@ -775,6 +879,15 @@ namespace join } } + /** + * @brief method called when handle is closed. + * @param fd file descriptor. + */ + void onClose ([[maybe_unused]] int fd) override final + { + this->disconnect (); + } + #ifdef DEBUG /* * @brief default callback called when a lookup sequence succeed. @@ -790,8 +903,8 @@ namespace join for (auto const& question : packet.questions) { std::cout << question.host; - std::cout << " " << BasicDns::typeName (question.type); - std::cout << " " << BasicDns::className (question.dnsclass); + std::cout << " " << DnsMessage::typeName (question.type); + std::cout << " " << DnsMessage::className (question.dnsclass); std::cout << std::endl; } @@ -800,22 +913,22 @@ namespace join for (auto const& answer : packet.answers) { std::cout << answer.host; - std::cout << " " << BasicDns::typeName (answer.type); - std::cout << " " << BasicDns::className (answer.dnsclass); + std::cout << " " << DnsMessage::typeName (answer.type); + std::cout << " " << DnsMessage::className (answer.dnsclass); std::cout << " " << answer.ttl; - if (answer.type == RecordType::A) + if (answer.type == DnsMessage::RecordType::A) { std::cout << " " << answer.addr; } - else if (answer.type == RecordType::NS) + else if (answer.type == DnsMessage::RecordType::NS) { std::cout << " " << answer.name; } - else if (answer.type == RecordType::CNAME) + else if (answer.type == DnsMessage::RecordType::CNAME) { std::cout << " " << answer.name; } - else if (answer.type == RecordType::SOA) + else if (answer.type == DnsMessage::RecordType::SOA) { std::cout << " " << answer.name; std::cout << " " << answer.mail; @@ -825,16 +938,16 @@ namespace join std::cout << " " << answer.expire; std::cout << " " << answer.minimum; } - else if (answer.type == RecordType::PTR) + else if (answer.type == DnsMessage::RecordType::PTR) { std::cout << " " << answer.name; } - else if (answer.type == RecordType::MX) + else if (answer.type == DnsMessage::RecordType::MX) { std::cout << " " << answer.mxpref; std::cout << " " << answer.name; } - else if (answer.type == RecordType::AAAA) + else if (answer.type == DnsMessage::RecordType::AAAA) { std::cout << " " << answer.addr; } @@ -856,8 +969,8 @@ namespace join for (auto const& question : packet.questions) { std::cout << question.host; - std::cout << " " << BasicDns::typeName (question.type); - std::cout << " " << BasicDns::className (question.dnsclass); + std::cout << " " << DnsMessage::typeName (question.type); + std::cout << " " << DnsMessage::className (question.dnsclass); std::cout << std::endl; } @@ -871,7 +984,7 @@ namespace join * @param func function to call. * @param packet DNS packet. */ - void notify (const DnsNotify& func, const DnsPacket& packet) const + void notify (const DnsNotify& func, const DnsPacket& packet) const noexcept { if (func) { @@ -879,6 +992,24 @@ namespace join } } + /// DNS message header size. + static constexpr size_t _headerSize = 12; + + /// DNS message codec. + DnsMessage _message; + + /// remote DNS server. + std::string _server; + + /// remote DNS server port. + uint16_t _port; + + /// event loop reactor. + Reactor* _reactor; + + /// reception buffer. + std::unique_ptr _buffer; + /// pending synchronous request. struct PendingRequest { @@ -893,6 +1024,397 @@ namespace join /// protection mutex. Mutex _syncMutex; }; + + /** + * @brief basic DNS resolver over TLS socket (DNS over TLS). + */ + template + class BasicTlsResolver : public BasicDatagramResolver + { + public: + using Socket = typename Protocol::Socket; + using Endpoint = typename Protocol::Endpoint; + using State = typename Socket::State; + + /** + * @brief construct the DoT resolver instance. + * @param server remote DNS server address. + * @param port remote DNS server port. + * @param reactor reactor instance. + */ + explicit BasicTlsResolver (const std::string& server = {}, uint16_t port = Protocol::defaultPort, + Reactor* reactor = nullptr) + : BasicDatagramResolver (server, port, reactor) + { + } + + /** + * @brief copy constructor. + * @param other other object to copy. + */ + BasicTlsResolver (const BasicTlsResolver& other) = delete; + + /** + * @brief copy assignment operator. + * @param other other object to copy. + * @return a reference to the current object. + */ + BasicTlsResolver& operator= (const BasicTlsResolver& other) = delete; + + /** + * @brief move constructor. + * @param other other object to move. + */ + BasicTlsResolver (BasicTlsResolver&& other) = delete; + + /** + * @brief move assignment operator. + * @param other other object to move. + * @return a reference to the current object. + */ + BasicTlsResolver& operator= (BasicTlsResolver&& other) = delete; + + /** + * @brief destroy instance. + */ + virtual ~BasicTlsResolver () noexcept = default; + + /** + * @brief make a connection to the given endpoint. + * @param endpoint endpoint to connect to. + * @return 0 on success, -1 on failure. + */ + virtual int connect (const Endpoint& endpoint) override + { + if (Socket::connect (endpoint) == -1) + { + return -1; + } + + this->_server = this->_remote.hostname (); + if (this->_server.empty ()) + { + this->_server = this->_remote.ip ().toString (); + } + this->_port = this->_remote.port (); + + return 0; + } + + /** + * @brief make an encrypted connection to the given endpoint. + * @param endpoint endpoint to connect to. + * @return 0 on success, -1 on failure. + */ + virtual int connectEncrypted (const Endpoint& endpoint) override + { + if (Socket::connectEncrypted (endpoint) == -1) + { + return -1; + } + + this->_reactor->addHandler (this->handle (), this); + + return 0; + } + + /** + * @brief wait until TLS handshake is performed or timeout occur (non blocking socket). + * @param timeout timeout in milliseconds (0: infinite). + * return true on success, false otherwise. + */ + virtual bool waitEncrypted (int timeout = 0) override + { + if (!Socket::waitEncrypted (timeout)) + { + return false; + } + + this->_reactor->addHandler (this->handle (), this); + + return true; + } + + /** + * @brief block until connected. + * @param timeout timeout in milliseconds. + * @return true if connected, false otherwise. + */ + virtual bool waitConnected (int timeout = 0) override + { + if (!Socket::waitConnected (timeout)) + { + return false; + } + + this->_server = this->_remote.hostname (); + if (this->_server.empty ()) + { + this->_server = this->_remote.ip ().toString (); + } + this->_port = this->_remote.port (); + + return true; + } + + /** + * @brief close the TLS connection and reset framing state. + */ + virtual void close () noexcept override + { + Socket::close (); + _size = 0; + _offset = 0; + } + + /** + * @brief read a framed DoT message (2-byte length prefix). + * @param data destination buffer. + * @param maxSize maximum number of bytes to read. + * @return number of bytes read, or -1 on error. + */ + virtual int read (char* data, unsigned long maxSize) noexcept override + { + if (_offset < _frameHeaderSize) + { + int nread = Socket::read (data + _offset, _frameHeaderSize - _offset); + if (nread == -1) + { + if (lastError != Errc::TemporaryError) + { + _offset = 0; + _size = 0; + } + return -1; + } + + _offset += static_cast (nread); + + if (_offset < _frameHeaderSize) + { + lastError = make_error_code (Errc::TemporaryError); + return -1; + } + + _size = ntohs (*reinterpret_cast (data)); + + if (_size > maxSize) + { + lastError = make_error_code (Errc::MessageTooLong); + _offset = 0; + _size = 0; + return -1; + } + } + + int nread = Socket::read (data + (_offset - _frameHeaderSize), _size - (_offset - _frameHeaderSize)); + if (nread == -1) + { + if (lastError != Errc::TemporaryError) + { + _offset = 0; + _size = 0; + } + return -1; + } + + _offset += static_cast (nread); + + if (_offset < (_size + _frameHeaderSize)) + { + lastError = make_error_code (Errc::TemporaryError); + return -1; + } + + int msgLen = static_cast (_size); + _offset = 0; + _size = 0; + + return msgLen; + } + + /** + * @brief write a framed DoT message (2-byte length prefix). + * @param data source buffer. + * @param size number of bytes to write. + * @return number of bytes written, or -1 on error. + */ + virtual int write (const char* data, unsigned long size) noexcept override + { + uint16_t msgLength = htons (static_cast (size)); + const char* p = reinterpret_cast (&msgLength); + unsigned long remaining = sizeof (msgLength); + + while (remaining > 0) + { + int result = Socket::write (p, remaining); + if (result == -1) + { + if (lastError == Errc::TemporaryError) + { + if (this->waitReadyWrite ()) + continue; + } + return -1; + } + p += result; + remaining -= result; + } + + p = data; + remaining = size; + + while (remaining > 0) + { + int result = Socket::write (p, remaining); + if (result == -1) + { + if (lastError == Errc::TemporaryError) + { + if (this->waitReadyWrite ()) + continue; + } + return -1; + } + p += result; + remaining -= result; + } + + return static_cast (size); + } + + /** + * @brief resolve host name using system name servers and return all IP addresses found. + * @param host host name to resolve. + * @param family address family. + * @return the resolved IP address list, empty on error. + */ + static IpAddressList lookupAllAddress (const std::string& host, int family) = delete; + + /** + * @brief resolve host name using system name servers and return all IP addresses found. + * @param host host name to resolve. + * @return the resolved IP address list, empty on error. + */ + static IpAddressList lookupAllAddress (const std::string& host) = delete; + + /** + * @brief resolve host name using system name servers. + * @param host host name to resolve. + * @param family address family. + * @return the first resolved IP address found, wildcard address on error. + */ + static IpAddress lookupAddress (const std::string& host, int family) = delete; + + /** + * @brief resolve host name using system name servers. + * @param host host name to resolve. + * @return the first resolved IP address found, wildcard address on error. + */ + static IpAddress lookupAddress (const std::string& host) = delete; + + /** + * @brief resolve all host address. + * @param address host address to resolve. + * @return the resolved alias list. + */ + static AliasList lookupAllName (const IpAddress& address) = delete; + + /** + * @brief resolve host address. + * @param address host address to resolve. + * @return the first resolved alias. + */ + static std::string lookupName (const IpAddress& address) = delete; + + /** + * @brief resolve all host name server. + * @param host host name to resolve. + * @return the resolved name server list. + */ + static ServerList lookupAllNameServer (const std::string& host) = delete; + + /** + * @brief resolve host name server. + * @param host host name to resolve. + * @return the first resolved name server. + */ + static std::string lookupNameServer (const std::string& host) = delete; + + /** + * @brief resolve host start of authority name server. + * @param host host name to resolve. + * @return the start of authority name server. + */ + static std::string lookupAuthority (const std::string& host) = delete; + + /** + * @brief resolve all host mail exchanger. + * @param host host name to resolve. + * @return the resolved mail exchanger list. + */ + static ExchangerList lookupAllMailExchanger (const std::string& host) = delete; + + /** + * @brief resolve host mail exchanger. + * @param host host name to resolve. + * @return the first resolved mail exchanger. + */ + static std::string lookupMailExchanger (const std::string& host) = delete; + + private: + /** + * @brief reconnect to the remote DNS server. + * @param endpoint endpoint to connect to. + * @param timeout timeout in milliseconds. + * @return 0 on success, -1 on failure. + */ + virtual int reconnect (const Endpoint& endpoint, std::chrono::milliseconds timeout) override + { + if (this->disconnect () == -1) + { + if (lastError != Errc::TemporaryError) + { + this->close (); + return -1; + } + + if (!this->waitDisconnected (timeout.count ())) + { + this->close (); + return -1; + } + } + + this->setAlpnProtocols ({"dot"}); + + if (this->connectEncrypted (endpoint) == -1) + { + if (lastError != Errc::TemporaryError) + { + this->close (); + return -1; + } + + if (!this->waitEncrypted (timeout.count ())) + { + this->close (); + return -1; + } + } + + return 0; + } + + /// DOT framing header size. + static constexpr size_t _frameHeaderSize = 2; + + /// total expected payload size. + size_t _size = 0; + + /// current position in the buffer. + size_t _offset = 0; + }; } #endif diff --git a/fabric/tests/CMakeLists.txt b/fabric/tests/CMakeLists.txt index 79795d04..4b4cddf9 100644 --- a/fabric/tests/CMakeLists.txt +++ b/fabric/tests/CMakeLists.txt @@ -2,11 +2,21 @@ cmake_minimum_required(VERSION 3.22.1) find_package(GTest REQUIRED) +add_executable(dnsmessage.gtest dnsmessage_test.cpp) +target_link_libraries(dnsmessage.gtest ${JOIN_FABRIC} GTest::gtest_main) +add_test(NAME dnsmessage.gtest COMMAND dnsmessage.gtest) +install(TARGETS dnsmessage.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + add_executable(dns.gtest dns_test.cpp) target_link_libraries(dns.gtest ${JOIN_FABRIC} GTest::gtest_main) add_test(NAME dns.gtest COMMAND dns.gtest) install(TARGETS dns.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) +add_executable(dot.gtest dot_test.cpp) +target_link_libraries(dot.gtest ${JOIN_FABRIC} GTest::gtest_main) +add_test(NAME dot.gtest COMMAND dot.gtest) +install(TARGETS dot.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + add_executable(neighbor.gtest neighbor_test.cpp) target_link_libraries(neighbor.gtest ${JOIN_FABRIC} GTest::gtest_main) add_test(NAME neighbor.gtest COMMAND neighbor.gtest) diff --git a/fabric/tests/dns_test.cpp b/fabric/tests/dns_test.cpp index ce0b15bb..49f75936 100644 --- a/fabric/tests/dns_test.cpp +++ b/fabric/tests/dns_test.cpp @@ -23,11 +23,12 @@ */ // libjoin. -#include +#include // Libraries. #include +using join::lastError; using join::Dns; using join::IpAddress; using join::IpAddressList; @@ -45,7 +46,13 @@ class DnsTest : public ::testing::Test _servers = Dns::Resolver::nameServers (); ASSERT_GT (_servers.size (), 0); - _resolver = std::make_unique ("", _servers.front ()); + _resolver = std::make_unique (_servers.front ().toString ()); + ASSERT_NE (_resolver, nullptr); + } + + void TearDown () override + { + EXPECT_EQ (_resolver->disconnect (), 0) << lastError.message (); } IpAddressList _servers; @@ -81,23 +88,22 @@ TEST_F (DnsTest, resolveAllAddress) addresses = Dns::Resolver::lookupAllAddress ("localhost"); EXPECT_GT (addresses.size (), 0); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + addresses = Dns::Resolver ("255.255.255.255").resolveAllAddress ("localhost", AF_INET); + EXPECT_EQ (addresses.size (), 0); - addresses = Dns::Resolver ("", "8.8.8.8", 53).resolveAllAddress ("joinframework.net", AF_INET, 1ms); + addresses = Dns::Resolver ("8.8.8.8", 53).resolveAllAddress ("joinframework.net", AF_INET, 1ms); EXPECT_EQ (addresses.size (), 0); - addresses = Dns::Resolver ("", "8.8.8.8", 53).resolveAllAddress ("joinframework.net", 1ms); + addresses = Dns::Resolver ("8.8.8.8", 53).resolveAllAddress ("joinframework.net", 1ms); EXPECT_EQ (addresses.size (), 0); - addresses = Dns::Resolver::lookupAllAddress ("www.netflix.com"); + addresses = Dns::Resolver::lookupAllAddress ("google.com"); EXPECT_GT (addresses.size (), 0); - addresses = _resolver->resolveAllAddress ("www.google.com"); + addresses = _resolver->resolveAllAddress ("google.com"); EXPECT_GT (addresses.size (), 0); - addresses = Dns::Resolver::lookupAllAddress ("www.amazon.com"); + addresses = Dns::Resolver::lookupAllAddress ("google.com"); EXPECT_GT (addresses.size (), 0); } @@ -132,23 +138,22 @@ TEST_F (DnsTest, resolveAddress) address = Dns::Resolver::lookupAddress ("localhost"); EXPECT_TRUE (address.isLoopBack ()); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + address = Dns::Resolver ("255.255.255.255").resolveAddress ("localhost", AF_INET); + EXPECT_TRUE (address.isWildcard ()); - address = Dns::Resolver ("", "8.8.8.8", 53).resolveAddress ("joinframework.net", AF_INET, 1ms); + address = Dns::Resolver ("8.8.8.8", 53).resolveAddress ("joinframework.net", AF_INET, 1ms); EXPECT_TRUE (address.isWildcard ()); - address = Dns::Resolver ("", "8.8.8.8", 53).resolveAddress ("joinframework.net", 1ms); + address = Dns::Resolver ("8.8.8.8", 53).resolveAddress ("joinframework.net", 1ms); EXPECT_TRUE (address.isWildcard ()); - address = Dns::Resolver::lookupAddress ("www.netflix.com"); + address = Dns::Resolver::lookupAddress ("google.com"); EXPECT_FALSE (address.isWildcard ()); - address = _resolver->resolveAddress ("www.google.com"); + address = _resolver->resolveAddress ("google.com"); EXPECT_FALSE (address.isWildcard ()); - address = Dns::Resolver::lookupAddress ("www.amazon.com"); + address = Dns::Resolver::lookupAddress ("google.com"); EXPECT_FALSE (address.isWildcard ()); } @@ -187,15 +192,17 @@ TEST_F (DnsTest, resolveAllName) aliases = Dns::Resolver::lookupAllName ("::1"); EXPECT_GT (aliases.size (), 0); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); + aliases = Dns::Resolver ("255.255.255.255").resolveAllName ("127.0.0.1"); + EXPECT_EQ (aliases.size (), 0); - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + aliases = Dns::Resolver ("255.255.255.255").resolveAllName ("::1"); + EXPECT_EQ (aliases.size (), 0); - aliases = Dns::Resolver ("", "8.8.8.8", 53) - .resolveAllName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET), 1ms); + aliases = + Dns::Resolver ("8.8.8.8", 53).resolveAllName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET), 1ms); EXPECT_EQ (aliases.size (), 0); - aliases = Dns::Resolver ("", "8.8.8.8", 53) + aliases = Dns::Resolver ("8.8.8.8", 53) .resolveAllName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET6), 1ms); EXPECT_EQ (aliases.size (), 0); } @@ -235,16 +242,18 @@ TEST_F (DnsTest, resolveName) alias = Dns::Resolver::lookupName ("::1"); EXPECT_FALSE (alias.empty ()); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); + alias = Dns::Resolver ("255.255.255.255").resolveName ("127.0.0.1"); + EXPECT_TRUE (alias.empty ()); - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + alias = Dns::Resolver ("255.255.255.255").resolveName ("::1"); + EXPECT_TRUE (alias.empty ()); - alias = Dns::Resolver ("", "8.8.8.8", 53) - .resolveName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET), 1ms); + alias = + Dns::Resolver ("8.8.8.8", 53).resolveName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET), 1ms); EXPECT_TRUE (alias.empty ()); - alias = Dns::Resolver ("", "8.8.8.8", 53) - .resolveName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET6), 1ms); + alias = + Dns::Resolver ("8.8.8.8", 53).resolveName (Dns::Resolver::lookupAddress ("joinframework.net", AF_INET6), 1ms); EXPECT_TRUE (alias.empty ()); } @@ -265,23 +274,23 @@ TEST_F (DnsTest, resolveAllNameServer) names = Dns::Resolver::lookupAllNameServer ("localhost"); EXPECT_EQ (names.size (), 0); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + names = Dns::Resolver ("255.255.255.255").resolveAllNameServer ("localhost"); + EXPECT_EQ (names.size (), 0); - names = Dns::Resolver ("", "8.8.8.8", 53).resolveAllNameServer ("joinframework.net", 1ms); + names = Dns::Resolver ("8.8.8.8", 53).resolveAllNameServer ("joinframework.net", 1ms); EXPECT_EQ (names.size (), 0); - names = Dns::Resolver::lookupAllNameServer ("netflix.com"); + names = Dns::Resolver::lookupAllNameServer ("google.com"); EXPECT_GT (names.size (), 0); names = _resolver->resolveAllNameServer ("google.com"); EXPECT_GT (names.size (), 0); - names = Dns::Resolver ("", Dns::Resolver::lookupAddress ("a.gtld-servers.net")).resolveAllNameServer ("google.com"); + names = Dns::Resolver (Dns::Resolver::lookupAddress ("a.gtld-servers.net").toString ()) + .resolveAllNameServer ("google.com"); EXPECT_EQ (names.size (), 0); - names = Dns::Resolver::lookupAllNameServer ("amazon.com"); + names = Dns::Resolver::lookupAllNameServer ("google.com"); EXPECT_GT (names.size (), 0); } @@ -302,23 +311,23 @@ TEST_F (DnsTest, resolveNameServer) name = Dns::Resolver::lookupNameServer ("localhost"); EXPECT_TRUE (name.empty ()); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + name = Dns::Resolver ("255.255.255.255").resolveNameServer ("localhost"); + EXPECT_TRUE (name.empty ()); - name = Dns::Resolver ("", "8.8.8.8", 53).resolveNameServer ("joinframework.net", 1ms); + name = Dns::Resolver ("8.8.8.8", 53).resolveNameServer ("joinframework.net", 1ms); EXPECT_TRUE (name.empty ()); - name = Dns::Resolver::lookupNameServer ("netflix.com"); + name = Dns::Resolver::lookupNameServer ("google.com"); EXPECT_FALSE (name.empty ()); name = _resolver->resolveNameServer ("google.com"); EXPECT_FALSE (name.empty ()); - name = Dns::Resolver ("", Dns::Resolver::lookupAddress ("a.gtld-servers.net")).resolveNameServer ("google.com"); + name = Dns::Resolver (Dns::Resolver::lookupAddress ("a.gtld-servers.net").toString ()) + .resolveNameServer ("google.com"); EXPECT_TRUE (name.empty ()); - name = Dns::Resolver::lookupNameServer ("amazon.com"); + name = Dns::Resolver::lookupNameServer ("google.com"); EXPECT_FALSE (name.empty ()); } @@ -339,20 +348,19 @@ TEST_F (DnsTest, resolveAuthority) name = Dns::Resolver::lookupAuthority ("localhost"); EXPECT_TRUE (name.empty ()); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + name = Dns::Resolver ("255.255.255.255").resolveAuthority ("localhost"); + EXPECT_TRUE (name.empty ()); - name = Dns::Resolver ("", "8.8.8.8", 53).resolveAuthority ("joinframework.net", 1ms); + name = Dns::Resolver ("8.8.8.8", 53).resolveAuthority ("joinframework.net", 1ms); EXPECT_TRUE (name.empty ()); - name = Dns::Resolver::lookupAuthority ("netflix.com"); + name = Dns::Resolver::lookupAuthority ("google.com"); EXPECT_FALSE (name.empty ()); name = _resolver->resolveAuthority ("google.com"); EXPECT_FALSE (name.empty ()); - name = Dns::Resolver::lookupAuthority ("amazon.com"); + name = Dns::Resolver::lookupAuthority ("google.com"); EXPECT_FALSE (name.empty ()); } @@ -373,20 +381,19 @@ TEST_F (DnsTest, resolveAllMailExchanger) exchangers = Dns::Resolver::lookupAllMailExchanger ("localhost"); EXPECT_EQ (exchangers.size (), 0); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + exchangers = Dns::Resolver ("255.255.255.255").resolveAllMailExchanger ("localhost"); + EXPECT_EQ (exchangers.size (), 0); - exchangers = Dns::Resolver ("", "8.8.8.8", 53).resolveAllMailExchanger ("joinframework.net", 1ms); + exchangers = Dns::Resolver ("8.8.8.8", 53).resolveAllMailExchanger ("joinframework.net", 1ms); EXPECT_EQ (exchangers.size (), 0); - exchangers = Dns::Resolver::lookupAllMailExchanger ("netflix.com"); + exchangers = Dns::Resolver::lookupAllMailExchanger ("google.com"); EXPECT_GT (exchangers.size (), 0); exchangers = _resolver->resolveAllMailExchanger ("google.com"); EXPECT_GT (exchangers.size (), 0); - exchangers = Dns::Resolver::lookupAllMailExchanger ("amazon.com"); + exchangers = Dns::Resolver::lookupAllMailExchanger ("google.com"); EXPECT_GT (exchangers.size (), 0); } @@ -407,20 +414,19 @@ TEST_F (DnsTest, resolveMailExchanger) exchanger = Dns::Resolver::lookupMailExchanger ("localhost"); EXPECT_TRUE (exchanger.empty ()); - EXPECT_THROW (Dns::Resolver ("foo", _servers.front ()), std::system_error); - - EXPECT_THROW (Dns::Resolver ("", "255.255.255.255"), std::system_error); + exchanger = Dns::Resolver ("255.255.255.255").resolveMailExchanger ("localhost"); + EXPECT_TRUE (exchanger.empty ()); - exchanger = Dns::Resolver ("", "8.8.8.8", 53).resolveMailExchanger ("joinframework.net", 1ms); + exchanger = Dns::Resolver ("8.8.8.8", 53).resolveMailExchanger ("joinframework.net", 1ms); EXPECT_TRUE (exchanger.empty ()); - exchanger = Dns::Resolver::lookupMailExchanger ("netflix.com"); + exchanger = Dns::Resolver::lookupMailExchanger ("google.com"); EXPECT_FALSE (exchanger.empty ()); exchanger = _resolver->resolveMailExchanger ("google.com"); EXPECT_FALSE (exchanger.empty ()); - exchanger = Dns::Resolver::lookupMailExchanger ("amazon.com"); + exchanger = Dns::Resolver::lookupMailExchanger ("google.com"); EXPECT_FALSE (exchanger.empty ()); } @@ -437,33 +443,6 @@ TEST_F (DnsTest, resolveService) EXPECT_EQ (Dns::Resolver::resolveService ("https"), 443); } -/** - * @brief test the typeName method. - */ -TEST_F (DnsTest, typeName) -{ - EXPECT_EQ (Dns::Resolver::typeName (0), "UNKNOWN"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::A), "A"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::NS), "NS"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::CNAME), "CNAME"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::SOA), "SOA"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::PTR), "PTR"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::MX), "MX"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::TXT), "TXT"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::AAAA), "AAAA"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::SRV), "SRV"); - EXPECT_EQ (Dns::Resolver::typeName (Dns::Resolver::ANY), "ANY"); -} - -/** - * @brief test the className method. - */ -TEST_F (DnsTest, className) -{ - EXPECT_EQ (Dns::Resolver::className (0), "UNKNOWN"); - EXPECT_EQ (Dns::Resolver::className (Dns::Resolver::IN), "IN"); -} - /** * @brief main function. */ diff --git a/fabric/tests/dnsmessage_test.cpp b/fabric/tests/dnsmessage_test.cpp new file mode 100644 index 00000000..04d48bc3 --- /dev/null +++ b/fabric/tests/dnsmessage_test.cpp @@ -0,0 +1,249 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// libjoin. +#include + +// Libraries. +#include + +using join::DnsMessage; + +/** + * @brief test serialize / deserialize methods. + */ +TEST (DnsMessage, roundTrip) +{ + join::DnsPacket out; + out.id = 0x1234; + out.flags = 0x0100; + + join::QuestionRecord question; + question.host = "google.com"; + question.type = DnsMessage::A; + question.dnsclass = DnsMessage::IN; + out.questions.push_back (question); + + join::ResourceRecord a; + a.host = "google.com"; + a.type = DnsMessage::A; + a.dnsclass = DnsMessage::IN; + a.ttl = 3600; + a.addr = join::IpAddress ("142.250.179.78"); + out.answers.push_back (a); + + join::ResourceRecord aaaa; + aaaa.host = "google.com"; + aaaa.type = DnsMessage::AAAA; + aaaa.dnsclass = DnsMessage::IN; + aaaa.ttl = 3600; + aaaa.addr = join::IpAddress ("2a00:1450:4007:80a::200e"); + out.answers.push_back (aaaa); + + join::ResourceRecord ns; + ns.host = "example.com"; + ns.type = DnsMessage::NS; + ns.dnsclass = DnsMessage::IN; + ns.name = "ns1.provider.net"; + out.answers.push_back (ns); + + join::ResourceRecord cname; + cname.host = "www.example.com"; + cname.type = DnsMessage::CNAME; + cname.dnsclass = DnsMessage::IN; + cname.name = "example.com"; + out.answers.push_back (cname); + + join::ResourceRecord ptr; + ptr.host = "1.0.0.127.in-addr.arpa"; + ptr.type = DnsMessage::PTR; + ptr.dnsclass = DnsMessage::IN; + ptr.name = "localhost"; + out.answers.push_back (ptr); + + join::ResourceRecord mx; + mx.host = "google.com"; + mx.type = DnsMessage::MX; + mx.dnsclass = DnsMessage::IN; + mx.mxpref = 10; + mx.name = "smtp.google.com"; + out.answers.push_back (mx); + + join::ResourceRecord txt; + txt.host = "google.com"; + txt.type = DnsMessage::TXT; + txt.dnsclass = DnsMessage::IN; + txt.txts.push_back ("v=spf1 include:_spf.google.com ~all"); + out.answers.push_back (txt); + + join::ResourceRecord soa; + soa.host = "google.com"; + soa.type = DnsMessage::SOA; + soa.dnsclass = DnsMessage::IN; + soa.name = "ns1.google.com"; + soa.mail = "admin@google.com"; + soa.serial = 2026042001; + soa.refresh = 7200; + soa.retry = 3600; + soa.expire = 1209600; + soa.minimum = 300; + out.authorities.push_back (soa); + + join::ResourceRecord srv; + srv.host = "_sip._udp.example.com"; + srv.type = DnsMessage::SRV; + srv.dnsclass = DnsMessage::IN; + srv.priority = 10; + srv.weight = 60; + srv.port = 5060; + srv.name = "sip.example.com"; + out.additionals.push_back (srv); + + DnsMessage codec; + std::stringstream stream; + ASSERT_EQ (codec.serialize (out, stream), 0); + + join::DnsPacket in; + ASSERT_EQ (codec.deserialize (in, stream), 0); + + EXPECT_EQ (in.id, out.id); + EXPECT_EQ (in.questions.size (), 1); + EXPECT_EQ (in.questions[0].host, "google.com"); + + EXPECT_EQ (in.answers.size (), 7); + EXPECT_EQ (in.answers[0].addr.toString (), "142.250.179.78"); + EXPECT_EQ (in.answers[1].addr.toString (), "2a00:1450:4007:80a::200e"); + EXPECT_EQ (in.answers[2].name, "ns1.provider.net"); + EXPECT_EQ (in.answers[3].name, "example.com"); + EXPECT_EQ (in.answers[4].name, "localhost"); + EXPECT_EQ (in.answers[5].mxpref, 10); + EXPECT_EQ (in.answers[5].name, "smtp.google.com"); + EXPECT_EQ (in.answers[6].txts[0], "v=spf1 include:_spf.google.com ~all"); + + EXPECT_EQ (in.authorities.size (), 1); + EXPECT_EQ (in.authorities[0].mail, "admin@google.com"); + EXPECT_EQ (in.authorities[0].serial, 2026042001); + + EXPECT_EQ (in.additionals.size (), 1); + EXPECT_EQ (in.additionals[0].port, 5060); +} + +/** + * @brief test recursive name decoding error. + */ +TEST (DnsMessage, recursiveError) +{ + DnsMessage codec; + std::stringstream stream; + + uint16_t header[] = {htons (0x1234), 0, htons (1), 0, 0, 0}; + stream.write ((char*)header, 12); + + uint16_t loop = htons (0xC000 | 12); + stream.write ((char*)&loop, 2); + + join::DnsPacket packet; + stream.seekg (0); + EXPECT_EQ (codec.deserialize (packet, stream), -1); +} + +/** + * @brief test unknown record types. + */ +TEST (DnsMessage, unknownRecord) +{ + DnsMessage codec; + std::stringstream stream; + + uint16_t header[] = {htons (1), 0, 0, htons (1), 0, 0}; + stream.write ((char*)header, 12); + + stream << (uint8_t)4 << "test" << (uint8_t)0; + uint16_t type = htons (99); + uint16_t dclass = htons (1); + uint32_t ttl = htonl (60); + uint16_t dlen = htons (4); + stream.write ((char*)&type, 2); + stream.write ((char*)&dclass, 2); + stream.write ((char*)&ttl, 4); + stream.write ((char*)&dlen, 2); + stream.write ("data", 4); + + join::DnsPacket packet; + stream.seekg (0); + + EXPECT_EQ (codec.deserialize (packet, stream), 0); + ASSERT_EQ (packet.answers.size (), 1); + EXPECT_EQ (packet.answers[0].type, 99); +} + +/** + * @brief test decodeError method. + */ +TEST (DnsMessage, decodeError) +{ + EXPECT_FALSE (DnsMessage::decodeError (0)); + EXPECT_EQ (DnsMessage::decodeError (1), join::make_error_code (join::Errc::InvalidParam)); + EXPECT_EQ (DnsMessage::decodeError (2), join::make_error_code (join::Errc::OperationFailed)); + EXPECT_EQ (DnsMessage::decodeError (3), join::make_error_code (join::Errc::NotFound)); + EXPECT_EQ (DnsMessage::decodeError (4), join::make_error_code (join::Errc::InvalidParam)); + EXPECT_EQ (DnsMessage::decodeError (5), join::make_error_code (join::Errc::PermissionDenied)); + EXPECT_EQ (DnsMessage::decodeError (99), join::make_error_code (join::Errc::UnknownError)); +} + +/** + * @brief test the typeName method. + */ +TEST (DnsMessage, typeName) +{ + EXPECT_EQ (DnsMessage::typeName (0), "UNKNOWN"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::A), "A"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::NS), "NS"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::CNAME), "CNAME"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::SOA), "SOA"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::PTR), "PTR"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::MX), "MX"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::TXT), "TXT"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::AAAA), "AAAA"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::SRV), "SRV"); + EXPECT_EQ (DnsMessage::typeName (DnsMessage::ANY), "ANY"); +} + +/** + * @brief test the className method. + */ +TEST (DnsMessage, className) +{ + EXPECT_EQ (DnsMessage::className (0), "UNKNOWN"); + EXPECT_EQ (DnsMessage::className (DnsMessage::IN), "IN"); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/fabric/tests/dot_test.cpp b/fabric/tests/dot_test.cpp new file mode 100644 index 00000000..a01b2bd4 --- /dev/null +++ b/fabric/tests/dot_test.cpp @@ -0,0 +1,326 @@ +/** + * MIT License + * + * Copyright (c) 2023 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// libjoin. +#include + +// Libraries. +#include + +using join::lastError; +using join::Dot; +using join::IpAddress; +using join::IpAddressList; +using join::AliasList; +using join::ServerList; +using join::ExchangerList; + +using namespace std::chrono_literals; + +class DotTest : public ::testing::Test +{ +protected: + void SetUp () override + { + _resolver = std::make_unique ("dns.google"); + ASSERT_NE (_resolver, nullptr); + } + + void TearDown () override + { + if (_resolver->disconnect () == -1) + { + ASSERT_EQ (join::lastError, join::Errc::TemporaryError) << join::lastError.message (); + } + ASSERT_TRUE (_resolver->waitDisconnected ()) << join::lastError.message (); + + _resolver->close (); + } + + std::unique_ptr _resolver; +}; + +/** + * @brief test the resolveAllAddress method. + */ +TEST_F (DotTest, resolveAllAddress) +{ + IpAddressList addresses = _resolver->resolveAllAddress ("", AF_INET); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("", AF_INET6); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress (""); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("localhost", AF_INET); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("localhost", AF_INET6); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("localhost"); + EXPECT_EQ (addresses.size (), 0); + + addresses = Dot::Resolver ("255.255.255.255").resolveAllAddress ("google.com", AF_INET); + EXPECT_EQ (addresses.size (), 0); + + addresses = Dot::Resolver ("8.8.8.8", 853).resolveAllAddress ("joinframework.net", AF_INET, 1ms); + EXPECT_EQ (addresses.size (), 0); + + addresses = Dot::Resolver ("8.8.8.8", 853).resolveAllAddress ("joinframework.net", 1ms); + EXPECT_EQ (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("joinframework.net", AF_INET); + EXPECT_GT (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("google.com", AF_INET); + EXPECT_GT (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("google.com", AF_INET6); + EXPECT_GT (addresses.size (), 0); + + addresses = _resolver->resolveAllAddress ("google.com"); + EXPECT_GT (addresses.size (), 0); +} + +/** + * @brief test the resolveAddress method. + */ +TEST_F (DotTest, resolveAddress) +{ + IpAddress address = _resolver->resolveAddress ("", AF_INET); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress ("", AF_INET6); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress (""); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress ("localhost", AF_INET); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress ("localhost", AF_INET6); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress ("localhost"); + EXPECT_TRUE (address.isWildcard ()); + + address = Dot::Resolver ("255.255.255.255").resolveAddress ("google.com", AF_INET); + EXPECT_TRUE (address.isWildcard ()); + + address = Dot::Resolver ("8.8.8.8", 853).resolveAddress ("joinframework.net", AF_INET, 1ms); + EXPECT_TRUE (address.isWildcard ()); + + address = Dot::Resolver ("8.8.8.8", 853).resolveAddress ("joinframework.net", 1ms); + EXPECT_TRUE (address.isWildcard ()); + + address = _resolver->resolveAddress ("joinframework.net", AF_INET); + EXPECT_FALSE (address.isWildcard ()); + + address = _resolver->resolveAddress ("google.com", AF_INET); + EXPECT_TRUE (address.isIpv4Address ()); + EXPECT_FALSE (address.isWildcard ()); + + address = _resolver->resolveAddress ("google.com", AF_INET6); + EXPECT_TRUE (address.isIpv6Address ()); + EXPECT_FALSE (address.isWildcard ()); + + address = _resolver->resolveAddress ("google.com"); + EXPECT_FALSE (address.isWildcard ()); +} + +/** + * @brief test the resolveAllName method. + */ +TEST_F (DotTest, resolveAllName) +{ + AliasList aliases = _resolver->resolveAllName ("0.0.0.0"); + EXPECT_EQ (aliases.size (), 0); + + aliases = _resolver->resolveAllName ("::"); + EXPECT_EQ (aliases.size (), 0); + + aliases = _resolver->resolveAllName ("192.168.24.32"); + EXPECT_EQ (aliases.size (), 0); + + aliases = Dot::Resolver ("255.255.255.255").resolveAllName ("1.1.1.1"); + EXPECT_EQ (aliases.size (), 0); + + aliases = + Dot::Resolver ("8.8.8.8", 853).resolveAllName (_resolver->resolveAddress ("joinframework.net", AF_INET), 1ms); + EXPECT_EQ (aliases.size (), 0); + + aliases = _resolver->resolveAllName ("1.1.1.1"); + EXPECT_GT (aliases.size (), 0); +} + +/** + * @brief test the resolveName method. + */ +TEST_F (DotTest, resolveName) +{ + std::string alias = _resolver->resolveName ("0.0.0.0"); + EXPECT_TRUE (alias.empty ()); + + alias = _resolver->resolveName ("::"); + EXPECT_TRUE (alias.empty ()); + + alias = _resolver->resolveName ("192.168.24.32"); + EXPECT_TRUE (alias.empty ()); + + alias = Dot::Resolver ("255.255.255.255").resolveName ("1.1.1.1"); + EXPECT_TRUE (alias.empty ()); + + alias = Dot::Resolver ("8.8.8.8", 853).resolveName (_resolver->resolveAddress ("joinframework.net", AF_INET), 1ms); + EXPECT_TRUE (alias.empty ()); + + alias = _resolver->resolveName ("1.1.1.1"); + EXPECT_FALSE (alias.empty ()); +} + +/** + * @brief test the resolveAllNameServer method. + */ +TEST_F (DotTest, resolveAllNameServer) +{ + ServerList servers = _resolver->resolveAllNameServer (""); + EXPECT_EQ (servers.size (), 0); + + servers = _resolver->resolveAllNameServer ("localhost"); + EXPECT_EQ (servers.size (), 0); + + servers = Dot::Resolver ("255.255.255.255").resolveAllNameServer ("google.com"); + EXPECT_EQ (servers.size (), 0); + + servers = Dot::Resolver ("8.8.8.8", 853).resolveAllNameServer ("joinframework.net", 1ms); + EXPECT_EQ (servers.size (), 0); + + servers = _resolver->resolveAllNameServer ("google.com"); + EXPECT_GT (servers.size (), 0); + + servers = _resolver->resolveAllNameServer ("joinframework.net"); + EXPECT_GT (servers.size (), 0); +} + +/** + * @brief test the resolveNameServer method. + */ +TEST_F (DotTest, resolveNameServer) +{ + std::string server = _resolver->resolveNameServer (""); + EXPECT_TRUE (server.empty ()); + + server = _resolver->resolveNameServer ("localhost"); + EXPECT_TRUE (server.empty ()); + + server = Dot::Resolver ("255.255.255.255").resolveNameServer ("google.com"); + EXPECT_TRUE (server.empty ()); + + server = Dot::Resolver ("8.8.8.8", 853).resolveNameServer ("joinframework.net", 1ms); + EXPECT_TRUE (server.empty ()); + + server = _resolver->resolveNameServer ("google.com"); + EXPECT_FALSE (server.empty ()); + + server = _resolver->resolveNameServer ("joinframework.net"); + EXPECT_FALSE (server.empty ()); +} + +/** + * @brief test the resolveAuthority method. + */ +TEST_F (DotTest, resolveAuthority) +{ + std::string authority = _resolver->resolveAuthority (""); + EXPECT_TRUE (authority.empty ()); + + authority = _resolver->resolveAuthority ("localhost"); + EXPECT_TRUE (authority.empty ()); + + authority = Dot::Resolver ("255.255.255.255").resolveAuthority ("google.com"); + EXPECT_TRUE (authority.empty ()); + + authority = Dot::Resolver ("8.8.8.8", 853).resolveAuthority ("joinframework.net", 1ms); + EXPECT_TRUE (authority.empty ()); + + authority = _resolver->resolveAuthority ("google.com"); + EXPECT_FALSE (authority.empty ()); + + authority = _resolver->resolveAuthority ("joinframework.net"); + EXPECT_FALSE (authority.empty ()); +} + +/** + * @brief test the resolveAllMailExchanger method. + */ +TEST_F (DotTest, resolveAllMailExchanger) +{ + ExchangerList exchangers = _resolver->resolveAllMailExchanger (""); + EXPECT_EQ (exchangers.size (), 0); + + exchangers = _resolver->resolveAllMailExchanger ("localhost"); + EXPECT_EQ (exchangers.size (), 0); + + exchangers = Dot::Resolver ("255.255.255.255").resolveAllMailExchanger ("google.com"); + EXPECT_EQ (exchangers.size (), 0); + + exchangers = Dot::Resolver ("8.8.8.8", 853).resolveAllMailExchanger ("joinframework.net", 1ms); + EXPECT_EQ (exchangers.size (), 0); + + exchangers = _resolver->resolveAllMailExchanger ("google.com"); + EXPECT_GT (exchangers.size (), 0); +} + +/** + * @brief test the resolveMailExchanger method. + */ +TEST_F (DotTest, resolveMailExchanger) +{ + std::string exchanger = _resolver->resolveMailExchanger (""); + EXPECT_TRUE (exchanger.empty ()); + + exchanger = _resolver->resolveMailExchanger ("localhost"); + EXPECT_TRUE (exchanger.empty ()); + + exchanger = Dot::Resolver ("255.255.255.255").resolveMailExchanger ("google.com"); + EXPECT_TRUE (exchanger.empty ()); + + exchanger = Dot::Resolver ("8.8.8.8", 853).resolveMailExchanger ("joinframework.net", 1ms); + EXPECT_TRUE (exchanger.empty ()); + + exchanger = _resolver->resolveMailExchanger ("google.com"); + EXPECT_FALSE (exchanger.empty ()); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/services/include/join/httpclient.hpp b/services/include/join/httpclient.hpp index 9f23d71f..e3017aca 100644 --- a/services/include/join/httpclient.hpp +++ b/services/include/join/httpclient.hpp @@ -29,9 +29,9 @@ #include #include #include +#include #include #include -#include // C++. #include diff --git a/services/include/join/smtpclient.hpp b/services/include/join/smtpclient.hpp index 3ac28795..e0cba28c 100644 --- a/services/include/join/smtpclient.hpp +++ b/services/include/join/smtpclient.hpp @@ -28,8 +28,8 @@ // libjoin. #include #include +#include #include -#include namespace join {