Skip to content

Commit 10d1b6c

Browse files
committed
Add support for SOCKS5 proxy
1 parent 1b54c87 commit 10d1b6c

31 files changed

+2704
-97
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ if(NOT CMAKE_CROSSCOMPILING)
418418
add_subdirectory(bin/elasticurl_cpp)
419419
add_subdirectory(bin/mqtt5_app)
420420
add_subdirectory(bin/mqtt5_canary)
421+
add_subdirectory(bin/mqtt5_socks5_app)
421422
endif()
422423
endif()
423424
endif()

bin/elasticurl_cpp/main.cpp

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <aws/crt/crypto/Hash.h>
77
#include <aws/crt/http/HttpConnection.h>
88
#include <aws/crt/http/HttpRequestResponse.h>
9+
10+
#include <aws/crt/io/Socks5ProxyOptions.h>
911
#include <aws/crt/io/Uri.h>
1012

1113
#include <aws/common/command_line_parser.h>
@@ -40,11 +42,52 @@ struct ElasticurlCtx
4042

4143
std::shared_ptr<Io::IStream> InputBody = nullptr;
4244
std::ofstream Output;
45+
46+
// SOCKS5 proxy support
47+
Aws::Crt::String ProxyHost;
48+
uint16_t ProxyPort = 0;
49+
bool UseProxy = false;
50+
Aws::Crt::Optional<Aws::Crt::Io::Socks5ProxyOptions> Socks5ProxyOptions;
4351
};
4452

45-
static void s_Usage(int exit_code)
53+
// Parse SOCKS5 proxy URI and fill ElasticurlCtx fields
54+
static bool s_ParseProxyUri(ElasticurlCtx &ctx, const char *proxy_arg)
4655
{
56+
if (!proxy_arg || proxy_arg[0] == '\0')
57+
{
58+
std::cerr << "Proxy URI must not be empty" << std::endl;
59+
return false;
60+
}
61+
ByteCursor uri_cursor = aws_byte_cursor_from_c_str(proxy_arg);
62+
Io::Uri parsed_uri(uri_cursor, ctx.allocator);
63+
if (!parsed_uri)
64+
{
65+
std::cerr << "Failed to parse proxy URI \"" << proxy_arg
66+
<< "\": " << aws_error_debug_str(parsed_uri.LastError()) << std::endl;
67+
return false;
68+
}
69+
auto proxyOptions = Io::Socks5ProxyOptions::CreateFromUri(parsed_uri, ctx.ConnectTimeout, ctx.allocator);
70+
if (!proxyOptions)
71+
{
72+
std::cerr << "Failed to create SOCKS5 proxy options from \"" << proxy_arg
73+
<< "\": " << aws_error_debug_str(Aws::Crt::LastError()) << std::endl;
74+
return false;
75+
}
76+
ctx.Socks5ProxyOptions = *proxyOptions;
77+
ByteCursor host_cursor = parsed_uri.GetHostName();
78+
ctx.ProxyHost.assign(reinterpret_cast<const char *>(host_cursor.ptr), host_cursor.len);
79+
uint32_t port = parsed_uri.GetPort();
80+
if (port == 0)
81+
{
82+
port = 1080;
83+
}
84+
ctx.ProxyPort = static_cast<uint16_t>(port);
85+
ctx.UseProxy = true;
86+
return true;
87+
}
4788

