Skip to content

Commit 63471e0

Browse files
committed
Add support for SOCKS5 proxy
1 parent 43da467 commit 63471e0

File tree

17 files changed

+1389
-19
lines changed

17 files changed

+1389
-19
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,6 @@ if (NOT BYO_CRYPTO AND BUILD_TESTING)
101101
add_subdirectory(tests)
102102
if (NOT CMAKE_CROSSCOMPILING)
103103
add_subdirectory(bin/elasticurl)
104+
add_subdirectory(bin/http_client_app)
104105
endif()
105106
endif()

bin/elasticurl/main.c

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <aws/common/command_line_parser.h>
99
#include <aws/common/condition_variable.h>
10+
#include <aws/common/error.h>
1011
#include <aws/common/hash_table.h>
1112
#include <aws/common/log_channel.h>
1213
#include <aws/common/log_formatter.h>
@@ -22,8 +23,13 @@
2223
#include <aws/io/stream.h>
2324
#include <aws/io/tls_channel_handler.h>
2425
#include <aws/io/uri.h>
26+
#include <aws/io/socks5.h>
27+
#include <aws/io/socks5_channel_handler.h>
2528

2629
#include <inttypes.h>
30+
#include <stdbool.h>
31+
#include <stdlib.h>
32+
#include <string.h>
2733

2834
#ifdef _MSC_VER
2935
# pragma warning(disable : 4996) /* Disable warnings about fopen() being insecure */
@@ -33,6 +39,120 @@
3339

3440
#define ELASTICURL_VERSION "0.2.0"
3541

