/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include #include #include #include "mock_dns_resolver.h" static const uint64_t FORCE_RESOLVE_SLEEP_TIME = 1500000000; struct default_host_callback_data { struct aws_host_address aaaa_address; struct aws_host_address a_address; bool has_aaaa_address; bool has_a_address; struct aws_condition_variable condition_variable; bool invoked; struct aws_mutex *mutex; aws_thread_id_t callback_thread_id; }; static bool s_default_host_resolved_predicate(void *arg) { struct default_host_callback_data *callback_data = arg; return callback_data->invoked; } static void s_default_host_resolved_test_callback( struct aws_host_resolver *resolver, const struct aws_string *host_name, int err_code, const struct aws_array_list *host_addresses, void *user_data) { (void)resolver; (void)host_name; (void)err_code; struct default_host_callback_data *callback_data = user_data; aws_mutex_lock(callback_data->mutex); if (host_addresses != NULL) { struct aws_host_address *host_address = NULL; if (aws_array_list_length(host_addresses) >= 2) { aws_array_list_get_at_ptr(host_addresses, (void **)&host_address, 0); aws_host_address_copy(host_address, &callback_data->aaaa_address); aws_array_list_get_at_ptr(host_addresses, (void **)&host_address, 1); aws_host_address_copy(host_address, &callback_data->a_address); callback_data->has_aaaa_address = true; callback_data->has_a_address = true; } else if (aws_array_list_length(host_addresses) == 1) { aws_array_list_get_at_ptr(host_addresses, (void **)&host_address, 0); if (host_address->record_type == AWS_ADDRESS_RECORD_TYPE_A) { aws_host_address_copy(host_address, &callback_data->a_address); callback_data->has_a_address = true; } else if (host_address->record_type == AWS_ADDRESS_RECORD_TYPE_AAAA) { aws_host_address_copy(host_address, &callback_data->aaaa_address); callback_data->has_aaaa_address = true; } } } callback_data->invoked = true; callback_data->callback_thread_id = aws_thread_current_thread_id(); aws_mutex_unlock(callback_data->mutex); aws_condition_variable_notify_one(&callback_data->condition_variable); } static int s_test_default_with_ipv6_lookup_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "s3.dualstack.us-east-1.amazonaws.com"); ASSERT_NOT_NULL(host_name); struct aws_host_resolution_config config = { .max_ttl = 10, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); callback_data.invoked = false; ASSERT_TRUE(callback_data.has_aaaa_address); ASSERT_INT_EQUALS(AWS_ADDRESS_RECORD_TYPE_AAAA, callback_data.aaaa_address.record_type); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_name), host_name->len, aws_string_bytes(callback_data.aaaa_address.host), callback_data.aaaa_address.host->len); ASSERT_TRUE(callback_data.has_a_address); ASSERT_INT_EQUALS(AWS_ADDRESS_RECORD_TYPE_A, callback_data.a_address.record_type); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_name), host_name->len, aws_string_bytes(callback_data.a_address.host), callback_data.a_address.host->len); ASSERT_TRUE(callback_data.aaaa_address.address->len > 1); ASSERT_TRUE(callback_data.a_address.address->len > 1); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_string_destroy((void *)host_name); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_default_with_ipv6_lookup, s_test_default_with_ipv6_lookup_fn) /* just FYI, this test assumes that "s3.us-east-1.amazonaws.com" does not return IPv6 addresses. */ static int s_test_default_with_ipv4_only_lookup_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "s3.us-east-1.amazonaws.com"); ASSERT_NOT_NULL(host_name); struct aws_host_resolution_config config = { .max_ttl = 10, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); callback_data.invoked = false; ASSERT_FALSE(callback_data.has_aaaa_address); ASSERT_TRUE(callback_data.has_a_address); ASSERT_INT_EQUALS(AWS_ADDRESS_RECORD_TYPE_A, callback_data.a_address.record_type); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_name), host_name->len, aws_string_bytes(callback_data.a_address.host), callback_data.a_address.host->len); ASSERT_TRUE(callback_data.a_address.address->len > 1); aws_mutex_unlock(&mutex); aws_host_address_clean_up(&callback_data.a_address); aws_string_destroy((void *)host_name); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_default_with_ipv4_only_lookup, s_test_default_with_ipv4_only_lookup_fn) /* there are multiple big assumptions in this test. * The first assumption is that s3.us-east-1.amazonaws.com will never return an IPv6 address. * The second assumption is that the TTLs for these records are one second and that the backend resolver * resolves at the TTL rate. * The third assumption is that this test runs in less than one second after the first background resolve. * The fourth assumption is that S3 does not return multiple addresses per A or AAAA record. * If any of these assumptions ever change, this test will likely be broken, but I don't know of a better way to test * this end-to-end. */ static int s_test_default_with_multiple_lookups_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name_1 = aws_string_new_from_c_str(allocator, "s3.dualstack.us-east-1.amazonaws.com"); const struct aws_string *host_name_2 = aws_string_new_from_c_str(allocator, "s3.us-east-1.amazonaws.com"); ASSERT_NOT_NULL(host_name_1); ASSERT_NOT_NULL(host_name_2); struct aws_host_resolution_config config = { .max_ttl = 10, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_1, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); struct aws_host_address host_1_original_ipv6_resolve; aws_host_address_copy(&callback_data.aaaa_address, &host_1_original_ipv6_resolve); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); callback_data.invoked = false; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_2, s_default_host_resolved_test_callback, &config, &callback_data)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); struct aws_host_address host_2_original_ipv4_resolve; aws_host_address_copy(&callback_data.a_address, &host_2_original_ipv4_resolve); aws_host_address_clean_up(&callback_data.a_address); /* this will invoke in the calling thread since the address is already cached. */ aws_mutex_unlock(&mutex); callback_data.invoked = false; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_1, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_1_original_ipv6_resolve.address), host_1_original_ipv6_resolve.address->len, aws_string_bytes(callback_data.aaaa_address.address), callback_data.aaaa_address.address->len); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* this will invoke in the calling thread since the address is already cached. */ callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_2, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_2_original_ipv4_resolve.address), host_2_original_ipv4_resolve.address->len, aws_string_bytes(callback_data.a_address.address), callback_data.a_address.address->len); aws_host_address_clean_up(&callback_data.a_address); aws_mutex_unlock(&mutex); aws_host_address_clean_up(&host_1_original_ipv6_resolve); aws_host_address_clean_up(&host_2_original_ipv4_resolve); aws_string_destroy((void *)host_name_1); aws_string_destroy((void *)host_name_2); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_default_with_multiple_lookups, s_test_default_with_multiple_lookups_fn) static int s_test_resolver_ttls_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "host_address"); const struct aws_string *addr1_ipv4 = aws_string_new_from_c_str(allocator, "address1ipv4"); const struct aws_string *addr1_ipv6 = aws_string_new_from_c_str(allocator, "address1ipv6"); const struct aws_string *addr2_ipv4 = aws_string_new_from_c_str(allocator, "address2ipv4"); const struct aws_string *addr2_ipv6 = aws_string_new_from_c_str(allocator, "address2ipv6"); struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS(mock_dns_resolver_init(&mock_resolver, 2, allocator)); struct aws_host_resolution_config config = { .max_ttl = 1, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; struct aws_host_address host_address_1_ipv4 = { .address = addr1_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_1_ipv6 = { .address = addr1_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_array_list address_list_1; ASSERT_SUCCESS(aws_array_list_init_dynamic(&address_list_1, allocator, 2, sizeof(struct aws_host_address))); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv4)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(&mock_resolver, &address_list_1)); struct aws_host_address host_address_2_ipv4 = { .address = addr2_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv6 = { .address = addr2_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_array_list address_list_2; ASSERT_SUCCESS(aws_array_list_init_dynamic(&address_list_2, allocator, 2, sizeof(struct aws_host_address))); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_2, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_2, &host_address_2_ipv4)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(&mock_resolver, &address_list_2)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* sleep a bit more than one second, as a result the next resolve should run.*/ aws_thread_current_sleep(FORCE_RESOLVE_SLEEP_TIME); callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* sleep a bit more than one second, as a result the next resolve should run.*/ aws_thread_current_sleep(FORCE_RESOLVE_SLEEP_TIME); /* note that normally, the first address would come back, but the TTL is expired (we set it to one second). * As a result, we should get the second one again.*/ callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* sleep so entry two expires. Now everything is expired, but because the last thing we resolved was addr 2, it * should still be there. */ aws_thread_current_sleep(FORCE_RESOLVE_SLEEP_TIME); callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_mutex_unlock(&mutex); aws_host_resolver_release(resolver); aws_string_destroy((void *)host_name); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE(test_resolver_ttls, s_test_resolver_ttls_fn) static int s_test_resolver_connect_failure_recording_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "host_address"); const struct aws_string *addr1_ipv4 = aws_string_new_from_c_str(allocator, "address1ipv4"); const struct aws_string *addr1_ipv6 = aws_string_new_from_c_str(allocator, "address1ipv6"); const struct aws_string *addr2_ipv4 = aws_string_new_from_c_str(allocator, "address2ipv4"); const struct aws_string *addr2_ipv6 = aws_string_new_from_c_str(allocator, "address2ipv6"); struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS(mock_dns_resolver_init(&mock_resolver, 1000, allocator)); struct aws_host_resolution_config config = { .max_ttl = 30, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; struct aws_host_address host_address_1_ipv4 = { .address = addr1_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_1_ipv6 = { .address = addr1_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv4 = { .address = addr2_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv6 = { .address = addr2_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_array_list address_list_1; ASSERT_SUCCESS(aws_array_list_init_dynamic(&address_list_1, allocator, 2, sizeof(struct aws_host_address))); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv4)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv4)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(&mock_resolver, &address_list_1)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); callback_data.invoked = false; /* this should still be cached don't need the mutex here. */ aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_1_ipv4)); /* following the LRU policy, address 1 should be what gets returned here, however we marked it as failed, so it * should be skipped and address 2 should be returned. */ aws_mutex_unlock(&mutex); callback_data.invoked = false; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_2_ipv4)); callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); /* here address 1 should be returned since it is now the least recently used address and all of them have failed.. */ aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* let it re-resolve, and we should still have the other connections marked as connection failures. */ aws_thread_current_sleep(FORCE_RESOLVE_SLEEP_TIME); callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); /* here address 1 should still be the one returned because though we re-resolved, we don't trust the dns entries yet * and we kept them as bad addresses. */ aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_mutex_unlock(&mutex); aws_host_resolver_release(resolver); aws_string_destroy((void *)host_name); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE(test_resolver_connect_failure_recording, s_test_resolver_connect_failure_recording_fn) static int s_test_resolver_ttl_refreshes_on_resolve_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "host_address"); const struct aws_string *addr1_ipv4 = aws_string_new_from_c_str(allocator, "address1ipv4"); const struct aws_string *addr1_ipv6 = aws_string_new_from_c_str(allocator, "address1ipv6"); const struct aws_string *addr2_ipv4 = aws_string_new_from_c_str(allocator, "address2ipv4"); const struct aws_string *addr2_ipv6 = aws_string_new_from_c_str(allocator, "address2ipv6"); struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS(mock_dns_resolver_init(&mock_resolver, 1000, allocator)); struct aws_host_resolution_config config = { .max_ttl = 30, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; struct aws_host_address host_address_1_ipv4 = { .address = addr1_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_1_ipv6 = { .address = addr1_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv4 = { .address = addr2_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv6 = { .address = addr2_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_array_list address_list_1; ASSERT_SUCCESS(aws_array_list_init_dynamic(&address_list_1, allocator, 2, sizeof(struct aws_host_address))); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv4)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv4)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(&mock_resolver, &address_list_1)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); uint64_t address_1_expiry = callback_data.aaaa_address.expiry; aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); callback_data.invoked = false; /* this will resolve in the calling thread, so don't take the lock. */ aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); uint64_t address_2_expiry = callback_data.aaaa_address.expiry; aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_thread_current_sleep(FORCE_RESOLVE_SLEEP_TIME); /* now we loop back around, we resolved, but the TTLs should not have expired at all (they were actually refreshed). */ callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); /* here address 1 should be returned since it is now the least recently used address.. */ aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr1_ipv4, callback_data.a_address.address)); ASSERT_TRUE(address_1_expiry < callback_data.aaaa_address.expiry); ASSERT_TRUE(address_1_expiry < callback_data.a_address.expiry); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); /* let it re-resolve, we should get addr 2 back, but with a later expiry than before.. */ callback_data.invoked = false; aws_mutex_unlock(&mutex); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); aws_mutex_lock(&mutex); /* here address 1 should still be the one returned because though we re-resolved, we don't trust the dns entries yet * and we kept them as bad addresses. */ aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv6, callback_data.aaaa_address.address)); ASSERT_INT_EQUALS(0, aws_string_compare(addr2_ipv4, callback_data.a_address.address)); ASSERT_TRUE(address_2_expiry < callback_data.aaaa_address.expiry); ASSERT_TRUE(address_2_expiry < callback_data.a_address.expiry); aws_host_address_clean_up(&callback_data.aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_mutex_unlock(&mutex); aws_host_resolver_release(resolver); aws_string_destroy((void *)host_name); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE(test_resolver_ttl_refreshes_on_resolve, s_test_resolver_ttl_refreshes_on_resolve_fn) static int s_test_resolver_ipv4_address_lookup_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "127.0.0.1"); ASSERT_NOT_NULL(host_name); struct aws_host_resolution_config config = { .max_ttl = 10, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); callback_data.invoked = false; ASSERT_TRUE(callback_data.has_a_address); ASSERT_INT_EQUALS(AWS_ADDRESS_RECORD_TYPE_A, callback_data.a_address.record_type); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_name), host_name->len, aws_string_bytes(callback_data.a_address.host), callback_data.a_address.host->len); ASSERT_TRUE(callback_data.a_address.address->len > 1); ASSERT_FALSE(callback_data.has_aaaa_address); aws_host_address_clean_up(&callback_data.a_address); aws_mutex_unlock(&mutex); aws_string_destroy((void *)host_name); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_ipv4_address_lookup, s_test_resolver_ipv4_address_lookup_fn) static int s_test_resolver_ipv6_address_lookup_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "::1"); ASSERT_NOT_NULL(host_name); struct aws_host_resolution_config config = { .max_ttl = 10, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_mutex mutex = AWS_MUTEX_INIT; struct default_host_callback_data callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &callback_data); callback_data.invoked = false; ASSERT_FALSE(callback_data.has_a_address); ASSERT_TRUE(callback_data.has_aaaa_address); ASSERT_INT_EQUALS(AWS_ADDRESS_RECORD_TYPE_AAAA, callback_data.aaaa_address.record_type); ASSERT_BIN_ARRAYS_EQUALS( aws_string_bytes(host_name), host_name->len, aws_string_bytes(callback_data.aaaa_address.host), callback_data.aaaa_address.host->len); ASSERT_TRUE(callback_data.aaaa_address.address->len > 1); aws_host_address_clean_up(&callback_data.aaaa_address); aws_mutex_unlock(&mutex); aws_string_destroy((void *)host_name); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_ipv6_address_lookup, s_test_resolver_ipv6_address_lookup_fn) struct listener_test_callback_type_data { struct aws_allocator *allocator; bool callback_invoked; uint32_t error_code; uint32_t expected_num_addresses; struct aws_array_list address_list; }; struct listener_test_callback_data { struct aws_allocator *allocator; struct aws_condition_variable condition_variable; struct aws_mutex *mutex; bool shutdown_invoked; struct listener_test_callback_type_data new_address_callback_data; struct listener_test_callback_type_data expired_address_callback_data; }; static bool s_listener_new_address_invoked_predicate(void *data) { struct listener_test_callback_data *callback_data = data; return callback_data->new_address_callback_data.callback_invoked; } static bool s_listener_expired_address_invoked_predicate(void *data) { struct listener_test_callback_data *callback_data = data; return callback_data->expired_address_callback_data.callback_invoked; } static bool s_listener_new_address_complete_set_predicate(void *data) { struct listener_test_callback_data *callback_data = data; return callback_data->new_address_callback_data.callback_invoked && aws_array_list_length(&callback_data->new_address_callback_data.address_list) == callback_data->new_address_callback_data.expected_num_addresses; } static bool s_listener_expired_address_complete_set_predicate(void *data) { struct listener_test_callback_data *callback_data = data; return callback_data->expired_address_callback_data.callback_invoked && aws_array_list_length(&callback_data->expired_address_callback_data.address_list) == callback_data->expired_address_callback_data.expected_num_addresses; } static bool s_listener_shutdown_invoked_predicate(void *data) { struct listener_test_callback_data *callback_data = data; return callback_data->shutdown_invoked; } static void s_listener_test_callback_type_data_init( struct listener_test_callback_type_data *callback_data, struct aws_allocator *allocator, uint32_t expected_num_addresses) { callback_data->expected_num_addresses = expected_num_addresses; aws_array_list_init_dynamic(&callback_data->address_list, allocator, 4, sizeof(struct aws_host_address *)); } static void s_listener_test_callback_data_init( struct aws_allocator *allocator, struct aws_mutex *mutex, uint32_t expected_new_addresses, uint32_t expected_expired_addresses, struct listener_test_callback_data *callback_data) { AWS_ZERO_STRUCT(*callback_data); callback_data->allocator = allocator; aws_condition_variable_init(&callback_data->condition_variable); callback_data->mutex = mutex; s_listener_test_callback_type_data_init( &callback_data->new_address_callback_data, allocator, expected_new_addresses); s_listener_test_callback_type_data_init( &callback_data->expired_address_callback_data, allocator, expected_expired_addresses); } static void s_clear_host_address_array_list(struct aws_array_list *host_address_array_list) { for (size_t i = 0; i < aws_array_list_length(host_address_array_list); ++i) { struct aws_host_address *host_address = NULL; aws_array_list_get_at(host_address_array_list, &host_address, i); if (host_address != NULL) { struct aws_allocator *allocator = host_address->allocator; aws_host_address_clean_up(host_address); aws_mem_release(allocator, host_address); } } aws_array_list_clear(host_address_array_list); } static void s_listener_test_callback_type_data_clean_up(struct listener_test_callback_type_data *callback_data) { s_clear_host_address_array_list(&callback_data->address_list); aws_array_list_clean_up(&callback_data->address_list); } static void s_listener_test_callback_data_clean_up(struct listener_test_callback_data *callback_data) { s_listener_test_callback_type_data_clean_up(&callback_data->new_address_callback_data); s_listener_test_callback_type_data_clean_up(&callback_data->expired_address_callback_data); } static void s_listener_address_callback( struct listener_test_callback_data *callback_data, struct listener_test_callback_type_data *callback_type_data, const struct aws_array_list *address_list) { bool expected_num_addresses_received = false; aws_mutex_lock(callback_data->mutex); callback_type_data->callback_invoked = true; size_t address_count = aws_array_list_length(address_list); for (size_t address_index = 0; address_index < address_count; ++address_index) { struct aws_host_address *host_address = NULL; aws_array_list_get_at_ptr(address_list, (void **)&host_address, address_index); struct aws_host_address *host_address_copy = aws_mem_acquire(callback_data->allocator, sizeof(struct aws_host_address)); aws_host_address_copy(host_address, host_address_copy); aws_array_list_push_back(&callback_type_data->address_list, &host_address_copy); } expected_num_addresses_received = aws_array_list_length(&callback_type_data->address_list) == callback_type_data->expected_num_addresses; AWS_LOGF_INFO( AWS_LS_IO_DNS, "Listener received callback for %d of %d addresses.", (uint32_t)address_count, (uint32_t)callback_type_data->expected_num_addresses); aws_mutex_unlock(callback_data->mutex); if (expected_num_addresses_received) { aws_condition_variable_notify_one(&callback_data->condition_variable); } } static void s_listener_new_address_callback( struct aws_host_listener *listener, const struct aws_array_list *new_address_list, void *user_data) { (void)listener; struct listener_test_callback_data *callback_data = user_data; struct listener_test_callback_type_data *callback_type_data = &callback_data->new_address_callback_data; AWS_LOGF_INFO(AWS_LS_IO_DNS, "Listener new address callback."); s_listener_address_callback(callback_data, callback_type_data, new_address_list); } static void s_listener_expired_address_callback( struct aws_host_listener *listener, const struct aws_array_list *expired_address_list, void *user_data) { (void)listener; struct listener_test_callback_data *callback_data = user_data; struct listener_test_callback_type_data *callback_type_data = &callback_data->expired_address_callback_data; AWS_LOGF_INFO(AWS_LS_IO_DNS, "Listener expired address callback."); s_listener_address_callback(callback_data, callback_type_data, expired_address_list); } /* For test cases where we don't care at all about the result of the initial non-listener resolver callback. */ void s_listener_test_initial_resolved_callback_empty( struct aws_host_resolver *resolver, const struct aws_string *host_name, int err_code, const struct aws_array_list *host_addresses, void *user_data) { (void)resolver; (void)host_name; (void)err_code; (void)host_addresses; (void)user_data; } /* Setup a mock host resolver with the specified test host name and number of addresses. */ static int s_setup_mock_host( struct aws_allocator *allocator, struct aws_host_resolver *resolver, struct mock_dns_resolver *mock_resolver, struct aws_string *host_name, uint32_t num_ipv4, uint32_t num_ipv6, uint32_t max_num_resolves) { (void)resolver; struct aws_array_list address_list; ASSERT_SUCCESS( aws_array_list_init_dynamic(&address_list, allocator, num_ipv4 + num_ipv6, sizeof(struct aws_host_address))); for (uint32_t i = 0; i < num_ipv4; ++i) { char address_buffer[128] = ""; snprintf(address_buffer, sizeof(address_buffer), "test_address_%d_ipv4", i); struct aws_host_address host_address_ipv4 = { .address = aws_string_new_from_c_str(allocator, address_buffer), .allocator = allocator, .expiry = 0, .host = aws_string_new_from_string(allocator, host_name), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; ASSERT_SUCCESS(aws_array_list_push_back(&address_list, &host_address_ipv4)); } for (uint32_t i = 0; i < num_ipv6; ++i) { char address_buffer[128] = ""; snprintf(address_buffer, sizeof(address_buffer), "test_address_%d_ipv6", i); struct aws_host_address host_address_ipv6 = { .address = aws_string_new_from_c_str(allocator, address_buffer), .allocator = allocator, .expiry = 0, .host = aws_string_new_from_string(allocator, host_name), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; ASSERT_SUCCESS(aws_array_list_push_back(&address_list, &host_address_ipv6)); } ASSERT_SUCCESS(mock_dns_resolver_init(mock_resolver, max_num_resolves, allocator)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(mock_resolver, &address_list)); return AWS_OP_SUCCESS; } /* Verify that a list of addresses has the expected number of ipv4 and ipv6 addresses, and has no duplicates. */ static int s_verify_mock_address_list( struct aws_array_list *address_list, uint32_t expected_num_ipv4, uint32_t expected_num_ipv6) { /* We shouldn't have any more addresses than the sum of our expected amount. */ ASSERT_TRUE(aws_array_list_length(address_list) == (expected_num_ipv4 + expected_num_ipv6)); uint32_t num_ipv4 = 0; uint32_t num_ipv6 = 0; /* Make sure we have the expected number of ipv4 and ipv6 addresses. */ for (size_t i = 0; i < aws_array_list_length(address_list); ++i) { struct aws_host_address *host_address = NULL; ASSERT_SUCCESS(aws_array_list_get_at(address_list, &host_address, i)); if (host_address->record_type == AWS_ADDRESS_RECORD_TYPE_A) { ++num_ipv4; } else if (host_address->record_type == AWS_ADDRESS_RECORD_TYPE_AAAA) { ++num_ipv6; } else { AWS_FATAL_ASSERT(false); } } ASSERT_TRUE(num_ipv4 == expected_num_ipv4); ASSERT_TRUE(num_ipv6 == expected_num_ipv6); /* Make sure we don't have any duplicates. This is n^2, but list size is expected to be small, and this is only used * for testing. */ for (size_t i = 0; i < aws_array_list_length(address_list); ++i) { struct aws_host_address *host_address0 = NULL; ASSERT_SUCCESS(aws_array_list_get_at(address_list, &host_address0, i)); struct aws_byte_cursor address_byte_cursor0 = aws_byte_cursor_from_string(host_address0->address); for (size_t j = 0; j < aws_array_list_length(address_list); ++j) { if (i == j) { continue; } struct aws_host_address *host_address1 = NULL; ASSERT_SUCCESS(aws_array_list_get_at(address_list, &host_address1, j)); struct aws_byte_cursor address_byte_cursor1 = aws_byte_cursor_from_string(host_address1->address); ASSERT_FALSE(aws_byte_cursor_eq(&address_byte_cursor0, &address_byte_cursor1)); } } return AWS_OP_SUCCESS; } static int s_verify_address_in_list( struct aws_array_list *address_list, struct aws_byte_cursor address, enum aws_address_record_type address_type) { /* Check for address in list */ for (size_t i = 0; i < aws_array_list_length(address_list); ++i) { struct aws_host_address *host_address = NULL; ASSERT_SUCCESS(aws_array_list_get_at(address_list, &host_address, i)); if (host_address->record_type != address_type) { continue; } struct aws_byte_cursor record_address = aws_byte_cursor_from_string(host_address->address); if (!aws_byte_cursor_eq(&address, &record_address)) { continue; } return AWS_OP_SUCCESS; } return AWS_OP_ERR; } static void s_listener_shutdown_callback(void *user_data) { struct listener_test_callback_data *callback_data = user_data; aws_mutex_lock(callback_data->mutex); callback_data->shutdown_invoked = true; aws_mutex_unlock(callback_data->mutex); aws_condition_variable_notify_one(&callback_data->condition_variable); } static void s_wait_on_listener_shutdown(struct listener_test_callback_data *callback_data) { aws_mutex_lock(callback_data->mutex); aws_condition_variable_wait_pred( &callback_data->condition_variable, callback_data->mutex, s_listener_shutdown_invoked_predicate, callback_data); aws_mutex_unlock(callback_data->mutex); } static int s_test_resolver_listener_create_destroy_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_host"); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, 0, 0, &callback_data); /* Setup listener and then release the listener */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; } s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); s_listener_test_callback_data_clean_up(&callback_data); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_listener_create_destroy, s_test_resolver_listener_create_destroy_fn) static int s_test_resolver_add_listener_before_host_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("s3.us-east-1.amazonaws.com"); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, 1, 0, &callback_data); /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 1, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config, NULL)); aws_string_destroy(host_name_str); } /* Wait for listener to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_TRUE(aws_array_list_length(&callback_data.new_address_callback_data.address_list) > 0); aws_mutex_unlock(&mutex); } aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); s_listener_test_callback_data_clean_up(&callback_data); aws_mutex_clean_up(&mutex); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_add_listener_before_host, s_test_resolver_add_listener_before_host_fn) /* Callback used for waiting until the initial host resolution has completed. */ void s_listener_test_initial_resolved_callback_wait( struct aws_host_resolver *resolver, const struct aws_string *host_name, int err_code, const struct aws_array_list *host_addresses, void *user_data) { (void)resolver; (void)host_name; (void)err_code; (void)host_addresses; struct listener_test_callback_data *callback_data = user_data; aws_mutex_lock(callback_data->mutex); callback_data->new_address_callback_data.callback_invoked = true; aws_mutex_unlock(callback_data->mutex); aws_condition_variable_notify_one(&callback_data->condition_variable); } static int s_test_resolver_add_listener_after_host_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("s3.us-east-1.amazonaws.com"); struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, 1, 0, &callback_data); /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 30, .impl = aws_default_dns_resolve, .impl_data = NULL, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_wait, &config, &callback_data)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; aws_mutex_unlock(&mutex); } /* Setup listener after host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } /* Wait for listeners to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_TRUE(aws_array_list_length(&callback_data.new_address_callback_data.address_list) > 0); aws_mutex_unlock(&mutex); } aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); s_listener_test_callback_data_clean_up(&callback_data); aws_mutex_clean_up(&mutex); aws_string_destroy(host_name_str); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_add_listener_after_host, s_test_resolver_add_listener_after_host_fn) static int s_test_resolver_add_multiple_listeners_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("s3.us-east-1.amazonaws.com"); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener1 = NULL; struct aws_host_listener *listener2 = NULL; struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data1; struct listener_test_callback_data callback_data2; s_listener_test_callback_data_init(allocator, &mutex, 1, 0, &callback_data1); s_listener_test_callback_data_init(allocator, &mutex, 1, 0, &callback_data2); /* Setup listener before host is added */ { struct aws_host_listener_options listener_options1 = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data1, }; listener1 = aws_host_resolver_add_host_listener(resolver, &listener_options1); struct aws_host_listener_options listener_options2 = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data2, }; listener2 = aws_host_resolver_add_host_listener(resolver, &listener_options2); } /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 1, .impl = aws_default_dns_resolve, .impl_data = NULL, }; struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config, NULL)); aws_string_destroy(host_name_str); } /* Wait for listener to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data1.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data1); /* Reset flag for re-use */ callback_data1.new_address_callback_data.callback_invoked = false; aws_condition_variable_wait_pred( &callback_data2.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data2); /* Reset flag for re-use */ callback_data2.new_address_callback_data.callback_invoked = false; ASSERT_TRUE(aws_array_list_length(&callback_data1.new_address_callback_data.address_list) > 0); ASSERT_TRUE(aws_array_list_length(&callback_data2.new_address_callback_data.address_list) > 0); aws_mutex_unlock(&mutex); } aws_host_resolver_remove_host_listener(resolver, listener1); listener1 = NULL; aws_host_resolver_remove_host_listener(resolver, listener2); listener2 = NULL; s_wait_on_listener_shutdown(&callback_data1); s_wait_on_listener_shutdown(&callback_data2); aws_host_resolver_release(resolver); s_listener_test_callback_data_clean_up(&callback_data1); s_listener_test_callback_data_clean_up(&callback_data2); aws_mutex_clean_up(&mutex); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); return 0; } AWS_TEST_CASE(test_resolver_add_multiple_listeners_fn, s_test_resolver_add_multiple_listeners_fn) /* Test to make sure that a host listener still works even when a host entry is removed and re-added. */ static int s_test_resolver_listener_host_re_add_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); const uint32_t num_ipv4 = 1; const uint32_t num_ipv6 = 1; const size_t max_ttl_seconds = 1; struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_host"); struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, num_ipv4 + num_ipv6, 0, &callback_data); /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } /* Trigger resolve host */ struct mock_dns_resolver mock_resolver_0; ASSERT_SUCCESS(s_setup_mock_host(allocator, resolver, &mock_resolver_0, host_name_str, num_ipv4, num_ipv6, 1)); struct aws_host_resolution_config config_0 = { .max_ttl = max_ttl_seconds, .impl = mock_dns_resolve, .impl_data = &mock_resolver_0, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config_0, NULL)); /* Wait for listener to receive host resolved callback. */ ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_SUCCESS( s_verify_mock_address_list(&callback_data.new_address_callback_data.address_list, num_ipv4, num_ipv6)); aws_mutex_unlock(&mutex); s_listener_test_callback_data_clean_up(&callback_data); s_listener_test_callback_data_init(allocator, &mutex, num_ipv4 + num_ipv6, 0, &callback_data); /* Wait for TTL + half a second so that the host gets completely removed. */ { const uint64_t half_second_in_millseconds = 500; const uint64_t wait_interval = aws_timestamp_convert(max_ttl_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL) + aws_timestamp_convert(half_second_in_millseconds, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL); aws_thread_current_sleep(wait_interval); } size_t address_count = aws_host_resolver_get_host_address_count( resolver, host_name_str, AWS_GET_HOST_ADDRESS_COUNT_RECORD_TYPE_A | AWS_GET_HOST_ADDRESS_COUNT_RECORD_TYPE_AAAA); /* If the host is really gone, we shouldn't have any addresses. */ ASSERT_TRUE(address_count == 0); /* Trigger resolve host with a new mock resolver, effectively re-adding the host. */ struct mock_dns_resolver mock_resolver_1; ASSERT_SUCCESS(s_setup_mock_host(allocator, resolver, &mock_resolver_1, host_name_str, num_ipv4, num_ipv6, 1)); struct aws_host_resolution_config config_1 = { .max_ttl = max_ttl_seconds, .impl = mock_dns_resolve, .impl_data = &mock_resolver_1, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config_1, NULL)); /* Wait for listener to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_SUCCESS( s_verify_mock_address_list(&callback_data.new_address_callback_data.address_list, num_ipv4, num_ipv6)); aws_mutex_unlock(&mutex); } s_listener_test_callback_data_clean_up(&callback_data); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); aws_mutex_clean_up(&mutex); aws_string_destroy(host_name_str); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver_0); mock_dns_resolver_clean_up(&mock_resolver_1); return 0; } AWS_TEST_CASE(test_resolver_listener_host_re_add_fn, s_test_resolver_listener_host_re_add_fn) static int s_test_resolver_listener_multiple_results_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); const uint32_t num_ipv4 = 4; const uint32_t num_ipv6 = 4; const uint32_t max_num_resolves = 1; struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_host"); struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS( s_setup_mock_host(allocator, resolver, &mock_resolver, host_name_str, num_ipv4, num_ipv6, max_num_resolves)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, num_ipv4 + num_ipv6, 0, &callback_data); /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 30, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config, NULL)); } /* Wait for listener to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_SUCCESS( s_verify_mock_address_list(&callback_data.new_address_callback_data.address_list, num_ipv4, num_ipv6)); aws_mutex_unlock(&mutex); } s_listener_test_callback_data_clean_up(&callback_data); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); aws_mutex_clean_up(&mutex); aws_string_destroy(host_name_str); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE(test_resolver_listener_multiple_results, s_test_resolver_listener_multiple_results_fn) static uint64_t s_mocked_time = 0; static struct aws_mutex s_mocked_time_lock = AWS_MUTEX_INIT; int s_get_mock_system_clock(uint64_t *timestamp) { aws_mutex_lock(&s_mocked_time_lock); *timestamp = s_mocked_time; aws_mutex_unlock(&s_mocked_time_lock); return AWS_OP_SUCCESS; } void s_set_mock_system_clock(uint64_t timestamp) { aws_mutex_lock(&s_mocked_time_lock); s_mocked_time = timestamp; aws_mutex_unlock(&s_mocked_time_lock); } static int s_test_resolver_listener_address_expired_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); const uint32_t num_ipv4 = 4; const uint32_t num_ipv6 = 4; const uint32_t max_num_resolves = 1; struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_host"); struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, .system_clock_override_fn = s_get_mock_system_clock, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS( s_setup_mock_host(allocator, resolver, &mock_resolver, host_name_str, num_ipv4, num_ipv6, max_num_resolves)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; /* * Internal default resolver detail: we don't expire the last remaining address, so we expect to see * everything but one in each of the a and aaaa lists expire. */ uint32_t expected_expirations = num_ipv4 + num_ipv6 - 2; s_listener_test_callback_data_init(allocator, &mutex, num_ipv4 + num_ipv6, expected_expirations, &callback_data); /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .resolved_address_callback = s_listener_new_address_callback, .expired_address_callback = s_listener_expired_address_callback, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 30, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_listener_test_initial_resolved_callback_empty, &config, NULL)); } /* Wait for listener to receive host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.new_address_callback_data.callback_invoked = false; ASSERT_SUCCESS( s_verify_mock_address_list(&callback_data.new_address_callback_data.address_list, num_ipv4, num_ipv6)); ASSERT_INT_EQUALS(0, aws_array_list_length(&callback_data.expired_address_callback_data.address_list)); aws_mutex_unlock(&mutex); } /* advance time far enough that the addresses should expire */ uint64_t expiration_time = aws_timestamp_convert(30, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL) + 1; s_set_mock_system_clock(expiration_time); /* Wait for listener to receive address expired callback */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &callback_data.condition_variable, &mutex, s_listener_expired_address_invoked_predicate, &callback_data); /* Reset flag for re-use */ callback_data.expired_address_callback_data.callback_invoked = false; ASSERT_SUCCESS(s_verify_mock_address_list( &callback_data.expired_address_callback_data.address_list, num_ipv4 - 1, num_ipv6 - 1)); aws_mutex_unlock(&mutex); } s_listener_test_callback_data_clean_up(&callback_data); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); aws_mutex_clean_up(&mutex); aws_string_destroy(host_name_str); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE(test_resolver_listener_address_expired_fn, s_test_resolver_listener_address_expired_fn) /* * This test works on the following assumption: * * Failed resolves will always generate a callback from the host's resolution thread (not true for successful * resolves, and new/expired callbacks are unreliable to generate on a pinned resolver). So we can test * whether or not a host entry is successfully pinned by making two failure-bound resolution calls inbetween * a wait that is substantially longer than the TTL of the resolution and then checking the thread ids captured * in the two callbacks. If they're different, we know that the resolution thread was destroyed and remade; if * they're the same, we know the resolution thread was successfully pinned. */ static int s_test_host_entry_pinning(struct aws_allocator *allocator, bool pin_host_entry) { aws_io_library_init(allocator); struct aws_byte_cursor host_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_host"); struct aws_string *host_name_str = aws_string_new_from_c_str(allocator, (const char *)host_name.ptr); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); struct aws_host_listener *listener = NULL; struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS(s_setup_mock_host(allocator, resolver, &mock_resolver, host_name_str, 0, 0, 0)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data callback_data; s_listener_test_callback_data_init(allocator, &mutex, 0, 0, &callback_data); struct default_host_callback_data resolve_callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = host_name, .shutdown_callback = s_listener_shutdown_callback, .user_data = &callback_data, .pin_host_entry = pin_host_entry, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } aws_thread_id_t first_thread_id; /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 1, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_default_host_resolved_test_callback, &config, &resolve_callback_data)); } /* Wait for listener to receive (failed) host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &resolve_callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &resolve_callback_data); first_thread_id = resolve_callback_data.callback_thread_id; resolve_callback_data.invoked = false; aws_mutex_unlock(&mutex); } aws_thread_current_sleep(aws_timestamp_convert(3, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); aws_thread_id_t second_thread_id; /* Trigger resolve host */ { struct aws_host_resolution_config config = { .max_ttl = 1, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name_str, s_default_host_resolved_test_callback, &config, &resolve_callback_data)); } /* Wait for listener to receive (failed) host resolved callback. */ { ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &resolve_callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &resolve_callback_data); second_thread_id = resolve_callback_data.callback_thread_id; aws_mutex_unlock(&mutex); } /* compare the two thread ids */ ASSERT_TRUE(aws_thread_thread_id_equal(first_thread_id, second_thread_id) == pin_host_entry); s_listener_test_callback_data_clean_up(&callback_data); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&callback_data); aws_host_resolver_release(resolver); aws_mutex_clean_up(&mutex); aws_string_destroy(host_name_str); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); mock_dns_resolver_clean_up(&mock_resolver); return AWS_OP_SUCCESS; } static int s_test_resolver_pinned_host_entry(struct aws_allocator *allocator, void *ctx) { (void)ctx; ASSERT_SUCCESS(s_test_host_entry_pinning(allocator, true)); return 0; } AWS_TEST_CASE(test_resolver_pinned_host_entry, s_test_resolver_pinned_host_entry) static int s_test_resolver_unpinned_host_entry(struct aws_allocator *allocator, void *ctx) { (void)ctx; ASSERT_SUCCESS(s_test_host_entry_pinning(allocator, false)); return 0; } AWS_TEST_CASE(test_resolver_unpinned_host_entry, s_test_resolver_unpinned_host_entry) /* * A variant of the connect_failure_recording test that checks to see if failed addresses that are * re-promoted to good end up invoking the new address listener callback. Also checks that failed * addresses invoke the expiration callback */ static int s_test_resolver_address_promote_demote_listener_callbacks_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_io_library_init(allocator); struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); struct aws_host_resolver_default_options resolver_options = { .el_group = el_group, .max_entries = 10, }; struct aws_host_resolver *resolver = aws_host_resolver_new_default(allocator, &resolver_options); const struct aws_string *host_name = aws_string_new_from_c_str(allocator, "host_address"); const struct aws_string *addr1_ipv4 = aws_string_new_from_c_str(allocator, "address1ipv4"); const struct aws_string *addr1_ipv6 = aws_string_new_from_c_str(allocator, "address1ipv6"); const struct aws_string *addr2_ipv4 = aws_string_new_from_c_str(allocator, "address2ipv4"); const struct aws_string *addr2_ipv6 = aws_string_new_from_c_str(allocator, "address2ipv6"); struct mock_dns_resolver mock_resolver; ASSERT_SUCCESS(mock_dns_resolver_init(&mock_resolver, 1000, allocator)); struct aws_host_resolution_config config = { .max_ttl = 30, .impl = mock_dns_resolve, .impl_data = &mock_resolver, }; struct aws_host_address host_address_1_ipv4 = { .address = addr1_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_1_ipv6 = { .address = addr1_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv4 = { .address = addr2_ipv4, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_A, .use_count = 0, .weight = 0, }; struct aws_host_address host_address_2_ipv6 = { .address = addr2_ipv6, .allocator = allocator, .expiry = 0, .host = aws_string_new_from_c_str(allocator, "host_address"), .connection_failure_count = 0, .record_type = AWS_ADDRESS_RECORD_TYPE_AAAA, .use_count = 0, .weight = 0, }; struct aws_array_list address_list_1; ASSERT_SUCCESS(aws_array_list_init_dynamic(&address_list_1, allocator, 2, sizeof(struct aws_host_address))); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_1_ipv4)); ASSERT_SUCCESS(aws_array_list_push_back(&address_list_1, &host_address_2_ipv4)); ASSERT_SUCCESS(mock_dns_resolver_append_address_list(&mock_resolver, &address_list_1)); struct aws_mutex mutex = AWS_MUTEX_INIT; struct listener_test_callback_data listener_callback_data; s_listener_test_callback_data_init(allocator, &mutex, 4, 2, &listener_callback_data); struct aws_host_listener *listener = NULL; /* Setup listener before host is added */ { struct aws_host_listener_options listener_options = { .host_name = aws_byte_cursor_from_string(host_name), .shutdown_callback = s_listener_shutdown_callback, .resolved_address_callback = s_listener_new_address_callback, .expired_address_callback = s_listener_expired_address_callback, .user_data = &listener_callback_data, .pin_host_entry = true, }; listener = aws_host_resolver_add_host_listener(resolver, &listener_options); } struct default_host_callback_data resolve_callback_data = { .condition_variable = AWS_CONDITION_VARIABLE_INIT, .invoked = false, .has_aaaa_address = false, .has_a_address = false, .mutex = &mutex, }; /* * Resolve #1 * Wait on new_address listener callback, should get 4 new addresses */ ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_listener_test_initial_resolved_callback_empty, &config, NULL)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); aws_condition_variable_wait_pred( &listener_callback_data.condition_variable, &mutex, s_listener_new_address_invoked_predicate, &listener_callback_data); /* Check new address data */ ASSERT_INT_EQUALS(4, aws_array_list_length(&listener_callback_data.new_address_callback_data.address_list)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv4), AWS_ADDRESS_RECORD_TYPE_A)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv6), AWS_ADDRESS_RECORD_TYPE_AAAA)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr2_ipv4), AWS_ADDRESS_RECORD_TYPE_A)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr2_ipv6), AWS_ADDRESS_RECORD_TYPE_AAAA)); /* Reset new address data */ s_clear_host_address_array_list(&listener_callback_data.new_address_callback_data.address_list); listener_callback_data.new_address_callback_data.callback_invoked = false; aws_mutex_unlock(&mutex); /* * Resolve #2 * Wait on resolver callback (will be a previously seen address) * Then fail address 1 both ipv6 and ipv4 */ ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &resolve_callback_data)); aws_mutex_lock(&mutex); aws_condition_variable_wait_pred( &resolve_callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &resolve_callback_data); aws_host_address_clean_up(&resolve_callback_data.aaaa_address); aws_host_address_clean_up(&resolve_callback_data.a_address); resolve_callback_data.invoked = false; ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_1_ipv6)); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_1_ipv4)); /* should not see any new or expired addresses at this point */ ASSERT_FALSE(listener_callback_data.new_address_callback_data.callback_invoked); ASSERT_FALSE(listener_callback_data.expired_address_callback_data.callback_invoked); aws_mutex_unlock(&mutex); /* * Resolve #3 * Following the LRU policy, address 1 should be what gets returned here, however we marked it as failed, so it * should be skipped and address 2 should be returned. * * We should also get expiration notices for the two failed addresses */ ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &resolve_callback_data)); aws_mutex_lock(&mutex); /* * Wait for both expiration and resolution completion */ aws_condition_variable_wait_pred( &resolve_callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &resolve_callback_data); aws_condition_variable_wait_pred( &listener_callback_data.condition_variable, &mutex, s_listener_expired_address_invoked_predicate, &listener_callback_data); aws_host_address_clean_up(&resolve_callback_data.aaaa_address); aws_host_address_clean_up(&resolve_callback_data.a_address); resolve_callback_data.invoked = false; /* * Check listener callback data. We expect two expired addresses due to failure and no new addresses. */ ASSERT_INT_EQUALS(2, aws_array_list_length(&listener_callback_data.expired_address_callback_data.address_list)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.expired_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv4), AWS_ADDRESS_RECORD_TYPE_A)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.expired_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv6), AWS_ADDRESS_RECORD_TYPE_AAAA)); ASSERT_FALSE(listener_callback_data.new_address_callback_data.callback_invoked); listener_callback_data.expired_address_callback_data.callback_invoked = false; s_clear_host_address_array_list(&listener_callback_data.expired_address_callback_data.address_list); /* * Prep for the new address callbacks which we should receive when the failed ones get promoted * next resolve. */ listener_callback_data.new_address_callback_data.callback_invoked = false; listener_callback_data.new_address_callback_data.expected_num_addresses = 2; ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_2_ipv6)); ASSERT_SUCCESS(aws_host_resolver_record_connection_failure(resolver, &host_address_2_ipv4)); aws_mutex_unlock(&mutex); /* * Resolve #4 * By failing address2 previously, all addresses should now be failed. In response, the resolver should * promote address 1 back to a good state. So we should see * (1) 2 expired addresses (from the fail calls above) * (2) 2 new addresses (previously failed addresses promoted out of desperation) */ ASSERT_SUCCESS(aws_host_resolver_resolve_host( resolver, host_name, s_default_host_resolved_test_callback, &config, &resolve_callback_data)); /* here address 1 should be returned since it is now the least recently used address and all of them have failed.. */ aws_mutex_lock(&mutex); /* * Wait for resolution, expiration, and new listener callbacks */ aws_condition_variable_wait_pred( &resolve_callback_data.condition_variable, &mutex, s_default_host_resolved_predicate, &resolve_callback_data); aws_condition_variable_wait_pred( &listener_callback_data.condition_variable, &mutex, s_listener_new_address_complete_set_predicate, &listener_callback_data); aws_condition_variable_wait_pred( &listener_callback_data.condition_variable, &mutex, s_listener_expired_address_complete_set_predicate, &listener_callback_data); aws_host_address_clean_up(&resolve_callback_data.aaaa_address); aws_host_address_clean_up(&resolve_callback_data.a_address); /* * Check new address callback data */ ASSERT_TRUE(2 == aws_array_list_length(&listener_callback_data.new_address_callback_data.address_list)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv4), AWS_ADDRESS_RECORD_TYPE_A)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.new_address_callback_data.address_list, aws_byte_cursor_from_string(addr1_ipv6), AWS_ADDRESS_RECORD_TYPE_AAAA)); /* * Check expired address callback data */ ASSERT_TRUE(2 == aws_array_list_length(&listener_callback_data.expired_address_callback_data.address_list)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.expired_address_callback_data.address_list, aws_byte_cursor_from_string(addr2_ipv4), AWS_ADDRESS_RECORD_TYPE_A)); ASSERT_SUCCESS(s_verify_address_in_list( &listener_callback_data.expired_address_callback_data.address_list, aws_byte_cursor_from_string(addr2_ipv6), AWS_ADDRESS_RECORD_TYPE_AAAA)); aws_mutex_unlock(&mutex); s_listener_test_callback_data_clean_up(&listener_callback_data); aws_host_resolver_remove_host_listener(resolver, listener); listener = NULL; s_wait_on_listener_shutdown(&listener_callback_data); aws_host_resolver_release(resolver); aws_event_loop_group_release(el_group); aws_io_library_clean_up(); aws_string_destroy((void *)host_name); mock_dns_resolver_clean_up(&mock_resolver); return 0; } AWS_TEST_CASE( test_resolver_address_promote_demote_listener_callbacks, s_test_resolver_address_promote_demote_listener_callbacks_fn)