Skip to content

Commit e638eb0

Browse files
alextwoodsTingDaoK
andauthored
Add no_proxy_hosts configuration to proxy options/config. (#532)
Co-authored-by: Dengke Tang <[email protected]>
1 parent bbfc5a7 commit e638eb0

File tree

10 files changed

+189
-40
lines changed

10 files changed

+189
-40
lines changed

include/aws/http/private/proxy_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ struct aws_http_proxy_config {
5757
struct aws_tls_connection_options *tls_options;
5858

5959
struct aws_http_proxy_strategy *proxy_strategy;
60+
61+
struct aws_byte_buf no_proxy_hosts;
6062
};
6163

6264
/*

include/aws/http/proxy.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ struct aws_http_proxy_options {
152152
* Replaced by instantiating a proxy_strategy via aws_http_proxy_strategy_new_basic_auth()
153153
*/
154154
struct aws_byte_cursor auth_password;
155+
156+
/**
157+
* Optional
158+
* No proxy hosts - Comma seperated list of hosts for which not to use a proxy, if one is specified.
159+
*/
160+
struct aws_byte_cursor no_proxy_hosts;
155161
};
156162

157163
/**

source/proxy_connection.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,8 +1163,9 @@ static int s_proxy_uri_init_from_env_variable(
11631163
if (aws_http_host_matches_no_proxy(allocator, host_cursor, no_proxy_str)) {
11641164
AWS_LOGF_DEBUG(
11651165
AWS_LS_HTTP_CONNECTION,
1166-
"Host \"" PRInSTR "\" found in NO_PROXY, bypassing proxy",
1167-
AWS_BYTE_CURSOR_PRI(host_cursor));
1166+
"Host \"" PRInSTR "\" found in no_proxy_hosts: \" %s \", bypassing proxy",
1167+
AWS_BYTE_CURSOR_PRI(options->host_name),
1168+
aws_string_c_str(no_proxy_str));
11681169
aws_string_destroy(no_proxy_str);
11691170
return AWS_OP_SUCCESS;
11701171
}
@@ -1204,6 +1205,25 @@ static int s_connect_proxy(const struct aws_http_client_connection_options *opti
12041205
return AWS_OP_ERR;
12051206
}
12061207

1208+
if (options->proxy_options->no_proxy_hosts.len > 0) {
1209+
struct aws_string *no_proxy_host_str =
1210+
aws_string_new_from_cursor(options->allocator, &options->proxy_options->no_proxy_hosts);
1211+
if (aws_http_host_matches_no_proxy(options->allocator, options->host_name, no_proxy_host_str)) {
1212+
AWS_LOGF_DEBUG(
1213+
AWS_LS_HTTP_CONNECTION,
1214+
"Host \"" PRInSTR "\" found in NO_PROXY, bypassing proxy",
1215+
AWS_BYTE_CURSOR_PRI(options->host_name));
1216+
aws_string_destroy(no_proxy_host_str);
1217+
1218+
/* host matched no_proxy, connect without a proxy.: Fill in a new connection options with NULL proxy_options
1219+
*/
1220+
struct aws_http_client_connection_options options_copy = *options;
1221+
options_copy.proxy_options = NULL;
1222+
return aws_http_client_connect_internal(&options_copy, NULL);
1223+
}
1224+
aws_string_destroy(no_proxy_host_str);
1225+
}
1226+
12071227
enum aws_http_proxy_connection_type proxy_connection_type =
12081228
s_determine_proxy_connection_type(options->proxy_options->connection_type, options->tls_options != NULL);
12091229

@@ -1360,6 +1380,10 @@ static struct aws_http_proxy_config *s_aws_http_proxy_config_new(
13601380
goto on_error;
13611381
}
13621382

1383+
if (aws_byte_buf_init_copy_from_cursor(&config->no_proxy_hosts, allocator, proxy_options->no_proxy_hosts)) {
1384+
goto on_error;
1385+
}
1386+
13631387
if (proxy_options->tls_options) {
13641388
config->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options));
13651389
if (aws_tls_connection_options_copy(config->tls_options, proxy_options->tls_options)) {
@@ -1480,6 +1504,11 @@ struct aws_http_proxy_config *aws_http_proxy_config_new_clone(
14801504
goto on_error;
14811505
}
14821506

1507+
if (aws_byte_buf_init_copy_from_cursor(
1508+
&config->no_proxy_hosts, allocator, aws_byte_cursor_from_buf(&proxy_config->no_proxy_hosts))) {
1509+
goto on_error;
1510+
}
1511+
14831512
if (proxy_config->tls_options) {
14841513
config->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options));
14851514
if (aws_tls_connection_options_copy(config->tls_options, proxy_config->tls_options)) {
@@ -1506,6 +1535,7 @@ void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config) {
15061535
}
15071536

