From 0d20ee083b69d0cbde5a57a745386b987752af46 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Wed, 5 Feb 2014 20:18:20 +0100 Subject: [PATCH] Better polling. Fixed possible connection issues on POSIX with slow network. Better error handling in POSIX sockets. --- src/vmime/net/imap/IMAPParser.hpp | 12 +- src/vmime/net/pop3/POP3Response.cpp | 12 +- src/vmime/net/smtp/SMTPResponse.cpp | 2 +- src/vmime/net/socket.hpp | 23 ++- src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp | 123 ++++++------ src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp | 5 +- .../net/tls/openssl/TLSSocket_OpenSSL.cpp | 62 ++++-- .../net/tls/openssl/TLSSocket_OpenSSL.hpp | 5 +- src/vmime/platform.hpp | 7 - src/vmime/platforms/posix/posixHandler.cpp | 18 -- src/vmime/platforms/posix/posixHandler.hpp | 2 - src/vmime/platforms/posix/posixSocket.cpp | 180 ++++++++++++------ src/vmime/platforms/posix/posixSocket.hpp | 5 + .../platforms/windows/windowsHandler.cpp | 6 - .../platforms/windows/windowsHandler.hpp | 2 - src/vmime/platforms/windows/windowsSocket.cpp | 85 ++++++--- src/vmime/platforms/windows/windowsSocket.hpp | 12 +- src/vmime/security/sasl/SASLSocket.cpp | 12 ++ src/vmime/security/sasl/SASLSocket.hpp | 3 + tests/testUtils.cpp | 12 ++ tests/testUtils.hpp | 3 + 21 files changed, 377 insertions(+), 214 deletions(-) diff --git a/src/vmime/net/imap/IMAPParser.hpp b/src/vmime/net/imap/IMAPParser.hpp index 533f78fb..4ea9cb57 100644 --- a/src/vmime/net/imap/IMAPParser.hpp +++ b/src/vmime/net/imap/IMAPParser.hpp @@ -5748,7 +5748,11 @@ public: if (receiveBuffer.empty()) // buffer is empty { - platform::getHandler()->wait(); + if (sok->getStatus() & socket::STATUS_WANT_WRITE) + sok->waitForWrite(); + else + sok->waitForRead(); + continue; } @@ -5807,7 +5811,11 @@ public: if (receiveBuffer.empty()) // buffer is empty { - platform::getHandler()->wait(); + if (sok->getStatus() & socket::STATUS_WANT_WRITE) + sok->waitForWrite(); + else + sok->waitForRead(); + continue; } diff --git a/src/vmime/net/pop3/POP3Response.cpp b/src/vmime/net/pop3/POP3Response.cpp index 1dc5ee76..a3961200 100644 --- a/src/vmime/net/pop3/POP3Response.cpp +++ b/src/vmime/net/pop3/POP3Response.cpp @@ -178,7 +178,11 @@ void POP3Response::readResponseImpl(string& buffer, const bool multiLine) if (receiveBuffer.empty()) // buffer is empty { - platform::getHandler()->wait(); + if (m_socket->getStatus() & socket::STATUS_WANT_WRITE) + m_socket->waitForWrite(); + else + m_socket->waitForRead(); + continue; } @@ -269,7 +273,11 @@ void POP3Response::readResponseImpl if (read == 0) // buffer is empty { - platform::getHandler()->wait(); + if (m_socket->getStatus() & socket::STATUS_WANT_WRITE) + m_socket->waitForWrite(); + else + m_socket->waitForRead(); + continue; } diff --git a/src/vmime/net/smtp/SMTPResponse.cpp b/src/vmime/net/smtp/SMTPResponse.cpp index f7980351..3d8bf15c 100644 --- a/src/vmime/net/smtp/SMTPResponse.cpp +++ b/src/vmime/net/smtp/SMTPResponse.cpp @@ -160,7 +160,7 @@ const string SMTPResponse::readResponseLine() if (receiveBuffer.empty()) // buffer is empty { - platform::getHandler()->wait(); + m_socket->waitForRead(); continue; } diff --git a/src/vmime/net/socket.hpp b/src/vmime/net/socket.hpp index 7f878a73..72f0445f 100644 --- a/src/vmime/net/socket.hpp +++ b/src/vmime/net/socket.hpp @@ -49,7 +49,9 @@ public: enum Status { - STATUS_WOULDBLOCK = 0x1 /**< The receive operation would block. */ + STATUS_WOULDBLOCK = 0xf, /**< The operation would block. Retry later. */ + STATUS_WANT_READ = 0x1, /**< The socket wants to read data, retry when data is available. */ + STATUS_WANT_WRITE = 0x2 /**< The socket wants to write data, retry when data can be written. */ }; @@ -74,6 +76,25 @@ public: */ virtual bool isConnected() const = 0; + /** Block until new data is available for reading. The function will + * timeout after msecs milliseconds. + * + * @param timeout maximum wait time, in milliseconds (default is 30000); + * resolution is 10ms + * @return true if data is available, or false if the operation timed out + */ + virtual bool waitForRead(const int msecs = 30000) = 0; + + /** Block until pending data has been written and new data can be written. + * The function will timeout after msecs milliseconds. + * + * @param timeout maximum wait time, in milliseconds (default is 30000); + * resolution is 10ms + * @return true if new data can be written immediately, or false if the + * operation timed out + */ + virtual bool waitForWrite(const int msecs = 30000) = 0; + /** Receive text data from the socket. * * @param buffer buffer in which to write received data diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp index 13b7eb24..3832326c 100644 --- a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp +++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp" #include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp" @@ -57,7 +59,7 @@ shared_ptr TLSSocket::wrap(shared_ptr session, shared_p TLSSocket_GnuTLS::TLSSocket_GnuTLS(shared_ptr session, shared_ptr sok) : m_session(session), m_wrapped(sok), m_connected(false), - m_handshaking(false), m_ex(NULL), m_status(0) + m_ex(NULL), m_status(0) { gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this); @@ -144,6 +146,18 @@ shared_ptr TLSSocket_GnuTLS::getTimeoutHandler() } +bool TLSSocket_GnuTLS::waitForRead(const int msecs) +{ + return m_wrapped->waitForRead(msecs); +} + + +bool TLSSocket_GnuTLS::waitForWrite(const int msecs) +{ + return m_wrapped->waitForWrite(msecs); +} + + void TLSSocket_GnuTLS::receive(string& buffer) { const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); @@ -165,7 +179,7 @@ void TLSSocket_GnuTLS::send(const char* str) size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count) { - m_status &= ~STATUS_WOULDBLOCK; + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); const ssize_t ret = gnutls_record_recv (*m_session->m_gnutlsSession, @@ -178,7 +192,11 @@ size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count) { if (ret == GNUTLS_E_AGAIN) { - m_status |= STATUS_WOULDBLOCK; + if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0) + m_status |= STATUS_WANT_READ; + else + m_status |= STATUS_WANT_WRITE; + return 0; } @@ -191,7 +209,7 @@ size_t TLSSocket_GnuTLS::receiveRaw(byte_t* buffer, const size_t count) void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count) { - m_status &= ~STATUS_WOULDBLOCK; + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); for (size_t size = count ; size > 0 ; ) { @@ -206,7 +224,11 @@ void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count) { if (ret == GNUTLS_E_AGAIN) { - platform::getHandler()->wait(); + if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0) + m_wrapped->waitForRead(); + else + m_wrapped->waitForWrite(); + continue; } @@ -223,6 +245,8 @@ void TLSSocket_GnuTLS::sendRaw(const byte_t* buffer, const size_t count) size_t TLSSocket_GnuTLS::sendRawNonBlocking(const byte_t* buffer, const size_t count) { + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); + ssize_t ret = gnutls_record_send (*m_session->m_gnutlsSession, buffer, static_cast (count)); @@ -234,7 +258,11 @@ size_t TLSSocket_GnuTLS::sendRawNonBlocking(const byte_t* buffer, const size_t c { if (ret == GNUTLS_E_AGAIN) { - m_status |= STATUS_WOULDBLOCK; + if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0) + m_status |= STATUS_WANT_READ; + else + m_status |= STATUS_WANT_WRITE; + return 0; } @@ -259,8 +287,6 @@ void TLSSocket_GnuTLS::handshake() toHandler->resetTimeOut(); // Start handshaking process - m_handshaking = true; - try { while (true) @@ -272,11 +298,17 @@ void TLSSocket_GnuTLS::handshake() if (ret < 0) { - if (ret == GNUTLS_E_AGAIN || - ret == GNUTLS_E_INTERRUPTED) + if (ret == GNUTLS_E_AGAIN) + { + if (gnutls_record_get_direction(*m_session->m_gnutlsSession) == 0) + m_wrapped->waitForRead(); + else + m_wrapped->waitForWrite(); + } + else if (ret == GNUTLS_E_INTERRUPTED) { // Non-fatal error - platform::getHandler()->wait(); + m_wrapped->waitForRead(); } else { @@ -292,12 +324,9 @@ void TLSSocket_GnuTLS::handshake() } catch (...) { - m_handshaking = false; throw; } - m_handshaking = false; - // Verify server's certificate(s) shared_ptr certs = getPeerCertificates(); @@ -321,14 +350,21 @@ ssize_t TLSSocket_GnuTLS::gnutlsPushFunc (sok->m_wrapped->sendRawNonBlocking (reinterpret_cast (data), len)); - if (ret == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) - return GNUTLS_E_AGAIN; + if (ret == 0) + { + if (sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, EAGAIN); + else + gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, 0); + + return -1; + } return ret; } catch (exception& e) { - // Workaround for bad behaviour when throwing C++ exceptions + // Workaround for non-portable behaviour when throwing C++ exceptions // from C functions (GNU TLS) sok->m_ex = e.clone(); return -1; @@ -343,54 +379,25 @@ ssize_t TLSSocket_GnuTLS::gnutlsPullFunc try { - // Workaround for cross-platform asynchronous handshaking: - // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv() - // returns -1 and errno is set to EGAIN... - if (sok->m_handshaking) + const ssize_t n = static_cast + (sok->m_wrapped->receiveRaw + (reinterpret_cast (data), len)); + + if (n == 0) { - shared_ptr toHandler = sok->m_wrapped->getTimeoutHandler(); + if (sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, EAGAIN); + else + gnutls_transport_set_errno(*sok->m_session->m_gnutlsSession, 0); - while (true) - { - const ssize_t ret = static_cast - (sok->m_wrapped->receiveRaw - (reinterpret_cast (data), len)); - - if (ret == 0) - { - // No data available yet - platform::getHandler()->wait(); - } - else - { - return ret; - } - - // Check whether the time-out delay is elapsed - if (toHandler && toHandler->isTimeOut()) - { - if (!toHandler->handleTimeOut()) - throw exceptions::operation_timed_out(); - - toHandler->resetTimeOut(); - } - } + return -1; } - else - { - const ssize_t n = static_cast - (sok->m_wrapped->receiveRaw - (reinterpret_cast (data), len)); - if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) - return GNUTLS_E_AGAIN; - - return n; - } + return n; } catch (exception& e) { - // Workaround for bad behaviour when throwing C++ exceptions + // Workaround for non-portable behaviour when throwing C++ exceptions // from C functions (GNU TLS) sok->m_ex = e.clone(); return -1; diff --git a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp index ddba9d0e..faa3a423 100644 --- a/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp +++ b/src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp @@ -63,6 +63,9 @@ public: void disconnect(); bool isConnected() const; + bool waitForRead(const int msecs = 30000); + bool waitForWrite(const int msecs = 30000); + void receive(string& buffer); size_t receiveRaw(byte_t* buffer, const size_t count); @@ -100,8 +103,6 @@ private: byte_t m_buffer[65536]; - bool m_handshaking; - exception* m_ex; unsigned int m_status; diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp index 595a0091..bec41612 100644 --- a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp +++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp @@ -113,7 +113,7 @@ void TLSSocket_OpenSSL::createSSLHandle() SSL_set_bio(m_ssl, sockBio, sockBio); SSL_set_connect_state(m_ssl); - SSL_set_mode(m_ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_set_mode(m_ssl, SSL_MODE_AUTO_RETRY | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); } else { @@ -193,6 +193,18 @@ shared_ptr TLSSocket_OpenSSL::getTimeoutHandler() } +bool TLSSocket_OpenSSL::waitForRead(const int msecs) +{ + return m_wrapped->waitForRead(msecs); +} + + +bool TLSSocket_OpenSSL::waitForWrite(const int msecs) +{ + return m_wrapped->waitForWrite(msecs); +} + + void TLSSocket_OpenSSL::receive(string& buffer) { const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); @@ -221,7 +233,7 @@ size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count) if (!m_ssl) throw exceptions::socket_not_connected_exception(); - m_status &= ~STATUS_WOULDBLOCK; + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); int rc = SSL_read(m_ssl, buffer, static_cast (count)); @@ -232,9 +244,14 @@ size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count) { int error = SSL_get_error(m_ssl, rc); - if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ) + if (error == SSL_ERROR_WANT_WRITE) { - m_status |= STATUS_WOULDBLOCK; + m_status |= STATUS_WANT_WRITE; + return 0; + } + else if (error == SSL_ERROR_WANT_READ) + { + m_status |= STATUS_WANT_READ; return 0; } @@ -250,7 +267,7 @@ void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count) if (!m_ssl) throw exceptions::socket_not_connected_exception(); - m_status &= ~STATUS_WOULDBLOCK; + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); for (size_t size = count ; size > 0 ; ) { @@ -260,9 +277,14 @@ void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count) { int error = SSL_get_error(m_ssl, rc); - if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ) + if (error == SSL_ERROR_WANT_READ) { - platform::getHandler()->wait(); + m_wrapped->waitForRead(); + continue; + } + else if (error == SSL_ERROR_WANT_WRITE) + { + m_wrapped->waitForWrite(); continue; } @@ -282,7 +304,7 @@ size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t if (!m_ssl) throw exceptions::socket_not_connected_exception(); - m_status &= ~STATUS_WOULDBLOCK; + m_status &= ~(STATUS_WANT_WRITE | STATUS_WANT_READ); int rc = SSL_write(m_ssl, buffer, static_cast (count)); @@ -293,9 +315,14 @@ size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t { int error = SSL_get_error(m_ssl, rc); - if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ) + if (error == SSL_ERROR_WANT_WRITE) { - m_status |= STATUS_WOULDBLOCK; + m_status |= STATUS_WANT_WRITE; + return 0; + } + else if (error == SSL_ERROR_WANT_READ) + { + m_status |= STATUS_WANT_READ; return 0; } @@ -328,15 +355,12 @@ void TLSSocket_OpenSSL::handshake() { const int err = SSL_get_error(m_ssl, rc); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) - { - // No data available yet - platform::getHandler()->wait(); - } - else - { - handleError(rc); - } + if (err == SSL_ERROR_WANT_READ) + m_wrapped->waitForRead(); + else if (err == SSL_ERROR_WANT_WRITE) + m_wrapped->waitForWrite(); + else + handleError(rc); // Check whether the time-out delay is elapsed if (toHandler && toHandler->isTimeOut()) diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp index 5fbed19d..20712263 100644 --- a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp +++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp @@ -67,6 +67,9 @@ public: void disconnect(); bool isConnected() const; + bool waitForRead(const int msecs = 30000); + bool waitForWrite(const int msecs = 30000); + void receive(string& buffer); size_t receiveRaw(byte_t* buffer, const size_t count); @@ -115,7 +118,7 @@ private: unsigned long m_status; // Last exception thrown from C BIO functions - std::auto_ptr m_ex; + std::auto_ptr m_ex; }; diff --git a/src/vmime/platform.hpp b/src/vmime/platform.hpp index c72f160e..ac1834ac 100644 --- a/src/vmime/platform.hpp +++ b/src/vmime/platform.hpp @@ -104,13 +104,6 @@ public: */ virtual const charset getLocalCharset() const = 0; - /** This function is called when VMime library is waiting for - * something (for example, it is called when there is no data - * available in a socket). On POSIX-compliant systems, a - * simple call to sched_yield() should suffice. - */ - virtual void wait() const = 0; - #if VMIME_HAVE_MESSAGING_FEATURES /** Return a pointer to the default socket factory for * this platform. diff --git a/src/vmime/platforms/posix/posixHandler.cpp b/src/vmime/platforms/posix/posixHandler.cpp index e0bfd27f..f23f09ad 100644 --- a/src/vmime/platforms/posix/posixHandler.cpp +++ b/src/vmime/platforms/posix/posixHandler.cpp @@ -269,24 +269,6 @@ shared_ptr posixHandler::getChildProcessFa #endif -void posixHandler::wait() const -{ -/* -#ifdef _POSIX_PRIORITY_SCHEDULING - ::sched_yield(); -#else - ::sleep(1); -#endif // _POSIX_PRIORITY_SCHEDULING -*/ - - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 500000; // 500 microseconds - - nanosleep(&ts, NULL); -} - - void posixHandler::generateRandomBytes(unsigned char* buffer, const unsigned int count) { int fd = open("/dev/urandom", O_RDONLY); diff --git a/src/vmime/platforms/posix/posixHandler.hpp b/src/vmime/platforms/posix/posixHandler.hpp index 0bba372b..d9eb4fbd 100644 --- a/src/vmime/platforms/posix/posixHandler.hpp +++ b/src/vmime/platforms/posix/posixHandler.hpp @@ -76,8 +76,6 @@ public: shared_ptr getChildProcessFactory(); #endif - void wait() const; - void generateRandomBytes(unsigned char* buffer, const unsigned int count); shared_ptr createCriticalSection(); diff --git a/src/vmime/platforms/posix/posixSocket.cpp b/src/vmime/platforms/posix/posixSocket.cpp index e7eba9f1..4bb709e6 100644 --- a/src/vmime/platforms/posix/posixSocket.cpp +++ b/src/vmime/platforms/posix/posixSocket.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -111,13 +112,23 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port // Connect to host int sock = -1; struct ::addrinfo* res = res0; + int connectErrno = 0; - for ( ; sock == -1 && res != NULL ; res = res->ai_next) + if (m_timeoutHandler != NULL) + m_timeoutHandler->resetTimeOut(); + + for ( ; sock == -1 && res != NULL ; res = res->ai_next, connectErrno = ETIMEDOUT) { + if (res->ai_family != AF_INET && res->ai_family != AF_INET6) + continue; + sock = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) + { + connectErrno = errno; continue; // try next + } if (m_timeoutHandler != NULL) { @@ -142,37 +153,51 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port default: + connectErrno = errno; ::close(sock); sock = -1; continue; // try next } // Wait for socket to be connected. - // We will check for time out every second. - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); - - fd_set fdsError; - FD_ZERO(&fdsError); - FD_SET(sock, &fdsError); - - struct timeval tm; - tm.tv_sec = 1; - tm.tv_usec = 0; - - m_timeoutHandler->resetTimeOut(); - bool connected = false; + const int selectTimeout = 1000; // select() timeout (ms) + const int tryNextTimeout = 5000; // maximum time before trying next (ms) + + timeval startTime = { 0, 0 }; + gettimeofday(&startTime, /* timezone */ NULL); + do { - const int ret = select(sock + 1, NULL, &fds, &fdsError, &tm); + struct timeval tm; + tm.tv_sec = selectTimeout / 1000; + tm.tv_usec = selectTimeout % 1000; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + const int ret = select(sock + 1, NULL, &fds, NULL, &tm); // Success if (ret > 0) { - connected = true; + int error = 0; + socklen_t len = sizeof(error); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + { + connectErrno = errno; + } + else + { + if (error != 0) + connectErrno = error; + else + connected = true; + } + break; } // Error @@ -181,10 +206,11 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port if (errno != EINTR) { // Cancel connection + connectErrno = errno; break; } } - // 1-second timeout + // Check for timeout else if (ret == 0) { if (m_timeoutHandler->isTimeOut()) @@ -192,6 +218,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port if (!m_timeoutHandler->handleTimeOut()) { // Cancel connection + connectErrno = ETIMEDOUT; break; } else @@ -206,7 +233,15 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port } } - ::sched_yield(); + timeval curTime = { 0, 0 }; + gettimeofday(&curTime, /* timezone */ NULL); + + if (res->ai_next != NULL && + curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000) + { + connectErrno = ETIMEDOUT; + break; + } } while (true); @@ -219,11 +254,17 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port break; } + else + { + // Connection successful + break; + } } else { if (::connect(sock, res->ai_addr, res->ai_addrlen) < 0) { + connectErrno = errno; ::close(sock); sock = -1; continue; // try next @@ -237,7 +278,7 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port { try { - throwSocketError(errno); + throwSocketError(connectErrno); } catch (exceptions::socket_exception& e) { @@ -434,6 +475,65 @@ size_t posixSocket::getBlockSize() const } +bool posixSocket::waitForData(const bool read, const bool write, const int msecs) +{ + for (int i = 0 ; i <= msecs / 10 ; ++i) + { + // Check whether data is available + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_desc, &fds); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; // 10 ms + + ssize_t ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv); + + if (ret <= 0) + { + if (ret < 0 && !IS_EAGAIN(errno)) + throwSocketError(errno); + + // No data available at this time + // Check if we are timed out + if (m_timeoutHandler && + m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + { + // Server did not react within timeout delay + throw exceptions::operation_timed_out(); + } + else + { + // Reset timeout + m_timeoutHandler->resetTimeOut(); + } + } + } + else if (ret > 0) + { + return true; + } + } + + return false; // time out +} + + +bool posixSocket::waitForRead(const int msecs) +{ + return waitForData(/* read */ true, /* write */ false, msecs); +} + + +bool posixSocket::waitForWrite(const int msecs) +{ + return waitForData(/* read */ false, /* write */ true, msecs); +} + + void posixSocket::receive(vmime::string& buffer) { const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); @@ -446,38 +546,8 @@ size_t posixSocket::receiveRaw(byte_t* buffer, const size_t count) m_status &= ~STATUS_WOULDBLOCK; // Check whether data is available - fd_set fds; - FD_ZERO(&fds); - FD_SET(m_desc, &fds); - - struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - - ssize_t ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv); - - if (ret < 0) + if (!waitForRead(50 /* msecs */)) { - if (!IS_EAGAIN(errno)) - throwSocketError(errno); - - // No data available at this time - // Check if we are timed out - if (m_timeoutHandler && - m_timeoutHandler->isTimeOut()) - { - if (!m_timeoutHandler->handleTimeOut()) - { - // Server did not react within timeout delay - throwSocketError(errno); - } - else - { - // Reset timeout - m_timeoutHandler->resetTimeOut(); - } - } - m_status |= STATUS_WOULDBLOCK; // Continue waiting for data @@ -485,7 +555,7 @@ size_t posixSocket::receiveRaw(byte_t* buffer, const size_t count) } // Read available data - ret = ::recv(m_desc, buffer, count, 0); + ssize_t ret = ::recv(m_desc, buffer, count, 0); if (ret < 0) { @@ -556,7 +626,7 @@ void posixSocket::sendRaw(const byte_t* buffer, const size_t count) if (ret < 0 && !IS_EAGAIN(errno)) throwSocketError(errno); - platform::getHandler()->wait(); + waitForWrite(50 /* msecs */); } else { @@ -589,7 +659,7 @@ size_t posixSocket::sendRawNonBlocking(const byte_t* buffer, const size_t count) if (!m_timeoutHandler->handleTimeOut()) { // Could not send data within timeout delay - throwSocketError(errno); + throw exceptions::operation_timed_out(); } else { diff --git a/src/vmime/platforms/posix/posixSocket.hpp b/src/vmime/platforms/posix/posixSocket.hpp index 5d29d710..b8009154 100644 --- a/src/vmime/platforms/posix/posixSocket.hpp +++ b/src/vmime/platforms/posix/posixSocket.hpp @@ -50,6 +50,9 @@ public: bool isConnected() const; void disconnect(); + bool waitForRead(const int msecs = 30000); + bool waitForWrite(const int msecs = 30000); + void receive(vmime::string& buffer); size_t receiveRaw(byte_t* buffer, const size_t count); @@ -69,6 +72,8 @@ public: protected: + bool waitForData(const bool read, const bool write, const int msecs); + static void throwSocketError(const int err); private: diff --git a/src/vmime/platforms/windows/windowsHandler.cpp b/src/vmime/platforms/windows/windowsHandler.cpp index 9c96b271..4bc32dae 100644 --- a/src/vmime/platforms/windows/windowsHandler.cpp +++ b/src/vmime/platforms/windows/windowsHandler.cpp @@ -299,12 +299,6 @@ shared_ptr windowsHandler::getChildProcess #endif -void windowsHandler::wait() const -{ - ::Sleep(100); -} - - void windowsHandler::generateRandomBytes(unsigned char* buffer, const unsigned int count) { HCRYPTPROV cryptProvider = 0; diff --git a/src/vmime/platforms/windows/windowsHandler.hpp b/src/vmime/platforms/windows/windowsHandler.hpp index 4a3678eb..b80f0de8 100644 --- a/src/vmime/platforms/windows/windowsHandler.hpp +++ b/src/vmime/platforms/windows/windowsHandler.hpp @@ -75,8 +75,6 @@ public: shared_ptr getChildProcessFactory(); #endif - void wait() const; - void generateRandomBytes(unsigned char* buffer, const unsigned int count); shared_ptr createCriticalSection(); diff --git a/src/vmime/platforms/windows/windowsSocket.cpp b/src/vmime/platforms/windows/windowsSocket.cpp index bd20e5d4..d18fa1a1 100644 --- a/src/vmime/platforms/windows/windowsSocket.cpp +++ b/src/vmime/platforms/windows/windowsSocket.cpp @@ -249,10 +249,7 @@ size_t windowsSocket::receiveRaw(byte_t* buffer, const size_t count) m_status &= ~STATUS_WOULDBLOCK; // Check whether data is available - bool timedout; - waitForData(READ, timedout); - - if (timedout) + if (!waitForRead(50 /* msecs */)) { // No data available at this time // Check if we are timed out @@ -335,8 +332,7 @@ void windowsSocket::sendRaw(const byte_t* buffer, const size_t count) if (err != WSAEWOULDBLOCK) throwSocketError(err); - bool timedout; - waitForData(WRITE, timedout); + waitForWrite(50 /* msecs */); } else { @@ -430,33 +426,62 @@ void windowsSocket::throwSocketError(const int err) } -void windowsSocket::waitForData(const WaitOpType t, bool& timedOut) +bool windowsSocket::waitForData(const bool read, const bool write, const int msecs) { - // Check whether data is available - fd_set fds; - FD_ZERO(&fds); - FD_SET(m_desc, &fds); - - struct timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - - int ret; - - if (t & READ) - ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv); - else if (t & WRITE) - ret = ::select(m_desc + 1, NULL, &fds, NULL, &tv); - else - ret = ::select(m_desc + 1, &fds, &fds, NULL, &tv); - - timedOut = (ret == 0); - - if (ret == SOCKET_ERROR) + for (int i = 0 ; i <= msecs / 10 ; ++i) { - int err = WSAGetLastError(); - throwSocketError(err); + // Check whether data is available + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_desc, &fds); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; // 10 ms + + ssize_t ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv); + + if (ret == SOCKET_ERROR) + { + int err = WSAGetLastError(); + throwSocketError(err); + } + else if (ret > 0) + { + return true; + } + + // No data available at this time + // Check if we are timed out + if (m_timeoutHandler && + m_timeoutHandler->isTimeOut()) + { + if (!m_timeoutHandler->handleTimeOut()) + { + // Server did not react within timeout delay + throw exceptions::operation_timed_out(); + } + else + { + // Reset timeout + m_timeoutHandler->resetTimeOut(); + } + } } + + return false; // time out +} + + +bool windowsSocket::waitForRead(const int msecs) +{ + return waitForData(/* read */ true, /* write */ false, msecs); +} + + +bool windowsSocket::waitForWrite(const int msecs = 30000) +{ + return waitForData(/* read */ false, /* write */ true, msecs); } diff --git a/src/vmime/platforms/windows/windowsSocket.hpp b/src/vmime/platforms/windows/windowsSocket.hpp index 31e1488b..42ba3156 100644 --- a/src/vmime/platforms/windows/windowsSocket.hpp +++ b/src/vmime/platforms/windows/windowsSocket.hpp @@ -54,6 +54,9 @@ public: bool isConnected() const; void disconnect(); + bool waitForRead(const int msecs = 30000); + bool waitForWrite(const int msecs = 30000); + void receive(vmime::string& buffer); size_t receiveRaw(byte_t* buffer, const size_t count); @@ -75,14 +78,7 @@ protected: void throwSocketError(const int err); - enum WaitOpType - { - READ = 1, - WRITE = 2, - BOTH = 4 - }; - - void waitForData(const WaitOpType t, bool& timedOut); + bool waitForData(const bool read, const bool write, const int msecs); private: diff --git a/src/vmime/security/sasl/SASLSocket.cpp b/src/vmime/security/sasl/SASLSocket.cpp index 541fc904..de468fb3 100644 --- a/src/vmime/security/sasl/SASLSocket.cpp +++ b/src/vmime/security/sasl/SASLSocket.cpp @@ -102,6 +102,18 @@ shared_ptr SASLSocket::getTimeoutHandler() } +bool SASLSocket::waitForRead(const int msecs) +{ + return m_wrapped->waitForRead(msecs); +} + + +bool SASLSocket::waitForWrite(const int msecs) +{ + return m_wrapped->waitForWrite(msecs); +} + + void SASLSocket::receive(string& buffer) { const size_t n = receiveRaw(m_recvBuffer, sizeof(m_recvBuffer)); diff --git a/src/vmime/security/sasl/SASLSocket.hpp b/src/vmime/security/sasl/SASLSocket.hpp index d2d82411..474d5596 100644 --- a/src/vmime/security/sasl/SASLSocket.hpp +++ b/src/vmime/security/sasl/SASLSocket.hpp @@ -58,6 +58,9 @@ public: bool isConnected() const; + bool waitForRead(const int msecs = 30000); + bool waitForWrite(const int msecs = 30000); + void receive(string& buffer); size_t receiveRaw(byte_t* buffer, const size_t count); diff --git a/tests/testUtils.cpp b/tests/testUtils.cpp index ee642bea..c4fce447 100644 --- a/tests/testUtils.cpp +++ b/tests/testUtils.cpp @@ -85,6 +85,18 @@ vmime::shared_ptr testSocket::getTimeoutHandler() } +bool testSocket::waitForRead(const int msecs) +{ + return true; +} + + +bool testSocket::waitForWrite(const int msecs) +{ + return true; +} + + void testSocket::receive(vmime::string& buffer) { buffer = m_inBuffer; diff --git a/tests/testUtils.hpp b/tests/testUtils.hpp index 9e72158a..20ebdf20 100644 --- a/tests/testUtils.hpp +++ b/tests/testUtils.hpp @@ -248,6 +248,9 @@ public: bool isConnected() const; + bool waitForWrite(const int msecs = 30000); + bool waitForRead(const int msecs = 30000); + void receive(vmime::string& buffer); void send(const vmime::string& buffer); void send(const char* str);