Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketTLSOptions.cpp
ixwebsocket/IXSocks5.cpp
ixwebsocket/IXStrCaseCompare.cpp
ixwebsocket/IXUdpSocket.cpp
ixwebsocket/IXUrlParser.cpp
Expand Down Expand Up @@ -102,6 +103,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXHttpServer.h
ixwebsocket/IXNetSystem.h
ixwebsocket/IXProgressCallback.h
ixwebsocket/IXProxyOptions.h
ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXSelectInterruptPipe.h
Expand Down
35 changes: 35 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,3 +712,38 @@ bind address and uses the framework's TCP listen queue rather than the BSD backl
setting. Pending connections are bounded by `maxConnections`. Server TLS handshakes
have a ten-second limit and are interrupted when the server stops. Client connection
timeouts use the caller's cancellation callback, including HTTP/WebSocket timeouts.


### SOCKS5 client proxies

Configure an outbound SOCKS5 proxy before connecting a WebSocket or issuing HTTP
requests. The same options apply to HTTP, HTTPS, WS, and WSS:

```cpp
ix::ProxyOptions proxy;
proxy.host = "127.0.0.1";
proxy.port = 1080; // default
proxy.dnsMode = ix::ProxyOptions::DnsMode::Remote; // default: proxy resolves the destination

webSocket.setProxyOptions(proxy);
httpClient.setProxyOptions(proxy);
```

Use `DnsMode::Local` to resolve the destination locally and send an IPv4 or IPv6
address to the proxy. Numeric destinations are always sent as numeric addresses.
The proxy's own hostname is resolved locally in both modes. TLS continues to use
the original destination hostname for SNI and certificate verification.

Set both `proxy.username` and `proxy.password` to offer SOCKS username/password
authentication as well as the unauthenticated method. Each credential must contain
1–255 bytes. Proxy authentication takes place before the destination TLS handshake.
An empty proxy host disables this proxy configuration; call `setProxyOptions({})`
to clear it. Configure options before use; WebSocket reconnections use the current
configuration. Proxy negotiation shares the existing connection timeout and
cancellation mechanism, and failures do not fall back to a direct connection.

SOCKS5 CONNECT is supported for plain sockets, OpenSSL/LibreSSL, Mbed TLS, and
SecureTransport. SOCKS4 and HTTP CONNECT proxies are not implemented. The native
Network.framework TLS backend currently returns an explicit unsupported-proxy
error. Its native SOCKS API failed routing and fragmented-reply checks during
implementation, so it is not enabled by this feature.
7 changes: 6 additions & 1 deletion ixwebsocket/IXHttpClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ namespace ix
_tlsOptions = tlsOptions;
}

void HttpClient::setProxyOptions(const ProxyOptions& proxyOptions)
{
_proxyOptions = proxyOptions;
}

