/* * Copyright (C) 2017, 2018 Internet Systems Consortium, Inc. ("ISC") * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /*! \file */ #include #include #include #include #include #include #include #include "../hooks.h" #include "nstest.h" /***** ***** ns__query_sfcache() tests *****/ /*% * Structure containing parameters for ns__query_sfcache_test(). */ typedef struct { const ns_test_id_t id; /* libns test identifier */ unsigned int qflags; /* query flags */ isc_boolean_t cache_entry_present; /* whether a SERVFAIL cache entry matching the query should be present */ isc_uint32_t cache_entry_flags; /* NS_FAILCACHE_* flags to set for the SERVFAIL cache entry */ isc_boolean_t servfail_expected; /* whether a cached SERVFAIL is expected to be returned */ } ns__query_sfcache_test_params_t; /*% * Perform a single ns__query_sfcache() check using given parameters. */ static void ns__query_sfcache_test(const ns__query_sfcache_test_params_t *test) { query_ctx_t *qctx = NULL; isc_result_t result; REQUIRE(test != NULL); REQUIRE(test->id.description != NULL); REQUIRE(test->cache_entry_present == ISC_TRUE || test->cache_entry_flags == 0); /* * Interrupt execution if query_done() is called. */ ns_hook_t query_hooks[NS_QUERY_HOOKS_COUNT + 1] = { [NS_QUERY_DONE_BEGIN] = { .callback = ns_test_hook_catch_call, .callback_data = NULL, }, }; ns__hook_table = query_hooks; /* * Construct a query context for a ./NS query with given flags. */ { const ns_test_qctx_create_params_t qctx_params = { .qname = ".", .qtype = dns_rdatatype_ns, .qflags = test->qflags, .with_cache = ISC_TRUE, }; result = ns_test_qctx_create(&qctx_params, &qctx); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); } /* * If this test wants a SERVFAIL cache entry matching the query to * exist, create it. */ if (test->cache_entry_present) { isc_interval_t hour; isc_time_t expire; isc_interval_set(&hour, 3600, 0); result = isc_time_nowplusinterval(&expire, &hour); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); dns_badcache_add(qctx->client->view->failcache, dns_rootname, dns_rdatatype_ns, ISC_TRUE, test->cache_entry_flags, &expire); } /* * Check whether ns__query_sfcache() behaves as expected. */ ns__query_sfcache(qctx); if (test->servfail_expected) { ATF_CHECK_EQ_MSG(qctx->result, DNS_R_SERVFAIL, "test \"%s\" on line %d: " "expected SERVFAIL, got %s", test->id.description, test->id.lineno, isc_result_totext(qctx->result)); } else { ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS, "test \"%s\" on line %d: " "expected success, got %s", test->id.description, test->id.lineno, isc_result_totext(qctx->result)); } /* * Clean up. */ ns_test_qctx_destroy(&qctx); } ATF_TC(ns__query_sfcache); ATF_TC_HEAD(ns__query_sfcache, tc) { atf_tc_set_md_var(tc, "descr", "ns__query_sfcache()"); } ATF_TC_BODY(ns__query_sfcache, tc) { isc_result_t result; size_t i; const ns__query_sfcache_test_params_t tests[] = { /* * Sanity check for an empty SERVFAIL cache. */ { NS_TEST_ID("query: RD=1, CD=0; cache: empty"), .qflags = DNS_MESSAGEFLAG_RD, .cache_entry_present = ISC_FALSE, .servfail_expected = ISC_FALSE, }, /* * Query: RD=1, CD=0. Cache entry: CD=0. Should SERVFAIL. */ { NS_TEST_ID("query: RD=1, CD=0; cache: CD=0"), .qflags = DNS_MESSAGEFLAG_RD, .cache_entry_present = ISC_TRUE, .cache_entry_flags = 0, .servfail_expected = ISC_TRUE, }, /* * Query: RD=1, CD=1. Cache entry: CD=0. Should not SERVFAIL: * failed validation should not influence CD=1 queries. */ { NS_TEST_ID("query: RD=1, CD=1; cache: CD=0"), .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, .cache_entry_present = ISC_TRUE, .cache_entry_flags = 0, .servfail_expected = ISC_FALSE, }, /* * Query: RD=1, CD=1. Cache entry: CD=1. Should SERVFAIL: * SERVFAIL responses elicited by CD=1 queries can be * "replayed" for other CD=1 queries during the lifetime of the * SERVFAIL cache entry. */ { NS_TEST_ID("query: RD=1, CD=1; cache: CD=1"), .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, .cache_entry_present = ISC_TRUE, .cache_entry_flags = NS_FAILCACHE_CD, .servfail_expected = ISC_TRUE, }, /* * Query: RD=1, CD=0. Cache entry: CD=1. Should SERVFAIL: if * a CD=1 query elicited a SERVFAIL, a CD=0 query for the same * QNAME and QTYPE will SERVFAIL as well. */ { NS_TEST_ID("query: RD=1, CD=0; cache: CD=1"), .qflags = DNS_MESSAGEFLAG_RD, .cache_entry_present = ISC_TRUE, .cache_entry_flags = NS_FAILCACHE_CD, .servfail_expected = ISC_TRUE, }, /* * Query: RD=0, CD=0. Cache entry: CD=0. Should not SERVFAIL * despite a matching entry being present as the SERVFAIL cache * should not be consulted for non-recursive queries. */ { NS_TEST_ID("query: RD=0, CD=0; cache: CD=0"), .qflags = 0, .cache_entry_present = ISC_TRUE, .cache_entry_flags = 0, .servfail_expected = ISC_FALSE, }, }; UNUSED(tc); result = ns_test_begin(NULL, ISC_TRUE); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { ns__query_sfcache_test(&tests[i]); } ns_test_end(); } /***** ***** ns__query_start() tests *****/ /*% * Structure containing parameters for ns__query_start_test(). */ typedef struct { const ns_test_id_t id; /* libns test identifier */ const char *qname; /* QNAME */ dns_rdatatype_t qtype; /* QTYPE */ unsigned int qflags; /* query flags */ isc_boolean_t disable_name_checks; /* if set to ISC_TRUE, owner name checks will be disabled for the view created */ isc_boolean_t recursive_service; /* if set to ISC_TRUE, the view created will have a cache database attached */ const char *auth_zone_origin; /* origin name of the zone the created view will be authoritative for */ const char *auth_zone_path; /* path to load the authoritative zone from */ enum { /* expected result: */ NS__QUERY_START_R_INVALID, NS__QUERY_START_R_REFUSE, /* query should be REFUSED */ NS__QUERY_START_R_CACHE, /* query should be answered from cache */ NS__QUERY_START_R_AUTH, /* query should be answered using authoritative data */ } expected_result; } ns__query_start_test_params_t; /*% * Perform a single ns__query_start() check using given parameters. */ static void ns__query_start_test(const ns__query_start_test_params_t *test) { query_ctx_t *qctx = NULL; isc_result_t result; REQUIRE(test != NULL); REQUIRE(test->id.description != NULL); REQUIRE((test->auth_zone_origin == NULL && test->auth_zone_path == NULL) || (test->auth_zone_origin != NULL && test->auth_zone_path != NULL)); /* * Interrupt execution if query_lookup() or query_done() is called. */ ns_hook_t query_hooks[NS_QUERY_HOOKS_COUNT + 1] = { [NS_QUERY_LOOKUP_BEGIN] = { .callback = ns_test_hook_catch_call, .callback_data = NULL, }, [NS_QUERY_DONE_BEGIN] = { .callback = ns_test_hook_catch_call, .callback_data = NULL, }, }; ns__hook_table = query_hooks; /* * Construct a query context using the supplied parameters. */ { const ns_test_qctx_create_params_t qctx_params = { .qname = test->qname, .qtype = test->qtype, .qflags = test->qflags, .with_cache = test->recursive_service, }; result = ns_test_qctx_create(&qctx_params, &qctx); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); } /* * Enable view->checknames by default, disable if requested. */ qctx->client->view->checknames = !test->disable_name_checks; /* * Load zone from file and attach it to the client's view, if * requested. */ if (test->auth_zone_path != NULL) { result = ns_test_serve_zone(test->auth_zone_origin, test->auth_zone_path, qctx->client->view); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); } /* * Check whether ns__query_start() behaves as expected. */ ns__query_start(qctx); switch (test->expected_result) { case NS__QUERY_START_R_REFUSE: ATF_CHECK_EQ_MSG(qctx->result, DNS_R_REFUSED, "test \"%s\" on line %d: " "expected REFUSED, got %s", test->id.description, test->id.lineno, isc_result_totext(qctx->result)); ATF_CHECK_EQ_MSG(qctx->zone, NULL, "test \"%s\" on line %d: " "no zone was expected to be attached to " "query context, but some was", test->id.description, test->id.lineno); ATF_CHECK_EQ_MSG(qctx->db, NULL, "test \"%s\" on line %d: " "no database was expected to be attached to " "query context, but some was", test->id.description, test->id.lineno); break; case NS__QUERY_START_R_CACHE: ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS, "test \"%s\" on line %d: " "expected success, got %s", test->id.description, test->id.lineno, isc_result_totext(qctx->result)); ATF_CHECK_EQ_MSG(qctx->zone, NULL, "test \"%s\" on line %d: " "no zone was expected to be attached to " "query context, but some was", test->id.description, test->id.lineno); ATF_CHECK_MSG((qctx->db != NULL && qctx->db == qctx->client->view->cachedb), "test \"%s\" on line %d: " "cache database was expected to be attached to " "query context, but it was not", test->id.description, test->id.lineno); break; case NS__QUERY_START_R_AUTH: ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS, "test \"%s\" on line %d: " "expected success, got %s", test->id.description, test->id.lineno, isc_result_totext(qctx->result)); ATF_CHECK_MSG(qctx->zone != NULL, "test \"%s\" on line %d: " "a zone was expected to be attached to query " "context, but it was not", test->id.description, test->id.lineno); ATF_CHECK_MSG((qctx->db != NULL && qctx->db != qctx->client->view->cachedb), "test \"%s\" on line %d: " "cache database was not expected to be attached " "to query context, but it is", test->id.description, test->id.lineno); break; case NS__QUERY_START_R_INVALID: ATF_REQUIRE_MSG(ISC_FALSE, "test \"%s\" on line %d has no expected " "result set", test->id.description, test->id.lineno); break; default: INSIST(0); break; } /* * Clean up. */ if (test->auth_zone_path != NULL) { ns_test_cleanup_zone(); } ns_test_qctx_destroy(&qctx); } ATF_TC(ns__query_start); ATF_TC_HEAD(ns__query_start, tc) { atf_tc_set_md_var(tc, "descr", "ns__query_start()"); } ATF_TC_BODY(ns__query_start, tc) { isc_result_t result; size_t i; const ns__query_start_test_params_t tests[] = { /* * Recursive foo/A query to a server without recursive service * and no zones configured. Query should be REFUSED. */ { NS_TEST_ID("foo/A, no cache, no auth"), .qname = "foo", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_FALSE, .expected_result = NS__QUERY_START_R_REFUSE, }, /* * Recursive foo/A query to a server with recursive service and * no zones configured. Query should be answered from cache. */ { NS_TEST_ID("foo/A, cache, no auth"), .qname = "foo", .qtype = dns_rdatatype_a, .recursive_service = ISC_TRUE, .expected_result = NS__QUERY_START_R_CACHE, }, /* * Recursive foo/A query to a server with recursive service and * zone "foo" configured. Query should be answered from * authoritative data. */ { NS_TEST_ID("foo/A, RD=1, cache, auth for foo"), .qname = "foo", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_AUTH, }, /* * Recursive bar/A query to a server without recursive service * and zone "foo" configured. Query should be REFUSED. */ { NS_TEST_ID("bar/A, RD=1, no cache, auth for foo"), .qname = "bar", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_FALSE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_REFUSE, }, /* * Recursive bar/A query to a server with recursive service and * zone "foo" configured. Query should be answered from * cache. */ { NS_TEST_ID("bar/A, RD=1, cache, auth for foo"), .qname = "bar", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_CACHE, }, /* * Recursive bar.foo/DS query to a server with recursive * service and zone "foo" configured. Query should be answered * from authoritative data. */ { NS_TEST_ID("bar.foo/DS, RD=1, cache, auth for foo"), .qname = "bar.foo", .qtype = dns_rdatatype_ds, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_AUTH, }, /* * Non-recursive bar.foo/DS query to a server with recursive * service and zone "foo" configured. Query should be answered * from authoritative data. */ { NS_TEST_ID("bar.foo/DS, RD=0, cache, auth for foo"), .qname = "bar.foo", .qtype = dns_rdatatype_ds, .qflags = 0, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_AUTH, }, /* * Recursive foo/DS query to a server with recursive service * and zone "foo" configured. Query should be answered from * cache. */ { NS_TEST_ID("foo/DS, RD=1, cache, auth for foo"), .qname = "foo", .qtype = dns_rdatatype_ds, .qflags = DNS_MESSAGEFLAG_RD, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_CACHE, }, /* * Non-recursive foo/DS query to a server with recursive * service and zone "foo" configured. Query should be answered * from authoritative data. */ { NS_TEST_ID("foo/DS, RD=0, cache, auth for foo"), .qname = "foo", .qtype = dns_rdatatype_ds, .qflags = 0, .recursive_service = ISC_TRUE, .auth_zone_origin = "foo", .auth_zone_path = "testdata/query/foo.db", .expected_result = NS__QUERY_START_R_AUTH, }, /* * Recursive _foo/A query to a server with recursive service, * no zones configured and owner name checks disabled. Query * should be answered from cache. */ { NS_TEST_ID("_foo/A, cache, no auth, name checks off"), .qname = "_foo", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .disable_name_checks = ISC_TRUE, .recursive_service = ISC_TRUE, .expected_result = NS__QUERY_START_R_CACHE, }, /* * Recursive _foo/A query to a server with recursive service, * no zones configured and owner name checks enabled. Query * should be REFUSED. */ { NS_TEST_ID("_foo/A, cache, no auth, name checks on"), .qname = "_foo", .qtype = dns_rdatatype_a, .qflags = DNS_MESSAGEFLAG_RD, .disable_name_checks = ISC_FALSE, .recursive_service = ISC_TRUE, .expected_result = NS__QUERY_START_R_REFUSE, }, }; UNUSED(tc); result = ns_test_begin(NULL, ISC_TRUE); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { ns__query_start_test(&tests[i]); } ns_test_end(); } /* * Main */ ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, ns__query_sfcache); ATF_TP_ADD_TC(tp, ns__query_start); return (atf_no_error()); }