/* The software in this package is distributed under the GNU General Public License version 2 (with a special exception described below). A copy of GNU General Public License (GPL) is included in this distribution, in the file COPYING.GPL. As a special exception, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other works to produce a work based on this file, this file does not by itself cause the resulting work to be covered by the GNU General Public License. However the source code for this file must still be made available in accordance with section (3) of the GNU General Public License. This exception does not invalidate any other reasons why a work based on this file might be covered by the GNU General Public License. */ #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK #include #include #include #include #include #include #include #include "host/pingprotocol.h" #include "dns/hostaddress.h" #include "dns/dnsipprotocol.h" #include "dns/dnsmaster.h" #include "dns/dnscache.h" #include "dns/resolverbase.h" #include "dns/cname.h" using boost::asio::ip::address; // constants for master address name_server = address::from_string("127.0.0.1"); const int resolved_ip_ttl_threshold = 5; // this should be greater than... const uint32_t min_time_between_resolves = 3; // ... this! const int max_address_resolution_attempts = 2; const int max_recursion_count = 10; const std::string cache_file = DnsCache::DoNotUseCacheFile; //------------------------------------------------------------------------------ // Global Test fixture (created once for test suite) //------------------------------------------------------------------------------ // rely on boost that there will only be one instance struct GlobalFixture; GlobalFixture *global_fixture; struct GlobalFixture { GlobalFixture() : IoService() , Cache() , Master() { BOOST_TEST_MESSAGE("Create global fixture"); // setup logging so we can see output from out code I2n::Logger::enable_stderr_log( true ); I2n::Logger::set_log_level( I2n::Logger::LogLevel::Debug ); I2n::Logger::GlobalLogger.debug() << "Logging enabled for DnsTest"; // IoService IoServiceItem io_service_temp( new boost::asio::io_service() ); io_service_temp.swap( IoService ); io_service_temp.reset(); // DNS Cache DnsCacheItem cache_temp = DnsCacheItem( new DnsCache(IoService, cache_file, min_time_between_resolves) ); cache_temp.swap( Cache ); cache_temp.reset(); fill_cache(); // create master DnsMaster::create_master(IoService, name_server, resolved_ip_ttl_threshold, max_address_resolution_attempts, max_recursion_count, Cache); Master = DnsMaster::get_instance(); // remember this instance, so we can later access all these variables if (global_fixture == 0) global_fixture = this; } ~GlobalFixture() { BOOST_TEST_MESSAGE("Destructing global fixture"); IoService->stop(); IoService.reset(); Master.reset(); } void fill_cache() { BOOST_TEST_MESSAGE( "Filling cache..." ); { HostAddress ip(address::from_string("192.168.42.1"), 61); HostAddressVec ips; ips.push_back(ip); Cache->update("host1.test", DNS_IPv4, ips); } { HostAddress ip1(address::from_string("192.168.42.2"), 92); HostAddress ip2(address::from_string("192.168.42.3"), 93); HostAddressVec ips; ips.push_back(ip1); ips.push_back(ip2); Cache->update("host2_3.test", DNS_IPv4, ips); } { // cname.test --> host1.test Cache->update("cname.test", Cname("host1.test", 35) ); // cname2.test --> cname.test --> host1.test Cache->update("cname2.test", Cname("cname.test", 33) ); // cname3.test --> cname2.test --> cname.test --> host1.test Cache->update("cname3.test", Cname("cname2.test", 37) ); } { // for cache_outdated_test HostAddressVec ips; ips.push_back( HostAddress( address::from_string("192.168.42.4"), 0 ) ); ips.push_back( HostAddress( address::from_string("192.168.42.5"), resolved_ip_ttl_threshold-1 ) ); ips.push_back( HostAddress( address::from_string("192.168.42.6"), resolved_ip_ttl_threshold ) ); ips.push_back( HostAddress( address::from_string("192.168.42.7"), resolved_ip_ttl_threshold+1 ) ); Cache->update("host_outdated.test", DNS_IPv4, ips); Cache->update( "cname_outdated.test", Cname("host_outdated.test", resolved_ip_ttl_threshold) ); Cache->update( "cname_up_to_date.test", Cname("host_outdated.test", resolved_ip_ttl_threshold+1)); } { // for cache_ttl_below_thresh_test // TTLs < min_time_between_resolves should be corrected HostAddressVec ips; ips.push_back( HostAddress( address::from_string("192.128.42.8"), 1 ) ); Cache->update("host_ttl_below_thresh.test", DNS_IPv4, ips); Cache->update( "cname_ttl_below_thresh.test", Cname("host_ttl_below_thresh.test", 2) ); } { // create a CNAME loop cname_loop1.test --> cname_loop2.test // --> cname_loop3.test --> cname_loop1.test Cache->update( "cname_loop1.test", Cname("cname_loop2.test", 60) ); Cache->update( "cname_loop2.test", Cname("cname_loop3.test", 60) ); Cache->update( "cname_loop3.test", Cname("cname_loop1.test", 60) ); } { // create IPv4 and IPv6 for same host HostAddressVec ips; ips.push_back( HostAddress( address::from_string("192.168.42.8"), 432 ) ); Cache->update("host_v4_and_v6.test", DNS_IPv4, ips); ips.clear(); ips.push_back( HostAddress( address::from_string("2a00:1450:4001:808::1004"), 543 ) ); Cache->update("host_v4_and_v6.test", DNS_IPv6, ips); } BOOST_TEST_MESSAGE( "Done filling cache." ); } // these variables will not be available in test cases: IoServiceItem IoService; DnsCacheItem Cache; DnsMasterItem Master; }; // this causes above fixture to be created only once before tests start and // destructed after tests end; however, variables are not accessible in test // cases BOOST_GLOBAL_FIXTURE( GlobalFixture ) // using this as suite-level fixture makes variable Master accessible in all // test cases struct TestFixture { IoServiceItem IoService; DnsCacheItem Cache; DnsMasterItem Master; TestFixture() : IoService() , Cache() , Master() { BOOST_TEST_MESSAGE("Create test-level fixture"); GlobalFixture *global = global_fixture; IoService = global->IoService; Cache = global->Cache; Master = global->Master; } virtual ~TestFixture() {} }; //------------------------------------------------------------------------------ // test suite //------------------------------------------------------------------------------ BOOST_FIXTURE_TEST_SUITE( TestDns, TestFixture ) BOOST_AUTO_TEST_CASE( create_master ) { // simple checks BOOST_CHECK_EQUAL( Master->get_resolved_ip_ttl_threshold(), resolved_ip_ttl_threshold ); BOOST_CHECK_EQUAL( Master->get_max_address_resolution_attempts(), max_address_resolution_attempts ); } //------------------------------------------------------------------------------ // test Cache //------------------------------------------------------------------------------ BOOST_FIXTURE_TEST_SUITE( TestDnsCache, TestFixture ) BOOST_AUTO_TEST_CASE( cache_retrieve_ip1 ) { HostAddressVec ips = Cache->get_ips("host1.test", DNS_IPv4); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); HostAddress ip = ips.front(); BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 61 ); } BOOST_AUTO_TEST_CASE( cache_retrieve_ip2 ) { HostAddressVec ips = Cache->get_ips("host2_3.test", DNS_IPv4); BOOST_CHECK_EQUAL( ips.size(), 2 ); HostAddress ip = ips[0]; BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.2" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 92 ); ip = ips[1]; BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.3" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 93 ); } BOOST_AUTO_TEST_CASE( cache_retrieve_cname ) { HostAddressVec ips = Cache->get_ips("cname.test", DNS_IPv4); BOOST_CHECK( ips.empty() ); Cname cname = Cache->get_cname("cname.test"); BOOST_CHECK_EQUAL( cname.Host, "host1.test" ); BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 35 ); } BOOST_AUTO_TEST_CASE( cache_retrieve_recursive1 ) { // should get IP from host1 but ttl from cname since is smaller HostAddressVec ips = Cache->get_ips_recursive("cname.test", DNS_IPv4); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); HostAddress ip = ips.front(); BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 35 ); } BOOST_AUTO_TEST_CASE( cache_retrieve_recursive2 ) { // should get IP from host1 but ttl from cname2 since is smaller HostAddressVec ips = Cache->get_ips_recursive("cname3.test", DNS_IPv4); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); HostAddress ip = ips.front(); BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.1" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 33 ); } void cname_skip_test(const uint32_t ttl1, const uint32_t ttl2, const uint32_t ttl3, const uint32_t ttl4, const DnsCacheItem &cache, const std::string &correct_host_after_skip) { { // create cname chain: // skip_chain_first -(ttl1)-> skip_chain_second -(ttl2)-> // skip_chain_third -(ttl3)-> skip_chain_fourth -(ttl4)-> IPs cache->update( "skip_chain_first.test", Cname("skip_chain_second.test", ttl1) ); cache->update( "skip_chain_second.test", Cname("skip_chain_third.test", ttl2) ); cache->update( "skip_chain_third.test", Cname("skip_chain_fourth.test", ttl3) ); HostAddressVec ips; ips.push_back( HostAddress( address::from_string("192.168.42.100"), ttl4 ) ); cache->update("skip_chain_fourth.test", DNS_IPv4, ips); } // normal recursive call should give nothing since one cname is outdated bool check_up_to_date = true; HostAddressVec ips = cache->get_ips_recursive("skip_chain_first.test", DNS_IPv4, check_up_to_date); bool one_is_out_of_date = (ttl1 < 5) || (ttl2 < 5) || (ttl3 < 5) || (ttl4 < 5); BOOST_CHECK_EQUAL( ips.empty(), one_is_out_of_date ); // now find host to resolve after the outdated one std::string first_outdated = cache->get_first_outdated_cname( "skip_chain_first.test", 5); BOOST_CHECK_EQUAL( first_outdated, correct_host_after_skip ); } BOOST_AUTO_TEST_CASE( cache_skip_tests ) { // build a cname chain where first one is out of date cname_skip_test(0, 120, 120, 60, Cache, "skip_chain_second.test"); // build a cname chain where second one is out of date cname_skip_test(120, 0, 120, 60, Cache, "skip_chain_third.test"); // build a cname chain where third one is out of date cname_skip_test(120, 120, 0, 120, Cache, "skip_chain_fourth.test"); // build a cname chain where just IPs are out of date cname_skip_test(120, 120, 120, 0, Cache, ""); // build a cname chain where all are out of date cname_skip_test(0, 0, 0, 0, Cache, "skip_chain_second.test"); // build a cname chain where all is up to date cname_skip_test(120, 120, 120, 120, Cache, ""); // test case with very short cname list that is up-to-date BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("cname.test", 5), "" ); // test case where there is no cname at all BOOST_CHECK_EQUAL( Cache->get_first_outdated_cname("host1.test", 5), "" ); } void create_cache(const std::string &file_name, IoServiceItem io_serv) { BOOST_TEST_MESSAGE( "creating cache for file " << file_name ); if ( I2n::file_exists(file_name) ) BOOST_CHECK( I2n::unlink(file_name) ); // try to remove DnsCache save_cache( io_serv, file_name, min_time_between_resolves ); HostAddressVec ips; ips.push_back( HostAddress(address::from_string("11.22.33.44"), 567) ); save_cache.update("abc.xyz", DNS_IPv4, ips); save_cache.update("cname1.xyz", Cname("abc.xyz", 27)); // is saved when destructed } void test_cache(const std::string &file_name, IoServiceItem io_serv) { BOOST_TEST_MESSAGE( "loading cache from file " << file_name ); DnsCache loaded_cache( io_serv, file_name, min_time_between_resolves ); HostAddressVec ips = loaded_cache.get_ips("abc.xyz", DNS_IPv4); BOOST_CHECK_EQUAL( ips.size(), 1 ); if (ips.size() == 1) { HostAddress ip = ips.front(); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "11.22.33.44" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 567 ); //BOOST_CHECK_EQUAL( ip.get_ttl().get_updated_value(), 0 ); } Cname cname = loaded_cache.get_cname("cname1.xyz"); BOOST_CHECK_EQUAL( cname.Host, "abc.xyz" ); BOOST_CHECK_EQUAL( cname.Ttl.get_value(), 27 ); //BOOST_CHECK_EQUAL( cname.Ttl.get_updated_value(), 0 ); // not testing Ttl set time since is private } BOOST_AUTO_TEST_CASE( cache_save_and_load_test ) { std::stringstream file_name_temp; file_name_temp << DATA_DIR_STRING << "/" << "dns_cache_save_load_test.xml"; std::string file_name = file_name_temp.str(); // create and save create_cache( file_name, IoService ); // now load and test test_cache( file_name, IoService ); } BOOST_AUTO_TEST_CASE( cache_load_compatibility_test ) { std::stringstream file_name; file_name << DATA_DIR_STRING << "/" << "dns_cache_compatibility_test.xml"; test_cache( file_name.str(), IoService ); // If this test fails because the cache structure was changed, // you can overwrite the dns_cache_compatibility_test.xml by uncommenting // the following code line. //create_cache( file_name.str(), IoService ); // // Run test again with arg --log_level=MESSAGE and look out for message // "create cache for file [...]dns_cache_compatibility_test.xml" // You should then copy the dns_cache_compatibility_test.xml to test/data // in git and commit together with the new cache structure code } BOOST_AUTO_TEST_CASE( cache_outdated_test ) { bool check_up_to_date = false; HostAddressVec ips = Cache->get_ips("host_outdated.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 4 ); ips = Cache->get_ips_recursive("host_outdated.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 4 ); Cname cname = Cache->get_cname("cname_outdated.test", check_up_to_date); BOOST_CHECK( !cname.Host.empty() ); ips = Cache->get_ips_recursive("cname_outdated.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 4 ); ips = Cache->get_ips_recursive("cname_up_to_date.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 4 ); check_up_to_date = true; ips = Cache->get_ips( "host_outdated.test", DNS_IPv4, check_up_to_date ); BOOST_CHECK_EQUAL( ips.size(), 1 ); ips = Cache->get_ips_recursive("host_outdated.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 1 ); cname = Cache->get_cname("cname_outdated.test", check_up_to_date); BOOST_CHECK( cname.Host.empty() ); ips = Cache->get_ips_recursive("cname_outdated.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 0 ); ips = Cache->get_ips_recursive("cname_up_to_date.test", DNS_IPv4, check_up_to_date); BOOST_CHECK_EQUAL( ips.size(), 1 ); } BOOST_AUTO_TEST_CASE( cache_ttl_below_thresh_test ) { HostAddressVec ips = Cache->get_ips("host_ttl_below_thresh.test", DNS_IPv4, false); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); HostAddress ip = ips.front(); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), min_time_between_resolves ); Cname cname = Cache->get_cname("cname_ttl_below_thresh.test", false); BOOST_CHECK_EQUAL( cname.Ttl.get_value(), min_time_between_resolves ); } BOOST_AUTO_TEST_CASE( cache_retrieve_loop ) { BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop1.test", DNS_IPv4).size(), 0 ); BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop2.test", DNS_IPv4).size(), 0 ); BOOST_CHECK_EQUAL( Cache->get_ips_recursive("cname_loop3.test", DNS_IPv4).size(), 0 ); } BOOST_AUTO_TEST_CASE( cache_IPv6 ) { HostAddressVec ips = Cache->get_ips("host_v4_and_v6.test", DNS_IPv4); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); HostAddress ip = ips.front(); BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "192.168.42.8" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 432 ); ips = Cache->get_ips("host_v4_and_v6.test", DNS_IPv6); BOOST_REQUIRE_EQUAL( ips.size(), 1 ); ip = ips.front(); BOOST_CHECK( ip.is_valid() ); BOOST_CHECK_EQUAL( ip.get_ip().to_string(), "2a00:1450:4001:808::1004" ); BOOST_CHECK_EQUAL( ip.get_ttl().get_value(), 543 ); } BOOST_AUTO_TEST_SUITE_END() // of TestDnsCache // ----------------------------------------------------------------------------- // test resolver // ----------------------------------------------------------------------------- BOOST_FIXTURE_TEST_SUITE( TestDnsResolver, TestFixture ) BOOST_AUTO_TEST_CASE( create_resolver_v4 ) { // create resolver std::string hostname = "www.intra2net.com"; ResolverItem resolver = Master->get_resolver_for(hostname, PingProtocol_ICMP); BOOST_CHECK_EQUAL( resolver->get_hostname(), hostname ); BOOST_CHECK( !resolver->is_resolving() ); BOOST_CHECK( !resolver->is_waiting_to_resolve() ); // cancel should have no effect resolver->cancel_resolve(); BOOST_CHECK( !resolver->is_resolving() ); BOOST_CHECK( !resolver->is_waiting_to_resolve() ); // should not have any ips since cache did not load anything BOOST_CHECK_EQUAL( resolver->get_resolved_ip_count(), 0 ); BOOST_CHECK( !resolver->have_up_to_date_ip() ); std::string no_ip = "0.0.0.0"; BOOST_CHECK_EQUAL( resolver->get_next_ip().get_ip().to_string(), no_ip ); } struct ResolveCallback { public: IoServiceItem IoService; ResolverItem Resolver; int CallCount; bool LastCallWasSuccess; int LastCallRecursionCount; ResolveCallback(IoServiceItem &io_serv, ResolverItem &resolver) : IoService(io_serv) , Resolver(resolver) , CallCount(0) , LastCallWasSuccess(true) , LastCallRecursionCount(-10) {} void call(const bool was_success, const int recursion_count) { BOOST_TEST_MESSAGE( "Callback called" ); ++CallCount; Resolver->cancel_resolve(); IoService->stop(); LastCallWasSuccess = was_success; LastCallRecursionCount = recursion_count; } }; BOOST_AUTO_TEST_CASE( do_resolve ) { // create resolver std::string hostname = "www.intra2net.com"; ResolverItem resolver = Master->get_resolver_for(hostname, PingProtocol_ICMP); BOOST_CHECK( !resolver->is_resolving() ); BOOST_CHECK( !resolver->is_waiting_to_resolve() ); // set callback ResolveCallback *callback_obj = new ResolveCallback(IoService, resolver); // have to manually delete this obj in the end! callback_type callback = boost::bind( &ResolveCallback::call, callback_obj, _1, _2 ); // start resolving resolver->async_resolve(callback); BOOST_CHECK( resolver->is_resolving() ); IoService->run(); // this will block until io service is stopped in resolve_callback BOOST_TEST_MESSAGE( "Back to synchronous operation" ); IoService->reset(); // check for result BOOST_CHECK_EQUAL( callback_obj->CallCount, 1 ); BOOST_CHECK( callback_obj->LastCallWasSuccess ); BOOST_CHECK_EQUAL( callback_obj->LastCallRecursionCount, 0 ); BOOST_CHECK( resolver->have_up_to_date_ip() ); delete callback_obj; } BOOST_AUTO_TEST_SUITE_END() // of TestDnsResolver BOOST_AUTO_TEST_SUITE_END()