diff --git a/CMakeLists.txt b/CMakeLists.txt index a010abf5..409c6608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,8 @@ set( IXWEBSOCKET_SOURCES ixwebsocket/IXWebSocketProxyServer.cpp ixwebsocket/IXWebSocketServer.cpp ixwebsocket/IXWebSocketTransport.cpp + ixwebsocket/IXProxyOptions.cpp + ixwebsocket/IXProxySocket.cpp ) set( IXWEBSOCKET_HEADERS @@ -117,6 +119,8 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketTransport.h ixwebsocket/IXWebSocketVersion.h + ixwebsocket/IXProxyOptions.h + ixwebsocket/IXProxySocket.h ) option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) diff --git a/ixwebsocket/IXHttpClient.cpp b/ixwebsocket/IXHttpClient.cpp index 871be336..a0a49e3d 100644 --- a/ixwebsocket/IXHttpClient.cpp +++ b/ixwebsocket/IXHttpClient.cpp @@ -53,6 +53,11 @@ namespace ix _tlsOptions = tlsOptions; } + void HttpClient::setProxyOptions(const ProxyOptions& proxyOptions) + { + _proxyOptions = proxyOptions; + } + void HttpClient::setForceBody(bool value) { _forceBody = value; @@ -158,7 +163,9 @@ namespace ix bool tls = protocol == "https"; std::string errorMsg; - _socket = createSocket(tls, -1, errorMsg, _tlsOptions); + + bool proxy = !_proxyOptions.host.empty(); + _socket = createSocket(tls, -1, errorMsg, _tlsOptions, proxy, _proxyOptions); if (!_socket) { diff --git a/ixwebsocket/IXHttpClient.h b/ixwebsocket/IXHttpClient.h index c4b05845..bcfcffb0 100644 --- a/ixwebsocket/IXHttpClient.h +++ b/ixwebsocket/IXHttpClient.h @@ -7,6 +7,7 @@ #pragma once #include "IXHttp.h" +#include "IXProxyOptions.h" #include "IXSocket.h" #include "IXSocketTLSOptions.h" #include "IXWebSocketHttpHeaders.h" @@ -80,6 +81,9 @@ namespace ix // TLS void setTLSOptions(const SocketTLSOptions& tlsOptions); + // Proxy + void setProxyOptions(const ProxyOptions& proxyOptions); + std::string serializeHttpParameters(const HttpParameters& httpParameters); std::string serializeHttpFormDataParameters( @@ -117,6 +121,7 @@ namespace ix // might be called recursively to follow HTTP redirections SocketTLSOptions _tlsOptions; + ProxyOptions _proxyOptions; bool _forceBody; }; diff --git a/ixwebsocket/IXProxyOptions.cpp b/ixwebsocket/IXProxyOptions.cpp new file mode 100644 index 00000000..dbb61361 --- /dev/null +++ b/ixwebsocket/IXProxyOptions.cpp @@ -0,0 +1 @@ +#include "IXProxyOptions.h" diff --git a/ixwebsocket/IXProxyOptions.h b/ixwebsocket/IXProxyOptions.h new file mode 100644 index 00000000..8e4d0753 --- /dev/null +++ b/ixwebsocket/IXProxyOptions.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +enum class SocksVersion +{ + SOCKS4 = 0, + SOCKS4a = 1, + SOCKS5 = 2, + SOCKS5h = 3, +}; + +struct ProxyOptions +{ +public: + std::string host; + unsigned short port; + SocksVersion socksVersion; + std::string userId; + std::string username; + std::string password; + bool auth; +}; diff --git a/ixwebsocket/IXProxySocket.cpp b/ixwebsocket/IXProxySocket.cpp new file mode 100644 index 00000000..2d7fb3fb --- /dev/null +++ b/ixwebsocket/IXProxySocket.cpp @@ -0,0 +1,406 @@ +#include "IXProxySocket.h" + +#include "IXSocketConnect.h" +#include + +#ifdef IXWEBSOCKET_USE_TLS +#ifdef IXWEBSOCKET_USE_OPEN_SSL +#include "IXSocketOpenSSL.h" + + template <> + bool ix::ProxySocket::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + { + bool handshakeSuccessful = false; + { + std::lock_guard lock(_mutex); + + if (!_openSSLInitializationSuccessful) + { + errMsg = "OPENSSL_init_ssl failure"; + return false; + } + + _sockfd = SocketConnect::connect(_proxyOptions.host, _proxyOptions.port, errMsg, isCancellationRequested); + if (_sockfd == -1) return false; + + if (_proxyOptions.socksVersion == SocksVersion::SOCKS4 || _proxyOptions.socksVersion == SocksVersion::SOCKS4a) + { + if (!socks4Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } else + { + if (!socks5Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } + + _ssl_context = openSSLCreateContext(errMsg); + if (_ssl_context == nullptr) + { + return false; + } + + if (!handleTLSOptions(errMsg)) + { + return false; + } + + _ssl_connection = SSL_new(_ssl_context); + if (_ssl_connection == nullptr) + { + errMsg = "OpenSSL failed to connect"; + SSL_CTX_free(_ssl_context); + _ssl_context = nullptr; + return false; + } + SSL_set_fd(_ssl_connection, _sockfd); + + // SNI support + SSL_set_tlsext_host_name(_ssl_connection, host.c_str()); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // Support for server name verification + // (The docs say that this should work from 1.0.2, and is the default from + // 1.1.0, but it does not. To be on the safe side, the manual test + // below is enabled for all versions prior to 1.1.0.) + if (!_tlsOptions.disable_hostname_validation) + { + X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection); + X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size()); + } +#endif + handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested); + } + + if (!handshakeSuccessful) + { + close(); + return false; + } + + return true; +} + +template bool ix::ProxySocket::socks5Connect(const std::string& host, + + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + +#elif defined(IXWEBSOCKET_USE_MBED_TLS) +#include "IXSocketMbedTLS.h" + +template <> +bool ix::ProxySocket::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) +{ + { + std::lock_guard lock(_mutex); + _sockfd = SocketConnect::connect(_proxyOptions.host, _proxyOptions.port, errMsg, isCancellationRequested); + if (_sockfd == -1) return false; + if (_proxyOptions.socksVersion == SocksVersion::SOCKS4 || _proxyOptions.socksVersion == SocksVersion::SOCKS4a) + { + if (!socks4Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } else + { + if (!socks5Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } + } + + bool isClient = true; + bool initialized = init(host, isClient, errMsg); + if (!initialized) + { + close(); + return false; + } + + mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL); + + int res; + do + { + { + std::lock_guard lock(_mutex); + res = mbedtls_ssl_handshake(&_ssl); + } + + if (isCancellationRequested()) + { + errMsg = "Cancellation requested"; + close(); + return false; + } + } while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE); + + if (res != 0) + { + char buf[256]; + mbedtls_strerror(res, buf, sizeof(buf)); + + errMsg = "error in handshake : "; + errMsg += buf; + + close(); + return false; + } + + return true; +} + +template bool ix::ProxySocket::socks5Connect(const std::string& host, + + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + +#elif defined(IXWEBSOCKET_USE_SECURE_TRANSPORT) +#include "IXSocketAppleSSL.h" + +template <> +bool ix::ProxySocket::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) +{ + OSStatus status; + { + std::lock_guard lock(_mutex); + _sockfd = SocketConnect::connect(_proxyOptions.host, _proxyOptions.port, errMsg, isCancellationRequested); + + if (_sockfd == -1) return false; + if (_proxyOptions.socksVersion == SocksVersion::SOCKS4 || _proxyOptions.socksVersion == SocksVersion::SOCKS4a) + { + if (!socks4Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } else + { + if (!socks5Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } + + _sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); + + SSLSetIOFuncs( + _sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket); + SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd); + SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12); + + if (!_tlsOptions.disable_hostname_validation) + SSLSetPeerDomainName(_sslContext, host.c_str(), host.size()); + + if (_tlsOptions.isPeerVerifyDisabled()) + { + Boolean option(1); + SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option); + + status = tlsHandShake(errMsg, isCancellationRequested); + + if (status == errSSLServerAuthCompleted) + { + // proceed with the handshake + status = tlsHandShake(errMsg, isCancellationRequested); + } + } + else + { + status = tlsHandShake(errMsg, isCancellationRequested); + } + } + + if (status != noErr) + { + errMsg = getSSLErrorDescription(status); + close(); + return false; + } + + return true; +} + +template bool ix::ProxySocket::socks5Connect(const std::string& host, + + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + +#endif +#endif + +template <> +bool ix::ProxySocket::socks4Connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) +{ + return false; +} + +template <> +bool ix::ProxySocket::connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) +{ + { + std::lock_guard lock(_socketMutex); + _sockfd = SocketConnect::connect(_proxyOptions.host, _proxyOptions.port, errMsg, isCancellationRequested); + } + if (_sockfd == -1) return false; + if (_proxyOptions.socksVersion == SocksVersion::SOCKS4 || _proxyOptions.socksVersion == SocksVersion::SOCKS4a) + { + if (!socks4Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } else + { + if (!socks5Connect(host, port, errMsg, isCancellationRequested)) + { + Socket::close(); + return false; + } + } + + return true; +} + +template +bool ix::ProxySocket::socks5Connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) + +{ + std::stringstream ss; + ss << '\x05'; + if (_proxyOptions.auth) + { + ss << '\x02' << '\x00' << '\x02'; + } else + { + ss << '\x01' << '\x00'; + } + + if (!rawWriteBytes(ss.str(), isCancellationRequested)) + { + errMsg = "Failed to perform socks5 handshake"; + return false; + } + auto resp = rawReadBytes(2, nullptr, nullptr, isCancellationRequested); + if (!resp.first) + { + errMsg = "Failed to perform socks5 handshake"; + return false; + } + ss.str(""); + if (resp.second.at(1) == '\xFF') + { + errMsg = "Socks server does not support this type of auth"; + return false; + } + if (resp.second.at(1) == '\x02') + { + auto size = static_cast(_proxyOptions.username.size()); + ss << '\x01'; + ss.write(reinterpret_cast(&size), sizeof(size)); + ss << _proxyOptions.username; + size = static_cast(_proxyOptions.password.size()); + ss.write(reinterpret_cast(&size), sizeof(size)); + ss << _proxyOptions.password; + if (!rawWriteBytes(ss.str(), isCancellationRequested)) + { + errMsg = "Failed to perform auth"; + return false; + } + resp = this->rawReadBytes(2, nullptr, nullptr, isCancellationRequested); + if (resp.second.at(1) != '\x00') + { + errMsg = "Invalid proxy auth"; + return false; + } + ss.str(""); + } + ss << '\x05' << '\x01' << '\x00' << '\x03'; + + auto rawSize = static_cast(host.size()); + ss.write(reinterpret_cast(&rawSize), sizeof(rawSize)); + ss << host; + + auto rawPort = static_cast(port); + rawPort = htons(rawPort); + + ss.write(reinterpret_cast(&rawPort), sizeof(rawPort)); + if (!rawWriteBytes (ss.str(), isCancellationRequested)) + { + errMsg = "Failed to connect to host"; + return false; + } + resp = this->rawReadBytes(4, nullptr, nullptr, isCancellationRequested); + if (resp.second.at(1) != '\x00') + { + errMsg = "Failed to connect to host"; + return false; + } + switch (resp.second.at(3)) + { + case '\x01': + { + rawReadBytes(6, nullptr, nullptr, isCancellationRequested); + break; + } + case '\x03': + { + rawReadBytes(8, nullptr, nullptr, isCancellationRequested); + break; + } + case '\x04': + { + resp = rawReadBytes(1, nullptr, nullptr, isCancellationRequested); + rawReadBytes(resp.second.at(0)+2, nullptr, nullptr, isCancellationRequested); + break; + } + default: + { + errMsg = "Somthing wrong happend"; + return false; + } + } + + return true; +} + +template bool ix::ProxySocket::socks5Connect(const std::string& host, + + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + diff --git a/ixwebsocket/IXProxySocket.h b/ixwebsocket/IXProxySocket.h new file mode 100644 index 00000000..982b2578 --- /dev/null +++ b/ixwebsocket/IXProxySocket.h @@ -0,0 +1,143 @@ +#pragma once + +#include "IXProxyOptions.h" +#include "IXSocket.h" +#include "IXSocketTLSOptions.h" +#include +#include +#include +#include +#include + +namespace ix +{ + template + class ProxySocket final : public T + { + public: + static_assert(std::is_base_of::value, "T must derive from socket"); + template ::value>> + ProxySocket(const ProxyOptions& proxyOptions, int fd = -1) : + T(fd), + _proxyOptions(proxyOptions) + {} + + template ::value>> + ProxySocket(const ProxyOptions& proxyOptions, + const SocketTLSOptions& tlsOptions, + int fd = -1) : + T(tlsOptions, fd), + _proxyOptions(proxyOptions) + {} + + virtual bool connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) final; + private: + ProxyOptions _proxyOptions; + + bool socks5Connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested); + + bool socks4Connect(const std::string& host, + int port, + std::string& errMsg, + const CancellationRequest& isCancellationRequested) {return false;} + + bool rawWriteBytes(const std::string& str, + const CancellationRequest& isCancellationRequested) + { + int offset = 0; + int len = (int) str.size(); + + while (true) + { + if (isCancellationRequested && isCancellationRequested()) return false; + + ssize_t ret = Socket::send((char*) &str[offset], len); + + // We wrote some bytes, as needed, all good. + if (ret > 0) + { + if (ret == len) + { + return true; + } + else + { + offset += ret; + len -= ret; + continue; + } + } + // There is possibly something to be writen, try again + else if (ret < 0 && Socket::isWaitNeeded()) + { + continue; + } + // There was an error during the write, abort + else + { + return false; + } + } + } + + std::pair rawReadBytes( + size_t length, + const OnProgressCallback& onProgressCallback, + const OnChunkCallback& onChunkCallback, + const CancellationRequest& isCancellationRequested) + { + std::array readBuffer; + std::vector output; + size_t bytesRead = 0; + + while (bytesRead != length) + { + if (isCancellationRequested && isCancellationRequested()) + { + const std::string errorMsg("Cancellation Requested"); + return std::make_pair(false, errorMsg); + } + + size_t size = std::min(readBuffer.size(), length - bytesRead); + ssize_t ret = Socket::recv((char*) &readBuffer[0], size); + + if (ret > 0) + { + if (onChunkCallback) + { + std::string chunk(readBuffer.begin(), readBuffer.begin() + ret); + onChunkCallback(chunk); + } + else + { + output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret); + } + bytesRead += ret; + } + else if (ret <= 0 && !Socket::isWaitNeeded()) + { + const std::string errorMsg("Recv Error"); + return std::make_pair(false, errorMsg); + } + + if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length); + + // Wait with a 1ms timeout until the socket is ready to read. + // This way we are not busy looping + if (Socket::isReadyToRead(1) == PollResultType::Error) + { + const std::string errorMsg("Poll Error"); + return std::make_pair(false, errorMsg); + } + } + + return std::make_pair(true, std::string(output.begin(), output.end())); + } + }; +} // namespace ix diff --git a/ixwebsocket/IXSocketAppleSSL.h b/ixwebsocket/IXSocketAppleSSL.h index a693a187..bdd64f5d 100644 --- a/ixwebsocket/IXSocketAppleSSL.h +++ b/ixwebsocket/IXSocketAppleSSL.h @@ -16,7 +16,7 @@ namespace ix { - class SocketAppleSSL final : public Socket + class SocketAppleSSL : public Socket { public: SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1); @@ -27,13 +27,13 @@ namespace ix virtual bool connect(const std::string& host, int port, std::string& errMsg, - const CancellationRequest& isCancellationRequested) final; + const CancellationRequest& isCancellationRequested); virtual void close() final; virtual ssize_t send(char* buffer, size_t length) final; virtual ssize_t recv(void* buffer, size_t length) final; - private: + protected: static std::string getSSLErrorDescription(OSStatus status); static OSStatus writeToSocket(SSLConnectionRef connection, const void* data, size_t* len); static OSStatus readFromSocket(SSLConnectionRef connection, void* data, size_t* len); diff --git a/ixwebsocket/IXSocketFactory.cpp b/ixwebsocket/IXSocketFactory.cpp index e542871d..0ee6873d 100644 --- a/ixwebsocket/IXSocketFactory.cpp +++ b/ixwebsocket/IXSocketFactory.cpp @@ -6,6 +6,7 @@ #include "IXSocketFactory.h" +#include "IXProxySocket.h" #include "IXUniquePtr.h" #ifdef IXWEBSOCKET_USE_TLS @@ -28,30 +29,57 @@ namespace ix std::unique_ptr createSocket(bool tls, int fd, std::string& errorMsg, - const SocketTLSOptions& tlsOptions) + const SocketTLSOptions& tlsOptions, + bool proxy, + const ProxyOptions& proxyOptions) { (void) tlsOptions; errorMsg.clear(); std::unique_ptr socket; - if (!tls) + if (!proxy) { - socket = ix::make_unique(fd); + if (!tls) + { + socket = ix::make_unique(fd); + } + else + { +#ifdef IXWEBSOCKET_USE_TLS +#if defined(IXWEBSOCKET_USE_MBED_TLS) + socket = ix::make_unique(tlsOptions, fd); +#elif defined(IXWEBSOCKET_USE_OPEN_SSL) || defined(IXWEBSOCKET_USE_LIBRE_SSL) + socket = ix::make_unique(tlsOptions, fd); +#elif defined(__APPLE__) + socket = ix::make_unique(tlsOptions, fd); +#endif +#else + errorMsg = "TLS support is not enabled on this platform."; + return nullptr; +#endif + } } else { + if (!tls) + { + socket = ix::make_unique>(proxyOptions,fd); + } + else + { #ifdef IXWEBSOCKET_USE_TLS #if defined(IXWEBSOCKET_USE_MBED_TLS) - socket = ix::make_unique(tlsOptions, fd); + socket = ix::make_unique>(proxyOptions, tlsOptions, fd); #elif defined(IXWEBSOCKET_USE_OPEN_SSL) || defined(IXWEBSOCKET_USE_LIBRE_SSL) - socket = ix::make_unique(tlsOptions, fd); + socket = ix::make_unique>(proxyOptions, tlsOptions, fd); #elif defined(__APPLE__) - socket = ix::make_unique(tlsOptions, fd); + socket = ix::make_unique>(proxyOptions, tlsOptions, fd); #endif #else - errorMsg = "TLS support is not enabled on this platform."; - return nullptr; + errorMsg = "TLS support is not enabled on this platform."; + return nullptr; #endif + } } if (!socket->init(errorMsg)) diff --git a/ixwebsocket/IXSocketFactory.h b/ixwebsocket/IXSocketFactory.h index de1eeda6..ded570c7 100644 --- a/ixwebsocket/IXSocketFactory.h +++ b/ixwebsocket/IXSocketFactory.h @@ -7,6 +7,7 @@ #pragma once +#include "IXProxyOptions.h" #include "IXSocketTLSOptions.h" #include #include @@ -17,5 +18,7 @@ namespace ix std::unique_ptr createSocket(bool tls, int fd, std::string& errorMsg, - const SocketTLSOptions& tlsOptions); + const SocketTLSOptions& tlsOptions, + bool proxy = false, + const ProxyOptions& proxyOptions = {}); } // namespace ix diff --git a/ixwebsocket/IXSocketMbedTLS.h b/ixwebsocket/IXSocketMbedTLS.h index 2fb662d0..ff9b861a 100644 --- a/ixwebsocket/IXSocketMbedTLS.h +++ b/ixwebsocket/IXSocketMbedTLS.h @@ -24,7 +24,7 @@ namespace ix { - class SocketMbedTLS final : public Socket + class SocketMbedTLS : public Socket { public: SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1); @@ -35,13 +35,13 @@ namespace ix virtual bool connect(const std::string& host, int port, std::string& errMsg, - const CancellationRequest& isCancellationRequested) final; + const CancellationRequest& isCancellationRequested); virtual void close() final; virtual ssize_t send(char* buffer, size_t length) final; virtual ssize_t recv(void* buffer, size_t length) final; - private: + protected: mbedtls_ssl_context _ssl; mbedtls_ssl_config _conf; #if MBEDTLS_VERSION_MAJOR < 4 diff --git a/ixwebsocket/IXSocketOpenSSL.h b/ixwebsocket/IXSocketOpenSSL.h index 9142bf82..5ed986dc 100644 --- a/ixwebsocket/IXSocketOpenSSL.h +++ b/ixwebsocket/IXSocketOpenSSL.h @@ -19,7 +19,7 @@ namespace ix { - class SocketOpenSSL final : public Socket + class SocketOpenSSL : public Socket { public: SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1); @@ -30,13 +30,13 @@ namespace ix virtual bool connect(const std::string& host, int port, std::string& errMsg, - const CancellationRequest& isCancellationRequested) final; + const CancellationRequest& isCancellationRequested); virtual void close() final; virtual ssize_t send(char* buffer, size_t length) final; virtual ssize_t recv(void* buffer, size_t length) final; - private: + protected: void openSSLInitialize(); std::string getSSLError(int ret); SSL_CTX* openSSLCreateContext(std::string& errMsg); diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index f9b8075d..1dd2f1e7 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -98,6 +98,12 @@ namespace ix _socketTLSOptions = socketTLSOptions; } + void WebSocket::setProxyOptions(const ProxyOptions& proxyOptions) + { + std::lock_guard lock(_configMutex); + _proxyOptions = proxyOptions; + } + const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const { std::lock_guard lock(_configMutex); @@ -204,7 +210,7 @@ namespace ix { std::lock_guard lock(_configMutex); _ws.configure( - _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs); + _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs, _proxyOptions); } WebSocketHttpHeaders headers(_extraHeaders); @@ -262,7 +268,7 @@ namespace ix { std::lock_guard lock(_configMutex); _ws.configure( - _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs); + _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs, _proxyOptions); } WebSocketInitResult status = _ws.connectToSocket( diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 1070836f..668fdbb0 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -11,6 +11,7 @@ #include "IXProgressCallback.h" #include "IXSocketTLSOptions.h" +#include "IXProxyOptions.h" #include "IXWebSocketCloseConstants.h" #include "IXWebSocketErrorInfo.h" #include "IXWebSocketHttpHeaders.h" @@ -54,6 +55,7 @@ namespace ix void setPerMessageDeflateOptions( const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); void setTLSOptions(const SocketTLSOptions& socketTLSOptions); + void setProxyOptions(const ProxyOptions& proxyOptions); void setPingMessage(const std::string& sendMessage, SendMessageKind pingType = SendMessageKind::Ping); void setPingInterval(int pingIntervalSecs); @@ -146,6 +148,7 @@ namespace ix WebSocketPerMessageDeflateOptions _perMessageDeflateOptions; SocketTLSOptions _socketTLSOptions; + ProxyOptions _proxyOptions; mutable std::mutex _configMutex; // protect all config variables access diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 39194f43..fbad36f3 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -92,13 +92,15 @@ namespace ix const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, const SocketTLSOptions& socketTLSOptions, bool enablePong, - int pingIntervalSecs) + int pingIntervalSecs, + const ProxyOptions& proxyOptions) { _perMessageDeflateOptions = perMessageDeflateOptions; _enablePerMessageDeflate = _perMessageDeflateOptions.enabled(); _socketTLSOptions = socketTLSOptions; _enablePong = enablePong; _pingIntervalSecs = pingIntervalSecs; + _proxyOptions = proxyOptions; } // Client @@ -126,7 +128,10 @@ namespace ix std::string errorMsg; bool tls = protocol == "wss"; - _socket = createSocket(tls, -1, errorMsg, _socketTLSOptions); + + + bool proxy = !_proxyOptions.host.empty(); + _socket = createSocket(tls, -1, errorMsg, _socketTLSOptions, proxy, _proxyOptions); _perMessageDeflate = ix::make_unique(); if (!_socket) diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index f2391e15..ace16307 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -13,6 +13,7 @@ #include "IXCancellationRequest.h" #include "IXProgressCallback.h" #include "IXSocketTLSOptions.h" +#include "IXProxyOptions.h" #include "IXWebSocketCloseConstants.h" #include "IXWebSocketHandshake.h" #include "IXWebSocketHttpHeaders.h" @@ -77,7 +78,8 @@ namespace ix void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, const SocketTLSOptions& socketTLSOptions, bool enablePong, - int pingIntervalSecs); + int pingIntervalSecs, + const ProxyOptions& proxyOptions = {}); // Client WebSocketInitResult connectToUrl(const std::string& url, @@ -216,6 +218,7 @@ namespace ix // Used to control TLS connection behavior SocketTLSOptions _socketTLSOptions; + ProxyOptions _proxyOptions; // Used to cancel dns lookup + socket connect + http upgrade std::atomic _requestInitCancellation;