Skip to content

Commit ec42882

Browse files
authored
Http Proxy raw channel establishment API (#319)
* Reworks http proxy layer to use pass-through handlers rather than stripping or re-using the proxy handler * Adds an API to create a raw channel through an http proxy.
1 parent e53ddaf commit ec42882

File tree

10 files changed

+559
-119
lines changed

10 files changed

+559
-119
lines changed

include/aws/http/http.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum aws_http_errors {
4949
AWS_ERROR_HTTP_PROXY_STRATEGY_NTLM_CHALLENGE_TOKEN_MISSING,
5050
AWS_ERROR_HTTP_PROXY_STRATEGY_TOKEN_RETRIEVAL_FAILURE,
5151
AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE,
52+
AWS_ERROR_HTTP_PROTOCOL_SWITCH_FAILURE,
5253

5354
AWS_ERROR_HTTP_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_HTTP_PACKAGE_ID)
5455
};

include/aws/http/private/connection_impl.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,32 @@ struct aws_crt_statistics_http1_channel *aws_h1_connection_get_statistics(struct
169169
AWS_HTTP_API
170170
uint32_t aws_http_connection_get_next_stream_id(struct aws_http_connection *connection);
171171

172+
/**
173+
* Layers an http channel handler/connection onto a channel. Moved from internal to private so that the proxy
174+
* logic could apply a new http connection/handler after tunneling proxy negotiation (into http) is finished.
175+
* This is a synchronous operation.
176+
*
177+
* @param alloc memory allocator to use
178+
* @param channel channel to apply the http handler/connection to
179+
* @param is_server should the handler behave like an http server
180+
* @param is_using_tls is tls is being used (do an alpn check of the to-the-left channel handler)
181+
* @param manual_window_management is manual window management enabled
182+
* @param initial_window_size what should the initial window size be
183+
* @param http1_options http1 options
184+
* @param http2_options http2 options
185+
* @return a new http connection or NULL on failure
186+
*/
187+
AWS_HTTP_API
188+
struct aws_http_connection *aws_http_connection_new_channel_handler(
189+
struct aws_allocator *alloc,
190+
struct aws_channel *channel,
191+
bool is_server,
192+
bool is_using_tls,
193+
bool manual_window_management,
194+
size_t initial_window_size,
195+
const struct aws_http1_connection_options *http1_options,
196+
const struct aws_http2_connection_options *http2_options);
197+
172198
AWS_EXTERN_C_END
173199

174200
#endif /* AWS_HTTP_CONNECTION_IMPL_H */

include/aws/http/private/proxy_impl.h

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <aws/http/connection.h>
1212
#include <aws/http/proxy.h>
1313
#include <aws/http/status_code.h>
14+
#include <aws/io/channel_bootstrap.h>
1415
#include <aws/io/socket.h>
1516

1617
struct aws_http_connection_manager_options;
@@ -74,7 +75,19 @@ struct aws_http_proxy_user_data {
7475
enum aws_proxy_bootstrap_state state;
7576
int error_code;
7677
enum aws_http_status_code connect_status_code;
77-
struct aws_http_connection *connection;
78+
79+
/*
80+
* The initial http connection object between the client and the proxy.
81+
*/
82+
struct aws_http_connection *proxy_connection;
83+
84+
/*
85+
* The http connection object that gets surfaced to callers if http is the final protocol of proxy
86+
* negotiation.
87+
*
88+
* In the case of a forwarding proxy, proxy_connection and final_connection are the same.
89+
*/
90+
struct aws_http_connection *final_connection;
7891
struct aws_http_message *connect_request;
7992
struct aws_http_stream *connect_stream;
8093
struct aws_http_proxy_negotiator *proxy_negotiator;
@@ -84,15 +97,27 @@ struct aws_http_proxy_user_data {
8497
*/
8598
struct aws_string *original_host;
8699
uint16_t original_port;
87-
aws_http_on_client_connection_setup_fn *original_on_setup;
88-
aws_http_on_client_connection_shutdown_fn *original_on_shutdown;
89100
void *original_user_data;
101+
struct aws_tls_connection_options *original_tls_options;
102+
struct aws_client_bootstrap *original_bootstrap;
103+
struct aws_socket_options original_socket_options;
104+
bool original_manual_window_management;
105+
size_t original_initial_window_size;
106+
struct aws_http1_connection_options original_http1_options;
90107

91-
struct aws_tls_connection_options *tls_options;
92-
struct aws_client_bootstrap *bootstrap;
93-
struct aws_socket_options socket_options;
94-
bool manual_window_management;
95-
size_t initial_window_size;
108+
/*
109+
* setup/shutdown callbacks. We enforce via fatal assert that either the http callbacks are supplied or
110+
* the channel callbacks are supplied but never both.
111+
*
112+
* When using a proxy to ultimately establish an http connection, use the http callbacks.
113+
* When using a proxy to establish any other protocol connection, use the raw channel callbacks.
114+
*
115+
* In the future, we might consider a further refactor which only use raw channel callbacks.
116+
*/
117+
aws_http_on_client_connection_setup_fn *original_http_on_setup;
118+
aws_http_on_client_connection_shutdown_fn *original_http_on_shutdown;
119+
aws_client_bootstrap_on_channel_event_fn *original_channel_on_setup;
120+
aws_client_bootstrap_on_channel_event_fn *original_channel_on_shutdown;
96121

97122
struct aws_http_proxy_config *proxy_config;
98123
};
@@ -106,7 +131,9 @@ AWS_EXTERN_C_BEGIN
106131
AWS_HTTP_API
107132
struct aws_http_proxy_user_data *aws_http_proxy_user_data_new(
108133
struct aws_allocator *allocator,
109-
const struct aws_http_client_connection_options *options);
134+
const struct aws_http_client_connection_options *options,
135+
aws_client_bootstrap_on_channel_event_fn *on_channel_setup,
136+
aws_client_bootstrap_on_channel_event_fn *on_channel_shutdown);
110137

111138
AWS_HTTP_API
112139
void aws_http_proxy_user_data_destroy(struct aws_http_proxy_user_data *user_data);

include/aws/http/proxy.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,19 @@ void aws_http_proxy_options_init_from_config(
488488
struct aws_http_proxy_options *options,
489489
const struct aws_http_proxy_config *config);
490490

491+
/**
492+
* Establish an arbitrary protocol connection through an http proxy via tunneling CONNECT. Alpn is
493+
* not required for this connection process to succeed, but we encourage its use if available.
494+
*
495+
* @param channel_options configuration options for the socket level connection
496+
* @param proxy_options configuration options for the proxy connection
497+
*
498+
* @return AWS_OP_SUCCESS if the asynchronous channel kickoff succeeded, AWS_OP_ERR otherwise
499+
*/
500+
AWS_HTTP_API int aws_http_proxy_new_socket_channel(
501+
struct aws_socket_channel_bootstrap_options *channel_options,
502+
struct aws_http_proxy_options *proxy_options);
503+
491504
AWS_EXTERN_C_END
492505

493-
#endif /* AWS_PROXY_H */
506+
#endif /* AWS_PROXY_STRATEGY_H */

source/connection.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ static void s_server_unlock_synced_data(struct aws_http_server *server) {
7070
}
7171

7272
/* Determine the http-version, create appropriate type of connection, and insert it into the channel. */
73-
static struct aws_http_connection *s_connection_new(
73+
struct aws_http_connection *aws_http_connection_new_channel_handler(
7474
struct aws_allocator *alloc,
7575
struct aws_channel *channel,
7676
bool is_server,
@@ -365,7 +365,7 @@ void aws_http_connection_release(struct aws_http_connection *connection) {
365365
/* When the channel's refcount reaches 0, it destroys its slots/handlers, which will destroy the connection */
366366
aws_channel_release_hold(connection->channel_slot->channel);
367367
} else {
368-
AWS_ASSERT(prev_refcount != 0);
368+
AWS_FATAL_ASSERT(prev_refcount != 0);
369369
AWS_LOGF_TRACE(
370370
AWS_LS_HTTP_CONNECTION,
371371
"id=%p: Connection refcount released, %zu remaining.",
@@ -402,7 +402,7 @@ static void s_server_bootstrap_on_accept_channel_setup(
402402
/* TODO: expose http1/2 options to server API */
403403
struct aws_http1_connection_options http1_options = AWS_HTTP1_CONNECTION_OPTIONS_INIT;
404404
struct aws_http2_connection_options http2_options = AWS_HTTP2_CONNECTION_OPTIONS_INIT;
405-
connection = s_connection_new(
405+
connection = aws_http_connection_new_channel_handler(
406406
server->alloc,
407407
channel,
408408
true,
@@ -733,7 +733,7 @@ static void s_client_bootstrap_on_channel_setup(
733733

734734
AWS_LOGF_TRACE(AWS_LS_HTTP_CONNECTION, "static: Socket connected, creating client connection object.");
735735

736-
http_bootstrap->connection = s_connection_new(
736+
http_bootstrap->connection = aws_http_connection_new_channel_handler(
737737
http_bootstrap->alloc,
738738
channel,
739739
false,

source/h1_connection.c

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,77 @@ static void s_cross_thread_work_task(struct aws_channel_task *channel_task, void
461461
}
462462
}
463463

464+
static bool s_aws_http_stream_was_successful_connect(struct aws_h1_stream *stream) {
465+
struct aws_http_stream *base = &stream->base;
466+
if (base->request_method != AWS_HTTP_METHOD_CONNECT) {
467+
return false;
468+
}
469+
470+
if (base->client_data == NULL) {
471+
return false;
472+
}
473+
474+
if (base->client_data->response_status != AWS_HTTP_STATUS_CODE_200_OK) {
475+
return false;
476+
}
477+
478+
return true;
479+
}
480+
481+
/**
482+
* Validate and perform a protocol switch on a connection. Protocol switching essentially turns the connection's
483+
* handler into a dummy pass-through. It is valid to switch protocols to the same protocol resulting in a channel
484+
* that has a "dead" http handler in the middle of the channel (which negotiated the CONNECT through the proxy) and
485+
* a "live" handler on the end which takes the actual http requests. By doing this, we get the exact same
486+
* behavior whether we're transitioning to http or any other protocol: once the CONNECT succeeds
487+
* the first http handler is put in pass-through mode and a new protocol (which could be http) is tacked onto the end.
488+
*/
489+
static int s_aws_http1_switch_protocols(struct aws_h1_connection *connection) {
490+
AWS_FATAL_ASSERT(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
491+
492+
/* Switching protocols while there are multiple streams is too complex to deal with.
493+
* Ensure stream_list has exactly this 1 stream in it. */
494+
if (aws_linked_list_begin(&connection->thread_data.stream_list) !=
495+
aws_linked_list_rbegin(&connection->thread_data.stream_list)) {
496+
AWS_LOGF_ERROR(
497+
AWS_LS_HTTP_CONNECTION,
498+
"id=%p: Cannot switch protocols while further streams are pending, closing connection.",
499+
(void *)&connection->base);
500+
501+
return aws_raise_error(AWS_ERROR_INVALID_STATE);
502+
}
503+
504+
AWS_LOGF_TRACE(
505+
AWS_LS_HTTP_CONNECTION,
506+
"id=%p: Connection has switched protocols, another channel handler must be installed to"
507+
" deal with further data.",
508+
(void *)&connection->base);
509+
510+
connection->thread_data.has_switched_protocols = true;
511+
{ /* BEGIN CRITICAL SECTION */
512+
aws_h1_connection_lock_synced_data(connection);
513+
connection->synced_data.new_stream_error_code = AWS_ERROR_HTTP_SWITCHED_PROTOCOLS;
514+
aws_h1_connection_unlock_synced_data(connection);
515+
} /* END CRITICAL SECTION */
516+
517+
return AWS_OP_SUCCESS;
518+
}
519+
464520
static void s_stream_complete(struct aws_h1_stream *stream, int error_code) {
465521
struct aws_h1_connection *connection =
466522
AWS_CONTAINER_OF(stream->base.owning_connection, struct aws_h1_connection, base);
467523

524+
/*
525+
* If this is the end of a successful CONNECT request, mark ourselves as pass-through since the proxy layer
526+
* will be tacking on a new http handler (and possibly a tls handler in-between).
527+
*/
528+
if (error_code == AWS_ERROR_SUCCESS && s_aws_http_stream_was_successful_connect(stream)) {
529+
if (s_aws_http1_switch_protocols(connection)) {
530+
error_code = AWS_ERROR_HTTP_PROTOCOL_SWITCH_FAILURE;
531+
s_shutdown_due_to_error(connection, error_code);
532+
}
533+
}
534+
468535
/* Remove stream from list. */
469536
aws_linked_list_remove(&stream->node);
470537

@@ -1034,31 +1101,9 @@ static int s_mark_head_done(struct aws_h1_stream *incoming_stream) {
10341101
/* Only clients can receive informational headers.
10351102
* Check whether we're switching protocols */
10361103
if (incoming_stream->base.client_data->response_status == AWS_HTTP_STATUS_CODE_101_SWITCHING_PROTOCOLS) {
1037-
1038-
/* Switching protocols while there are multiple streams is too complex to deal with.
1039-
* Ensure stream_list has exactly this 1 stream in it. */
1040-
if (aws_linked_list_begin(&connection->thread_data.stream_list) !=
1041-
aws_linked_list_rbegin(&connection->thread_data.stream_list)) {
1042-
AWS_LOGF_ERROR(
1043-
AWS_LS_HTTP_CONNECTION,
1044-
"id=%p: Cannot switch protocols while further streams are pending, closing connection.",
1045-
(void *)&connection->base);
1046-
1047-
return aws_raise_error(AWS_ERROR_INVALID_STATE);
1104+
if (s_aws_http1_switch_protocols(connection)) {
1105+
return AWS_OP_ERR;
10481106
}
1049-
1050-
AWS_LOGF_TRACE(
1051-
AWS_LS_HTTP_CONNECTION,
1052-
"id=%p: Connection has switched protocols, another channel handler must be installed to"
1053-
" deal with further data.",
1054-
(void *)&connection->base);
1055-
1056-
connection->thread_data.has_switched_protocols = true;
1057-
{ /* BEGIN CRITICAL SECTION */
1058-
aws_h1_connection_lock_synced_data(connection);
1059-
connection->synced_data.new_stream_error_code = AWS_ERROR_HTTP_SWITCHED_PROTOCOLS;
1060-
aws_h1_connection_unlock_synced_data(connection);
1061-
} /* END CRITICAL SECTION */
10621107
}
10631108
}
10641109

source/http.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ static struct aws_error_info s_errors[] = {
124124
AWS_DEFINE_ERROR_INFO_HTTP(
125125
AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE,
126126
"Proxy connection attempt failed but the negotiation could be continued on a new connection"),
127+
AWS_DEFINE_ERROR_INFO_HTTP(
128+
AWS_ERROR_HTTP_PROTOCOL_SWITCH_FAILURE,
129+
"Internal state failure prevent connection from switching protocols"),
127130
};
128131
/* clang-format on */
129132

0 commit comments

Comments
 (0)