Skip to content
Open
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ if(NOT CMAKE_CROSSCOMPILING)
add_subdirectory(bin/elasticurl_cpp)
add_subdirectory(bin/mqtt5_app)
add_subdirectory(bin/mqtt5_canary)
add_subdirectory(bin/mqtt5_socks5_app)
endif()
endif()
endif()
111 changes: 87 additions & 24 deletions bin/elasticurl_cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <aws/crt/crypto/Hash.h>
#include <aws/crt/http/HttpConnection.h>
#include <aws/crt/http/HttpRequestResponse.h>

#include <aws/crt/io/Socks5ProxyOptions.h>
#include <aws/crt/io/Uri.h>

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

std::shared_ptr<Io::IStream> InputBody = nullptr;
std::ofstream Output;

// SOCKS5 proxy support
Aws::Crt::String ProxyHost;
uint16_t ProxyPort = 0;
bool UseProxy = false;
Aws::Crt::Optional<Aws::Crt::Io::Socks5ProxyOptions> Socks5ProxyOptions;
};

static void s_Usage(int exit_code)
// Parse SOCKS5 proxy URI and fill ElasticurlCtx fields
static bool s_ParseProxyUri(ElasticurlCtx &ctx, const char *proxy_arg)
{
if (!proxy_arg || proxy_arg[0] == '\0')
{
std::cerr << "Proxy URI must not be empty" << std::endl;
return false;
}
ByteCursor uri_cursor = aws_byte_cursor_from_c_str(proxy_arg);
Io::Uri parsed_uri(uri_cursor, ctx.allocator);
if (!parsed_uri)
{
std::cerr << "Failed to parse proxy URI \"" << proxy_arg
<< "\": " << aws_error_debug_str(parsed_uri.LastError()) << std::endl;
return false;
}
auto proxyOptions = Io::Socks5ProxyOptions::CreateFromUri(parsed_uri, ctx.ConnectTimeout, ctx.allocator);
if (!proxyOptions)
{
std::cerr << "Failed to create SOCKS5 proxy options from \"" << proxy_arg
<< "\": " << aws_error_debug_str(Aws::Crt::LastError()) << std::endl;
return false;
}
ctx.Socks5ProxyOptions = *proxyOptions;
ByteCursor host_cursor = parsed_uri.GetHostName();
ctx.ProxyHost.assign(reinterpret_cast<const char *>(host_cursor.ptr), host_cursor.len);
uint32_t port = parsed_uri.GetPort();
if (port == 0)
{
port = 1080;
}
ctx.ProxyPort = static_cast<uint16_t>(port);
ctx.UseProxy = true;
return true;
}

static void s_Usage(int exit_code)
{
std::cerr << "usage: elasticurl [options] url\n";
std::cerr << " url: url to make a request to. The default is a GET request.\n";
std::cerr << "\n Options:\n\n";
Expand All @@ -65,6 +108,7 @@ static void s_Usage(int exit_code)
std::cerr << " -o, --output FILE: dumps content-body to FILE instead of stdout.\n";
std::cerr << " -t, --trace FILE: dumps logs to FILE instead of stderr.\n";
std::cerr << " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n";
std::cerr << " --proxy URL: SOCKS5 proxy URI (socks5h://[user[:pass]@]host[:port]).\n";
std::cerr << " --version: print the version of elasticurl.\n";
std::cerr << " --http2: HTTP/2 connection required\n";
std::cerr << " --http1_1: HTTP/1.1 connection required\n";
Expand All @@ -91,6 +135,7 @@ static struct aws_cli_option s_LongOptions[] = {
{"output", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'o'},
{"trace", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 't'},
{"verbose", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'v'},
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr, 'X'},
{"version", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'V'},
{"http2", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'w'},
{"http1_1", AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr, 'W'},
Expand All @@ -104,7 +149,7 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
while (true)
{
int option_index = 0;
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);
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);
if (c == -1)
{
/* finished parsing */
Expand All @@ -126,6 +171,12 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
s_Usage(1);
}
break;
case 'X':
if (!s_ParseProxyUri(ctx, aws_cli_optarg))
{
s_Usage(1);
}
break;
case 'a':
ctx.CaCert = aws_cli_optarg;
break;
Expand Down Expand Up @@ -375,28 +426,30 @@ int main(int argc, char **argv)
std::promise<void> shutdownPromise;