42+
struct socks5_proxy_settings {
43+
char *host;
44+
char *username;
45+
char *password;
46+
uint16_t port;
47+
bool resolve_host_with_proxy;
48+
};
49+
50+
static void s_socks5_proxy_settings_clean_up(
51+
struct socks5_proxy_settings *settings,
52+
struct aws_allocator *allocator) {
53+
if (!settings) {
54+
return;
55+
}
56+
if (settings->host) {
57+
aws_mem_release(allocator, settings->host);
58+
}
59+
if (settings->username) {
60+
aws_mem_release(allocator, settings->username);
61+
}
62+
if (settings->password) {
63+
aws_mem_release(allocator, settings->password);
64+
}
65+
AWS_ZERO_STRUCT(*settings);
66+
}
67+
68+
static int s_socks5_proxy_settings_init_from_uri(
69+
struct socks5_proxy_settings *settings,
70+
struct aws_allocator *allocator,
71+
const char *proxy_uri) {
72+
73+
if (!settings || !allocator || !proxy_uri) {
74+
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
75+
}
76+
77+
s_socks5_proxy_settings_clean_up(settings, allocator);
78+
79+
struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str(proxy_uri);
80+
struct aws_uri uri;
81+
AWS_ZERO_STRUCT(uri);
82+
83+
if (aws_uri_init_parse(&uri, allocator, &uri_cursor)) {
84+
fprintf(stderr, "Failed to parse proxy URI \"%s\": %s\n", proxy_uri, aws_error_debug_str(aws_last_error()));
85+
goto on_error;
86+
}
87+
88+
const struct aws_byte_cursor *scheme = aws_uri_scheme(&uri);
89+
if (!scheme || !scheme->len) {
90+
fprintf(stderr, "Proxy URI \"%s\" must include scheme socks5h://\n", proxy_uri);
91+
goto on_error;
92+
}
93+
94+
if (aws_byte_cursor_eq_c_str_ignore_case(scheme, "socks5h")) {
95+
settings->resolve_host_with_proxy = true;
96+
} else if (aws_byte_cursor_eq_c_str_ignore_case(scheme, "socks5")) {
97+
settings->resolve_host_with_proxy = false;
98+
} else {
99+
fprintf(stderr, "Unsupported proxy scheme in \"%s\". Expected socks5h://\n", proxy_uri);
100+
goto on_error;
101+
}
102+
103+
const struct aws_byte_cursor *host = aws_uri_host_name(&uri);
104+
if (!host || host->len == 0) {
105+
fprintf(stderr, "Proxy URI \"%s\" must include a host\n", proxy_uri);
106+
goto on_error;
107+
}
108+
109+
settings->host = aws_mem_calloc(allocator, host->len + 1, sizeof(char));
110+
if (!settings->host) {
111+
fprintf(stderr, "Failed to allocate memory for proxy host\n");
112+
goto on_error;
113+
}
114+
memcpy(settings->host, host->ptr, host->len);
115+
settings->host[host->len] = '\0';
116+
117+
uint32_t parsed_port = aws_uri_port(&uri);
118+
if (parsed_port == 0) {
119+
parsed_port = 1080;
120+
}
121+
if (parsed_port > UINT16_MAX) {
122+
fprintf(stderr, "Proxy port %" PRIu32 " exceeds uint16_t range\n", parsed_port);
123+
goto on_error;
124+
}
125+
settings->port = (uint16_t)parsed_port;
126+
127+
if (uri.user.len > 0) {
128+
settings->username = aws_mem_calloc(allocator, uri.user.len + 1, sizeof(char));
129+
if (!settings->username) {
130+
fprintf(stderr, "Failed to allocate memory for proxy username\n");
131+
goto on_error;
132+
}
133+
memcpy(settings->username, uri.user.ptr, uri.user.len);
134+
settings->username[uri.user.len] = '\0';
135+
}
136+
137+
if (uri.password.len > 0) {
138+
settings->password = aws_mem_calloc(allocator, uri.password.len + 1, sizeof(char));
139+
if (!settings->password) {
140+
fprintf(stderr, "Failed to allocate memory for proxy password\n");
141+
goto on_error;
142+
}
143+
memcpy(settings->password, uri.password.ptr, uri.password.len);
144+
settings->password[uri.password.len] = '\0';
145+
}
146+
147+
aws_uri_clean_up(&uri);
148+
return AWS_OP_SUCCESS;
149+
150+
on_error:
151+
aws_uri_clean_up(&uri);
152+
s_socks5_proxy_settings_clean_up(settings, allocator);
153+
return AWS_OP_ERR;
154+
}
155+
36156
struct elasticurl_ctx {
37157
struct aws_allocator *allocator;
38158
const char *verb;
@@ -64,6 +184,8 @@ struct elasticurl_ctx {
64184
enum aws_log_level log_level;
65185
enum aws_http_version required_http_version;
66186
bool exchange_completed;
187+
struct socks5_proxy_settings proxy;
188+
bool use_proxy;
67189
};
68190

69191
static void s_usage(int exit_code) {
@@ -76,6 +198,7 @@ static void s_usage(int exit_code) {
76198
fprintf(stderr, " --cert FILE: path to a PEM encoded certificate to use with mTLS\n");
77199
fprintf(stderr, " --key FILE: Path to a PEM encoded private key that matches cert.\n");
78200
fprintf(stderr, " --connect-timeout INT: time in milliseconds to wait for a connection.\n");
201+
fprintf(stderr, " --proxy URL: SOCKS5 proxy URI (socks5h://... for proxy DNS, socks5://... for local DNS)\n");
79202
fprintf(stderr, " -H, --header LINE: line to send as a header in format [header-key]: [header-value]\n");
80203
fprintf(stderr, " -d, --data STRING: Data to POST or PUT\n");
81204
fprintf(stderr, " --data-file FILE: File to read from file and POST or PUT\n");
@@ -107,6 +230,7 @@ static struct aws_cli_option s_long_options[] = {
107230
{"cert", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'c'},
108231
{"key", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'e'},
109232
{"connect-timeout", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'f'},
233+
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'x'},
110234
{"header", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'H'},
111235
{"data", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'd'},
112236
{"data-file", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'g'},
@@ -162,7 +286,7 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
162286
while (true) {
163287
int option_index = 0;
164288
int c =
165-
aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWh", s_long_options, &option_index);
289+
aws_cli_getopt_long(argc, argv, "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWhx:", s_long_options, &option_index);
166290
if (c == -1) {
167291
break;
168292
}
@@ -186,6 +310,12 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
186310
case 'f':
187311
ctx->connect_timeout = atoi(aws_cli_optarg);
188312
break;
313+
case 'x':
314+
if (s_socks5_proxy_settings_init_from_uri(&ctx->proxy, ctx->allocator, aws_cli_optarg)) {
315+
s_usage(1);
316+
}
317+
ctx->use_proxy = true;
318+
break;
189319
case 'H':
190320
if (ctx->header_line_count >= sizeof(ctx->header_lines) / sizeof(const char *)) {
191321
fprintf(stderr, "currently only 10 header lines are supported.\n");
@@ -618,6 +748,9 @@ int main(int argc, char **argv) {
618748
struct aws_tls_connection_options tls_connection_options;
619749
AWS_ZERO_STRUCT(tls_connection_options);
620750
struct aws_tls_connection_options *tls_options = NULL;
751+
bool socks5_options_valid = false;
752+
struct aws_socks5_proxy_options socks5_options;
753+
AWS_ZERO_STRUCT(socks5_options);
621754

622755
if (use_tls) {
623756
if (app_ctx.cert && app_ctx.key) {
@@ -710,6 +843,40 @@ int main(int argc, char **argv) {
710843
.keep_alive_interval_sec = 0,
711844
};
712845

846+
if (app_ctx.use_proxy) {
847+
if (!app_ctx.proxy.host) {
848+
fprintf(stderr, "Proxy URI was requested but no host was parsed.\n");
849+
exit(1);
850+
}
851+
852+
struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str(app_ctx.proxy.host);
853+
if (aws_socks5_proxy_options_init(&socks5_options, allocator, proxy_host, app_ctx.proxy.port) != AWS_OP_SUCCESS) {
854+
fprintf(
855+
stderr,
856+
"Failed to initialize SOCKS5 proxy options: %s\n",
857+
aws_error_debug_str(aws_last_error()));
858+
exit(1);
859+
}
860+
aws_socks5_proxy_options_set_host_resolution_mode(
861+
&socks5_options,
862+
app_ctx.proxy.resolve_host_with_proxy ? AWS_SOCKS5_HOST_RESOLUTION_PROXY
863+
: AWS_SOCKS5_HOST_RESOLUTION_CLIENT);
864+
865+
if (app_ctx.proxy.username && app_ctx.proxy.password) {
866+
struct aws_byte_cursor username = aws_byte_cursor_from_c_str(app_ctx.proxy.username);
867+
struct aws_byte_cursor password = aws_byte_cursor_from_c_str(app_ctx.proxy.password);
868+
if (aws_socks5_proxy_options_set_auth(&socks5_options, allocator, username, password) != AWS_OP_SUCCESS) {
869+
fprintf(
870+
stderr,
871+
"Failed to set SOCKS5 auth: %s\n",
872+
aws_error_debug_str(aws_last_error()));
873+
exit(1);
874+
}
875+
}
876+
877+
socks5_options_valid = true;
878+
}
879+
713880
struct aws_http_client_connection_options http_client_options = {
714881
.self_size = sizeof(struct aws_http_client_connection_options),
715882
.socket_options = &socket_options,
@@ -719,6 +886,7 @@ int main(int argc, char **argv) {
719886
.bootstrap = bootstrap,
720887
.initial_window_size = SIZE_MAX,
721888
.tls_options = tls_options,
889+
.socks5_proxy_options = socks5_options_valid ? &socks5_options : NULL,
722890
.user_data = &app_ctx,
723891
.on_setup = s_on_client_connection_setup,
724892
.on_shutdown = s_on_client_connection_shutdown,
@@ -732,6 +900,10 @@ int main(int argc, char **argv) {
732900
aws_condition_variable_wait_pred(&app_ctx.c_var, &app_ctx.mutex, s_completion_predicate, &app_ctx);
733901
aws_mutex_unlock(&app_ctx.mutex);
734902

903+
if (socks5_options_valid) {
904+
aws_socks5_proxy_options_clean_up(&socks5_options);
905+
}
906+
735907
aws_client_bootstrap_release(bootstrap);
736908
aws_host_resolver_release(resolver);
737909
aws_event_loop_group_release(el_group);
@@ -748,6 +920,7 @@ int main(int argc, char **argv) {
748920
aws_logger_clean_up(&logger);
749921
}
750922

923+
s_socks5_proxy_settings_clean_up(&app_ctx.proxy, app_ctx.allocator);
751924
aws_uri_clean_up(&app_ctx.uri);
752925

753926
aws_http_message_destroy(app_ctx.request);

bin/http_client_app/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
project(http_client_c_socks5_app C)
2+
3+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_PREFIX_PATH}/lib/cmake")
4+
5+
file(GLOB HTTP_CLIENT_APP_SRC
6+
"*.c"
7+
)
8+
9+
set(HTTP_CLIENT_APP_PROJECT_NAME http_client_app)
10+
add_executable(${HTTP_CLIENT_APP_PROJECT_NAME} ${HTTP_CLIENT_APP_SRC})
11+
aws_set_common_properties(${HTTP_CLIENT_APP_PROJECT_NAME})
12+
13+
target_include_directories(${HTTP_CLIENT_APP_PROJECT_NAME} PUBLIC
14+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
15+
$<INSTALL_INTERFACE:include>)
16+
17+
target_link_libraries(${HTTP_CLIENT_APP_PROJECT_NAME} PRIVATE aws-c-http)
18+
19+
if (BUILD_SHARED_LIBS AND NOT WIN32)
20+
message(INFO " http client app will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application")
21+
endif()
22+
23+
install(TARGETS ${HTTP_CLIENT_APP_PROJECT_NAME}
24+
EXPORT ${HTTP_CLIENT_APP_PROJECT_NAME}-targets
25+
COMPONENT Runtime
26+
RUNTIME
27+
DESTINATION bin
28+
COMPONENT Runtime)

0 commit comments

Comments
 (0)