void HttpClient::setForceBody(bool value)
{
_forceBody = value;
Expand Down Expand Up @@ -158,7 +163,7 @@ namespace ix

bool tls = protocol == "https";
std::string errorMsg;
_socket = createSocket(tls, -1, errorMsg, _tlsOptions);
_socket = createSocket(tls, -1, errorMsg, _tlsOptions, _proxyOptions);

if (!_socket)
{
Expand Down
5 changes: 5 additions & 0 deletions ixwebsocket/IXHttpClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once

#include "IXHttp.h"
#include "IXProxyOptions.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketExport.h"
Expand Down Expand Up @@ -81,6 +82,9 @@ namespace ix
// TLS
void setTLSOptions(const SocketTLSOptions& tlsOptions);

// Configure the outbound SOCKS5 proxy before issuing requests.
void setProxyOptions(const ProxyOptions& proxyOptions);

std::string serializeHttpParameters(const HttpParameters& httpParameters);

std::string serializeHttpFormDataParameters(
Expand Down Expand Up @@ -118,6 +122,7 @@ namespace ix
// might be called recursively to follow HTTP redirections

SocketTLSOptions _tlsOptions;
ProxyOptions _proxyOptions;

bool _forceBody;
};
Expand Down
28 changes: 28 additions & 0 deletions ixwebsocket/IXProxyOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* IXProxyOptions.h
* Copyright (c) 2026 Machine Zone, Inc. All rights reserved.
*/
#pragma once

#include <string>

namespace ix
{
struct ProxyOptions
{
enum class DnsMode
{
Local,
Remote
};

// An empty host disables the SOCKS5 proxy. Configure before connecting.
std::string host;
int port = 1080;
DnsMode dnsMode = DnsMode::Remote;

// Optional username/password authentication; supply both fields.
std::string username;
std::string password;
};
} // namespace ix
24 changes: 23 additions & 1 deletion ixwebsocket/IXSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h"
#include "IXSocketConnect.h"
#include "IXSocks5.h"
#include <algorithm>
#include <array>
#include <assert.h>
Expand Down Expand Up @@ -236,10 +237,31 @@ namespace ix

if (!_selectInterrupt->clear()) return false;

_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
_sockfd = connectToHost(host, port, errMsg, isCancellationRequested);
return _sockfd != -1;
}

void Socket::setProxyOptions(const ProxyOptions& options)
{
_proxyOptions = options;
}

int Socket::connectToHost(const std::string& host,
int port,
std::string& error,
const CancellationRequest& cancelled)
{
if (_proxyOptions.host.empty()) return SocketConnect::connect(host, port, error, cancelled);
if (!Socks5::validate(_proxyOptions, error)) return -1;
int fd = SocketConnect::connect(_proxyOptions.host, _proxyOptions.port, error, cancelled);
if (fd != -1 && !Socks5::connect(fd, host, port, _proxyOptions, error, cancelled))
{
Socket::closeSocket(fd);
return -1;
}
return fd;
}

void Socket::close()
{
std::lock_guard<std::mutex> lock(_socketMutex);
Expand Down
8 changes: 8 additions & 0 deletions ixwebsocket/IXSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "IXCancellationRequest.h"
#include "IXNetSystem.h"
#include "IXProgressCallback.h"
#include "IXProxyOptions.h"
#include "IXSelectInterrupt.h"
#include "IXWebSocketExport.h"
#include <atomic>
Expand Down Expand Up @@ -37,6 +38,8 @@ namespace ix
Socket(int fd = -1);
virtual ~Socket();
bool init(std::string& errorMsg);
// Configure before connecting; proxy options are immutable during connection setup.
void setProxyOptions(const ProxyOptions& options);

// Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
Expand Down Expand Up @@ -86,6 +89,11 @@ namespace ix
const SelectInterruptPtr& selectInterrupt);

protected:
int connectToHost(const std::string& host,
int port,
std::string& error,
const CancellationRequest& cancelled);
ProxyOptions _proxyOptions;
std::atomic<int> _sockfd;
std::mutex _socketMutex;

Expand Down
2 changes: 1 addition & 1 deletion ixwebsocket/IXSocketAppleSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_mutex);

_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
_sockfd = connectToHost(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;

_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
Expand Down
4 changes: 3 additions & 1 deletion ixwebsocket/IXSocketFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ namespace ix
std::unique_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
const SocketTLSOptions& tlsOptions,
const ProxyOptions& proxyOptions)
{
(void) tlsOptions;
errorMsg.clear();
Expand Down Expand Up @@ -64,6 +65,7 @@ namespace ix
#endif
}

socket->setProxyOptions(proxyOptions);
if (!socket->init(errorMsg))
{
socket.reset();
Expand Down
4 changes: 3 additions & 1 deletion ixwebsocket/IXSocketFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#pragma once

#include "IXProxyOptions.h"
#include "IXSocketTLSOptions.h"
#include <memory>
#include <string>
Expand All @@ -17,5 +18,6 @@ namespace ix
std::unique_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions);
const SocketTLSOptions& tlsOptions,
const ProxyOptions& proxyOptions = {});
} // namespace ix
2 changes: 1 addition & 1 deletion ixwebsocket/IXSocketMbedTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_mutex);
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
_sockfd = connectToHost(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
}

Expand Down
5 changes: 5 additions & 0 deletions ixwebsocket/IXSocketNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ namespace ix
error = "Invalid TLS port";
return false;
}
if (!_proxyOptions.host.empty())
{
error = "SOCKS5 is not supported by the Network.framework backend";
return false;
}
nw_parameters_t params = makeParameters(_options, false, host, error);
if (!params) return false;
nw_endpoint_t endpoint =
Expand Down
2 changes: 1 addition & 1 deletion ixwebsocket/IXSocketOpenSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ namespace ix
return false;
}

_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
_sockfd = connectToHost(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;

_ssl_context = openSSLCreateContext(errMsg);
Expand Down
Loading
Loading