From 709ac5695d76d2c887e8283f930079edde8093a8 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 16 Aug 2019 14:21:45 -0700 Subject: [PATCH 1/4] WIP --- include/aws/http/connection_manager.h | 2 +- include/aws/http/private/proxy_impl.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/aws/http/connection_manager.h b/include/aws/http/connection_manager.h index a89a919b3..438b5d8ef 100644 --- a/include/aws/http/connection_manager.h +++ b/include/aws/http/connection_manager.h @@ -23,7 +23,6 @@ struct aws_client_bootstrap; struct aws_http_connection; struct aws_http_connection_manager; -struct aws_http_connection_manager_mocks; struct aws_socket_options; struct aws_tls_connection_options; @@ -46,6 +45,7 @@ struct aws_http_connection_manager_options { size_t initial_window_size; struct aws_socket_options *socket_options; struct aws_tls_connection_options *tls_connection_options; + struct aws_http_proxy_options *proxy_options; struct aws_byte_cursor host; uint16_t port; diff --git a/include/aws/http/private/proxy_impl.h b/include/aws/http/private/proxy_impl.h index 3eb99e6d7..9be16b15b 100644 --- a/include/aws/http/private/proxy_impl.h +++ b/include/aws/http/private/proxy_impl.h @@ -40,6 +40,24 @@ enum aws_proxy_bootstrap_state { AWS_PBS_FAILURE, }; +struct aws_http_proxy_options_clonable { + + struct aws_string *host; + + uint16_t port; + + struct aws_tls_connection_options *tls_options; + + enum aws_http_proxy_authentication_type auth_type; + + struct aws_string *auth_username; + + struct aws_string *auth_password; +}; + +struct aws_http_proxy_options_clonable *aws_http_proxy_options_clone(struct aws_http_proxy_options *options); +void aws_http_proxy_options_clonable_destroy(struct aws_http_proxy_options_clonable *options); + /* * When a proxy connection is made, we wrap the user-supplied user data with this * proxy user data. Callbacks are passed properly to the user. By having this data From 1aa0aee4056382f2e1000eddb4fbd1f16fe08343 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Fri, 16 Aug 2019 16:02:18 -0700 Subject: [PATCH 2/4] Initial draft --- include/aws/http/private/proxy_impl.h | 33 +++++--- source/connection_manager.c | 24 +++++- source/proxy_connection.c | 105 +++++++++++++++++++++----- tests/CMakeLists.txt | 2 + tests/test_connection_manager.c | 86 ++++++++++++++++++--- 5 files changed, 210 insertions(+), 40 deletions(-) diff --git a/include/aws/http/private/proxy_impl.h b/include/aws/http/private/proxy_impl.h index 9be16b15b..d6ac13e85 100644 --- a/include/aws/http/private/proxy_impl.h +++ b/include/aws/http/private/proxy_impl.h @@ -40,9 +40,14 @@ enum aws_proxy_bootstrap_state { AWS_PBS_FAILURE, }; -struct aws_http_proxy_options_clonable { +/** + * A persistent copy of the aws_http_proxy_options struct. Clones everything appropriate. + */ +struct aws_http_proxy_config { + + struct aws_allocator *allocator; - struct aws_string *host; + struct aws_byte_buf host; uint16_t port; @@ -50,14 +55,11 @@ struct aws_http_proxy_options_clonable { enum aws_http_proxy_authentication_type auth_type; - struct aws_string *auth_username; + struct aws_byte_buf auth_username; - struct aws_string *auth_password; + struct aws_byte_buf auth_password; }; -struct aws_http_proxy_options_clonable *aws_http_proxy_options_clone(struct aws_http_proxy_options *options); -void aws_http_proxy_options_clonable_destroy(struct aws_http_proxy_options_clonable *options); - /* * When a proxy connection is made, we wrap the user-supplied user data with this * proxy user data. Callbacks are passed properly to the user. By having this data @@ -83,9 +85,7 @@ struct aws_http_proxy_user_data { struct aws_tls_connection_options *tls_options; - enum aws_http_proxy_authentication_type auth_type; - struct aws_string *username; - struct aws_string *password; + struct aws_http_proxy_config *proxy_config; }; struct aws_http_proxy_system_vtable { @@ -113,6 +113,19 @@ int aws_http_rewrite_uri_for_proxy_request( AWS_HTTP_API void aws_http_proxy_system_set_vtable(struct aws_http_proxy_system_vtable *vtable); +AWS_HTTP_API +struct aws_http_proxy_config *aws_http_proxy_config_new( + struct aws_allocator *allocator, + const struct aws_http_proxy_options *options); + +AWS_HTTP_API +void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config); + +AWS_HTTP_API +void aws_http_proxy_options_init_from_config( + struct aws_http_proxy_options *options, + const struct aws_http_proxy_config *config); + AWS_EXTERN_C_END #endif /* AWS_HTTP_PROXY_IMPL_H */ diff --git a/source/connection_manager.c b/source/connection_manager.c index c1379c346..4298026c6 100644 --- a/source/connection_manager.c +++ b/source/connection_manager.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,7 @@ struct aws_http_connection_manager { size_t initial_window_size; struct aws_socket_options socket_options; struct aws_tls_connection_options *tls_connection_options; + struct aws_http_proxy_config *proxy_config; struct aws_string *host; uint16_t port; @@ -387,6 +389,10 @@ static void s_aws_http_connection_manager_destroy(struct aws_http_connection_man aws_mem_release(manager->allocator, manager->tls_connection_options); } + if (manager->proxy_config) { + aws_http_proxy_config_destroy(manager->proxy_config); + } + aws_mutex_clean_up(&manager->lock); aws_mem_release(manager->allocator, manager); @@ -429,13 +435,19 @@ struct aws_http_connection_manager *aws_http_connection_manager_new( } if (options->tls_connection_options) { - manager->tls_connection_options = aws_mem_acquire(allocator, sizeof(struct aws_tls_connection_options)); - AWS_ZERO_STRUCT(*manager->tls_connection_options); + manager->tls_connection_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); if (aws_tls_connection_options_copy(manager->tls_connection_options, options->tls_connection_options)) { goto on_error; } } + if (options->proxy_options) { + manager->proxy_config = aws_http_proxy_config_new(allocator, options->proxy_options); + if (manager->proxy_config == NULL) { + goto on_error; + } + } + manager->state = AWS_HCMST_READY; manager->initial_window_size = options->initial_window_size; manager->port = options->port; @@ -608,6 +620,14 @@ static int s_aws_http_connection_manager_new_connection(struct aws_http_connecti options.on_setup = s_aws_http_connection_manager_on_connection_setup; options.on_shutdown = s_aws_http_connection_manager_on_connection_shutdown; + struct aws_http_proxy_options proxy_options; + AWS_ZERO_STRUCT(proxy_options); + + if (manager->proxy_config) { + aws_http_proxy_options_init_from_config(&proxy_options, manager->proxy_config); + options.proxy_options = &proxy_options; + } + if (manager->system_vtable->create_connection(&options)) { AWS_LOGF_ERROR( AWS_LS_HTTP_CONNECTION_MANAGER, diff --git a/source/proxy_connection.c b/source/proxy_connection.c index 6a617082b..21e1026d2 100644 --- a/source/proxy_connection.c +++ b/source/proxy_connection.c @@ -53,8 +53,9 @@ void aws_http_proxy_user_data_destroy(struct aws_http_proxy_user_data *user_data } aws_string_destroy(user_data->original_host); - aws_string_destroy(user_data->username); - aws_string_destroy(user_data->password); + if (user_data->proxy_config) { + aws_http_proxy_config_destroy(user_data->proxy_config); + } if (user_data->tls_options) { aws_tls_connection_options_clean_up(user_data->tls_options); @@ -68,6 +69,8 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new( struct aws_allocator *allocator, const struct aws_http_client_connection_options *options) { + AWS_FATAL_ASSERT(options->proxy_options != NULL); + struct aws_http_proxy_user_data *user_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_user_data)); if (user_data == NULL) { return NULL; @@ -83,19 +86,10 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new( } user_data->original_port = options->port; - user_data->auth_type = options->proxy_options->auth_type; - if (user_data->auth_type == AWS_HPAT_BASIC) { - const struct aws_byte_cursor *user_name = &options->proxy_options->auth_username; - user_data->username = aws_string_new_from_array(allocator, user_name->ptr, user_name->len); - if (user_data->username == NULL) { - goto on_error; - } - const struct aws_byte_cursor *password = &options->proxy_options->auth_password; - user_data->password = aws_string_new_from_array(allocator, password->ptr, password->len); - if (user_data->password == NULL) { - goto on_error; - } + user_data->proxy_config = aws_http_proxy_config_new(allocator, options->proxy_options); + if (user_data->proxy_config == NULL) { + goto on_error; } if (options->tls_options) { @@ -146,12 +140,12 @@ static int s_add_basic_proxy_authentication_header( if (aws_byte_buf_init( &base64_input_value, proxy_user_data->allocator, - proxy_user_data->username->len + proxy_user_data->password->len + 1)) { + proxy_user_data->proxy_config->auth_username.len + proxy_user_data->proxy_config->auth_password.len + 1)) { goto done; } /* First build a buffer with "username:password" in it */ - struct aws_byte_cursor username_cursor = aws_byte_cursor_from_string(proxy_user_data->username); + struct aws_byte_cursor username_cursor = aws_byte_cursor_from_buf(&proxy_user_data->proxy_config->auth_username); if (aws_byte_buf_append(&base64_input_value, &username_cursor)) { goto done; } @@ -161,7 +155,7 @@ static int s_add_basic_proxy_authentication_header( goto done; } - struct aws_byte_cursor password_cursor = aws_byte_cursor_from_string(proxy_user_data->password); + struct aws_byte_cursor password_cursor = aws_byte_cursor_from_buf(&proxy_user_data->proxy_config->auth_password); if (aws_byte_buf_append(&base64_input_value, &password_cursor)) { goto done; } @@ -345,7 +339,8 @@ static struct aws_http_message *s_build_proxy_connect_request(struct aws_http_pr goto on_error; } - if (user_data->auth_type == AWS_HPAT_BASIC && s_add_basic_proxy_authentication_header(request, user_data)) { + if (user_data->proxy_config->auth_type == AWS_HPAT_BASIC && + s_add_basic_proxy_authentication_header(request, user_data)) { goto on_error; } @@ -672,7 +667,8 @@ static int s_proxy_http_request_transform(struct aws_http_message *request, void int result = AWS_OP_ERR; - if (proxy_ud->auth_type == AWS_HPAT_BASIC && s_add_basic_proxy_authentication_header(request, proxy_ud)) { + if (proxy_ud->proxy_config->auth_type == AWS_HPAT_BASIC && + s_add_basic_proxy_authentication_header(request, proxy_ud)) { goto done; } @@ -791,3 +787,74 @@ int aws_http_client_connect_via_proxy(const struct aws_http_client_connection_op return s_aws_http_client_connect_via_proxy_http(options); } } + +struct aws_http_proxy_config *aws_http_proxy_config_new( + struct aws_allocator *allocator, + const struct aws_http_proxy_options *options) { + AWS_FATAL_ASSERT(options != NULL); + struct aws_http_proxy_config *config = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_config)); + if (config == NULL) { + return NULL; + } + + if (aws_byte_buf_init_copy_from_cursor(&config->host, allocator, options->host)) { + goto on_error; + } + + if (aws_byte_buf_init_copy_from_cursor(&config->auth_username, allocator, options->auth_username)) { + goto on_error; + } + + if (aws_byte_buf_init_copy_from_cursor(&config->auth_password, allocator, options->auth_password)) { + goto on_error; + } + + if (options->tls_options) { + config->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); + if (aws_tls_connection_options_copy(config->tls_options, options->tls_options)) { + goto on_error; + } + } + + config->allocator = allocator; + config->auth_type = options->auth_type; + config->port = options->port; + + return config; + +on_error: + + aws_http_proxy_config_destroy(config); + + return NULL; +} + +void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config) { + if (config == NULL) { + return; + } + + aws_byte_buf_clean_up(&config->host); + aws_byte_buf_clean_up(&config->auth_username); + aws_byte_buf_clean_up(&config->auth_password); + + if (config->tls_options) { + aws_tls_connection_options_clean_up(config->tls_options); + aws_mem_release(config->allocator, config->tls_options); + } + + aws_mem_release(config->allocator, config); +} + +void aws_http_proxy_options_init_from_config( + struct aws_http_proxy_options *options, + const struct aws_http_proxy_config *config) { + AWS_FATAL_ASSERT(options && config); + + options->host = aws_byte_cursor_from_buf(&config->host); + options->auth_username = aws_byte_cursor_from_buf(&config->auth_username); + options->auth_password = aws_byte_cursor_from_buf(&config->auth_password); + options->auth_type = config->auth_type; + options->port = config->port; + options->tls_options = config->tls_options; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index effbba602..acd24edee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -191,6 +191,8 @@ add_test_case(test_connection_manager_acquire_release_mix_synchronous) add_test_case(test_connection_manager_connect_callback_failure) add_test_case(test_connection_manager_connect_immediate_failure) add_test_case(test_connection_manager_success_then_cancel_pending_from_failure) +add_test_case(test_connection_manager_proxy_setup_shutdown) +add_test_case(test_connection_manager_proxy_acquire_single) add_test_case(h1_server_sanity_check) add_test_case(h1_server_receive_1line_request) diff --git a/tests/test_connection_manager.c b/tests/test_connection_manager.c index 361db0bd5..6bd52a109 100644 --- a/tests/test_connection_manager.c +++ b/tests/test_connection_manager.c @@ -32,7 +32,7 @@ enum new_connection_result_type { AWS_NCRT_SUCCESS, AWS_NCRT_ERROR_VIA_CALLBACK, AWS_NCRT_ERROR_FROM_CREATE }; -struct mock_connection_proxy { +struct mock_connection { enum new_connection_result_type result; bool is_closed_on_release; }; @@ -40,6 +40,7 @@ struct mock_connection_proxy { struct cm_tester_options { struct aws_allocator *allocator; struct aws_http_connection_manager_system_vtable *mock_table; + struct aws_http_proxy_options *proxy_options; size_t max_connections; }; @@ -55,6 +56,7 @@ struct cm_tester { struct aws_tls_ctx *tls_ctx; struct aws_tls_ctx_options tls_ctx_options; struct aws_tls_connection_options tls_connection_options; + struct aws_http_proxy_options *proxy_options; struct aws_mutex lock; struct aws_condition_variable signal; @@ -109,11 +111,13 @@ int s_cm_tester_init(struct cm_tester_options *options) { aws_tls_connection_options_init_from_ctx(&tester->tls_connection_options, tester->tls_ctx); + tester->proxy_options = options->proxy_options; + struct aws_http_connection_manager_options cm_options = {.bootstrap = tester->client_bootstrap, .initial_window_size = SIZE_MAX, .socket_options = &socket_options, - .tls_connection_options = - NULL, //&tester->tls_connection_options, + .tls_connection_options = NULL, + .proxy_options = tester->proxy_options, .host = aws_byte_cursor_from_c_str("www.google.com"), .port = 80, .max_connections = options->max_connections}; @@ -130,7 +134,7 @@ int s_cm_tester_init(struct cm_tester_options *options) { aws_atomic_store_int(&tester->next_connection_id, 0); ASSERT_SUCCESS(aws_array_list_init_dynamic( - &tester->mock_connections, tester->allocator, 10, sizeof(struct mock_connection_proxy *))); + &tester->mock_connections, tester->allocator, 10, sizeof(struct mock_connection *))); return AWS_OP_SUCCESS; } @@ -139,7 +143,7 @@ void s_add_mock_connections(size_t count, enum new_connection_result_type result struct cm_tester *tester = &s_tester; for (size_t i = 0; i < count; ++i) { - struct mock_connection_proxy *mock = aws_mem_acquire(tester->allocator, sizeof(struct mock_connection_proxy)); + struct mock_connection *mock = aws_mem_acquire(tester->allocator, sizeof(struct mock_connection)); AWS_ZERO_STRUCT(*mock); mock->result = result; @@ -273,7 +277,7 @@ int s_cm_tester_clean_up(void) { aws_array_list_clean_up(&tester->connections); for (size_t i = 0; i < aws_array_list_length(&tester->mock_connections); ++i) { - struct mock_connection_proxy *mock = NULL; + struct mock_connection *mock = NULL; if (aws_array_list_get_at(&tester->mock_connections, &mock, i)) { continue; @@ -435,7 +439,17 @@ static int s_aws_http_connection_manager_create_connection_sync_mock( tester->release_connection_fn = options->on_shutdown; aws_mutex_unlock(&tester->lock); - struct mock_connection_proxy *connection = NULL; + /* Verify that any proxy options have been propagated to the connection attempt */ + if (tester->proxy_options) { + ASSERT_BIN_ARRAYS_EQUALS( + tester->proxy_options->host.ptr, + tester->proxy_options->host.len, + options->proxy_options->host.ptr, + options->proxy_options->host.len); + ASSERT_TRUE(options->proxy_options->port == tester->proxy_options->port); + } + + struct mock_connection *connection = NULL; if (next_connection_id < aws_array_list_length(&tester->mock_connections)) { aws_array_list_get_at(&tester->mock_connections, &connection, next_connection_id); @@ -471,7 +485,7 @@ static void s_aws_http_connection_manager_close_connection_sync_mock(struct aws_ static bool s_aws_http_connection_manager_is_connection_open_sync_mock(const struct aws_http_connection *connection) { (void)connection; - struct mock_connection_proxy *proxy = (struct mock_connection_proxy *)(void *)connection; + struct mock_connection *proxy = (struct mock_connection *)(void *)connection; return !proxy->is_closed_on_release; } @@ -595,4 +609,58 @@ static int s_test_connection_manager_success_then_cancel_pending_from_failure( } AWS_TEST_CASE( test_connection_manager_success_then_cancel_pending_from_failure, - s_test_connection_manager_success_then_cancel_pending_from_failure); \ No newline at end of file + s_test_connection_manager_success_then_cancel_pending_from_failure); + +static int s_test_connection_manager_proxy_setup_shutdown(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_options proxy_options = { + .host = aws_byte_cursor_from_c_str("127.0.0.1"), + .port = 8080, + }; + + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 1, + .mock_table = &s_synchronous_mocks, + .proxy_options = &proxy_options, + }; + + ASSERT_SUCCESS(s_cm_tester_init(&options)); + + ASSERT_SUCCESS(s_cm_tester_clean_up()); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_connection_manager_proxy_setup_shutdown, s_test_connection_manager_proxy_setup_shutdown); + +static int s_test_connection_manager_proxy_acquire_single(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_options proxy_options = { + .host = aws_byte_cursor_from_c_str("127.0.0.1"), + .port = 8080, + }; + + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 1, + .mock_table = &s_synchronous_mocks, + .proxy_options = &proxy_options, + }; + + ASSERT_SUCCESS(s_cm_tester_init(&options)); + + s_add_mock_connections(1, AWS_NCRT_SUCCESS, true); + s_acquire_connections(1); + s_wait_on_connection_reply_count(1); + + ASSERT_TRUE(s_tester.connection_errors == 0); + + ASSERT_SUCCESS(s_release_connections(1, true)); + + ASSERT_SUCCESS(s_cm_tester_clean_up()); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_connection_manager_proxy_acquire_single, s_test_connection_manager_proxy_acquire_single); From 02cd3fdcce37417b589e43067b8e663c5fd9ed1a Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 19 Aug 2019 09:32:38 -0700 Subject: [PATCH 3/4] websocket proxy configuration --- include/aws/http/websocket.h | 6 ++++++ source/websocket_bootstrap.c | 1 + 2 files changed, 7 insertions(+) diff --git a/include/aws/http/websocket.h b/include/aws/http/websocket.h index d3d0f49e1..456623b51 100644 --- a/include/aws/http/websocket.h +++ b/include/aws/http/websocket.h @@ -158,6 +158,12 @@ struct aws_websocket_client_connection_options { */ struct aws_tls_connection_options *tls_options; + /** + * Optional + * Configuration options related to http proxy usage. + */ + struct aws_http_proxy_options *proxy_options; + /** * Required. * aws_websocket_client_connect() makes a copy. diff --git a/source/websocket_bootstrap.c b/source/websocket_bootstrap.c index b9f2b6eb6..3e5a0330d 100644 --- a/source/websocket_bootstrap.c +++ b/source/websocket_bootstrap.c @@ -191,6 +191,7 @@ int aws_websocket_client_connect(const struct aws_websocket_client_connection_op http_options.host_name = options->host; http_options.socket_options = options->socket_options; http_options.tls_options = options->tls_options; + http_options.proxy_options = options->proxy_options; http_options.initial_window_size = 1024; /* Adequate space for response data to trickle in */ http_options.user_data = ws_bootstrap; http_options.on_setup = s_ws_bootstrap_on_http_setup; From 57126b1c264279031afbc6d90ad103d5f4f3f063 Mon Sep 17 00:00:00 2001 From: Bret Ambrose Date: Mon, 19 Aug 2019 10:11:58 -0700 Subject: [PATCH 4/4] formatting --- tests/test_connection_manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_connection_manager.c b/tests/test_connection_manager.c index bb68549da..1746b532a 100644 --- a/tests/test_connection_manager.c +++ b/tests/test_connection_manager.c @@ -92,7 +92,7 @@ int s_cm_tester_init(struct cm_tester_options *options) { ASSERT_SUCCESS(aws_mutex_init(&tester->lock)); ASSERT_SUCCESS(aws_condition_variable_init(&tester->signal)); - + struct aws_logger_standard_options logger_options = { .level = AWS_LOG_LEVEL_TRACE, .file = stderr,