auto onConnectionSetup =
[&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
if (!errorCode)
[&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode)
{
if (!errorCode)
{
if (appCtx.RequiredHttpVersion != Http::HttpVersion::Unknown)
{
if (appCtx.RequiredHttpVersion != Http::HttpVersion::Unknown)
if (newConnection->GetVersion() != appCtx.RequiredHttpVersion)
{
if (newConnection->GetVersion() != appCtx.RequiredHttpVersion)
{
std::cerr << "Error. The requested HTTP version, " << appCtx.Alpn
<< ", is not supported by the peer." << std::endl;
exit(1);
}
std::cerr << "Error. The requested HTTP version, " << appCtx.Alpn
<< ", is not supported by the peer." << std::endl;
exit(1);
}
}
else
{
std::cerr << "Connection failed with error " << aws_error_debug_str(errorCode) << std::endl;
exit(1);
}
connectionPromise.set_value(newConnection);
};
}
else
{
std::cerr << "Connection failed with error " << aws_error_debug_str(errorCode) << std::endl;
exit(1);
}
connectionPromise.set_value(newConnection);
};

auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode) {
auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode)
{
(void)newConnection;
if (errorCode)
{
Expand All @@ -418,6 +471,10 @@ int main(int argc, char **argv)
}
httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
httpClientConnectionOptions.Port = port;
if (appCtx.UseProxy && appCtx.Socks5ProxyOptions.has_value())
{
httpClientConnectionOptions.Socks5ProxyOptions = appCtx.Socks5ProxyOptions.value();
}

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

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

requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode) {
requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode)
{
(void)stream;
if (errorCode)
{
Expand All @@ -443,7 +501,8 @@ int main(int argc, char **argv)
requestOptions.onIncomingHeaders = [&](Http::HttpStream &stream,
enum aws_http_header_block header_block,
const Http::HttpHeader *header,
std::size_t len) {
std::size_t len)
{
/* Ignore informational headers */
if (header_block == AWS_HTTP_HEADER_BLOCK_INFORMATIONAL)
{
Expand All @@ -468,7 +527,8 @@ int main(int argc, char **argv)
}
}
};
requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data) {
requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data)
{
if (appCtx.Output.is_open())
{
appCtx.Output.write((char *)data.ptr, data.len);
Expand All @@ -481,9 +541,12 @@ int main(int argc, char **argv)

request.SetMethod(ByteCursorFromCString(appCtx.verb));
auto pathAndQuery = appCtx.uri.GetPathAndQuery();
if (pathAndQuery.len > 0) {
if (pathAndQuery.len > 0)
{
request.SetPath(pathAndQuery);
} else {
}
else
{
request.SetPath(ByteCursorFromCString("/"));
}

Expand Down
87 changes: 82 additions & 5 deletions bin/mqtt5_app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <aws/crt/crypto/Hash.h>
#include <aws/crt/http/HttpConnection.h>
#include <aws/crt/http/HttpRequestResponse.h>
#include <aws/crt/io/Socks5ProxyOptions.h>
#include <aws/crt/io/Uri.h>

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

const char *TraceFile;
Aws::Crt::LogLevel LogLevel;

Aws::Crt::String proxy_host;
uint16_t proxy_port;
bool use_proxy = false;
Aws::Crt::Optional<Io::Socks5ProxyOptions> socks5_proxy_options;
};

static bool s_parse_proxy_uri(struct app_ctx &ctx, const char *proxy_arg)
{
if (!proxy_arg || proxy_arg[0] == '\0')
{
std::cerr << "Proxy URI must not be empty" << std::endl;
return false;
}

ByteCursor uri_cursor = aws_byte_cursor_from_c_str(proxy_arg);
Io::Uri parsed_uri(uri_cursor, ctx.allocator);
if (!parsed_uri)
{
std::cerr << "Failed to parse proxy URI \"" << proxy_arg
<< "\": " << aws_error_debug_str(parsed_uri.LastError()) << std::endl;
return false;
}

auto proxyOptions = Io::Socks5ProxyOptions::CreateFromUri(parsed_uri, ctx.connect_timeout, ctx.allocator);
if (!proxyOptions)
{
std::cerr << "Failed to create SOCKS5 proxy options from \"" << proxy_arg
<< "\": " << aws_error_debug_str(Aws::Crt::LastError()) << std::endl;
return false;
}

ctx.socks5_proxy_options = *proxyOptions;

ByteCursor host_cursor = parsed_uri.GetHostName();
ctx.proxy_host.assign(reinterpret_cast<const char *>(host_cursor.ptr), host_cursor.len);

uint32_t port = parsed_uri.GetPort();
if (port == 0)
{
port = 1080;
}
ctx.proxy_port = static_cast<uint16_t>(port);

ctx.use_proxy = true;
return true;
}

static void s_usage(int exit_code)
{

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

fprintf(stderr, " -h, --help\n");
fprintf(stderr, " Display this message and quit.\n");
Expand All @@ -66,6 +114,7 @@ static struct aws_cli_option s_long_options[] = {
{"connect-timeout", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'f'},
{"log", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'l'},
{"verbose", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'v'},
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'X'},
{"help", AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 'h'},
/* Per getopt(3) the last element of the array has to be filled with all zeros */
{NULL, AWS_CLI_OPTIONS_NO_ARGUMENT, NULL, 0},
Expand All @@ -76,7 +125,7 @@ static void s_parse_options(int argc, char **argv, struct app_ctx &ctx)
while (true)
{
int option_index = 0;
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);
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);
if (c == -1)
{
/* finished parsing */
Expand Down Expand Up @@ -130,6 +179,12 @@ static void s_parse_options(int argc, char **argv, struct app_ctx &ctx)
}
break;
}
case 'X':
if (!s_parse_proxy_uri(ctx, aws_cli_optarg))
{
s_usage(1);
}
break;
default:
std::cerr << "Unknown option\n";
s_usage(1);
Expand Down Expand Up @@ -314,6 +369,26 @@ int main(int argc, char **argv)
mqtt5OptionsBuilder.WithTlsConnectionOptions(tlsConnectionOptions);
}

