diff --git a/src/resolve/meson.build b/src/resolve/meson.build index ccc1f8be97..91ca34127d 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -243,6 +243,17 @@ executables += [ ], 'include_directories' : resolve_includes, }, + test_template + { + 'sources' : [ + files('test-dns-query.c'), + basic_dns_sources, + systemd_resolved_sources, + ], + 'dependencies' : [ + systemd_resolved_dependencies, + ], + 'include_directories' : resolve_includes, + }, test_template + { 'sources' : [ files('test-resolved-stream.c'), diff --git a/src/resolve/test-dns-query.c b/src/resolve/test-dns-query.c new file mode 100644 index 0000000000..53c4588474 --- /dev/null +++ b/src/resolve/test-dns-query.c @@ -0,0 +1,883 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "log.h" +#include "resolved-dns-query.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-scope.h" +#include "resolved-dns-server.h" +#include "resolved-link.h" +#include "resolved-manager.h" +#include "tests.h" + +static char* checked_strdup(const char *str) { + char *copy = strdup(str); + ASSERT_NOT_NULL(copy); + return copy; +} + +/* ================================================================ + * dns_query_new() + * ================================================================ */ + +TEST(dns_query_new_single_question) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, question, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(query); +} + +TEST(dns_query_new_multi_question_same_domain) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceKey *key = NULL; + + question = dns_question_new(2); + ASSERT_NOT_NULL(question); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_question_add(question, key, 0)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, "www.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_question_add(question, key, 0)); + dns_resource_key_unref(key); + + ASSERT_OK(dns_query_new(&manager, &query, question, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(query); +} + +TEST(dns_query_new_multi_question_different_domain) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceKey *key = NULL; + + question = dns_question_new(2); + ASSERT_NOT_NULL(question); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "ns1.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_question_add(question, key, 0)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, "ns2.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_question_add(question, key, 0)); + dns_resource_key_unref(key); + + ASSERT_ERROR(dns_query_new(&manager, &query, question, NULL, NULL, 1, 0), EINVAL); + ASSERT_NULL(query); +} + +#if HAVE_LIBIDN || HAVE_LIBIDN2 +TEST(dns_query_new_same_utf8_and_idna) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *q_utf8 = NULL, *q_idna = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&q_utf8, AF_INET, "www.\xF0\x9F\x98\xB1.com", false)); + ASSERT_NOT_NULL(q_utf8); + + ASSERT_OK(dns_question_new_address(&q_idna, AF_INET, "www.\xF0\x9F\x98\xB1.com", true)); + ASSERT_NOT_NULL(q_idna); + + ASSERT_OK(dns_query_new(&manager, &query, q_utf8, q_idna, NULL, 1, 0)); + ASSERT_NOT_NULL(query); +} + +TEST(dns_query_new_different_utf8_and_idna) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *q_utf8 = NULL, *q_idna = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&q_utf8, AF_INET, "www.\xF0\x9F\x98\xB1.com", false)); + ASSERT_NOT_NULL(q_utf8); + + ASSERT_OK(dns_question_new_address(&q_idna, AF_INET, "www.\xF0\x9F\x8E\xBC.com", true)); + ASSERT_NOT_NULL(q_idna); + + ASSERT_OK(dns_query_new(&manager, &query, q_utf8, q_idna, NULL, 1, 0)); + ASSERT_NOT_NULL(query); +} +#endif + +TEST(dns_query_new_bypass_ok) { + Manager manager = {}; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + + ASSERT_OK(dns_packet_new_query(&packet, DNS_PROTOCOL_DNS, 0, false)); + ASSERT_NOT_NULL(packet); + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_packet_append_question(packet, question)); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, NULL, packet, 1, 0)); + ASSERT_NOT_NULL(query); +} + +TEST(dns_query_new_bypass_conflict) { + Manager manager = {}; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL, *extra_q = NULL; + + ASSERT_OK(dns_packet_new_query(&packet, DNS_PROTOCOL_DNS, 0, false)); + ASSERT_NOT_NULL(packet); + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_packet_append_question(packet, question)); + + ASSERT_OK(dns_question_new_address(&extra_q, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(extra_q); + + ASSERT_ERROR(dns_query_new(&manager, &query, extra_q, NULL, packet, 1, 0), EINVAL); + ASSERT_NULL(query); +} + +#define MAX_QUERIES 2048 + +TEST(dns_query_new_too_many_questions) { + Manager manager = {}; + DnsQuestion *question = NULL; + DnsQuery *queries[MAX_QUERIES + 1]; + + for (size_t i = 0; i < MAX_QUERIES; i++) { + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &queries[i], question, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(queries[i]); + + dns_question_unref(question); + } + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_ERROR(dns_query_new(&manager, &queries[MAX_QUERIES], question, NULL, NULL, 1, 0), EBUSY); + dns_question_unref(question); + + for (size_t i = 0; i < MAX_QUERIES; i++) + dns_query_free(queries[i]); +} + +/* ================================================================ + * dns_query_make_auxiliary() + * ================================================================ */ + +TEST(dns_query_make_auxiliary) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *qn1 = NULL, *qn2 = NULL, *qn3 = NULL; + _cleanup_(dns_query_freep) DnsQuery *q1 = NULL, *q2 = NULL, *q3 = NULL; + + ASSERT_OK(dns_question_new_address(&qn1, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(qn1); + ASSERT_OK(dns_query_new(&manager, &q1, qn1, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(q1); + + ASSERT_OK(dns_question_new_address(&qn2, AF_INET, "www.example.net", false)); + ASSERT_NOT_NULL(qn2); + ASSERT_OK(dns_query_new(&manager, &q2, qn2, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(q2); + + ASSERT_OK(dns_question_new_address(&qn3, AF_INET, "www.example.org", false)); + ASSERT_NOT_NULL(qn3); + ASSERT_OK(dns_query_new(&manager, &q3, qn3, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(q3); + + ASSERT_OK(dns_query_make_auxiliary(q2, q1)); + ASSERT_OK(dns_query_make_auxiliary(q3, q1)); + + ASSERT_EQ(q1->n_auxiliary_queries, 2u); + ASSERT_TRUE(q1->auxiliary_queries == q3); + ASSERT_TRUE(q1->auxiliary_queries->auxiliary_queries_next == q2); + + ASSERT_TRUE(q2->auxiliary_for == q1); + ASSERT_TRUE(q3->auxiliary_for == q1); +} + +/* ================================================================ + * dns_query_process_cname_one() + * ================================================================ */ + +TEST(dns_query_process_cname_one_null) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_MATCH); +} + +TEST(dns_query_process_cname_one_success_exact_match) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->a.in_addr.s_addr = htobe32(0xc0a8017f); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_MATCH); + + ASSERT_EQ(query->n_cname_redirects, 0u); +} + +TEST(dns_query_process_cname_one_success_no_match) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "tmp.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->a.in_addr.s_addr = htobe32(0xc0a8017f); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_NOMATCH); + + ASSERT_EQ(query->n_cname_redirects, 0u); +} + +TEST(dns_query_process_cname_one_success_match_cname) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer_query_flags = SD_RESOLVED_FROM_NETWORK; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->cname.name = checked_strdup("example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_CNAME); + + ASSERT_FALSE(dns_query_fully_authenticated(query)); + ASSERT_FALSE(dns_query_fully_confidential(query)); + ASSERT_FALSE(dns_query_fully_authoritative(query)); + + ASSERT_GT(query->flags & SD_RESOLVED_NO_SEARCH, 0u); + + ASSERT_EQ(query->n_cname_redirects, 1u); + + ASSERT_EQ(dns_question_size(query->collected_questions), 1u); + ASSERT_NULL(query->question_utf8); + ASSERT_EQ(dns_question_size(query->question_idna), 1u); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_idna, key)); + dns_resource_key_unref(key); +} + +TEST(dns_query_process_cname_one_success_flags) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + query->answer_query_flags = SD_RESOLVED_FROM_NETWORK | + SD_RESOLVED_AUTHENTICATED | + SD_RESOLVED_CONFIDENTIAL | + SD_RESOLVED_SYNTHETIC; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->cname.name = checked_strdup("example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_CNAME); + + ASSERT_TRUE(dns_query_fully_authenticated(query)); + ASSERT_TRUE(dns_query_fully_confidential(query)); + ASSERT_TRUE(dns_query_fully_authoritative(query)); +} + +TEST(dns_query_process_cname_one_success_match_dname) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNAME, "example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->dname.name = checked_strdup("v2.example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_CNAME); + + ASSERT_EQ(query->n_cname_redirects, 1u); + + ASSERT_EQ(dns_question_size(query->collected_questions), 1u); + ASSERT_NULL(query->question_utf8); + ASSERT_EQ(dns_question_size(query->question_idna), 1u); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.v2.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_idna, key)); + dns_resource_key_unref(key); +} + +#if HAVE_LIBIDN || HAVE_LIBIDN2 +TEST(dns_query_process_cname_one_success_match_dname_utf8_same) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *q_utf8 = NULL, *q_idna = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_question_new_address(&q_utf8, AF_INET, "www.xn--tl8h.com", false)); + ASSERT_NOT_NULL(q_utf8); + + ASSERT_OK(dns_question_new_address(&q_idna, AF_INET, "www.\xF0\x9F\x8E\xBC.com", true)); + ASSERT_NOT_NULL(q_idna); + + ASSERT_OK(dns_query_new(&manager, &query, q_utf8, q_idna, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNAME, "xn--tl8h.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->dname.name = checked_strdup("v2.xn--tl8h.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_CNAME); + + ASSERT_EQ(query->n_cname_redirects, 1u); + + ASSERT_EQ(dns_question_size(query->collected_questions), 1u); + ASSERT_EQ(dns_question_size(query->question_utf8), 1u); + ASSERT_EQ(dns_question_size(query->question_idna), 1u); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.xn--tl8h.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.v2.xn--tl8h.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_utf8, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.v2.xn--tl8h.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_idna, key)); + dns_resource_key_unref(key); +} + +TEST(dns_query_process_cname_one_success_match_dname_utf8_different) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *q_utf8 = NULL, *q_idna = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_question_new_address(&q_utf8, AF_INET, "www.\xF0\x9F\x98\xB1.com", false)); + ASSERT_NOT_NULL(q_utf8); + + ASSERT_OK(dns_question_new_address(&q_idna, AF_INET, "www.\xF0\x9F\x8E\xBC.com", true)); + ASSERT_NOT_NULL(q_idna); + + ASSERT_OK(dns_query_new(&manager, &query, q_utf8, q_idna, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer = dns_answer_new(1); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNAME, "xn--tl8h.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->dname.name = checked_strdup("v2.xn--tl8h.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_one(query), DNS_QUERY_CNAME); + + ASSERT_EQ(query->n_cname_redirects, 1u); + + ASSERT_EQ(dns_question_size(query->collected_questions), 2u); + ASSERT_NULL(query->question_utf8); + ASSERT_EQ(dns_question_size(query->question_idna), 1u); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.\xF0\x9F\x98\xB1.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.xn--tl8h.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.v2.xn--tl8h.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_idna, key)); + dns_resource_key_unref(key); +} +#endif + +/* ================================================================ + * dns_query_process_cname_many() + * ================================================================ */ + +TEST(dns_query_process_cname_many_success_match_multiple_cname) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + DnsResourceRecord *rr = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->state = DNS_TRANSACTION_SUCCESS; + query->answer_protocol = DNS_PROTOCOL_DNS; + query->answer_family = AF_INET; + query->answer_query_flags = SD_RESOLVED_FROM_NETWORK; + query->answer = dns_answer_new(4); + ASSERT_NOT_NULL(query->answer); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->a.in_addr.s_addr = htobe32(0xc0a8017f); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->cname.name = checked_strdup("tmp1.example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "tmp2.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->cname.name = checked_strdup("example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "tmp1.example.com"); + ASSERT_NOT_NULL(rr); + rr->ttl = 3600; + rr->cname.name = checked_strdup("tmp2.example.com"); + dns_answer_add(query->answer, rr, 1, 0, NULL); + dns_resource_record_unref(rr); + + ASSERT_EQ(dns_query_process_cname_many(query), DNS_QUERY_MATCH); + + ASSERT_FALSE(dns_query_fully_authenticated(query)); + ASSERT_FALSE(dns_query_fully_confidential(query)); + ASSERT_FALSE(dns_query_fully_authoritative(query)); + + ASSERT_GT(query->flags & SD_RESOLVED_NO_SEARCH, 0u); + + ASSERT_EQ(query->n_cname_redirects, 3u); + + ASSERT_EQ(dns_question_size(query->collected_questions), 3u); + ASSERT_NULL(query->question_utf8); + ASSERT_EQ(dns_question_size(query->question_idna), 1u); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "tmp1.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "tmp2.example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->collected_questions, key)); + dns_resource_key_unref(key); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "example.com"); + ASSERT_NOT_NULL(key); + ASSERT_TRUE(dns_question_contains_key(query->question_idna, key)); + dns_resource_key_unref(key); +} + +/* ================================================================ + * dns_query_string() + * ================================================================ */ + +TEST(dns_query_string_question_utf8) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "utf8.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, question, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + const char *str = dns_query_string(query); + ASSERT_STREQ(str, "utf8.example.com"); +} + +TEST(dns_query_string_question_idna) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "idna.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, question, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + const char *str = dns_query_string(query); + ASSERT_STREQ(str, "idna.example.com"); +} + +TEST(dns_query_string_question_bypass) { + Manager manager = {}; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket * packet = NULL; + + ASSERT_OK(dns_packet_new_query(&packet, DNS_PROTOCOL_DNS, 0, false)); + ASSERT_NOT_NULL(packet); + + ASSERT_OK(dns_question_new_address(&packet->question, AF_INET, "bypass.example.com", false)); + ASSERT_NOT_NULL(packet->question); + + ASSERT_OK(dns_query_new(&manager, &query, NULL, NULL, packet, 1, 0)); + ASSERT_NOT_NULL(query); + + const char *str = dns_query_string(query); + ASSERT_STREQ(str, "bypass.example.com"); +} + +TEST(dns_query_string_request_address) { + Manager manager = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + ASSERT_OK(dns_question_new_address(&question, AF_INET, "www.example.com", false)); + ASSERT_NOT_NULL(question); + + ASSERT_OK(dns_query_new(&manager, &query, question, NULL, NULL, 1, 0)); + ASSERT_NOT_NULL(query); + + query->request_family = AF_INET; + query->request_address.in.s_addr = htobe32(0x7f000001); + query->request_address_valid = true; + + const char *str = dns_query_string(query); + ASSERT_STREQ(str, "127.0.0.1"); +} + +/* ================================================================ + * dns_query_go() + * ================================================================ */ + +/* Testing this function is somewhat problematic since, in addition to setting up the state for query + * candidates, their scopes and transactions, it also directly initiates I/O to files and the network. In + * particular: + * + * - The very first thing it does is try to respond to the query by reading the system /etc/hosts file, which + * may be symlinked to a SystemD resource. Ideally we could test this without accessing global files. + * + * - dns_scope_get_dns_server() calls manager_get_dns_server(), which tries to read /etc/resolv.conf. + * + * - A potential solution to these issues would be to let these file paths be configured instead of + * hard-coded into the source. + * + * - dns_scope_good_domain(), by checking dns_scope_get_dns_server(), will not match with a scope that does + * not have a server configured, either on the scope's link (if it has one) or the manager's main/fallback + * server. Configuring a server means that dns_query_candidate_go() and then dns_transaction_go() will send + * UDP/TCP traffic to that server. Ideally we'd like to test that we can set up all the candidate and + * transaction state without actually causing any requests to be sent. + */ + +typedef struct GoConfig { + bool has_scope; + bool use_link; + bool use_bypass; + size_t n_search_domains; +} GoConfig; + +static GoConfig mk_go_config(void) { + return (GoConfig) { + .has_scope = true, + .use_link = false, + .use_bypass = false, + .n_search_domains = 0 + }; +} + +typedef struct GoEnvironment { + Manager manager; + DnsProtocol protocol; + int family; + int ifindex; + Link *link; + DnsScope *scope; + DnsServerType server_type; + union in_addr_union server_addr; + char *server_name; + uint16_t server_port; + DnsServer *server; + size_t n_search_domains; + DnsSearchDomain *search_domains[4]; +} GoEnvironment; + +static void go_env_teardown(GoEnvironment *env) { + ASSERT_NOT_NULL(env); + + for (size_t i = 0 ; i < env->n_search_domains; i++) + dns_search_domain_unref(env->search_domains[i]); + + dns_server_unref(env->server); + dns_server_unref(env->server); + free(env->server_name); + dns_scope_free(env->scope); + sd_event_unref(env->manager.event); +} + +static const char *SEARCH_DOMAINS[] = { "local", "example.com", "org", "edu" }; + +static void go_env_setup(GoEnvironment *env, GoConfig *cfg) { + ASSERT_NOT_NULL(env); + ASSERT_NOT_NULL(cfg); + + env->manager = (Manager) {}; + env->protocol = DNS_PROTOCOL_DNS; + env->family = AF_INET; + + ASSERT_OK(sd_event_new(&env->manager.event)); + ASSERT_NOT_NULL(env->manager.event); + + if (cfg->use_link) { + env->ifindex = 1; + ASSERT_OK(link_new(&env->manager, &env->link, env->ifindex)); + ASSERT_NOT_NULL(env->link); + env->server_type = DNS_SERVER_LINK; + } else { + env->ifindex = 0; + env->link = NULL; + env->server_type = DNS_SERVER_FALLBACK; + } + + if (cfg->has_scope) { + ASSERT_OK(dns_scope_new(&env->manager, &env->scope, env->link, env->protocol, env->family)); + ASSERT_NOT_NULL(env->scope); + + env->server_addr.in.s_addr = htobe32(0x7f000001); + env->server_name = checked_strdup("localhost"); + env->server_port = 53; + + ASSERT_OK(dns_server_new(&env->manager, &env->server, env->server_type, + env->link, env->family, &env->server_addr, env->server_port, + env->ifindex, env->server_name, RESOLVE_CONFIG_SOURCE_DBUS)); + + ASSERT_NOT_NULL(env->server); + } else { + env->scope = NULL; + env->server_name = NULL; + env->server = NULL; + } + + env->n_search_domains = cfg->n_search_domains; + + for (size_t i = 0 ; i < env->n_search_domains; i++) { + DnsSearchDomainType type = (env->link == NULL) ? DNS_SEARCH_DOMAIN_SYSTEM : DNS_SEARCH_DOMAIN_LINK; + ASSERT_OK(dns_search_domain_new(&env->manager, &env->search_domains[i], type, env->link, SEARCH_DOMAINS[i])); + ASSERT_NOT_NULL(env->search_domains[i]); + } +} + +static void exercise_dns_query_go(GoConfig *cfg, void (*check_query)(DnsQuery *query)) { + _cleanup_(go_env_teardown) GoEnvironment env = {}; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + _cleanup_(dns_query_freep) DnsQuery *query = NULL; + + go_env_setup(&env, cfg); + + int flags = SD_RESOLVED_FLAGS_MAKE(env.protocol, env.family, false, false); + + if (cfg->n_search_domains > 0) { + /* search domains trigger on single-label domains */ + ASSERT_OK(dns_question_new_address(&question, env.family, "berlin", false)); + flags &= ~SD_RESOLVED_NO_SEARCH; + } else { + ASSERT_OK(dns_question_new_address(&question, env.family, "www.example.com", false)); + } + + ASSERT_NOT_NULL(question); + + if (cfg->use_bypass) { + ASSERT_OK(dns_packet_new_query(&packet, env.protocol, 0, false)); + ASSERT_NOT_NULL(packet); + DNS_PACKET_HEADER(packet)->qdcount = htobe16(1); + packet->question = dns_question_ref(question); + ASSERT_OK(dns_packet_append_question(packet, question)); + + /* search domains must be turned off for bypass queries, otherwise dns_query_add_candidate() + * tries to extract the domain name from question_idna which cannot exist on bypasses. */ + flags |= SD_RESOLVED_NO_SEARCH; + ASSERT_OK(dns_query_new(&env.manager, &query, NULL, NULL, packet, env.ifindex, flags)); + } else { + ASSERT_OK(dns_query_new(&env.manager, &query, question, question, NULL, env.ifindex, flags)); + } + + ASSERT_NOT_NULL(query); + ASSERT_TRUE(dns_query_go(query)); + + if (check_query != NULL) + check_query(query); +} + +static void check_query_no_servers(DnsQuery *query) { + ASSERT_NOT_NULL(query); + + ASSERT_EQ(dns_answer_size(query->answer), 0u); + ASSERT_EQ(query->answer_rcode, DNS_RCODE_SUCCESS); +} + +TEST(dns_query_go) { + GoConfig cfg; + + cfg = mk_go_config(); + exercise_dns_query_go(&cfg, NULL); + + cfg = mk_go_config(); + cfg.use_link = true; + exercise_dns_query_go(&cfg, NULL); + + cfg = mk_go_config(); + cfg.use_bypass = true; + exercise_dns_query_go(&cfg, NULL); + + cfg = mk_go_config(); + cfg.has_scope = false; + exercise_dns_query_go(&cfg, check_query_no_servers); + + cfg = mk_go_config(); + cfg.n_search_domains = 2; + exercise_dns_query_go(&cfg, NULL); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);