Skip to content

Commit 652e2fe

Browse files
waahm7graebm
andauthored
Bind to a list of Network Interfaces (#471)
Co-authored-by: Michael Graeb <[email protected]>
1 parent 079ccfd commit 652e2fe

File tree

5 files changed

+124
-2
lines changed

5 files changed

+124
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ jobs:
161161
python3 builder.pyz build -p aws-c-http --cmake-extra=-DENABLE_LOCALHOST_INTEGRATION_TESTS=ON --config Debug
162162
163163
localhost-test-mac:
164-
runs-on: macos-11 # latest
164+
runs-on: macos-13 # latest
165165
steps:
166166
- name: Checkout
167167
uses: actions/checkout@v3

include/aws/http/connection_manager.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ struct aws_http_connection_manager_options {
124124
* timeout will be closed automatically.
125125
*/
126126
uint64_t max_connection_idle_in_milliseconds;
127+
128+
/**
129+
* (Optional)
130+
* An array of network interface names. The manager will distribute the
131+
* connections across network interface names provided in this array. If any interface name is invalid, goes down,
132+
* or has any issues like network access, you will see connection failures. If
133+
* `socket_options.network_interface_name` is also set, an `AWS_ERROR_INVALID_ARGUMENT` error will be raised.
134+
*
135+
* This option is only supported on Linux, MacOS, and platforms that have either SO_BINDTODEVICE or IP_BOUND_IF. It
136+
* is not supported on Windows. `AWS_ERROR_PLATFORM_NOT_SUPPORTED` will be raised on unsupported platforms.
137+
*/
138+
const struct aws_byte_cursor *network_interface_names_array;
139+
size_t num_network_interface_names;
127140
};
128141

129142
AWS_EXTERN_C_BEGIN

source/connection_manager.c

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,17 @@ struct aws_http_connection_manager {
292292
*/
293293
struct aws_task *cull_task;
294294
struct aws_event_loop *cull_event_loop;
295+
296+
/*
297+
* An aws_array_list<struct aws_string *> of network interface names to distribute the connections using the
298+
* round-robin algorithm. We picked round-robin because it is trivial to implement and good enough. We can later
299+
* update to a more complex distribution algorithm if required.
300+
*/
301+
struct aws_array_list network_interface_names;
302+
/*
303+
* Current index in the network_interface_names array_list.
304+
*/
305+
size_t network_interface_names_index;
295306
};
296307

297308
struct aws_http_connection_manager_snapshot {
@@ -703,6 +714,13 @@ static void s_aws_http_connection_manager_finish_destroy(struct aws_http_connect
703714
aws_http_proxy_config_destroy(manager->proxy_config);
704715
}
705716

717+
for (size_t i = 0; i < aws_array_list_length(&manager->network_interface_names); i++) {
718+
struct aws_string *interface_name = NULL;
719+
aws_array_list_get_at(&manager->network_interface_names, &interface_name, i);
720+
aws_string_destroy(interface_name);
721+
}
722+
aws_array_list_clean_up(&manager->network_interface_names);
723+
706724
/*
707725
* If this task exists then we are actually in the corresponding event loop running the final destruction task.
708726
* In that case, we've already cancelled this task and when you cancel, it runs synchronously. So in that
@@ -819,6 +837,15 @@ struct aws_http_connection_manager *aws_http_connection_manager_new(
819837
return NULL;
820838
}
821839

840+
if (options->socket_options->network_interface_name[0] != '\0' && options->num_network_interface_names > 0) {
841+
AWS_LOGF_ERROR(
842+
AWS_LS_HTTP_CONNECTION_MANAGER,
843+
"Invalid options - socket_options.network_interface_name and network_interface_names_array cannot be both "
844+
"set.");
845+
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
846+
return NULL;
847+
}
848+
822849
struct aws_http_connection_manager *manager =
823850
aws_mem_calloc(allocator, 1, sizeof(struct aws_http_connection_manager));
824851
if (manager == NULL) {
@@ -896,6 +923,20 @@ struct aws_http_connection_manager *aws_http_connection_manager_new(
896923
manager->max_closed_streams = options->max_closed_streams;
897924
manager->http2_conn_manual_window_management = options->http2_conn_manual_window_management;
898925

926+
manager->network_interface_names_index = 0;
927+
if (options->num_network_interface_names > 0) {
928+
aws_array_list_init_dynamic(
929+
&manager->network_interface_names,
930+
allocator,
931+
options->num_network_interface_names,
932+
sizeof(struct aws_string *));
933+
for (size_t i = 0; i < options->num_network_interface_names; i++) {
934+
struct aws_byte_cursor interface_name = options->network_interface_names_array[i];
935+
struct aws_string *interface_name_str = aws_string_new_from_cursor(allocator, &interface_name);
936+
aws_array_list_push_back(&manager->network_interface_names, &interface_name_str);
937+
}
938+
}
939+
899940
/* NOTHING can fail after here */
900941
s_schedule_connection_culling(manager);
901942

@@ -990,7 +1031,26 @@ static int s_aws_http_connection_manager_new_connection(struct aws_http_connecti
9901031
options.host_name = aws_byte_cursor_from_string(manager->host);
9911032
options.port = manager->port;
9921033
options.initial_window_size = manager->initial_window_size;
993-
options.socket_options = &manager->socket_options;
1034+
struct aws_socket_options socket_options = manager->socket_options;
1035+
if (aws_array_list_length(&manager->network_interface_names)) {
1036+
struct aws_string *interface_name = NULL;
1037+
aws_array_list_get_at(
1038+
&manager->network_interface_names, &interface_name, manager->network_interface_names_index);
1039+
manager->network_interface_names_index =
1040+
(manager->network_interface_names_index + 1) % aws_array_list_length(&manager->network_interface_names);
1041+
#if defined(_MSC_VER)
1042+
# pragma warning(push)
1043+
# pragma warning(disable : 4996) /* allow strncpy() */
1044+
#endif
1045+
/* If the interface_name is too long or not null terminated, it will be caught in the `aws_socket_init` function
1046+
* so we don't need to worry about that here.*/
1047+
strncpy(
1048+
socket_options.network_interface_name, aws_string_c_str(interface_name), AWS_NETWORK_INTERFACE_NAME_MAX);
1049+
#if defined(_MSC_VER)
1050+
# pragma warning(pop)
1051+
#endif
1052+
}
1053+
options.socket_options = &socket_options;
9941054
options.on_setup = s_aws_http_connection_manager_on_connection_setup;
9951055
options.on_shutdown = s_aws_http_connection_manager_on_connection_shutdown;
9961056
options.manual_window_management = manager->enable_read_back_pressure;

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ add_net_test_case(test_connection_manager_idle_culling_single)
525525
add_net_test_case(test_connection_manager_idle_culling_many)
526526
add_net_test_case(test_connection_manager_idle_culling_mixture)
527527
add_net_test_case(test_connection_manager_idle_culling_refcount)
528+
add_net_test_case(test_connection_manager_with_network_interface_list)
528529

529530
# tests where we establish real connections
530531
add_net_test_case(test_connection_manager_single_connection)

tests/test_connection_manager.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ struct cm_tester_options {
5858
struct aws_http2_setting *initial_settings_array;
5959
size_t num_initial_settings;
6060
bool self_lib_init;
61+
const struct aws_byte_cursor *verify_network_interface_names_array;
62+
size_t num_network_interface_names;
6163
};
6264

6365
struct cm_tester {
@@ -73,6 +75,8 @@ struct cm_tester {
7375
struct aws_tls_ctx_options tls_ctx_options;
7476
struct aws_tls_connection_options tls_connection_options;
7577
struct aws_http_proxy_options *verify_proxy_options;
78+
const struct aws_byte_cursor *verify_network_interface_names_array;
79+
size_t num_network_interface_names;
7680

7781
struct aws_mutex lock;
7882
struct aws_condition_variable signal;
@@ -219,6 +223,8 @@ static int s_cm_tester_init(struct cm_tester_options *options) {
219223
.http2_prior_knowledge = !options->use_tls && options->http2,
220224
.initial_settings_array = options->initial_settings_array,
221225
.num_initial_settings = options->num_initial_settings,
226+
.network_interface_names_array = options->verify_network_interface_names_array,
227+
.num_network_interface_names = options->num_network_interface_names,
222228
};
223229

224230
if (options->mock_table) {
@@ -234,6 +240,8 @@ static int s_cm_tester_init(struct cm_tester_options *options) {
234240
}
235241

236242
tester->mock_table = options->mock_table;
243+
tester->verify_network_interface_names_array = options->verify_network_interface_names_array;
244+
tester->num_network_interface_names = options->num_network_interface_names;
237245

238246
aws_atomic_store_int(&tester->next_connection_id, 0);
239247

@@ -729,6 +737,13 @@ static int s_aws_http_connection_manager_create_connection_sync_mock(
729737
const struct aws_http_client_connection_options *options) {
730738
struct cm_tester *tester = &s_tester;
731739

740+
if (tester->num_network_interface_names) {
741+
struct aws_byte_cursor interface_name =
742+
tester->verify_network_interface_names_array
743+
[aws_atomic_load_int(&tester->next_connection_id) % tester->num_network_interface_names];
744+
ASSERT_TRUE(aws_byte_cursor_eq_c_str(&interface_name, options->socket_options->network_interface_name));
745+
}
746+
732747
size_t next_connection_id = aws_atomic_fetch_add(&tester->next_connection_id, 1);
733748

734749
ASSERT_SUCCESS(aws_mutex_lock(&tester->lock));
@@ -819,6 +834,39 @@ static struct aws_http_connection_manager_system_vtable s_synchronous_mocks = {
819834
.aws_http_connection_get_version = s_aws_http_connection_manager_connection_get_version_sync_mock,
820835
};
821836

837+
static int s_test_connection_manager_with_network_interface_list(struct aws_allocator *allocator, void *ctx) {
838+
(void)ctx;
839+
struct aws_byte_cursor *interface_names_array = aws_mem_calloc(allocator, 3, sizeof(struct aws_byte_cursor));
840+
interface_names_array[0] = aws_byte_cursor_from_c_str("ens32");
841+
interface_names_array[1] = aws_byte_cursor_from_c_str("ens64");
842+
interface_names_array[2] = aws_byte_cursor_from_c_str("ens96");
843+
844+
struct cm_tester_options options = {
845+
.allocator = allocator,
846+
.max_connections = 20,
847+
.mock_table = &s_synchronous_mocks,
848+
.verify_network_interface_names_array = interface_names_array,
849+
};
850+
851+
ASSERT_SUCCESS(s_cm_tester_init(&options));
852+
size_t num_connections = 6;
853+
for (size_t i = 0; i < num_connections; ++i) {
854+
s_add_mock_connections(1, AWS_NCRT_SUCCESS, i % 1 == 0);
855+
}
856+
s_acquire_connections(num_connections);
857+
858+
ASSERT_SUCCESS(s_wait_on_connection_reply_count(num_connections));
859+
ASSERT_SUCCESS(s_release_connections(num_connections, false));
860+
ASSERT_UINT_EQUALS(0, s_tester.connection_errors);
861+
862+
ASSERT_SUCCESS(s_cm_tester_clean_up());
863+
aws_mem_release(allocator, interface_names_array);
864+
return AWS_OP_SUCCESS;
865+
}
866+
AWS_TEST_CASE(
867+
test_connection_manager_with_network_interface_list,
868+
s_test_connection_manager_with_network_interface_list);
869+
822870
static int s_test_connection_manager_acquire_release_mix_synchronous(struct aws_allocator *allocator, void *ctx) {
823871
(void)ctx;
824872

0 commit comments

Comments
 (0)