89+
static void s_Usage(int exit_code)
90+
{
4891
std::cerr << "usage: elasticurl [options] url\n";
4992
std::cerr << " url: url to make a request to. The default is a GET request.\n";
5093
std::cerr << "\n Options:\n\n";
@@ -65,6 +108,7 @@ static void s_Usage(int exit_code)
65108
std::cerr << " -o, --output FILE: dumps content-body to FILE instead of stdout.\n";
66109
std::cerr << " -t, --trace FILE: dumps logs to FILE instead of stderr.\n";
67110
std::cerr << " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n";
111+
std::cerr << " --proxy URL: SOCKS5 proxy URI (socks5h://[user[:pass]@]host[:port]).\n";
68112
std::cerr << " --version: print the version of elasticurl.\n";
69113
std::cerr << " --http2: HTTP/2 connection required\n";
70114
std::cerr << " --http1_1: HTTP/1.1 connection required\n";
@@ -91,6 +135,7 @@ static struct aws_cli_option s_LongOptions[] = {
91135
{"output", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'o'},
92136
{"trace", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 't'},
93137
{"verbose", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'v'},
138+
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'X'},
94139
{"version", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'V'},
95140
{"http2", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'w'},
96141
{"http1_1", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'W'},
@@ -104,7 +149,7 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
104149
while (true)
105150
{
106151
int option_index = 0;
107-
int c = aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWh", s_LongOptions, &option_index);
152+
int c = aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWhX:", s_LongOptions, &option_index);
108153
if (c == -1)
109154
{
110155
/* finished parsing */
@@ -126,6 +171,12 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
126171
s_Usage(1);
127172
}
128173
break;
174+
case 'X':
175+
if (!s_ParseProxyUri(ctx, aws_cli_optarg))
176+
{
177+
s_Usage(1);
178+
}
179+
break;
129180
case 'a':
130181
ctx.CaCert = aws_cli_optarg;
131182
break;
@@ -375,28 +426,30 @@ int main(int argc, char **argv)
375426
std::promise<void> shutdownPromise;
376427

377428
auto onConnectionSetup =
378-
[&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
379-
if (!errorCode)
429+
[&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode)
430+
{
431+
if (!errorCode)
432+
{
433+
if (appCtx.RequiredHttpVersion != Http::HttpVersion::Unknown)
380434
{
381-
if (appCtx.RequiredHttpVersion != Http::HttpVersion::Unknown)
435+
if (newConnection->GetVersion() != appCtx.RequiredHttpVersion)
382436
{
383-
if (newConnection->GetVersion() != appCtx.RequiredHttpVersion)
384-
{
385-
std::cerr << "Error. The requested HTTP version, " << appCtx.Alpn
386-
<< ", is not supported by the peer." << std::endl;
387-
exit(1);
388-
}
437+
std::cerr << "Error. The requested HTTP version, " << appCtx.Alpn
438+
<< ", is not supported by the peer." << std::endl;
439+
exit(1);
389440
}
390441
}
391-
else
392-
{
393-
std::cerr << "Connection failed with error " << aws_error_debug_str(errorCode) << std::endl;
394-
exit(1);
395-
}
396-
connectionPromise.set_value(newConnection);
397-
};
442+
}
443+
else
444+
{
445+
std::cerr << "Connection failed with error " << aws_error_debug_str(errorCode) << std::endl;
446+
exit(1);
447+
}
448+
connectionPromise.set_value(newConnection);
449+
};
398450

399-
auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode) {
451+
auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode)
452+
{
400453
(void)newConnection;
401454
if (errorCode)
402455
{
@@ -418,6 +471,10 @@ int main(int argc, char **argv)
418471
}
419472
httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
420473
httpClientConnectionOptions.Port = port;
474+
if (appCtx.UseProxy && appCtx.Socks5ProxyOptions.has_value())
475+
{
476+
httpClientConnectionOptions.Socks5ProxyOptions = appCtx.Socks5ProxyOptions.value();
477+
}
421478

422479
Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator);
423480

@@ -430,7 +487,8 @@ int main(int argc, char **argv)
430487
requestOptions.request = &request;
431488
std::promise<void> streamCompletePromise;
432489

433-
requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode) {
490+
requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode)
491+
{
434492
(void)stream;
435493
if (errorCode)
436494
{
@@ -443,7 +501,8 @@ int main(int argc, char **argv)
443501
requestOptions.onIncomingHeaders = [&](Http::HttpStream &stream,
444502
enum aws_http_header_block header_block,
445503
const Http::HttpHeader *header,
446-
std::size_t len) {
504+
std::size_t len)
505+
{
447506
/* Ignore informational headers */
448507
if (header_block == AWS_HTTP_HEADER_BLOCK_INFORMATIONAL)
449508
{
@@ -468,7 +527,8 @@ int main(int argc, char **argv)
468527
}
469528
}
470529
};
471-
requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data) {
530+
requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data)
531+
{
472532
if (appCtx.Output.is_open())
473533
{
474534
appCtx.Output.write((char *)data.ptr, data.len);
@@ -481,9 +541,12 @@ int main(int argc, char **argv)
481541

482542
request.SetMethod(ByteCursorFromCString(appCtx.verb));
483543
auto pathAndQuery = appCtx.uri.GetPathAndQuery();
484-
if (pathAndQuery.len > 0) {
544+
if (pathAndQuery.len > 0)
545+
{
485546
request.SetPath(pathAndQuery);
486-
} else {
547+
}
548+
else
549+
{
487550
request.SetPath(ByteCursorFromCString("/"));
488551
}
489552

bin/mqtt5_app/main.cpp

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <aws/crt/crypto/Hash.h>
88
#include <aws/crt/http/HttpConnection.h>
99
#include <aws/crt/http/HttpRequestResponse.h>
10+
#include <aws/crt/io/Socks5ProxyOptions.h>
1011
#include <aws/crt/io/Uri.h>
1112

1213
#include <aws/crt/mqtt/Mqtt5Packets.h>
@@ -40,8 +41,54 @@ struct app_ctx
4041

4142
const char *TraceFile;
4243
Aws::Crt::LogLevel LogLevel;
44+
45+
Aws::Crt::String proxy_host;
46+
uint16_t proxy_port;
47+
bool use_proxy = false;
48+
Aws::Crt::Optional<Io::Socks5ProxyOptions> socks5_proxy_options;
4349
};
4450

51+
static bool s_parse_proxy_uri(struct app_ctx &ctx, const char *proxy_arg)
52+
{
53+
if (!proxy_arg || proxy_arg[0] == '\0')
54+
{
55+
std::cerr << "Proxy URI must not be empty" << std::endl;
56+
return false;
57+
}
58+
59+
ByteCursor uri_cursor = aws_byte_cursor_from_c_str(proxy_arg);
60+
Io::Uri parsed_uri(uri_cursor, ctx.allocator);
61+
if (!parsed_uri)
62+
{
63+
std::cerr << "Failed to parse proxy URI \"" << proxy_arg
64+
<< "\": " << aws_error_debug_str(parsed_uri.LastError()) << std::endl;
65+
return false;
66+
}
67+
68+
auto proxyOptions = Io::Socks5ProxyOptions::CreateFromUri(parsed_uri, ctx.connect_timeout, ctx.allocator);
69+
if (!proxyOptions)
70+
{
71+
std::cerr << "Failed to create SOCKS5 proxy options from \"" << proxy_arg
72+
<< "\": " << aws_error_debug_str(Aws::Crt::LastError()) << std::endl;
73+
return false;
74+
}
75+
76+
ctx.socks5_proxy_options = *proxyOptions;
77+
78+
ByteCursor host_cursor = parsed_uri.GetHostName();
79+
ctx.proxy_host.assign(reinterpret_cast<const char *>(host_cursor.ptr), host_cursor.len);
80+
81+
uint32_t port = parsed_uri.GetPort();
82+
if (port == 0)
83+
{
84+
port = 1080;
85+
}
86+
ctx.proxy_port = static_cast<uint16_t>(port);
87+
88+
ctx.use_proxy = true;
89+
return true;
90+
}
91+
4592
static void s_usage(int exit_code)
4693
{
4794

@@ -53,6 +100,7 @@ static void s_usage(int exit_code)
53100
fprintf(stderr, " --key FILE: Path to a PEM encoded private key that matches cert.\n");
54101
fprintf(stderr, " -l, --log FILE: dumps logs to FILE instead of stderr.\n");
55102
fprintf(stderr, " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n");
103+
fprintf(stderr, " --proxy URL: SOCKS5 proxy URI (socks5h://[user[:pass]@]host[:port]).\n");
56104

57105
fprintf(stderr, " -h, --help\n");
58106
fprintf(stderr, " Display this message and quit.\n");
@@ -66,6 +114,7 @@ static struct aws_cli_option s_long_options[] = {
66114
{"connect-timeout", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'f'},
67115
{"log", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'l'},
68116
{"verbose", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'v'},
117+
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'X'},
69118
{"help", AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 'h'},
70119
/* Per getopt(3) the last element of the array has to be filled with all zeros */
71120
{NULL, AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 0},
@@ -76,7 +125,7 @@ static void s_parse_options(int argc, char **argv, struct app_ctx &ctx)
76125
while (true)
77126
{
78127
int option_index = 0;
79-
int c = aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWh", s_long_options, &option_index);
128+
int c = aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWhX:", s_long_options, &option_index);
80129
if (c == -1)
81130
{
82131
/* finished parsing */
@@ -130,6 +179,12 @@ static void s_parse_options(int argc, char **argv, struct app_ctx &ctx)
130179
}
131180
break;
132181
}
182+
case 'X':
183+
if (!s_parse_proxy_uri(ctx, aws_cli_optarg))
184+
{
185+
s_usage(1);
186+
}
187+
break;
133188
default:
134189
std::cerr << "Unknown option\n";
135190
s_usage(1);
@@ -314,6 +369,26 @@ int main(int argc, char **argv)
314369
mqtt5OptionsBuilder.WithTlsConnectionOptions(tlsConnectionOptions);
315370
}
316371

372+
if (app_ctx.use_proxy && app_ctx.socks5_proxy_options && !app_ctx.proxy_host.empty())
373+
{
374+
std::cout << "**********************************************************" << std::endl;
375+
std::cout << "MQTT5: Using SOCKS5 proxy " << app_ctx.proxy_host << ":" << app_ctx.proxy_port << std::endl;
376+
const auto &proxy_opts = *app_ctx.socks5_proxy_options;
377+
Aws::Crt::String username, password;
378+
if (proxy_opts.GetUsername().has_value())
379+
{
380+
username = proxy_opts.GetUsername().value();
381+
std::cout << "MQTT5: Proxy username: " << username << std::endl;
382+
}
383+
if (proxy_opts.GetPassword().has_value())
384+
{
385+
password = proxy_opts.GetPassword().value();
386+
std::cout << "MQTT5: Proxy password: " << password << std::endl;
387+
}
388+
mqtt5OptionsBuilder.WithSocks5ProxyOptions(proxy_opts);
389+
std::cout << "**********************************************************" << std::endl;
390+
}
391+
317392
std::promise<bool> connectionPromise;
318393
std::promise<void> disconnectionPromise;
319394
std::promise<void> stoppedPromise;
@@ -364,13 +439,14 @@ int main(int argc, char **argv)
364439
else
365440
{
366441
std::cout << "**********************************************************" << std::endl;
367-
std::cout << "MQTT5:DisConnection failed with error " << aws_error_debug_str(eventData.errorCode) << std::endl;
442+
std::cout << "MQTT5:DisConnection failed with error " << aws_error_debug_str(eventData.errorCode)
443+
<< std::endl;
368444
if (eventData.disconnectPacket != NULL)
369445
{
370446
if (eventData.disconnectPacket->getReasonString().has_value())
371447
{
372-
std::cout << "disconnect packet: " << eventData.disconnectPacket->getReasonString().value().c_str()
373-
<< std::endl;
448+
std::cout << "disconnect packet: "
449+
<< eventData.disconnectPacket->getReasonString().value().c_str() << std::endl;
374450
}
375451
}
376452
std::cout << "**********************************************************" << std::endl;
@@ -451,7 +527,8 @@ int main(int argc, char **argv)
451527
subscribe,
452528
[](int, std::shared_ptr<Mqtt5::SubAckPacket> packet)
453529
{
454-
if(packet == nullptr) return;
530+
if (packet == nullptr)
531+
return;
455532
std::cout << "**********************************************************" << std::endl;
456533
std::cout << "MQTT5: check suback packet : " << std::endl;
457534
for (auto code : packet->getReasonCodes())

0 commit comments

Comments
 (0)