diff --git a/CMakeLists.txt b/CMakeLists.txt index 153e1c7a..e7096d51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1003,6 +1003,19 @@ ELSE(PTHREAD_LIB) SET(VMIME_HAVE_PTHREAD 0) ENDIF(PTHREAD_LIB) +# getaddrinfo_a() - GNU libc +LIST(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) +LIST(APPEND CMAKE_REQUIRED_LIBRARIES anl) +CHECK_SYMBOL_EXISTS(getaddrinfo_a netdb.h VMIME_HAVE_GETADDRINFO_A) + +IF(VMIME_HAVE_GETADDRINFO_A) + TARGET_LINK_LIBRARIES( + ${VMIME_LIBRARY_NAME} + ${TARGET_LINK_LIBRARIES} + anl + ) +ENDIF() + ############################################################################## # Additional compiler flags diff --git a/cmake/config.hpp.cmake b/cmake/config.hpp.cmake index a79268cd..219d1000 100644 --- a/cmake/config.hpp.cmake +++ b/cmake/config.hpp.cmake @@ -76,6 +76,7 @@ typedef unsigned @VMIME_64BIT_TYPE@ vmime_uint64; #cmakedefine01 VMIME_PLATFORM_IS_WINDOWS #cmakedefine01 VMIME_HAVE_PTHREAD #cmakedefine01 VMIME_HAVE_GETADDRINFO +#cmakedefine01 VMIME_HAVE_GETADDRINFO_A #cmakedefine01 VMIME_HAVE_GETTID #cmakedefine01 VMIME_HAVE_SYSCALL #cmakedefine01 VMIME_HAVE_SYSCALL_GETTID diff --git a/examples/example6_timeoutHandler.hpp b/examples/example6_timeoutHandler.hpp index 7c2eb0e2..3e188baf 100644 --- a/examples/example6_timeoutHandler.hpp +++ b/examples/example6_timeoutHandler.hpp @@ -1,3 +1,4 @@ +#include /** Time out handler. @@ -8,19 +9,27 @@ class timeoutHandler : public vmime::net::timeoutHandler { public: + timeoutHandler() + : m_start(time(NULL)) + { + } + bool isTimeOut() { // This is a cancellation point: return true if you want to cancel // the current operation. If you return true, handleTimeOut() will // be called just after this, and before actually cancelling the // operation - return false; + + // 10 seconds timeout + return (time(NULL) - m_start) >= 10; // seconds } void resetTimeOut() { // Called at the beginning of an operation (eg. connecting, // a read() or a write() on a socket...) + m_start = time(NULL); } bool handleTimeOut() @@ -29,10 +38,14 @@ public: // allows you to interact with the user, ie. display a prompt to // know whether he wants to cancel the operation. - // If you return true here, the operation will be actually cancelled. - // If not, the time out is reset and the operation continues. - return true; + // If you return false here, the operation will be actually cancelled. + // If true, the time out is reset and the operation continues. + return false; } + +private: + + time_t m_start; }; diff --git a/src/vmime/platforms/posix/posixSocket.cpp b/src/vmime/platforms/posix/posixSocket.cpp index 6ebd2766..aa5ec58a 100644 --- a/src/vmime/platforms/posix/posixSocket.cpp +++ b/src/vmime/platforms/posix/posixSocket.cpp @@ -30,6 +30,10 @@ #include "vmime/platforms/posix/posixSocket.hpp" #include "vmime/platforms/posix/posixHandler.hpp" +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // for getaddrinfo_a() in +#endif + #include #include #include @@ -95,42 +99,26 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port #if VMIME_HAVE_GETADDRINFO // use thread-safe and IPv6-aware getaddrinfo() if available // Resolve address, if needed - struct ::addrinfo hints; - memset(&hints, 0, sizeof(hints)); - - hints.ai_flags = AI_CANONNAME; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - std::ostringstream portStr; - portStr.imbue(std::locale::classic()); - - portStr << port; - - struct ::addrinfo* res0; - - if (::getaddrinfo(address.c_str(), portStr.str().c_str(), &hints, &res0) != 0) - { - // Error: cannot resolve address - throw vmime::exceptions::connection_error("Cannot resolve address."); - } - m_serverAddress = address; + struct ::addrinfo* addrInfo = NULL; // resolved addresses + resolve(&addrInfo, address, port); + // Connect to host int sock = -1; - struct ::addrinfo* res = res0; int connectErrno = 0; if (m_timeoutHandler != NULL) m_timeoutHandler->resetTimeOut(); - for ( ; sock == -1 && res != NULL ; res = res->ai_next, connectErrno = ETIMEDOUT) + for (struct ::addrinfo* curAddrInfo = addrInfo ; + sock == -1 && curAddrInfo != NULL ; + curAddrInfo = curAddrInfo->ai_next, connectErrno = ETIMEDOUT) { - if (res->ai_family != AF_INET && res->ai_family != AF_INET6) + if (curAddrInfo->ai_family != AF_INET && curAddrInfo->ai_family != AF_INET6) continue; - sock = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol); + sock = ::socket(curAddrInfo->ai_family, curAddrInfo->ai_socktype, curAddrInfo->ai_protocol); if (sock < 0) { @@ -152,7 +140,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port { ::fcntl(sock, F_SETFL, ::fcntl(sock, F_GETFL) | O_NONBLOCK); - if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0) + if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0) { switch (errno) { @@ -254,7 +242,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port timeval curTime = { 0, 0 }; gettimeofday(&curTime, /* timezone */ NULL); - if (res->ai_next != NULL && + if (curAddrInfo->ai_next != NULL && curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000) { connectErrno = ETIMEDOUT; @@ -280,7 +268,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port } else { - if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0) + if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0) { connectErrno = errno; ::close(sock); @@ -290,7 +278,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port } } - ::freeaddrinfo(res0); + ::freeaddrinfo(addrInfo); if (sock == -1) { @@ -373,6 +361,101 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port } +void posixSocket::resolve(struct ::addrinfo** addrInfo, const vmime::string& address, const vmime::port_t port) +{ + std::ostringstream portStr; + portStr.imbue(std::locale::classic()); + portStr << port; + + + struct ::addrinfo hints; + memset(&hints, 0, sizeof(hints)); + + hints.ai_flags = AI_CANONNAME | AI_NUMERICSERV; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + +#if VMIME_HAVE_GETADDRINFO_A + + // If getaddrinfo_a() is available, use asynchronous resolving to allow + // the timeout handler to cancel the operation + + struct ::gaicb gaiRequest; + memset(&gaiRequest, 0, sizeof(gaiRequest)); + + gaiRequest.ar_name = address.c_str(); + gaiRequest.ar_service = portStr.str().c_str(); + gaiRequest.ar_request = &hints; + + struct ::gaicb* gaiRequests = &gaiRequest; + int gaiError; + + if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0) + { + throw vmime::exceptions::connection_error + ("getaddrinfo_a() failed: " + std::string(gai_strerror(gaiError))); + } + + if (m_timeoutHandler != NULL) + m_timeoutHandler->resetTimeOut(); + + while (true) + { + struct timespec gaiTimeout; + gaiTimeout.tv_sec = 1; // query timeout handler every second + gaiTimeout.tv_nsec = 0; + + gaiError = gai_suspend(&gaiRequests, 1, &gaiTimeout); + + if (gaiError == 0 || gaiError == EAI_ALLDONE) + { + const int ret = gai_error(&gaiRequest); + + if (ret != 0) + { + throw vmime::exceptions::connection_error + ("getaddrinfo_a() request failed: " + std::string(gai_strerror(ret))); + } + else + { + *addrInfo = gaiRequest.ar_result; + break; + } + } + else if (gaiError != EAI_AGAIN) + { + throw vmime::exceptions::connection_error + ("gai_suspend() failed: " + std::string(gai_strerror(gaiError))); + } + + // Check for timeout + if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + { + throw exceptions::operation_timed_out(); + } + else + { + // Reset timeout and keep waiting for connection + m_timeoutHandler->resetTimeOut(); + } + } + } + +#else // !VMIME_HAVE_GETADDRINFO_A + + if (::getaddrinfo(address.c_str(), portStr.str().c_str(), &hints, addrInfo) != 0) + { + // Error: cannot resolve address + throw vmime::exceptions::connection_error("Cannot resolve address."); + } + +#endif // VMIME_HAVE_GETADDRINFO_A + +} + + bool posixSocket::isConnected() const { if (m_desc == -1) diff --git a/src/vmime/platforms/posix/posixSocket.hpp b/src/vmime/platforms/posix/posixSocket.hpp index 575a2a25..ebcb1a04 100644 --- a/src/vmime/platforms/posix/posixSocket.hpp +++ b/src/vmime/platforms/posix/posixSocket.hpp @@ -34,6 +34,9 @@ #include "vmime/net/socket.hpp" +struct addrinfo; + + namespace vmime { namespace platforms { namespace posix { @@ -75,6 +78,8 @@ public: protected: + void resolve(struct ::addrinfo** addrInfo, const vmime::string& address, const vmime::port_t port); + bool waitForData(const bool read, const bool write, const int msecs); static void throwSocketError(const int err);