if (app_ctx.use_proxy && app_ctx.socks5_proxy_options && !app_ctx.proxy_host.empty())
{
std::cout << "**********************************************************" << std::endl;
std::cout << "MQTT5: Using SOCKS5 proxy " << app_ctx.proxy_host << ":" << app_ctx.proxy_port << std::endl;
const auto &proxy_opts = *app_ctx.socks5_proxy_options;
Aws::Crt::String username, password;
if (proxy_opts.GetUsername().has_value())
{
username = proxy_opts.GetUsername().value();
std::cout << "MQTT5: Proxy username: " << username << std::endl;
}
if (proxy_opts.GetPassword().has_value())
{
password = proxy_opts.GetPassword().value();
std::cout << "MQTT5: Proxy password: " << password << std::endl;
}
mqtt5OptionsBuilder.WithSocks5ProxyOptions(proxy_opts);
std::cout << "**********************************************************" << std::endl;
}

std::promise<bool> connectionPromise;
std::promise<void> disconnectionPromise;
std::promise<void> stoppedPromise;
Expand Down Expand Up @@ -364,13 +439,14 @@ int main(int argc, char **argv)
else
{
std::cout << "**********************************************************" << std::endl;
std::cout << "MQTT5:DisConnection failed with error " << aws_error_debug_str(eventData.errorCode) << std::endl;
std::cout << "MQTT5:DisConnection failed with error " << aws_error_debug_str(eventData.errorCode)
<< std::endl;
if (eventData.disconnectPacket != NULL)
{
if (eventData.disconnectPacket->getReasonString().has_value())
{
std::cout << "disconnect packet: " << eventData.disconnectPacket->getReasonString().value().c_str()
<< std::endl;
std::cout << "disconnect packet: "
<< eventData.disconnectPacket->getReasonString().value().c_str() << std::endl;
}
}
std::cout << "**********************************************************" << std::endl;
Expand Down Expand Up @@ -451,7 +527,8 @@ int main(int argc, char **argv)
subscribe,
[](int, std::shared_ptr<Mqtt5::SubAckPacket> packet)
{
if(packet == nullptr) return;
if (packet == nullptr)
return;
std::cout << "**********************************************************" << std::endl;
std::cout << "MQTT5: check suback packet : " << std::endl;
for (auto code : packet->getReasonCodes())
Expand Down
Loading