diff --git a/pjlib-util/include/pjlib-util/config.h b/pjlib-util/include/pjlib-util/config.h index 79f8637017..4365909a64 100644 --- a/pjlib-util/include/pjlib-util/config.h +++ b/pjlib-util/include/pjlib-util/config.h @@ -202,9 +202,10 @@ * trusted, or when the nameserver refuses it or fails a DNS A query, e.g. * on a captive portal. The addresses are reported as its response and * cached for #PJ_DNS_RESOLVER_BAD_NS_TTL; an answer of a nameserver - * replaces them. A query of another type, e.g. DNS SRV, fails as without - * the setting, but asynchronously, so that #pj_dns_srv_resolve() falls - * back to the address of the domain as on a negative answer. + * replaces them, and #pj_dns_resolver_reset_ns_state() drops them. A + * query of another type, e.g. DNS SRV, fails as without the setting, but + * asynchronously, so that #pj_dns_srv_resolve() falls back to the address + * of the domain as on a negative answer. * * The setting doesn't change the state of the nameservers: while one is * trusted, i.e. answered within #PJ_DNS_RESOLVER_GOOD_NS_TTL, a timeout is diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h index 6d77bd784b..ad199aa317 100644 --- a/pjlib-util/include/pjlib-util/resolver.h +++ b/pjlib-util/include/pjlib-util/resolver.h @@ -362,8 +362,16 @@ PJ_DECL(pj_status_t) pj_dns_resolver_set_ns(pj_dns_resolver *resolver, * Reset the state of the name servers, so that they are all tried again * as when they were set with #pj_dns_resolver_set_ns(), e.g. after a * network change: a name server marked as bad while the previous network - * was going down may answer on the new one. Unlike setting them again, - * this keeps the cache and the pending queries untouched. + * was going down may answer on the new one. + * + * The cached responses whose TTL the resolver chose itself are dropped + * too: errors, NXDOMAIN and empty answers, cached for + * #PJ_DNS_RESOLVER_INVALID_TTL, and the answers of the system resolver, + * see #PJ_DNS_RESOLVER_SYS_FALLBACK. The answers of the nameservers with + * records and the entries added with #pj_dns_resolver_add_entry() without + * TTL are kept. An entry added with TTL is dropped or kept by the same + * rule. The pending queries complete as usual, but their responses are + * not cached. * * @param resolver The resolver instance. * @@ -553,7 +561,7 @@ PJ_DECL(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver); /** * Clear the response cache, e.g: after the nameservers have changed. * Entries added with #pj_dns_resolver_add_entry() without - * TTL are kept. + * TTL are kept. The responses of the pending queries are not cached. * * @param resolver The resolver instance. * diff --git a/pjlib-util/src/pjlib-util-test/resolver_test.c b/pjlib-util/src/pjlib-util-test/resolver_test.c index 9c0be0a78d..a4b98f93d3 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -2724,20 +2724,98 @@ static int wait_pkt_count(unsigned count) /* Fail the test, cleaning up at its end */ #define SYS_FAIL(code) { rc = (code); goto on_return; } -/* After the reset, a nameserver marked as bad is tried again, while the - * cache and the pending queries are kept. +/* Add an A entry: an answer, or an error with no answer */ +static pj_status_t reset_add_entry(pj_dns_resolver *res, const char *name, + int rcode, pj_bool_t with_ans, + pj_bool_t set_ttl) +{ + pj_dns_parsed_packet pkt; + pj_dns_parsed_query question; + pj_dns_parsed_rr ans; + + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(&question, sizeof(question)); + pj_bzero(&ans, sizeof(ans)); + pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(rcode); + pkt.hdr.qdcount = 1; + pkt.q = &question; + question.name = pj_str((char*)name); + question.type = PJ_DNS_TYPE_A; + question.dnsclass = 1; + if (with_ans) { + pkt.hdr.anscount = 1; + pkt.ans = &ans; + ans.name = question.name; + ans.type = PJ_DNS_TYPE_A; + ans.dnsclass = 1; + ans.ttl = 300; + ans.rdata.a.ip_addr.s_addr = IP_ADDR0; + } + return pj_dns_resolver_add_entry(res, &pkt, set_ttl); +} + +/* Whether the query is answered from the cache, with the status */ +static pj_bool_t reset_is_cached(pj_dns_resolver *res, const char *name, + pj_status_t status) +{ + pj_str_t n = pj_str((char*)name); + pj_dns_async_query *q = NULL; + + reset_ns_cb_called = PJ_FALSE; + if (pj_dns_resolver_start_query(res, &n, PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, NULL, &q) != + PJ_SUCCESS) + { + return PJ_FALSE; + } + return q == NULL && reset_ns_cb_called && reset_ns_cb_status == status; +} + +/* Wait for the response of a query to be in the cache: it is added after + * the callback + */ +static int wait_cached_count(pj_dns_resolver *res, unsigned count) +{ + unsigned i; + + for (i = 0; pj_dns_resolver_get_cached_count(res) < count && i < 500; + ++i) + { + pj_thread_sleep(10); + } + return pj_dns_resolver_get_cached_count(res) >= count ? 0 : -1; +} + +/* After the reset, a nameserver marked as bad is tried again, the pending + * queries go on but their responses are not cached, and the cached + * responses whose TTL the resolver chose are dropped. */ static int dns_reset_ns_state_test(void) { pj_str_t name1 = pj_str("name_reset1"); pj_str_t name2 = pj_str("name_reset2"); + pj_str_t name3 = pj_str("name_reset3"); + static const int drop_rcode[] = { + PJ_DNS_RCODE_FORMERR, PJ_DNS_RCODE_SERVFAIL, + PJ_DNS_RCODE_NOTIMPL, PJ_DNS_RCODE_NXDOMAIN, + PJ_DNS_RCODE_NOTAUTH + }; + /* The last two need PJ_DNS_RESOLVER_INVALID_TTL to be cached */ + static const char *drop_name[] = { + "name_reset_err1", "name_reset_err2", "name_reset_err3", + "name_reset_err4", "name_reset_err5", "name_reset1", + "name_reset_nodata" + }; + pj_dns_async_query *drop_q[PJ_ARRAY_SIZE(drop_name)] = { NULL }; + unsigned drop_cnt = PJ_ARRAY_SIZE(drop_name); + unsigned i; pj_str_t ns_addr = pj_str("127.0.0.1"); pj_uint16_t port = g_server[0].port; pj_dns_parsed_packet *r; pj_dns_resolver *res = NULL; pj_dns_settings lset; pj_dns_async_query *q; - unsigned sent; + unsigned sent, cached; int rc = 0; PJ_LOG(3,(THIS_FILE, " reset nameserver state test")); @@ -2747,7 +2825,7 @@ static int dns_reset_ns_state_test(void) &res), NULL, SYS_FAIL(-900)); pj_dns_resolver_get_settings(res, &lset); - lset.qretr_delay = 200; + lset.qretr_delay = 1000; lset.qretr_count = 3; pj_dns_resolver_set_settings(res, &lset); PJ_TEST_SUCCESS(pj_dns_resolver_set_ns(res, 1, &ns_addr, &port), @@ -2785,7 +2863,7 @@ static int dns_reset_ns_state_test(void) PJ_TEST_EQ(wait_pkt_count(sent + 1), 0, NULL, SYS_FAIL(-909)); /* The pending query goes on after another reset, and gets the answer - * to its retransmission + * to its retransmission, which is not cached */ PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-910)); r = &g_server[0].resp; @@ -2806,26 +2884,122 @@ static int dns_reset_ns_state_test(void) PJ_TEST_SUCCESS(reset_ns_cb_status, NULL, SYS_FAIL(-912)); PJ_TEST_EQ(reset_ns_cb_addr, IP_ADDR0, NULL, SYS_FAIL(-913)); - /* The cache is kept: answered from it, nothing sent */ - sent = g_server[0].pkt_count + g_server[1].pkt_count; - PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-914)); + cached = pj_dns_resolver_get_cached_count(res); reset_ns_cb_called = PJ_FALSE; PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, &q), - NULL, SYS_FAIL(-915)); - PJ_TEST_TRUE(q == NULL, NULL, SYS_FAIL(-916)); - PJ_TEST_TRUE(reset_ns_cb_called, NULL, SYS_FAIL(-917)); + NULL, SYS_FAIL(-914)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-915)); + PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-916)); + PJ_TEST_SUCCESS(reset_ns_cb_status, NULL, SYS_FAIL(-917)); + + /* The answer of a query started after the reset is cached */ + PJ_TEST_EQ(wait_cached_count(res, cached + 1), 0, NULL, SYS_FAIL(-918)); + +#if PJ_DNS_RESOLVER_INVALID_TTL + /* A refusal from a reply is cached */ + g_server[0].action = PJ_DNS_RCODE_REFUSED; + reset_ns_cb_called = PJ_FALSE; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, NULL, + NULL), + NULL, SYS_FAIL(-919)); + PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-920)); + PJ_TEST_EQ(wait_cached_count(res, cached + 2), 0, NULL, SYS_FAIL(-921)); + PJ_TEST_TRUE(reset_is_cached(res, "name_reset1", + PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED)), + NULL, SYS_FAIL(-922)); + + /* An empty answer */ + PJ_TEST_SUCCESS(reset_add_entry(res, "name_reset_nodata", 0, PJ_FALSE, + PJ_TRUE), + NULL, SYS_FAIL(-923)); + PJ_TEST_TRUE(reset_is_cached(res, "name_reset_nodata", PJ_SUCCESS), NULL, + SYS_FAIL(-924)); +#else + drop_cnt -= 2; +#endif + + /* Errors and NXDOMAIN added with TTL, with an answer so that they are + * cached even if PJ_DNS_RESOLVER_INVALID_TTL is 0 + */ + for (i = 0; i < PJ_ARRAY_SIZE(drop_rcode); ++i) { + PJ_TEST_SUCCESS(reset_add_entry(res, drop_name[i], drop_rcode[i], + PJ_TRUE, PJ_TRUE), + NULL, SYS_FAIL(-925)); + PJ_TEST_TRUE(reset_is_cached(res, drop_name[i], + PJ_STATUS_FROM_DNS_RCODE(drop_rcode[i])), + NULL, SYS_FAIL(-926)); + } + + /* An error added without TTL, which is kept */ + PJ_TEST_SUCCESS(reset_add_entry(res, "name_reset_nottl", + PJ_DNS_RCODE_SERVFAIL, PJ_FALSE, + PJ_FALSE), + NULL, SYS_FAIL(-927)); + + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-928)); + + /* The answer with records and the entry without TTL are kept */ + sent = g_server[0].pkt_count + g_server[1].pkt_count; + PJ_TEST_TRUE(reset_is_cached(res, "name_reset2", PJ_SUCCESS), NULL, + SYS_FAIL(-929)); + PJ_TEST_EQ(reset_ns_cb_addr, IP_ADDR0, NULL, SYS_FAIL(-930)); + PJ_TEST_TRUE(reset_is_cached(res, "name_reset_nottl", + PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_SERVFAIL)), + NULL, SYS_FAIL(-931)); PJ_TEST_EQ(g_server[0].pkt_count + g_server[1].pkt_count, sent, NULL, - SYS_FAIL(-918)); + SYS_FAIL(-932)); - pj_dns_resolver_destroy(res, PJ_FALSE); - res = NULL; + /* Nor is the answer of a query pending across + * pj_dns_resolver_clear_cache(): the server replies after its delay + */ + r->q[0].name = name3; + r->ans[0].name = name3; + g_server[0].action = ACTION_IGNORE; + reset_ns_cb_called = PJ_FALSE; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, NULL, + &q), + NULL, SYS_FAIL(-936)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-937)); + PJ_TEST_SUCCESS(pj_dns_resolver_clear_cache(res), NULL, SYS_FAIL(-938)); + g_server[0].action = ACTION_REPLY; + PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-939)); + PJ_TEST_SUCCESS(reset_ns_cb_status, NULL, SYS_FAIL(-940)); + reset_ns_cb_called = PJ_FALSE; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, NULL, + &q), + NULL, SYS_FAIL(-941)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-942)); + PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-943)); + /* The others are sent, left unanswered until cancelled at the end */ + g_server[0].action = ACTION_IGNORE; + lset.qretr_delay = 10000; + pj_dns_resolver_set_settings(res, &lset); + for (i = 0; i < drop_cnt; ++i) { + pj_str_t name = pj_str((char*)drop_name[i]); + + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name, + PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, + NULL, &drop_q[i]), + NULL, SYS_FAIL(-933)); + PJ_TEST_NOT_NULL(drop_q[i], NULL, SYS_FAIL(-934)); + } + PJ_TEST_EQ(wait_pkt_count(sent + drop_cnt), 0, NULL, SYS_FAIL(-935)); on_return: - if (res) + if (res) { + for (i = 0; i < PJ_ARRAY_SIZE(drop_q); ++i) { + if (drop_q[i]) + pj_dns_resolver_cancel_query(drop_q[i], PJ_FALSE); + } pj_dns_resolver_destroy(res, PJ_FALSE); + } return rc; } @@ -2936,7 +3110,14 @@ static pj_status_t test_sys_lookup(int af, const pj_str_t *name, /* Read once: the cleanup of a test clears it */ pj_sem_t *block = sys_state.block; +#if PJ_HAS_THREADS + /* The lookup threads may run at once */ + lock(); sys_state.count++; + unlock(); +#else + sys_state.count++; +#endif sys_state.af = af; pj_ansi_strxcpy2(sys_state.name, name, sizeof(sys_state.name)); if (block) { @@ -3236,6 +3417,32 @@ int resolver_nothreads_test(void) PJ_TEST_EQ(sys_cb[3].called, 1, NULL, SYS_FAIL(-1429)); PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1430)); + /* Not cached when the nameservers' state is reset meanwhile */ + nt_poll(3 * 50); + PJ_TEST_TRUE(sys_none_works(nt.res), NULL, SYS_FAIL(-1442)); + sys_reset(); + sys_state.qretr_delay = 50; + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_f", PJ_DNS_TYPE_A, 0, + NULL), + NULL, SYS_FAIL(-1443)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(nt.res), NULL, + SYS_FAIL(-1444)); + PJ_TEST_EQ(nt_wait_cb(0), 0, NULL, SYS_FAIL(-1445)); + PJ_TEST_SUCCESS(sys_cb[0].status, NULL, SYS_FAIL(-1446)); + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_f", PJ_DNS_TYPE_A, 1, &q), + NULL, SYS_FAIL(-1447)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-1448)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1449)); + + /* The nameserver bad again */ + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_g", PJ_DNS_TYPE_A, 2, + NULL), + NULL, SYS_FAIL(-1478)); + PJ_TEST_EQ(nt_wait_cb(2), 0, NULL, SYS_FAIL(-1479)); + nt_poll(3 * 50); + PJ_TEST_TRUE(sys_none_works(nt.res), NULL, SYS_FAIL(-1480)); + /* Destroyed from a callback: the query waiting is reported as * cancelled, once, the joined one with the answer, nothing is left */ @@ -3723,15 +3930,21 @@ static int dns_sys_fallback_cache_test(void) PJ_TEST_EQ(sys_cb[1].addr, IP_ADDR0, NULL, SYS_FAIL(-1066)); PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1067)); - /* The name doesn't exist: not asked for its other address type */ + /* The name doesn't exist: not asked for its other address type. With + * an answer, so that it is cached even if PJ_DNS_RESOLVER_INVALID_TTL + * is 0. + */ pj_bzero(&pkt, sizeof(pkt)); pj_bzero(&question, sizeof(question)); pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); pkt.hdr.qdcount = 1; pkt.q = &question; + pkt.hdr.anscount = 1; + pkt.ans = &ans; question.name = unknown; question.type = PJ_DNS_TYPE_A; question.dnsclass = 1; + ans.name = unknown; PJ_TEST_SUCCESS(pj_dns_resolver_add_entry(res, &pkt, PJ_TRUE), NULL, SYS_FAIL(-1068)); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &unknown, @@ -3816,6 +4029,110 @@ static int dns_sys_fallback_join_test(void) return rc; } +/* The reset drops the answers of the system resolver, and the response + * of a lookup going on meanwhile is not cached, be it an answer or the + * error of the nameservers. + */ +static int dns_sys_fallback_reset_test(void) +{ + pj_str_t name1 = pj_str("sysreset1"); + pj_str_t name2 = pj_str("sysreset2"); + pj_str_t name3 = pj_str("sysreset3"); + pj_dns_resolver *res = NULL; + pj_dns_settings lset; + pj_dns_async_query *q; + unsigned cached; + int rc = 0; + + PJ_LOG(3,(THIS_FILE, " system resolver fallback, reset test")); + + sys_reset(); + res = sys_resolver(PJ_TRUE, 100); + PJ_TEST_NOT_NULL(res, NULL, SYS_FAIL(-1450)); + PJ_TEST_EQ(sys_time_out(res, "sysreset0", 0), 0, NULL, SYS_FAIL(-1451)); + + /* Answered by the system resolver and cached, dropped by the reset */ + cached = pj_dns_resolver_get_cached_count(res); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)1, + NULL), + NULL, SYS_FAIL(-1452)); + PJ_TEST_EQ(wait_sys_cb(1), 0, NULL, SYS_FAIL(-1453)); + PJ_TEST_EQ(sys_cb[1].addr, SYS_ADDR, NULL, SYS_FAIL(-1454)); + PJ_TEST_EQ(wait_cached_count(res, cached + 1), 0, NULL, + SYS_FAIL(-1455)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1456)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)2, + &q), + NULL, SYS_FAIL(-1457)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-1458)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1459)); + + /* An answer of a lookup going on at the reset */ + PJ_TEST_EQ(sys_time_out(res, "sysreset4", 0), 0, NULL, SYS_FAIL(-1460)); + PJ_TEST_SUCCESS(pj_sem_create(pool, NULL, 0, 1, &sys_state.block), NULL, + SYS_FAIL(-1461)); + sys_cb[1].called = 0; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)1, + NULL), + NULL, SYS_FAIL(-1462)); + PJ_TEST_EQ(wait_sys_count(4), 0, NULL, SYS_FAIL(-1463)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1464)); + pj_sem_post(sys_state.block); + PJ_TEST_EQ(wait_sys_cb(1), 0, NULL, SYS_FAIL(-1465)); + PJ_TEST_SUCCESS(sys_cb[1].status, NULL, SYS_FAIL(-1466)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)2, + &q), + NULL, SYS_FAIL(-1467)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-1468)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1469)); + + /* The refusal of the nameservers, when the lookup going on at the + * reset fails. The reset first, for them to be sent to again, and a + * long retransmit delay, for the refusal to come before the timeout + * even after the packets the servers are still handling. + */ + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1481)); + pj_dns_resolver_get_settings(res, &lset); + lset.qretr_delay = 5000; + pj_dns_resolver_set_settings(res, &lset); + g_server[0].action = g_server[1].action = PJ_DNS_RCODE_REFUSED; + sys_state.status = PJ_ERESOLVE; + sys_cb[1].called = 0; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)1, + NULL), + NULL, SYS_FAIL(-1470)); + PJ_TEST_EQ(wait_sys_count(5), 0, NULL, SYS_FAIL(-1471)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1472)); + g_server[0].action = g_server[1].action = ACTION_IGNORE; + pj_sem_post(sys_state.block); + PJ_TEST_EQ(wait_sys_cb(1), 0, NULL, SYS_FAIL(-1473)); + PJ_TEST_EQ(sys_cb[1].status, + PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, + SYS_FAIL(-1474)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, + &dns_callback_sys, (void*)2, + &q), + NULL, SYS_FAIL(-1475)); + PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-1476)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1477)); + +on_return: + sys_cleanup(&res); + return rc; +} + /* Lets the lookup go on once the destroy of the resolver has started and * waits for it: a query is refused from then on. */ @@ -4369,6 +4686,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-1275)); PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1276)); +#if PJ_DNS_RESOLVER_INVALID_TTL sys_cb[1].called = 0; PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, &dns_callback_sys, (void*)1, @@ -4380,6 +4698,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-1280)); PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1281)); +#endif sys_state.status = PJ_SUCCESS; /* Server failure: an address from the system resolver, the server @@ -4448,6 +4767,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(sys_cb[0].status, PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-1306)); +#if PJ_DNS_RESOLVER_INVALID_TTL sys_cb[0].called = 0; PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, &dns_callback_sys, (void*)0, @@ -4456,6 +4776,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(q, NULL, NULL, SYS_FAIL(-1308)); PJ_TEST_EQ(sys_cb[0].called, 1, NULL, SYS_FAIL(-1309)); PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1310)); +#endif pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -5047,6 +5368,11 @@ int resolver_test(void) if (rc != 0) goto on_error; + PJ_LOG(3,(THIS_FILE, "dns_sys_fallback_reset_test")); + rc = dns_sys_fallback_reset_test(); + if (rc != 0) + goto on_error; + PJ_LOG(3,(THIS_FILE, "dns_sys_fallback_destroy_test")); rc = dns_sys_fallback_destroy_test(); if (rc != 0) diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c index 339ca966a2..6dc7c7801b 100644 --- a/pjlib-util/src/pjlib-util/resolver.c +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -168,6 +168,8 @@ struct pj_dns_async_query void *user_data; /**< Application data. */ pj_dns_callback *cb; /**< Callback to be called. */ struct query_head child_head; /**< Child queries list head. */ + unsigned cache_gen; /**< cache_gen of the resolver when + the query started. */ }; @@ -185,6 +187,8 @@ struct cached_res pj_time_val expiry_time; /**< Expiration time. */ pj_dns_parsed_packet *pkt; /**< The response packet. */ unsigned ref_cnt; /**< Reference counter. */ + pj_bool_t from_sys; /**< Answer of the system + resolver. */ }; @@ -244,6 +248,8 @@ struct pj_dns_resolver /* Hash table for cached response */ pj_hash_table_t *hrescache; /**< Cached response in hash table */ + unsigned cache_gen; /**< Incremented when the cache is + dropped, see drop_cache(). */ /* Pending asynchronous query, hashed by transaction ID. */ pj_hash_table_t *hquerybyid; @@ -299,11 +305,14 @@ static void sys_collect(pj_dns_resolver *resolver, struct query_head *list); static void report_sys(pj_dns_resolver *resolver, pj_dns_async_query *q); +static void drop_cache(pj_dns_resolver *resolver, pj_bool_t keep_answers); + static void update_res_cache(pj_dns_resolver *resolver, const struct res_key *key, pj_status_t status, pj_bool_t set_expiry, - const pj_dns_parsed_packet *pkt); + const pj_dns_parsed_packet *pkt, + pj_bool_t from_sys); /* Select which nameserver to use */ static pj_status_t select_nameservers(pj_dns_resolver *resolver, @@ -799,6 +808,9 @@ PJ_DEF(pj_status_t) pj_dns_resolver_reset_ns_state(pj_dns_resolver *resolver) resolver->ns[i].state = STATE_ACTIVE; resolver->ns[i].state_expiry = now; } + + /* What the resolver timed itself may not hold on the new network */ + drop_cache(resolver, PJ_TRUE); if (resolver->ns_count) PJ_LOG(4,(resolver->name.ptr, "Nameserver state reset")); @@ -879,6 +891,7 @@ static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver, q->options = options; q->user_data = user_data; q->cb = cb; + q->cache_gen = resolver->cache_gen; pj_list_init(&q->child_head); return q; @@ -1520,8 +1533,12 @@ static void report_sys(pj_dns_resolver *resolver, pj_dns_async_query *q) question.dnsclass = PJ_DNS_CLASS_IN; question.name = pj_str(q->key.name); pj_grp_lock_acquire(resolver->grp_lock); - if (!resolver->shutting_down) - update_res_cache(resolver, &q->key, status, PJ_TRUE, &pkt); + if (!resolver->shutting_down && + q->cache_gen == resolver->cache_gen) + { + update_res_cache(resolver, &q->key, status, PJ_TRUE, &pkt, + PJ_FALSE); + } pj_grp_lock_release(resolver->grp_lock); } } @@ -1554,8 +1571,12 @@ static void report_sys(pj_dns_resolver *resolver, pj_dns_async_query *q) pj_grp_lock_acquire(resolver->grp_lock); /* Cached as a nameserver's answer is, for bad_ns_ttl */ - if (resp && !resolver->shutting_down) - update_res_cache(resolver, &q->key, PJ_SUCCESS, PJ_TRUE, &pkt); + if (resp && !resolver->shutting_down && + q->cache_gen == resolver->cache_gen) + { + update_res_cache(resolver, &q->key, PJ_SUCCESS, PJ_TRUE, &pkt, + PJ_TRUE); + } q->user_data = NULL; cq = q->child_head.next; @@ -2287,7 +2308,8 @@ static void update_res_cache(pj_dns_resolver *resolver, const struct res_key *key, pj_status_t status, pj_bool_t set_expiry, - const pj_dns_parsed_packet *pkt) + const pj_dns_parsed_packet *pkt, + pj_bool_t from_sys) { struct cached_res *cache; pj_uint32_t hval=0, ttl; @@ -2386,6 +2408,7 @@ static void update_res_cache(pj_dns_resolver *resolver, /* Copy key to the cached response */ pj_memcpy(&cache->key, key, sizeof(*key)); + cache->from_sys = from_sys; /* Update the hash table */ pj_hash_set_np(resolver->hrescache, &cache->key, sizeof(*key), hval, @@ -2711,11 +2734,15 @@ static void on_read_complete(pj_ioqueue_key_t *key, /* Truncated responses MUST NOT be saved (cached). Skip caching as well * once destroy has started: destroy sweeps the cache, so a late entry - * would never be freed. + * would never be freed. Nor if the cache was dropped since the query + * started: the response may be of the previous network. */ - if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0 && !resolver->shutting_down) { + if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0 && !resolver->shutting_down && + q->cache_gen == resolver->cache_gen) + { /* Save/update response cache. */ - update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt); + update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt, + PJ_FALSE); } /* Recycle query objects, starting with the child queries */ @@ -2795,7 +2822,7 @@ PJ_DEF(pj_status_t) pj_dns_resolver_add_entry( pj_dns_resolver *resolver, } /* Insert entry. */ - update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt); + update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt, PJ_FALSE); pj_grp_lock_release(resolver->grp_lock); @@ -2820,16 +2847,16 @@ PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) } -/* - * Clear the response cache. +/* Remove the cached entries, but for those added by the application + * without TTL and, with keep_answers, the nameservers' answers with + * records. The responses of the pending queries won't be cached. Called + * with the lock held. */ -PJ_DEF(pj_status_t) pj_dns_resolver_clear_cache(pj_dns_resolver *resolver) +static void drop_cache(pj_dns_resolver *resolver, pj_bool_t keep_answers) { pj_hash_iterator_t it_buf, *it; - PJ_ASSERT_RETURN(resolver, PJ_EINVAL); - - pj_grp_lock_acquire(resolver->grp_lock); + ++resolver->cache_gen; it = pj_hash_first(resolver->hrescache, &it_buf); while (it) { @@ -2838,9 +2865,13 @@ PJ_DEF(pj_status_t) pj_dns_resolver_clear_cache(pj_dns_resolver *resolver) cache = (struct cached_res*) pj_hash_this(resolver->hrescache, it); it = pj_hash_next(resolver->hrescache, it); - /* Keep the entries added by the application without TTL */ if (cache->expiry_time.sec == 0x7FFFFFFFL) continue; + if (keep_answers && !cache->from_sys && cache->pkt->hdr.anscount && + PJ_DNS_GET_RCODE(cache->pkt->hdr.flags) == 0) + { + continue; + } pj_hash_set(NULL, resolver->hrescache, &cache->key, sizeof(cache->key), 0, NULL); @@ -2849,7 +2880,18 @@ PJ_DEF(pj_status_t) pj_dns_resolver_clear_cache(pj_dns_resolver *resolver) if (--cache->ref_cnt <= 0) free_entry(resolver, cache); } +} + + +/* + * Clear the response cache. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_clear_cache(pj_dns_resolver *resolver) +{ + PJ_ASSERT_RETURN(resolver, PJ_EINVAL); + pj_grp_lock_acquire(resolver->grp_lock); + drop_cache(resolver, PJ_FALSE); pj_grp_lock_release(resolver->grp_lock); return PJ_SUCCESS; }