15081537
aws_byte_buf_clean_up(&config->host);
1538+
aws_byte_buf_clean_up(&config->no_proxy_hosts);
15091539

15101540
if (config->tls_options) {
15111541
aws_tls_connection_options_clean_up(config->tls_options);
@@ -1527,6 +1557,7 @@ void aws_http_proxy_options_init_from_config(
15271557
options->port = config->port;
15281558
options->tls_options = config->tls_options;
15291559
options->proxy_strategy = config->proxy_strategy;
1560+
options->no_proxy_hosts = aws_byte_cursor_from_buf(&config->no_proxy_hosts);
15301561
}
15311562

15321563
int aws_http_options_validate_proxy_configuration(const struct aws_http_client_connection_options *options) {

tests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ add_test_case(http_proxy_adaptive_ntlm_success)
636636
add_test_case(http_proxy_adaptive_failure)
637637
add_test_case(http_forwarding_proxy_uri_rewrite)
638638
add_test_case(http_forwarding_proxy_uri_rewrite_options_star)
639+
add_test_case(http_proxy_no_proxy_hosts_match)
640+
add_test_case(http_proxy_no_proxy_hosts_no_match)
639641

640642
# NO_PROXY tests
641643
add_test_case(test_no_proxy_subdomain_matching)
@@ -693,11 +695,11 @@ add_net_test_case(h2_sm_connection_ping)
693695
add_net_test_case(h2_sm_acquire_stream)
694696
add_net_test_case(h2_sm_acquire_stream_multiple_connections)
695697
add_net_test_case(h2_sm_closing_before_connection_acquired)
696-
add_net_test_case(h2_sm_close_connection_on_server_error)
697698

698699
# Tests against local server
699700
if(ENABLE_LOCALHOST_INTEGRATION_TESTS)
700701
# Tests should be named with localhost_integ_*
702+
add_net_test_case(localhost_integ_h2_sm_close_connection_on_server_error)
701703
add_net_test_case(localhost_integ_h2_sm_prior_knowledge)
702704
add_net_test_case(localhost_integ_h2_sm_acquire_stream_stress)
703705
add_net_test_case(localhost_integ_h2_sm_acquire_stream_stress_with_body)

tests/proxy_test_helper.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,24 @@ int proxy_tester_verify_connection_attempt_was_to_proxy(
489489
return AWS_OP_SUCCESS;
490490
}
491491

492+
int proxy_tester_verify_connection_attempt_was_to_target(
493+
struct proxy_tester *tester,
494+
struct aws_byte_cursor expected_host,
495+
uint32_t expected_port) {
496+
ASSERT_BIN_ARRAYS_EQUALS(
497+
tester->connection_host_name.buffer,
498+
tester->connection_host_name.len,
499+
expected_host.ptr,
500+
expected_host.len,
501+
"Connection host should have been \"" PRInSTR "\", but was \"" PRInSTR "\".",
502+
AWS_BYTE_CURSOR_PRI(expected_host),
503+
AWS_BYTE_BUF_PRI(tester->connection_host_name));
504+
505+
ASSERT_TRUE(tester->connection_port == expected_port);
506+
507+
return AWS_OP_SUCCESS;
508+
}
509+
492510
struct testing_channel *proxy_tester_get_current_channel(struct proxy_tester *tester) {
493511
struct testing_channel_bootstrap_wrapper *wrapper = s_get_current_channel_bootstrap_wrapper(tester);
494512
if (wrapper == NULL) {

tests/proxy_test_helper.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ enum proxy_tester_test_mode {
2323
PTTM_HTTP_FORWARD = 0,
2424
PTTM_HTTP_TUNNEL,
2525
PTTM_HTTPS_TUNNEL,
26+
PTTM_NO_PROXY,
2627
};
2728

2829
enum proxy_tester_failure_type {
@@ -122,6 +123,11 @@ int proxy_tester_verify_connection_attempt_was_to_proxy(
122123
struct aws_byte_cursor expected_host,
123124
uint32_t expected_port);
124125

126+
int proxy_tester_verify_connection_attempt_was_to_target(
127+
struct proxy_tester *tester,
128+
struct aws_byte_cursor expected_host,
129+
uint32_t expected_port);
130+
125131
struct testing_channel *proxy_tester_get_current_channel(struct proxy_tester *tester);
126132

127133
#endif /* AWS_HTTP_PROXY_TEST_HELPER_H */

tests/py_localhost/README.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,39 @@ Local server based on [python-hyper/h2](https://github.com/python-hyper/h2).
66

77
Python 3.5+ required.
88

9-
- Install hyper h2 python module. `python3 -m pip install h2`
9+
* Install hyper h2 python module. `python3 -m pip install h2`
1010

1111
### TLS server
1212

13-
- The code is based the [example](https://github.com/python-hyper/h2/blob/master/examples/asyncio/asyncio-server.py) from hyper h2 server.
14-
- Have the cert/key ready. The script now using `../resources/unittests.crt`, you can either just run the script within this directory, which will find the certificates and key from the related path, or you can use your own and change the code coordinately.
15-
- Run python. `python3 ./server.py`.
13+
* The code is based the [example](https://github.com/python-hyper/h2/blob/master/examples/asyncio/asyncio-server.py) from hyper h2 server.
14+
* Have the cert/key ready. The script now using `../resources/unittests.crt`, you can either just run the script within this directory, which will find the certificates and key from the related path, or you can use your own and change the code coordinately.
15+
* Run python. `python3 ./server.py`.
1616

1717
#### Echo
1818

19-
- Minor changed based on the example to response the headers of requests back within the headers from `/echo`.
20-
- To test the server runs correctly, you can do `curl -k -v -H "foo:bar" https://localhost:3443/echo` and check the result.
19+
* Minor changed based on the example to response the headers of requests back within the headers from `/echo`.
20+
* To test the server runs correctly, you can do `curl -k -v -H "foo:bar" https://localhost:3443/echo` and check the result.
2121

2222
#### Download test
2323

24-
- To test download, when `:path` is `/downloadTest`, server will response a repeated string with length `self.download_test_length`, which is 2,500,000,000 now. It will be repeats of sting "This is CRT HTTP test."
25-
- To test the server runs correctly, you can do `curl -k -v -H "foo:bar" https://localhost:3443/downloadTest` and check the result.
24+
* To test download, when `:path` is `/downloadTest`, server will response a repeated string with length `self.download_test_length`, which is 2,500,000,000 now. It will be repeats of sting "This is CRT HTTP test."
25+
* To test the server runs correctly, you can do `curl -k -v -H "foo:bar" https://localhost:3443/downloadTest` and check the result.
2626

2727
#### Slow Connection Test
2828

29-
- Simulate a slow connection when `:path` is `/slowConnTest`. The speed is controlled by `out_bytes_per_second`. Default speed is 900 B/s, which will send 900 bytes of data and wait a sec to send new 900 bytes of data.
29+
* Simulate a slow connection when `:path` is `/slowConnTest`. The speed is controlled by `out_bytes_per_second`. Default speed is 900 B/s, which will send 900 bytes of data and wait a sec to send new 900 bytes of data.
3030

3131
#### Upload test
3232

33-
- To test upload, when `:method` is `POST` or `PUT`, server will response the length received from response body
34-
- To test the server runs correctly, you can do `curl -k -X POST -F'data=@upload_test.txt' https://localhost:3443/upload_test` where `upload_test.txt` is file to upload.
33+
* To test upload, when `:method` is `POST` or `PUT`, server will response the length received from response body
34+
* To test the server runs correctly, you can do `curl -k -X POST -F'data=@upload_test.txt' https://localhost:3443/upload_test` where `upload_test.txt` is file to upload.
35+
36+
#### expect500
37+
38+
* The server will always return `500` for `:status`, when the `:path` is `/expect500`
3539

3640
### Non-TLS server
3741

38-
- The code is based the non-tls [example](http://python-hyper.org/projects/h2/en/stable/basic-usage.html) from hyper h2 server.
39-
- Run python. `python3 ./non_tls_server.py`.
40-
- To test the server runs correctly, you can do `curl -v --http2-prior-knowledge http://localhost:3280` and check the result.
42+
* The code is based the non-tls [example](http://python-hyper.org/projects/h2/en/stable/basic-usage.html) from hyper h2 server.
43+
* Run python. `python3 ./non_tls_server.py`.
44+
* To test the server runs correctly, you can do `curl -v --http2-prior-knowledge http://localhost:3280` and check the result.

tests/py_localhost/server.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ def stream_complete(self, stream_id: int):
130130

131131
path = request_data.headers[':path']
132132
method = request_data.headers[':method']
133-
if method == "PUT" or method == "POST":
133+
if path == '/expect500':
134+
self.conn.send_headers(stream_id, [(':status', '500')])
135+
asyncio.ensure_future(self.send_data(b"Internal Server Error", stream_id))
136+
elif method == "PUT" or method == "POST":
134137
self.conn.send_headers(stream_id, [(':status', '200')])
135138
asyncio.ensure_future(self.send_data(
136139
str(self.num_sentence_received[stream_id]).encode(), stream_id))

tests/test_proxy.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ static int s_test_aws_proxy_new_socket_channel(struct aws_socket_channel_bootstr
195195
testing_channel_run_currently_queued_tasks(channel);
196196
}
197197

198+
if (tester.test_mode == PTTM_NO_PROXY) {
199+
return AWS_OP_SUCCESS;
200+
}
201+
198202
if (tester.failure_type == PTFT_NONE || tester.failure_type == PTFT_CONNECT_REQUEST ||
199203
tester.failure_type == PTFT_TLS_NEGOTIATION) {
200204
if (tester.proxy_options.connection_type == AWS_HPCT_HTTP_TUNNEL) {
@@ -1142,3 +1146,77 @@ static int s_test_http_forwarding_proxy_uri_rewrite_options_star(struct aws_allo
11421146
return AWS_OP_SUCCESS;
11431147
}
11441148
AWS_TEST_CASE(http_forwarding_proxy_uri_rewrite_options_star, s_test_http_forwarding_proxy_uri_rewrite_options_star);
1149+
1150+
/*
1151+
* Test no_proxy_hosts functionality - host matches no_proxy pattern, should bypass proxy
1152+
*/
1153+
static int s_test_http_proxy_no_proxy_hosts_match(struct aws_allocator *allocator, void *ctx) {
1154+
(void)ctx;
1155+
1156+
aws_http_connection_set_system_vtable(&s_proxy_connection_system_vtable);
1157+
aws_http_proxy_system_set_vtable(&s_proxy_table_for_tls);
1158+
1159+
struct aws_http_proxy_options proxy_options = {
1160+
.connection_type = AWS_HPCT_HTTP_TUNNEL,
1161+
.host = aws_byte_cursor_from_c_str(s_proxy_host_name),
1162+
.port = s_proxy_port,
1163+
.no_proxy_hosts = aws_byte_cursor_from_c_str("aws.amazon.com"),
1164+
};
1165+
1166+
struct proxy_tester_options options = {
1167+
.alloc = allocator,
1168+
.proxy_options = &proxy_options,
1169+
.host = aws_byte_cursor_from_c_str(s_host_name), /* aws.amazon.com */
1170+
.port = s_port,
1171+
.test_mode = PTTM_NO_PROXY,
1172+
.failure_type = PTFT_NONE,
1173+
};
1174+
1175+
ASSERT_SUCCESS(proxy_tester_init(&tester, &options));
1176+
proxy_tester_wait(&tester, proxy_tester_connection_setup_pred);
1177+
1178+
/* Should connect directly to target host, not proxy */
1179+
ASSERT_SUCCESS(
1180+
proxy_tester_verify_connection_attempt_was_to_target(&tester, aws_byte_cursor_from_c_str(s_host_name), s_port));
1181+
1182+
ASSERT_SUCCESS(proxy_tester_clean_up(&tester));
1183+
return AWS_OP_SUCCESS;
1184+
}
1185+
AWS_TEST_CASE(http_proxy_no_proxy_hosts_match, s_test_http_proxy_no_proxy_hosts_match);
1186+
1187+
/*
1188+
* Test no_proxy_hosts functionality - host does not match no_proxy pattern, should use proxy
1189+
*/
1190+
static int s_test_http_proxy_no_proxy_hosts_no_match(struct aws_allocator *allocator, void *ctx) {
1191+
(void)ctx;
1192+
1193+
aws_http_connection_set_system_vtable(&s_proxy_connection_system_vtable);
1194+
aws_http_proxy_system_set_vtable(&s_proxy_table_for_tls);
1195+
1196+
struct aws_http_proxy_options proxy_options = {
1197+
.connection_type = AWS_HPCT_HTTP_TUNNEL,
1198+
.host = aws_byte_cursor_from_c_str(s_proxy_host_name),
1199+
.port = s_proxy_port,
1200+
.no_proxy_hosts = aws_byte_cursor_from_c_str("example.com"),
1201+
};
1202+
1203+
struct proxy_tester_options options = {
1204+
.alloc = allocator,
1205+
.proxy_options = &proxy_options,
1206+
.host = aws_byte_cursor_from_c_str(s_host_name), /* aws.amazon.com */
1207+
.port = s_port,
1208+
.test_mode = PTTM_HTTP_TUNNEL,
1209+
.failure_type = PTFT_NONE,
1210+
};
1211+
1212+
ASSERT_SUCCESS(proxy_tester_init(&tester, &options));
1213+
proxy_tester_wait(&tester, proxy_tester_connection_setup_pred);
1214+
1215+
/* Should connect to proxy since host doesn't match no_proxy pattern */
1216+
ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy(
1217+
&tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port));
1218+
1219+
ASSERT_SUCCESS(proxy_tester_clean_up(&tester));
1220+
return AWS_OP_SUCCESS;
1221+
}
1222+
AWS_TEST_CASE(http_proxy_no_proxy_hosts_no_match, s_test_http_proxy_no_proxy_hosts_no_match);

tests/test_stream_manager.c

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,28 +1228,6 @@ TEST_CASE(h2_sm_acquire_stream_multiple_connections) {
12281228
return s_tester_clean_up();
12291229
}
12301230

1231-
/* Test that makes tons of real streams against real world */
1232-
TEST_CASE(h2_sm_close_connection_on_server_error) {
1233-
(void)ctx;
1234-
/* page not exist. */
1235-
struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str("https://www.amazon.com/non-exists");
1236-
struct sm_tester_options options = {
1237-
.max_connections = 1,
1238-
.max_concurrent_streams_per_connection = 10,
1239-
.alloc = allocator,
1240-
.uri_cursor = &uri_cursor,
1241-
.close_connection_on_server_error = true,
1242-
};
1243-
ASSERT_SUCCESS(s_tester_init(&options));
1244-
int num_to_acquire = 50;
1245-
ASSERT_SUCCESS(s_sm_stream_acquiring(num_to_acquire));
1246-
ASSERT_SUCCESS(s_wait_on_streams_completed_count(num_to_acquire));
1247-
ASSERT_TRUE((int)s_tester.acquiring_stream_errors == 0);
1248-
ASSERT_TRUE((int)s_tester.stream_200_count == 0);
1249-
1250-
return s_tester_clean_up();
1251-
}
1252-
12531231
static void s_sm_tester_on_connection_setup(struct aws_http_connection *connection, int error_code, void *user_data) {
12541232
if (s_tester.release_sm_during_connection_acquiring) {
12551233
aws_http2_stream_manager_release(s_tester.stream_manager);
@@ -1288,6 +1266,27 @@ TEST_CASE(h2_sm_closing_before_connection_acquired) {
12881266
return s_tester_clean_up();
12891267
}
12901268

1269+
TEST_CASE(localhost_integ_h2_sm_close_connection_on_server_error) {
1270+
(void)ctx;
1271+
/* server that will return 500 status code all the time. */
1272+
struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str("https://localhost:3443/expect500");
1273+
struct sm_tester_options options = {
1274+
.max_connections = 1,
1275+
.max_concurrent_streams_per_connection = 10,
1276+
.alloc = allocator,
1277+
.uri_cursor = &uri_cursor,
1278+
.close_connection_on_server_error = true,
1279+
};
1280+
ASSERT_SUCCESS(s_tester_init(&options));
1281+
int num_to_acquire = 50;
1282+
ASSERT_SUCCESS(s_sm_stream_acquiring(num_to_acquire));
1283+
ASSERT_SUCCESS(s_wait_on_streams_completed_count(num_to_acquire));
1284+
ASSERT_TRUE((int)s_tester.acquiring_stream_errors == 0);
1285+
ASSERT_TRUE((int)s_tester.stream_200_count == 0);
1286+
1287+
return s_tester_clean_up();
1288+
}
1289+
12911290
/* Test our http2 stream manager works with prior knowledge */
12921291
TEST_CASE(localhost_integ_h2_sm_prior_knowledge) {
12931292
(void)ctx;

0 commit comments

Comments
 (0)