From 941c394e94659b9bb919bf003395f8785ae99394 Mon Sep 17 00:00:00 2001 From: Vasily Laushkin Date: Fri, 9 Oct 2026 15:10:33 +0300 Subject: [PATCH 1/4] pjlib-util: forget the cached DNS errors when the nameservers' state is reset --- pjlib-util/include/pjlib-util/resolver.h | 5 +- .../src/pjlib-util-test/resolver_test.c | 81 +++++++++++++++++-- pjlib-util/src/pjlib-util/resolver.c | 33 ++++++-- 3 files changed, 104 insertions(+), 15 deletions(-) diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h index 6d77bd784b..9f5731720b 100644 --- a/pjlib-util/include/pjlib-util/resolver.h +++ b/pjlib-util/include/pjlib-util/resolver.h @@ -362,8 +362,9 @@ 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. Their cached errors, e.g. a + * refusal, are dropped too. The rest of the cache and the pending queries + * are kept. * * @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..7c2d39c7c5 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -2725,15 +2725,27 @@ static int wait_pkt_count(unsigned count) #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. + * cache and the pending queries are kept, but for the cached errors. */ 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 err_rcode[] = { + PJ_DNS_RCODE_FORMERR, PJ_DNS_RCODE_SERVFAIL, + PJ_DNS_RCODE_NOTIMPL, PJ_DNS_RCODE_NOTAUTH + }; + static const char *err_name[] = { + "name_reset_err1", "name_reset_err2", + "name_reset_err3", "name_reset_err4" + }; + pj_dns_async_query *err_q[PJ_ARRAY_SIZE(err_rcode) + 1] = { NULL }; + 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_parsed_packet *r, pkt; + pj_dns_parsed_query question; pj_dns_resolver *res = NULL; pj_dns_settings lset; pj_dns_async_query *q; @@ -2819,13 +2831,72 @@ static int dns_reset_ns_state_test(void) PJ_TEST_EQ(g_server[0].pkt_count + g_server[1].pkt_count, sent, NULL, SYS_FAIL(-918)); - pj_dns_resolver_destroy(res, PJ_FALSE); - res = NULL; + /* But the cached errors are dropped, while NXDOMAIN stays */ + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(&question, sizeof(question)); + pkt.hdr.qdcount = 1; + pkt.q = &question; + question.type = PJ_DNS_TYPE_A; + question.dnsclass = 1; + for (i = 0; i < PJ_ARRAY_SIZE(err_rcode); ++i) { + pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(err_rcode[i]); + question.name = pj_str((char*)err_name[i]); + PJ_TEST_SUCCESS(pj_dns_resolver_add_entry(res, &pkt, PJ_TRUE), NULL, + SYS_FAIL(-919)); + reset_ns_cb_called = PJ_FALSE; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &question.name, + PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, + NULL, &q), + NULL, SYS_FAIL(-920)); + PJ_TEST_TRUE(q == NULL && reset_ns_cb_called, NULL, SYS_FAIL(-921)); + PJ_TEST_EQ(reset_ns_cb_status, PJ_STATUS_FROM_DNS_RCODE(err_rcode[i]), + NULL, SYS_FAIL(-922)); + } + pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + question.name = name3; + PJ_TEST_SUCCESS(pj_dns_resolver_add_entry(res, &pkt, PJ_TRUE), NULL, + SYS_FAIL(-923)); + + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-924)); + /* Left unanswered until cancelled at the end */ + g_server[0].action = ACTION_IGNORE; + lset.qretr_delay = 10000; + pj_dns_resolver_set_settings(res, &lset); + sent = g_server[0].pkt_count + g_server[1].pkt_count; + for (i = 0; i < PJ_ARRAY_SIZE(err_q); ++i) { + pj_str_t name = i < PJ_ARRAY_SIZE(err_rcode) ? + pj_str((char*)err_name[i]) : name1; + + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name, + PJ_DNS_TYPE_A, 0, + &dns_callback_reset_ns, + NULL, &err_q[i]), + NULL, SYS_FAIL(-925)); + PJ_TEST_NOT_NULL(err_q[i], NULL, SYS_FAIL(-926)); + } + PJ_TEST_EQ(wait_pkt_count(sent + PJ_ARRAY_SIZE(err_q)), 0, NULL, + SYS_FAIL(-927)); + + 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(-928)); + PJ_TEST_TRUE(q == NULL && reset_ns_cb_called, NULL, SYS_FAIL(-929)); + PJ_TEST_EQ(reset_ns_cb_status, + PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN), NULL, + SYS_FAIL(-930)); on_return: - if (res) + if (res) { + for (i = 0; i < PJ_ARRAY_SIZE(err_q); ++i) { + if (err_q[i]) + pj_dns_resolver_cancel_query(err_q[i], PJ_FALSE); + } pj_dns_resolver_destroy(res, PJ_FALSE); + } return rc; } diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c index 339ca966a2..48de430a4e 100644 --- a/pjlib-util/src/pjlib-util/resolver.c +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -299,6 +299,8 @@ 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 errors_only); + static void update_res_cache(pj_dns_resolver *resolver, const struct res_key *key, pj_status_t status, @@ -799,6 +801,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; } + + /* Their errors may not hold on the new network either */ + drop_cache(resolver, PJ_TRUE); if (resolver->ns_count) PJ_LOG(4,(resolver->name.ptr, "Nameserver state reset")); @@ -2820,17 +2825,13 @@ PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) } -/* - * Clear the response cache. +/* Remove the cached entries, or only the errors. Entries added by the + * application without TTL are kept. 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 errors_only) { pj_hash_iterator_t it_buf, *it; - PJ_ASSERT_RETURN(resolver, PJ_EINVAL); - - pj_grp_lock_acquire(resolver->grp_lock); - it = pj_hash_first(resolver->hrescache, &it_buf); while (it) { struct cached_res *cache; @@ -2838,9 +2839,14 @@ 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 (errors_only) { + int rcode = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags); + + if (rcode == 0 || rcode == PJ_DNS_RCODE_NXDOMAIN) + continue; + } pj_hash_set(NULL, resolver->hrescache, &cache->key, sizeof(cache->key), 0, NULL); @@ -2849,7 +2855,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; } From d8f4f493f6f985eb6f940eff6e9716eaf9b78184 Mon Sep 17 00:00:00 2001 From: Vasily Laushkin Date: Mon, 12 Oct 2026 17:12:06 +0300 Subject: [PATCH 2/4] pjlib-util: drop the cache entries timed by the resolver on a nameserver reset --- pjlib-util/include/pjlib-util/config.h | 7 +- pjlib-util/include/pjlib-util/resolver.h | 15 +- .../src/pjlib-util-test/resolver_test.c | 390 ++++++++++++++---- pjlib-util/src/pjlib-util/resolver.c | 65 ++- 4 files changed, 379 insertions(+), 98 deletions(-) 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 9f5731720b..ad199aa317 100644 --- a/pjlib-util/include/pjlib-util/resolver.h +++ b/pjlib-util/include/pjlib-util/resolver.h @@ -362,9 +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. Their cached errors, e.g. a - * refusal, are dropped too. The rest of the cache and the pending queries - * are kept. + * 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. * @@ -554,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 7c2d39c7c5..8cde3cddf9 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -2724,32 +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, but for the cached errors. +/* 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 err_rcode[] = { + static const int drop_rcode[] = { PJ_DNS_RCODE_FORMERR, PJ_DNS_RCODE_SERVFAIL, - PJ_DNS_RCODE_NOTIMPL, PJ_DNS_RCODE_NOTAUTH + PJ_DNS_RCODE_NOTIMPL, PJ_DNS_RCODE_NXDOMAIN, + PJ_DNS_RCODE_NOTAUTH }; - static const char *err_name[] = { - "name_reset_err1", "name_reset_err2", - "name_reset_err3", "name_reset_err4" + /* 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 *err_q[PJ_ARRAY_SIZE(err_rcode) + 1] = { NULL }; + 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, pkt; - pj_dns_parsed_query question; + 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")); @@ -2759,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), @@ -2797,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; @@ -2818,82 +2884,119 @@ 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)); - PJ_TEST_EQ(g_server[0].pkt_count + g_server[1].pkt_count, sent, NULL, - SYS_FAIL(-918)); + 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)); - /* But the cached errors are dropped, while NXDOMAIN stays */ - pj_bzero(&pkt, sizeof(pkt)); - pj_bzero(&question, sizeof(question)); - pkt.hdr.qdcount = 1; - pkt.q = &question; - question.type = PJ_DNS_TYPE_A; - question.dnsclass = 1; - for (i = 0; i < PJ_ARRAY_SIZE(err_rcode); ++i) { - pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(err_rcode[i]); - question.name = pj_str((char*)err_name[i]); - PJ_TEST_SUCCESS(pj_dns_resolver_add_entry(res, &pkt, PJ_TRUE), NULL, - SYS_FAIL(-919)); - reset_ns_cb_called = PJ_FALSE; - PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &question.name, - PJ_DNS_TYPE_A, 0, - &dns_callback_reset_ns, - NULL, &q), - NULL, SYS_FAIL(-920)); - PJ_TEST_TRUE(q == NULL && reset_ns_cb_called, NULL, SYS_FAIL(-921)); - PJ_TEST_EQ(reset_ns_cb_status, PJ_STATUS_FROM_DNS_RCODE(err_rcode[i]), - NULL, SYS_FAIL(-922)); + /* 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)); } - pkt.hdr.flags = PJ_DNS_SET_QR(1) | PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); - question.name = name3; - PJ_TEST_SUCCESS(pj_dns_resolver_add_entry(res, &pkt, PJ_TRUE), NULL, - SYS_FAIL(-923)); - PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-924)); + /* 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(-932)); + + /* 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)); - /* Left unanswered until cancelled at the end */ + /* 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); - sent = g_server[0].pkt_count + g_server[1].pkt_count; - for (i = 0; i < PJ_ARRAY_SIZE(err_q); ++i) { - pj_str_t name = i < PJ_ARRAY_SIZE(err_rcode) ? - pj_str((char*)err_name[i]) : name1; + 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, &err_q[i]), - NULL, SYS_FAIL(-925)); - PJ_TEST_NOT_NULL(err_q[i], NULL, SYS_FAIL(-926)); + 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 + PJ_ARRAY_SIZE(err_q)), 0, NULL, - SYS_FAIL(-927)); - - 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(-928)); - PJ_TEST_TRUE(q == NULL && reset_ns_cb_called, NULL, SYS_FAIL(-929)); - PJ_TEST_EQ(reset_ns_cb_status, - PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN), NULL, - SYS_FAIL(-930)); + PJ_TEST_EQ(wait_pkt_count(sent + drop_cnt), 0, NULL, SYS_FAIL(-935)); on_return: if (res) { - for (i = 0; i < PJ_ARRAY_SIZE(err_q); ++i) { - if (err_q[i]) - pj_dns_resolver_cancel_query(err_q[i], PJ_FALSE); + 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); } @@ -3307,6 +3410,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 */ @@ -3794,15 +3923,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, @@ -3887,6 +4022,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. */ @@ -4440,6 +4679,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, @@ -4451,6 +4691,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 @@ -4519,6 +4760,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, @@ -4527,6 +4769,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; @@ -5118,6 +5361,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 48de430a4e..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,13 +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 errors_only); +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, @@ -802,7 +809,7 @@ PJ_DEF(pj_status_t) pj_dns_resolver_reset_ns_state(pj_dns_resolver *resolver) resolver->ns[i].state_expiry = now; } - /* Their errors may not hold on the new network either */ + /* 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")); @@ -884,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; @@ -1525,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); } } @@ -1559,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; @@ -2292,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; @@ -2391,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, @@ -2716,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 */ @@ -2800,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); @@ -2825,13 +2847,17 @@ PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) } -/* Remove the cached entries, or only the errors. Entries added by the - * application without TTL are kept. Called with the lock held. +/* 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. */ -static void drop_cache(pj_dns_resolver *resolver, pj_bool_t errors_only) +static void drop_cache(pj_dns_resolver *resolver, pj_bool_t keep_answers) { pj_hash_iterator_t it_buf, *it; + ++resolver->cache_gen; + it = pj_hash_first(resolver->hrescache, &it_buf); while (it) { struct cached_res *cache; @@ -2841,11 +2867,10 @@ static void drop_cache(pj_dns_resolver *resolver, pj_bool_t errors_only) if (cache->expiry_time.sec == 0x7FFFFFFFL) continue; - if (errors_only) { - int rcode = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags); - - if (rcode == 0 || rcode == PJ_DNS_RCODE_NXDOMAIN) - 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, From 68c4d3f68c15b2cc0b39ed581703d07ff4c0668b Mon Sep 17 00:00:00 2001 From: Vasily Laushkin Date: Tue, 13 Oct 2026 15:47:06 +0300 Subject: [PATCH 3/4] pjlib-util: count the system resolver test lookups under the lock --- pjlib-util/src/pjlib-util-test/resolver_test.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pjlib-util/src/pjlib-util-test/resolver_test.c b/pjlib-util/src/pjlib-util-test/resolver_test.c index 8cde3cddf9..a4b98f93d3 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -3110,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) { From 63c42230221ca5f2ee6120b7b802e3d51fe0c2e7 Mon Sep 17 00:00:00 2001 From: Vasily Laushkin Date: Wed, 14 Oct 2026 04:42:28 +0300 Subject: [PATCH 4/4] pjlib-util: don't join or trust the queries pending at a nameserver reset --- pjlib-util/include/pjlib-util/resolver.h | 7 +- .../src/pjlib-util-test/resolver_test.c | 817 +++++++++++++++--- pjlib-util/src/pjlib-util/resolver.c | 71 +- 3 files changed, 771 insertions(+), 124 deletions(-) diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h index ad199aa317..5812c6ee69 100644 --- a/pjlib-util/include/pjlib-util/resolver.h +++ b/pjlib-util/include/pjlib-util/resolver.h @@ -371,7 +371,8 @@ PJ_DECL(pj_status_t) pj_dns_resolver_set_ns(pj_dns_resolver *resolver, * 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. + * not cached and don't mark a name server bad or bring it back, and a query + * started afterwards doesn't join them. * * @param resolver The resolver instance. * @@ -561,7 +562,9 @@ 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. The responses of the pending queries are not cached. + * TTL are kept. The responses of the pending queries are not cached and + * don't mark a name server bad or bring it back, and a query started + * afterwards doesn't join them. * * @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 a4b98f93d3..06f01baf6f 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -2693,19 +2693,58 @@ static void dns_callback_reset_ns(void *user_data, { PJ_UNUSED_ARG(user_data); + lock(); reset_ns_cb_status = status; if (status == PJ_SUCCESS && resp && resp->hdr.anscount) reset_ns_cb_addr = resp->ans[0].rdata.a.ip_addr.s_addr; reset_ns_cb_called = PJ_TRUE; + unlock(); +} + +static void reset_ns_cb_clear(void) +{ + lock(); + reset_ns_cb_called = PJ_FALSE; + unlock(); +} + +static pj_bool_t reset_ns_called(void) +{ + pj_bool_t called; + + lock(); + called = reset_ns_cb_called; + unlock(); + return called; +} + +static pj_status_t reset_ns_status(void) +{ + pj_status_t status; + + lock(); + status = reset_ns_cb_status; + unlock(); + return status; +} + +static pj_uint32_t reset_ns_addr(void) +{ + pj_uint32_t addr; + + lock(); + addr = reset_ns_cb_addr; + unlock(); + return addr; } static int wait_reset_ns_cb(void) { unsigned i; - for (i = 0; !reset_ns_cb_called && i < 500; ++i) + for (i = 0; !reset_ns_called() && i < 500; ++i) pj_thread_sleep(10); - return reset_ns_cb_called ? 0 : -1; + return reset_ns_called() ? 0 : -1; } /* Wait for the servers to have counted the packets sent to them */ @@ -2761,14 +2800,14 @@ static pj_bool_t reset_is_cached(pj_dns_resolver *res, const char *name, pj_str_t n = pj_str((char*)name); pj_dns_async_query *q = NULL; - reset_ns_cb_called = PJ_FALSE; + reset_ns_cb_clear(); 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; + return q == NULL && reset_ns_called() && reset_ns_status() == status; } /* Wait for the response of a query to be in the cache: it is added after @@ -2834,13 +2873,13 @@ static int dns_reset_ns_state_test(void) /* A refusal marks the nameserver as bad */ g_server[0].action = PJ_DNS_RCODE_REFUSED; g_server[0].pkt_count = 0; - reset_ns_cb_called = PJ_FALSE; + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, NULL), NULL, SYS_FAIL(-902)); PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-903)); - PJ_TEST_EQ(reset_ns_cb_status, + PJ_TEST_EQ(reset_ns_status(), PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-904)); @@ -2854,7 +2893,7 @@ static int dns_reset_ns_state_test(void) /* Tried again after the reset */ PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, SYS_FAIL(-906)); g_server[0].action = ACTION_IGNORE; - reset_ns_cb_called = PJ_FALSE; + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, &q), @@ -2881,18 +2920,18 @@ static int dns_reset_ns_state_test(void) r->ans[0].rdata.a.ip_addr.s_addr = IP_ADDR0; g_server[0].action = ACTION_REPLY; PJ_TEST_EQ(wait_reset_ns_cb(), 0, NULL, SYS_FAIL(-911)); - PJ_TEST_SUCCESS(reset_ns_cb_status, NULL, SYS_FAIL(-912)); - PJ_TEST_EQ(reset_ns_cb_addr, IP_ADDR0, NULL, SYS_FAIL(-913)); + PJ_TEST_SUCCESS(reset_ns_status(), NULL, SYS_FAIL(-912)); + PJ_TEST_EQ(reset_ns_addr(), IP_ADDR0, NULL, SYS_FAIL(-913)); cached = pj_dns_resolver_get_cached_count(res); - reset_ns_cb_called = PJ_FALSE; + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, &q), 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)); + PJ_TEST_SUCCESS(reset_ns_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)); @@ -2900,7 +2939,7 @@ static int dns_reset_ns_state_test(void) #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; + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name1, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, NULL), @@ -2945,20 +2984,39 @@ static int dns_reset_ns_state_test(void) 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_EQ(reset_ns_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(-932)); + /* 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); + sent = g_server[0].pkt_count + g_server[1].pkt_count; + 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)); + /* Nor is the answer of a query pending across * pj_dns_resolver_clear_cache(): the server replies after its delay */ + lset.qretr_delay = 1000; + pj_dns_resolver_set_settings(res, &lset); r->q[0].name = name3; r->ans[0].name = name3; g_server[0].action = ACTION_IGNORE; - reset_ns_cb_called = PJ_FALSE; + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, &q), @@ -2967,8 +3025,8 @@ static int dns_reset_ns_state_test(void) 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(reset_ns_status(), NULL, SYS_FAIL(-940)); + reset_ns_cb_clear(); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, &dns_callback_reset_ns, NULL, &q), @@ -2976,22 +3034,6 @@ static int dns_reset_ns_state_test(void) 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) { for (i = 0; i < PJ_ARRAY_SIZE(drop_q); ++i) { @@ -3004,6 +3046,325 @@ static int dns_reset_ns_state_test(void) } +/* A server which counts the queries of a name by id, and answers the + * first one (the old query) and the others (the new ones) as set: + * ACTION_IGNORE, ACTION_REPLY with its own address, or an rcode. + */ +#define JOIN_ADDR_OLD 0x0A000001 +#define JOIN_ADDR_NEW 0x0A000002 + +static struct { + const char *name; + pj_uint16_t ids[8]; + unsigned id_cnt; + int old_act; + int new_act; +} join_srv; + +static void join_action(const pj_dns_parsed_packet *pkt, + pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res = NULL; + int act = ACTION_IGNORE; + unsigned i; + + lock(); + if (join_srv.name && pj_strcmp2(&pkt->q[0].name, join_srv.name) == 0) { + for (i = 0; i < join_srv.id_cnt; ++i) { + if (join_srv.ids[i] == pkt->hdr.id) + break; + } + if (i == join_srv.id_cnt && i < PJ_ARRAY_SIZE(join_srv.ids)) + join_srv.ids[join_srv.id_cnt++] = pkt->hdr.id; + act = (i == 0 ? join_srv.old_act : join_srv.new_act); + } + if (act != ACTION_IGNORE) { + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + res->hdr.flags = PJ_DNS_SET_QR(1); + res->hdr.qdcount = 1; + res->q[0] = pkt->q[0]; + if (act == ACTION_REPLY) { + res->ans = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_rr); + res->hdr.anscount = 1; + res->ans[0].name = pkt->q[0].name; + res->ans[0].type = PJ_DNS_TYPE_A; + res->ans[0].dnsclass = 1; + res->ans[0].ttl = 300; + res->ans[0].rdata.a.ip_addr.s_addr = (i == 0 ? JOIN_ADDR_OLD : + JOIN_ADDR_NEW); + } else { + res->hdr.flags |= PJ_DNS_SET_RCODE(act); + } + } + unlock(); + *p_res = res; +} + +static void join_srv_set(const char *name, int old_act, int new_act) +{ + lock(); + if (name) { + pj_bzero(&join_srv, sizeof(join_srv)); + join_srv.name = name; + } + join_srv.old_act = old_act; + join_srv.new_act = new_act; + unlock(); +} + +static unsigned join_id_count(void) +{ + unsigned n; + + lock(); + n = join_srv.id_cnt; + unlock(); + return n; +} + +/* Wait for the server to have seen count queries of the name */ +static int wait_join_ids(unsigned count) +{ + unsigned i; + + for (i = 0; join_id_count() < count && i < 500; ++i) + pj_thread_sleep(10); + return join_id_count() >= count ? 0 : -1; +} + +static struct { + volatile pj_bool_t called; + pj_status_t status; + pj_uint32_t addr; +} join_cb[4]; +static pj_dns_resolver *join_res; +static pj_status_t join_start_status; + +static void dns_callback_join(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *resp) +{ + unsigned i = (unsigned)(pj_ssize_t)user_data; + + lock(); + join_cb[i].status = status; + if (status == PJ_SUCCESS && resp && resp->hdr.anscount) + join_cb[i].addr = resp->ans[0].rdata.a.ip_addr.s_addr; + join_cb[i].called = PJ_TRUE; + unlock(); +} + +static pj_bool_t join_called(unsigned i) +{ + pj_bool_t called; + + lock(); + called = join_cb[i].called; + unlock(); + return called; +} + +static pj_status_t join_status(unsigned i) +{ + pj_status_t status; + + lock(); + status = join_cb[i].status; + unlock(); + return status; +} + +static pj_uint32_t join_addr(unsigned i) +{ + pj_uint32_t addr; + + lock(); + addr = join_cb[i].addr; + unlock(); + return addr; +} + +static pj_status_t join_started(void) +{ + pj_status_t status; + + lock(); + status = join_start_status; + unlock(); + return status; +} + +/* The old query's callback: the query started here finds the old one done + * and the new one pending + */ +static void dns_callback_join_again(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *resp) +{ + pj_str_t name; + pj_status_t st; + + lock(); + name = pj_str((char*)join_srv.name); + unlock(); + st = pj_dns_resolver_start_query(join_res, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_join, (void*)2, NULL); + lock(); + join_start_status = st; + unlock(); + dns_callback_join(user_data, status, resp); +} + +static int wait_join_cb(unsigned i) +{ + unsigned n; + + for (n = 0; !join_called(i) && n < 1000; ++n) + pj_thread_sleep(10); + return join_called(i) ? 0 : -1; +} + +/* The old query, pending at the reset, and a new one for its name */ +static int join_start_old_new(const char *name, pj_dns_callback *old_cb, + pj_bool_t clear, pj_dns_async_query **p_new) +{ + pj_str_t n = pj_str((char*)name); + pj_status_t status; + + lock(); + pj_bzero(join_cb, sizeof(join_cb)); + join_start_status = PJ_EUNKNOWN; + unlock(); + if (pj_dns_resolver_start_query(join_res, &n, PJ_DNS_TYPE_A, 0, old_cb, + (void*)0, NULL) != PJ_SUCCESS) + { + return -1; + } + if (wait_join_ids(1) != 0) + return -2; + if (clear) + status = pj_dns_resolver_clear_cache(join_res); + else + status = pj_dns_resolver_reset_ns_state(join_res); + if (status != PJ_SUCCESS) + return -3; + if (pj_dns_resolver_start_query(join_res, &n, PJ_DNS_TYPE_A, 0, + &dns_callback_join, (void*)1, + p_new) != PJ_SUCCESS) + { + return -4; + } + /* Sent on its own, not joined to the old one: before the old one + * completes, which may start another query + */ + if (wait_join_ids(2) != 0 || join_called(0)) + return -5; + return 0; +} + +/* A query started after the reset doesn't join one pending from before: + * it is sent on its own, and the queries started after the old one + * completes, by a response or a timeout, join the new one. The old one's + * refusal doesn't mark the nameserver as bad. + */ +static int dns_reset_join_test(void) +{ + pj_str_t ns_addr = pj_str("127.0.0.1"); + pj_uint16_t port = g_server[0].port; + pj_str_t name = pj_str("name_join_c2"); + pj_dns_settings lset; + pj_dns_async_query *q = NULL; + int rc = 0; + + PJ_LOG(3,(THIS_FILE, " reset nameserver state, pending queries test")); + + join_res = NULL; + PJ_TEST_SUCCESS(pj_dns_resolver_create(mem, NULL, 0, timer_heap, ioqueue, + &join_res), + NULL, SYS_FAIL(-950)); + pj_dns_resolver_get_settings(join_res, &lset); + lset.qretr_delay = 1000; + lset.qretr_count = 3; + pj_dns_resolver_set_settings(join_res, &lset); + PJ_TEST_SUCCESS(pj_dns_resolver_set_ns(join_res, 1, &ns_addr, &port), + NULL, SYS_FAIL(-951)); + join_srv_set("name_join_a", ACTION_IGNORE, ACTION_IGNORE); + g_server[0].action_cb = &join_action; + g_server[0].action = ACTION_CB; + + /* The old one answered: its address to its own callback only */ + PJ_TEST_EQ(join_start_old_new("name_join_a", &dns_callback_join_again, + PJ_FALSE, NULL), + 0, NULL, SYS_FAIL(-952)); + join_srv_set(NULL, ACTION_REPLY, ACTION_IGNORE); + PJ_TEST_EQ(wait_join_cb(0), 0, NULL, SYS_FAIL(-953)); + PJ_TEST_EQ(join_addr(0), JOIN_ADDR_OLD, NULL, SYS_FAIL(-954)); + PJ_TEST_SUCCESS(join_started(), NULL, SYS_FAIL(-955)); + join_srv_set(NULL, ACTION_REPLY, ACTION_REPLY); + PJ_TEST_EQ(wait_join_cb(2), 0, NULL, SYS_FAIL(-956)); + PJ_TEST_EQ(join_addr(2), JOIN_ADDR_NEW, NULL, SYS_FAIL(-957)); + PJ_TEST_EQ(join_id_count(), 2, NULL, SYS_FAIL(-958)); + PJ_TEST_EQ(wait_join_cb(1), 0, NULL, SYS_FAIL(-959)); + PJ_TEST_EQ(join_addr(1), JOIN_ADDR_NEW, NULL, SYS_FAIL(-960)); + + /* The old one timed out: the one started then joins the new one */ + join_srv_set("name_join_b", ACTION_IGNORE, ACTION_IGNORE); + PJ_TEST_EQ(join_start_old_new("name_join_b", &dns_callback_join_again, + PJ_FALSE, NULL), + 0, NULL, SYS_FAIL(-961)); + PJ_TEST_EQ(wait_join_cb(0), 0, NULL, SYS_FAIL(-962)); + PJ_TEST_EQ(join_status(0), PJ_ETIMEDOUT, NULL, SYS_FAIL(-963)); + PJ_TEST_SUCCESS(join_started(), NULL, SYS_FAIL(-964)); + PJ_TEST_EQ(wait_join_cb(2), 0, NULL, SYS_FAIL(-965)); + PJ_TEST_EQ(join_status(2), PJ_ETIMEDOUT, NULL, SYS_FAIL(-966)); + PJ_TEST_EQ(join_id_count(), 2, NULL, SYS_FAIL(-967)); + PJ_TEST_EQ(wait_join_cb(1), 0, NULL, SYS_FAIL(-968)); + + /* The old one refused: the nameserver is not marked as bad */ + join_srv_set("name_join_c", ACTION_IGNORE, ACTION_IGNORE); + PJ_TEST_EQ(join_start_old_new("name_join_c", &dns_callback_join, + PJ_FALSE, &q), + 0, NULL, SYS_FAIL(-969)); + join_srv_set(NULL, PJ_DNS_RCODE_REFUSED, ACTION_IGNORE); + PJ_TEST_EQ(wait_join_cb(0), 0, NULL, SYS_FAIL(-970)); + PJ_TEST_EQ(join_status(0), + PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), + NULL, SYS_FAIL(-971)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-972)); + q = NULL; + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(join_res, &name, + PJ_DNS_TYPE_A, 0, + &dns_callback_join, + (void*)3, &q), + NULL, SYS_FAIL(-973)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-974)); + q = NULL; + + /* The same after the cache is cleared */ + join_srv_set("name_join_d", ACTION_IGNORE, ACTION_IGNORE); + PJ_TEST_EQ(join_start_old_new("name_join_d", &dns_callback_join, + PJ_TRUE, &q), + 0, NULL, SYS_FAIL(-975)); + +on_return: + if (q) + pj_dns_resolver_cancel_query(q, PJ_FALSE); + if (join_res) { + pj_dns_resolver_destroy(join_res, PJ_FALSE); + join_res = NULL; + } + join_srv_set(NULL, ACTION_IGNORE, ACTION_IGNORE); + lock(); + join_srv.name = NULL; + unlock(); + g_server[0].action = ACTION_IGNORE; + return rc; +} + + //////////////////////////////////////////////////////////////////////////// /* Cancelling a query */ @@ -3104,25 +3465,34 @@ static struct { unsigned qretr_delay; /* of the resolver of the test */ } sys_state; +/* The lookups so far */ +static int sys_count(void) +{ + int count; + + lock(); + count = sys_state.count; + unlock(); + return count; +} + static pj_status_t test_sys_lookup(int af, const pj_str_t *name, unsigned *count, pj_addrinfo ai[]) { /* 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)); + unlock(); if (block) { pj_sem_wait(block); + lock(); sys_state.unblocked = PJ_TRUE; + unlock(); } if (sys_state.status != PJ_SUCCESS) return sys_state.status; @@ -3151,6 +3521,7 @@ static void dns_callback_sys(void *user_data, { unsigned i = (unsigned)(pj_ssize_t)user_data; + lock(); sys_cb[i].called++; sys_cb[i].status = status; sys_cb[i].type = 0; @@ -3163,6 +3534,27 @@ static void dns_callback_sys(void *user_data, else if (resp->ans[0].type == PJ_DNS_TYPE_AAAA) sys_cb[i].addr6_last = resp->ans[0].rdata.aaaa.ip_addr.s6_addr[15]; } + unlock(); +} + +static int sys_cb_called(unsigned i) +{ + int called; + + lock(); + called = sys_cb[i].called; + unlock(); + return called; +} + +static pj_status_t sys_cb_status(unsigned i) +{ + pj_status_t status; + + lock(); + status = sys_cb[i].status; + unlock(); + return status; } static void sys_reset(void) @@ -3228,9 +3620,9 @@ static int nt_wait_cb(unsigned i) { unsigned n; - for (n = 0; !sys_cb[i].called && n < 500; ++n) + for (n = 0; !sys_cb_called(i) && n < 500; ++n) nt_poll(10); - return sys_cb[i].called ? 0 : -1; + return sys_cb_called(i) ? 0 : -1; } static void dns_callback_nt(void *user_data, @@ -3265,6 +3657,35 @@ static void dns_callback_nt_destroy(void *user_data, nt.res = NULL; } +/* The number of queries sent to the nameserver since the last call, + * waiting for the first one if asked: a packet sent may take a while to be + * readable, e.g. on macOS + */ +static unsigned nt_drain(pj_bool_t wait_first) +{ + char buf[512]; + unsigned n = 0; + + for (;;) { + pj_fd_set_t rset; + pj_time_val timeout = {0, 50}; + pj_ssize_t len = sizeof(buf); + + if (n == 0 && wait_first) + timeout.sec = 2; + PJ_FD_ZERO(&rset); + PJ_FD_SET(nt.sock, &rset); + if (pj_sock_select((int)nt.sock + 1, &rset, NULL, NULL, + &timeout) <= 0 || + pj_sock_recv(nt.sock, buf, &len, 0) != PJ_SUCCESS) + { + break; + } + ++n; + } + return n; +} + static int nt_start(pj_dns_callback *cb, const char *name, int type, unsigned i, pj_dns_async_query **p_q) { @@ -3286,8 +3707,11 @@ static int nt_setup(void) pj_bzero(&nt, sizeof(nt)); nt.sock = PJ_INVALID_SOCKET; nt.pool = pj_pool_create(mem, "nothreads", 4000, 4000, NULL); - if (!nt.pool) + if (!nt.pool || + pj_mutex_create_simple(nt.pool, "nothreads", &mutex) != PJ_SUCCESS) + { return -1; + } if (pj_timer_heap_create(nt.pool, 32, &nt.th) != PJ_SUCCESS || pj_ioqueue_create(nt.pool, 8, &nt.ioq) != PJ_SUCCESS) { @@ -3330,6 +3754,10 @@ static void nt_teardown(void) pj_ioqueue_destroy(nt.ioq); if (nt.th) pj_timer_heap_destroy(nt.th); + if (mutex) { + pj_mutex_destroy(mutex); + mutex = NULL; + } if (nt.pool) pj_pool_release(nt.pool); pj_bzero(&nt, sizeof(nt)); @@ -3356,14 +3784,14 @@ int resolver_nothreads_test(void) PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_a", PJ_DNS_TYPE_A, 1, NULL), NULL, SYS_FAIL(-1402)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1403)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1403)); PJ_TEST_EQ(nt_wait_cb(0), 0, NULL, SYS_FAIL(-1404)); PJ_TEST_EQ(nt_wait_cb(1), 0, NULL, SYS_FAIL(-1405)); PJ_TEST_SUCCESS(sys_cb[0].status, NULL, SYS_FAIL(-1406)); PJ_TEST_SUCCESS(sys_cb[1].status, NULL, SYS_FAIL(-1407)); PJ_TEST_EQ(sys_cb[0].addr, SYS_ADDR, NULL, SYS_FAIL(-1408)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1409)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1409)); /* The nameserver bad once its probing is over: the queries of a name * join the one being looked up, from the timer @@ -3387,7 +3815,7 @@ int resolver_nothreads_test(void) NULL, SYS_FAIL(-1414)); PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, SYS_FAIL(-1415)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1416)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1416)); PJ_TEST_EQ(nt_wait_cb(0), 0, NULL, SYS_FAIL(-1417)); PJ_TEST_EQ(nt_wait_cb(1), 0, NULL, SYS_FAIL(-1418)); PJ_TEST_EQ(nt_wait_cb(2), 0, NULL, SYS_FAIL(-1419)); @@ -3398,7 +3826,7 @@ int resolver_nothreads_test(void) PJ_TEST_EQ(sys_cb[2].type, PJ_DNS_TYPE_AAAA, NULL, SYS_FAIL(-1422)); /* One lookup per type, the first query reported before the joined */ - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1423)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1423)); PJ_TEST_TRUE(nt.seq_of[0] < nt.seq_of[1], NULL, SYS_FAIL(-1424)); @@ -3415,7 +3843,7 @@ int resolver_nothreads_test(void) PJ_TEST_EQ(nt_wait_cb(3), 0, NULL, SYS_FAIL(-1427)); PJ_TEST_SUCCESS(sys_cb[3].status, NULL, SYS_FAIL(-1428)); PJ_TEST_EQ(sys_cb[3].called, 1, NULL, SYS_FAIL(-1429)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1430)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1430)); /* Not cached when the nameservers' state is reset meanwhile */ nt_poll(3 * 50); @@ -3428,7 +3856,7 @@ int resolver_nothreads_test(void) 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(sys_cb_status(0), 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)); @@ -3443,6 +3871,48 @@ int resolver_nothreads_test(void) nt_poll(3 * 50); PJ_TEST_TRUE(sys_none_works(nt.res), NULL, SYS_FAIL(-1480)); + /* After the reset, a query doesn't join one waiting for the lookup: + * it is sent + */ + sys_reset(); + sys_state.qretr_delay = 50; + nt_drain(PJ_FALSE); + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_h", PJ_DNS_TYPE_A, 0, + NULL), + NULL, SYS_FAIL(-1482)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(nt.res), NULL, + SYS_FAIL(-1483)); + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_h", PJ_DNS_TYPE_A, 1, &q), + NULL, SYS_FAIL(-1484)); + PJ_TEST_EQ(nt_drain(PJ_TRUE), 1, NULL, SYS_FAIL(-1485)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1486)); + PJ_TEST_EQ(nt_wait_cb(0), 0, NULL, SYS_FAIL(-1487)); + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_h2", PJ_DNS_TYPE_A, 2, + NULL), + NULL, SYS_FAIL(-1488)); + PJ_TEST_EQ(nt_wait_cb(2), 0, NULL, SYS_FAIL(-1489)); + nt_poll(3 * 50); + PJ_TEST_TRUE(sys_none_works(nt.res), NULL, SYS_FAIL(-1512)); + + /* Cancelled while waiting for the lookup: not looked up, and the next + * query for the name doesn't join it + */ + sys_reset(); + sys_state.qretr_delay = 50; + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_i", PJ_DNS_TYPE_A, 0, &q), + NULL, SYS_FAIL(-1513)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1514)); + nt_poll(100); + PJ_TEST_SUCCESS(nt_start(&dns_callback_nt, "nt_i", PJ_DNS_TYPE_A, 1, + NULL), + NULL, SYS_FAIL(-1515)); + PJ_TEST_EQ(nt_wait_cb(1), 0, NULL, SYS_FAIL(-1516)); + PJ_TEST_SUCCESS(sys_cb_status(1), NULL, SYS_FAIL(-1517)); + PJ_TEST_EQ(sys_cb_called(0), 0, NULL, SYS_FAIL(-1518)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1519)); + /* Destroyed from a callback: the query waiting is reported as * cancelled, once, the joined one with the answer, nothing is left */ @@ -3465,7 +3935,7 @@ int resolver_nothreads_test(void) PJ_TEST_EQ(sys_cb[2].called, 1, NULL, SYS_FAIL(-1438)); PJ_TEST_EQ(sys_cb[2].status, PJ_ECANCELLED, NULL, SYS_FAIL(-1439)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1440)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1440)); PJ_TEST_EQ(pj_timer_heap_count(nt.th), 0, NULL, SYS_FAIL(-1441)); @@ -3478,13 +3948,55 @@ int resolver_nothreads_test(void) #endif /* !PJ_HAS_THREADS */ #if PJ_HAS_THREADS +/* The address family and the name of the last lookup */ +static int sys_af(void) +{ + int af; + + lock(); + af = sys_state.af; + unlock(); + return af; +} + +static int sys_name_cmp(const char *name) +{ + int cmp; + + lock(); + cmp = pj_ansi_strcmp(sys_state.name, name); + unlock(); + return cmp; +} + +static pj_uint32_t sys_cb_addr(unsigned i) +{ + pj_uint32_t addr; + + lock(); + addr = sys_cb[i].addr; + unlock(); + return addr; +} + +/* Clears the callback state of query i, or of all with a negative i */ +static void sys_cb_clear(int i) +{ + lock(); + if (i < 0) + pj_bzero(sys_cb, sizeof(sys_cb)); + else + pj_bzero(&sys_cb[i], sizeof(sys_cb[i])); + unlock(); +} + static int wait_sys_cb(unsigned i) { unsigned n; - for (n = 0; !sys_cb[i].called && n < 300; ++n) + for (n = 0; !sys_cb_called(i) && n < 300; ++n) pj_thread_sleep(10); - return sys_cb[i].called ? 0 : -1; + return sys_cb_called(i) ? 0 : -1; } /* After a test, passed or not: the lookups waiting on the semaphore go @@ -3572,9 +4084,9 @@ static int wait_sys_count(int count) { unsigned i; - for (i = 0; sys_state.count < count && i < 500; ++i) + for (i = 0; sys_count() < count && i < 500; ++i) pj_thread_sleep(10); - return sys_state.count >= count ? 0 : -1; + return sys_count() >= count ? 0 : -1; } /* Time a query out on the servers, not trusted yet, and get the answer of @@ -3643,7 +4155,7 @@ static int dns_sys_fallback_timeout_test(void) NULL, SYS_FAIL(-1008)); PJ_TEST_EQ(wait_sys_cb(3), 0, NULL, SYS_FAIL(-1009)); PJ_TEST_EQ(sys_cb[3].addr, IP_ADDR0, NULL, SYS_FAIL(-1018)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1019)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1019)); PJ_TEST_EQ(wait_pkt_count(2), 0, NULL, SYS_FAIL(-1027)); sent = g_server[0].pkt_count + g_server[1].pkt_count; @@ -3652,7 +4164,7 @@ static int dns_sys_fallback_timeout_test(void) */ PJ_TEST_EQ(sys_time_out(res, "sysname1", 0), 0, NULL, SYS_FAIL(-1001)); PJ_TEST_EQ(sys_cb[0].status, PJ_ETIMEDOUT, NULL, SYS_FAIL(-1002)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1004)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1004)); PJ_TEST_EQ(wait_pkt_count(sent + 2), 0, NULL, SYS_FAIL(-1025)); sent = g_server[0].pkt_count + g_server[1].pkt_count; @@ -3664,9 +4176,9 @@ static int dns_sys_fallback_timeout_test(void) PJ_TEST_EQ(sys_time_out(res, "sysname1b", 1), 0, NULL, SYS_FAIL(-1007)); PJ_TEST_SUCCESS(sys_cb[1].status, NULL, SYS_FAIL(-1003)); PJ_TEST_EQ(sys_cb[1].addr, SYS_ADDR, NULL, SYS_FAIL(-1005)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1024)); - PJ_TEST_EQ(sys_state.af, pj_AF_INET(), NULL, SYS_FAIL(-1202)); - PJ_TEST_EQ(pj_ansi_strcmp(sys_state.name, "sysname1b"), 0, NULL, + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1024)); + PJ_TEST_EQ(sys_af(), pj_AF_INET(), NULL, SYS_FAIL(-1202)); + PJ_TEST_EQ(sys_name_cmp("sysname1b"), 0, NULL, SYS_FAIL(-1006)); PJ_TEST_TRUE(g_server[0].pkt_count + g_server[1].pkt_count > sent, NULL, SYS_FAIL(-1203)); @@ -3682,7 +4194,7 @@ static int dns_sys_fallback_timeout_test(void) PJ_TEST_EQ(wait_sys_cb(2), 0, NULL, SYS_FAIL(-1013)); PJ_TEST_SUCCESS(sys_cb[2].status, NULL, SYS_FAIL(-1014)); PJ_TEST_EQ(sys_cb[2].addr, SYS_ADDR, NULL, SYS_FAIL(-1015)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1016)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1016)); PJ_TEST_TRUE(sys_none_works(res), NULL, SYS_FAIL(-1017)); /* As is a DNS AAAA query */ @@ -3695,8 +4207,8 @@ static int dns_sys_fallback_timeout_test(void) PJ_TEST_SUCCESS(sys_cb[1].status, NULL, SYS_FAIL(-1056)); PJ_TEST_EQ(sys_cb[1].type, PJ_DNS_TYPE_AAAA, NULL, SYS_FAIL(-1057)); PJ_TEST_EQ(sys_cb[1].addr6_last, 1, NULL, SYS_FAIL(-1058)); - PJ_TEST_EQ(sys_state.af, pj_AF_INET6(), NULL, SYS_FAIL(-1059)); - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1074)); + PJ_TEST_EQ(sys_af(), pj_AF_INET6(), NULL, SYS_FAIL(-1059)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1074)); /* The name which timed out is answered now, the query was recycled; * the one answered by the system resolver is cached meanwhile @@ -3716,7 +4228,7 @@ static int dns_sys_fallback_timeout_test(void) PJ_TEST_EQ(q, NULL, NULL, SYS_FAIL(-1079)); PJ_TEST_EQ(sys_cb[2].called, 1, NULL, SYS_FAIL(-1204)); PJ_TEST_EQ(sys_cb[2].addr, SYS_ADDR, NULL, SYS_FAIL(-1099)); - PJ_TEST_EQ(sys_state.count, 4, NULL, SYS_FAIL(-1152)); + PJ_TEST_EQ(sys_count(), 4, NULL, SYS_FAIL(-1152)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -3726,7 +4238,7 @@ static int dns_sys_fallback_timeout_test(void) PJ_TEST_NOT_NULL(res, NULL, SYS_FAIL(-1020)); PJ_TEST_EQ(sys_time_out(res, "sysname3", 0), 0, NULL, SYS_FAIL(-1021)); PJ_TEST_EQ(sys_cb[0].status, PJ_ETIMEDOUT, NULL, SYS_FAIL(-1022)); - PJ_TEST_EQ(sys_state.count, 4, NULL, SYS_FAIL(-1023)); + PJ_TEST_EQ(sys_count(), 4, NULL, SYS_FAIL(-1023)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -3806,9 +4318,9 @@ static int dns_sys_fallback_srv_test(void) SYS_FAIL(-1038)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, SYS_ADDR, NULL, SYS_FAIL(-1039)); - PJ_TEST_EQ(pj_ansi_strcmp(sys_state.name, "example.test"), 0, NULL, + PJ_TEST_EQ(sys_name_cmp("example.test"), 0, NULL, SYS_FAIL(-1040)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1041)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1041)); /* Not asked to fall back: the failure of the nameservers */ pj_bzero(&sys_srv, sizeof(sys_srv)); @@ -3818,7 +4330,7 @@ static int dns_sys_fallback_srv_test(void) PJ_TEST_EQ(wait_sys_srv(), 0, NULL, SYS_FAIL(-1043)); PJ_TEST_EQ(sys_srv.status, PJLIB_UTIL_EDNSNOWORKINGNS, NULL, SYS_FAIL(-1044)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1045)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1045)); /* A cached negative answer, the domain having no SRV record: the * domain's address from the system resolver, on the default port @@ -3844,7 +4356,7 @@ static int dns_sys_fallback_srv_test(void) PJ_TEST_EQ(sys_srv.rec.entry[0].port, 5060, NULL, SYS_FAIL(-1186)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, SYS_ADDR, NULL, SYS_FAIL(-1187)); - PJ_TEST_EQ(pj_ansi_strcmp(sys_state.name, "none.test"), 0, NULL, + PJ_TEST_EQ(sys_name_cmp("none.test"), 0, NULL, SYS_FAIL(-1188)); /* A cached SRV record, its target not: the target's address from the @@ -3876,7 +4388,7 @@ static int dns_sys_fallback_srv_test(void) PJ_TEST_EQ(sys_srv.rec.entry[0].port, 5070, NULL, SYS_FAIL(-1051)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, SYS_ADDR, NULL, SYS_FAIL(-1052)); - PJ_TEST_EQ(pj_ansi_strcmp(sys_state.name, "host.cached.test"), 0, NULL, + PJ_TEST_EQ(sys_name_cmp("host.cached.test"), 0, NULL, SYS_FAIL(-1053)); PJ_TEST_TRUE(sys_none_works(res), NULL, SYS_FAIL(-1054)); @@ -3928,7 +4440,7 @@ static int dns_sys_fallback_cache_test(void) PJ_TEST_TRUE(q == NULL, NULL, SYS_FAIL(-1064)); PJ_TEST_EQ(sys_cb[1].called, 1, NULL, SYS_FAIL(-1065)); PJ_TEST_EQ(sys_cb[1].addr, IP_ADDR0, NULL, SYS_FAIL(-1066)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1067)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1067)); /* 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 @@ -3957,7 +4469,7 @@ static int dns_sys_fallback_cache_test(void) PJ_TEST_EQ(sys_cb[2].status, PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN), NULL, SYS_FAIL(-1072)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1073)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1073)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4000,7 +4512,7 @@ static int dns_sys_fallback_join_test(void) &q2), NULL, SYS_FAIL(-1085)); PJ_TEST_TRUE(q0 && q1 && q2, NULL, SYS_FAIL(-1086)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1087)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1087)); /* The first one silently, the last one with the notification */ PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q0, PJ_FALSE), NULL, @@ -4017,7 +4529,7 @@ static int dns_sys_fallback_join_test(void) /* The query is reported before the queries which joined it */ PJ_TEST_EQ(sys_cb[0].called, 0, NULL, SYS_FAIL(-1095)); PJ_TEST_EQ(sys_cb[2].called, 1, NULL, SYS_FAIL(-1096)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1097)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1097)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4058,7 +4570,7 @@ static int dns_sys_fallback_reset_test(void) 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(sys_cb_addr(1), 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, @@ -4075,7 +4587,7 @@ static int dns_sys_fallback_reset_test(void) 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; + sys_cb_clear(1); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name2, PJ_DNS_TYPE_A, 0, &dns_callback_sys, (void*)1, NULL), @@ -4085,7 +4597,7 @@ static int dns_sys_fallback_reset_test(void) 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(sys_cb_status(1), 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), @@ -4106,7 +4618,7 @@ static int dns_sys_fallback_reset_test(void) 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; + sys_cb_clear(1); PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, &dns_callback_sys, (void*)1, NULL), @@ -4117,7 +4629,7 @@ static int dns_sys_fallback_reset_test(void) 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_TEST_EQ(sys_cb_status(1), 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, @@ -4133,6 +4645,99 @@ static int dns_sys_fallback_reset_test(void) return rc; } +/* After the reset, a query doesn't join one being looked up with the + * system resolver, nor one which goes to it only after the reset. + */ +static int dns_sys_fallback_reset_join_test(void) +{ + pj_str_t name_a = pj_str("sysjoin_a"); + pj_str_t name_b = pj_str("sysjoin_b"); + pj_dns_resolver *res = NULL; + pj_dns_settings lset; + pj_dns_async_query *q = NULL; + int count; + int rc = 0; + + PJ_LOG(3,(THIS_FILE, " system resolver fallback, reset and pending " + "queries test")); + + sys_reset(); + res = sys_resolver_ns(PJ_TRUE, 1000, 1); + PJ_TEST_NOT_NULL(res, NULL, SYS_FAIL(-1490)); + pj_dns_resolver_get_settings(res, &lset); + lset.qretr_count = 4; + pj_dns_resolver_set_settings(res, &lset); + PJ_TEST_SUCCESS(pj_sem_create(pool, NULL, 0, 4, &sys_state.block), NULL, + SYS_FAIL(-1491)); + join_srv_set("sysjoin_a", PJ_DNS_RCODE_REFUSED, ACTION_IGNORE); + g_server[0].action_cb = &join_action; + g_server[0].action = ACTION_CB; + + /* Refused, being looked up at the reset: the new query is sent */ + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name_a, PJ_DNS_TYPE_A, + 0, &dns_callback_sys, + (void*)0, NULL), + NULL, SYS_FAIL(-1492)); + PJ_TEST_EQ(wait_sys_count(1), 0, NULL, SYS_FAIL(-1493)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1494)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name_a, PJ_DNS_TYPE_A, + 0, &dns_callback_sys, + (void*)1, &q), + NULL, SYS_FAIL(-1495)); + PJ_TEST_EQ(wait_join_ids(2), 0, NULL, SYS_FAIL(-1496)); + pj_sem_post(sys_state.block); + PJ_TEST_EQ(wait_sys_cb(0), 0, NULL, SYS_FAIL(-1497)); + PJ_TEST_EQ(sys_cb_addr(0), SYS_ADDR, NULL, SYS_FAIL(-1498)); + PJ_TEST_SUCCESS(pj_dns_resolver_cancel_query(q, PJ_FALSE), NULL, + SYS_FAIL(-1499)); + q = NULL; + + /* Refused after the reset, while a new query is pending: the query + * started then joins the new one, not the lookup + */ + sys_cb_clear(-1); + count = sys_count(); + join_srv_set("sysjoin_b", ACTION_IGNORE, ACTION_IGNORE); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name_b, PJ_DNS_TYPE_A, + 0, &dns_callback_sys, + (void*)0, NULL), + NULL, SYS_FAIL(-1500)); + PJ_TEST_EQ(wait_join_ids(1), 0, NULL, SYS_FAIL(-1501)); + PJ_TEST_SUCCESS(pj_dns_resolver_reset_ns_state(res), NULL, + SYS_FAIL(-1502)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name_b, PJ_DNS_TYPE_A, + 0, &dns_callback_sys, + (void*)1, NULL), + NULL, SYS_FAIL(-1503)); + PJ_TEST_EQ(wait_join_ids(2), 0, NULL, SYS_FAIL(-1504)); + join_srv_set(NULL, PJ_DNS_RCODE_REFUSED, ACTION_IGNORE); + PJ_TEST_EQ(wait_sys_count(count + 1), 0, NULL, SYS_FAIL(-1505)); + PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name_b, PJ_DNS_TYPE_A, + 0, &dns_callback_sys, + (void*)2, NULL), + NULL, SYS_FAIL(-1506)); + join_srv_set(NULL, PJ_DNS_RCODE_REFUSED, ACTION_REPLY); + PJ_TEST_EQ(wait_sys_cb(2), 0, NULL, SYS_FAIL(-1507)); + PJ_TEST_EQ(sys_cb_addr(2), JOIN_ADDR_NEW, NULL, SYS_FAIL(-1508)); + PJ_TEST_EQ(join_id_count(), 2, NULL, SYS_FAIL(-1509)); + pj_sem_post(sys_state.block); + PJ_TEST_EQ(wait_sys_cb(0), 0, NULL, SYS_FAIL(-1510)); + PJ_TEST_EQ(sys_cb_addr(0), SYS_ADDR, NULL, SYS_FAIL(-1511)); + +on_return: + if (q) + pj_dns_resolver_cancel_query(q, PJ_FALSE); + sys_cleanup(&res); + join_srv_set(NULL, ACTION_IGNORE, ACTION_IGNORE); + lock(); + join_srv.name = NULL; + unlock(); + g_server[0].action = ACTION_IGNORE; + 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. */ @@ -4362,7 +4967,7 @@ static int dns_sys_fallback_trusted_test(void) PJ_TEST_EQ(sys_time_out_type(res, "name.trusted.test", PJ_DNS_TYPE_AAAA, 3, PJ_ETIMEDOUT), 0, NULL, SYS_FAIL(-1213)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1214)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1214)); /* SRV still answered by DNS, with its port and target */ pj_bzero(&sys_srv, sizeof(sys_srv)); @@ -4375,7 +4980,7 @@ static int dns_sys_fallback_trusted_test(void) PJ_TEST_EQ(sys_srv.rec.entry[0].port, 5070, NULL, SYS_FAIL(-1218)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, IP_ADDR0, NULL, SYS_FAIL(-1219)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1220)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1220)); /* One name dropped, trusted all the same; the answer which * follows is trusted for a second only, for what comes next @@ -4390,7 +4995,7 @@ static int dns_sys_fallback_trusted_test(void) PJ_TEST_EQ(sys_time_out_type(res, "name2.trusted.test", PJ_DNS_TYPE_A, 1, PJ_SUCCESS), 0, NULL, SYS_FAIL(-1222)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1223)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1223)); sys_drop.drop_name = NULL; /* The resolver's own probing happens to be an AAAA query: the @@ -4418,11 +5023,11 @@ static int dns_sys_fallback_trusted_test(void) PJ_TEST_EQ(sys_srv.rec.entry[0].port, 5060, NULL, SYS_FAIL(-1230)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, SYS_ADDR, NULL, SYS_FAIL(-1231)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1232)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1232)); } else { PJ_TEST_EQ(status, PJLIB_UTIL_EDNSNOWORKINGNS, NULL, SYS_FAIL(-1233)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1234)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1234)); } pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4452,7 +5057,7 @@ static int dns_sys_fallback_trusted_test(void) PJ_SUCCESS), 0, NULL, SYS_FAIL(-1236)); PJ_TEST_EQ(sys_cb[0].addr, IP_ADDR0, NULL, SYS_FAIL(-1237)); - PJ_TEST_EQ(sys_state.count, 0, NULL, SYS_FAIL(-1238)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1238)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4495,8 +5100,8 @@ static int dns_sys_fallback_cancel_test(void) PJ_TEST_EQ(sys_time_out(res, "cancel1b", 1), 0, NULL, SYS_FAIL(-1243)); PJ_TEST_SUCCESS(sys_cb[1].status, NULL, SYS_FAIL(-1244)); PJ_TEST_EQ(sys_cb[0].called, 0, NULL, SYS_FAIL(-1245)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1246)); - PJ_TEST_EQ(pj_ansi_strcmp(sys_state.name, "cancel1b"), 0, NULL, + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1246)); + PJ_TEST_EQ(sys_name_cmp("cancel1b"), 0, NULL, SYS_FAIL(-1247)); /* Cancelled while waiting for the one lookup thread: skipped */ @@ -4519,14 +5124,14 @@ static int dns_sys_fallback_cancel_test(void) PJ_TEST_SUCCESS(sys_cb[2].status, NULL, SYS_FAIL(-1254)); pj_thread_sleep(200); PJ_TEST_EQ(sys_cb[3].called, 0, NULL, SYS_FAIL(-1255)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1256)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1256)); /* Started without a callback: the error of the nameservers */ PJ_TEST_EQ(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_A, 0, NULL, NULL, &q), PJLIB_UTIL_EDNSNOWORKINGNS, NULL, SYS_FAIL(-1257)); PJ_TEST_EQ(q, NULL, NULL, SYS_FAIL(-1258)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1259)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1259)); /* The option turned off while a query waits for the lookup thread, * as PJSUA does when destroying: the error of the nameservers @@ -4550,7 +5155,7 @@ static int dns_sys_fallback_cancel_test(void) PJ_TEST_EQ(wait_sys_cb(3), 0, NULL, SYS_FAIL(-1384)); PJ_TEST_EQ(sys_cb[3].status, PJLIB_UTIL_EDNSNOWORKINGNS, NULL, SYS_FAIL(-1385)); - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1386)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1386)); /* A query cancelled while waiting, one which joined it not: looked up * for the latter @@ -4580,7 +5185,7 @@ static int dns_sys_fallback_cancel_test(void) PJ_TEST_SUCCESS(sys_cb[2].status, NULL, SYS_FAIL(-1393)); PJ_TEST_EQ(sys_cb[2].addr, SYS_ADDR, NULL, SYS_FAIL(-1394)); PJ_TEST_EQ(sys_cb[1].called, 0, NULL, SYS_FAIL(-1395)); - PJ_TEST_EQ(sys_state.count, 5, NULL, SYS_FAIL(-1396)); + PJ_TEST_EQ(sys_count(), 5, NULL, SYS_FAIL(-1396)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4645,7 +5250,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(wait_sys_cb(0), 0, NULL, SYS_FAIL(-1262)); PJ_TEST_SUCCESS(sys_cb[0].status, NULL, SYS_FAIL(-1263)); PJ_TEST_EQ(sys_cb[0].addr, SYS_ADDR, NULL, SYS_FAIL(-1264)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1265)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1265)); /* What is cached is that answer, not the refusal; once the server * answers, its answer @@ -4670,7 +5275,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_NOT_NULL(q, NULL, SYS_FAIL(-1268)); PJ_TEST_EQ(wait_sys_cb(0), 0, NULL, SYS_FAIL(-1269)); PJ_TEST_EQ(sys_cb[0].addr, IP_ADDR0, NULL, SYS_FAIL(-1270)); - PJ_TEST_EQ(sys_state.count, 1, NULL, SYS_FAIL(-1271)); + PJ_TEST_EQ(sys_count(), 1, NULL, SYS_FAIL(-1271)); /* Refused and the system resolver fails: refused, and cached */ g_server[0].action = PJ_DNS_RCODE_REFUSED; @@ -4685,7 +5290,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(sys_cb[1].status, PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-1275)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1276)); + PJ_TEST_EQ(sys_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, @@ -4697,7 +5302,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(sys_cb[1].status, PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_REFUSED), NULL, SYS_FAIL(-1280)); - PJ_TEST_EQ(sys_state.count, 2, NULL, SYS_FAIL(-1281)); + PJ_TEST_EQ(sys_count(), 2, NULL, SYS_FAIL(-1281)); #endif sys_state.status = PJ_SUCCESS; @@ -4714,7 +5319,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(wait_sys_cb(2), 0, NULL, SYS_FAIL(-1284)); PJ_TEST_SUCCESS(sys_cb[2].status, NULL, SYS_FAIL(-1285)); PJ_TEST_EQ(sys_cb[2].addr, SYS_ADDR, NULL, SYS_FAIL(-1286)); - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1287)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1287)); sys_cb[2].called = 0; PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name3, PJ_DNS_TYPE_AAAA, 0, @@ -4725,7 +5330,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(sys_cb[2].status, PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_SERVFAIL), NULL, SYS_FAIL(-1290)); - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1291)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1291)); sent = g_server[0].pkt_count; sys_cb[2].called = 0; PJ_TEST_SUCCESS(pj_dns_resolver_start_query(res, &name4, PJ_DNS_TYPE_A, 0, @@ -4735,7 +5340,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(wait_sys_cb(2), 0, NULL, SYS_FAIL(-1293)); PJ_TEST_SUCCESS(sys_cb[2].status, NULL, SYS_FAIL(-1294)); PJ_TEST_TRUE(g_server[0].pkt_count > sent, NULL, SYS_FAIL(-1295)); - PJ_TEST_EQ(sys_state.count, 4, NULL, SYS_FAIL(-1296)); + PJ_TEST_EQ(sys_count(), 4, NULL, SYS_FAIL(-1296)); /* SRV failed by the server: the domain's address, from the system * resolver as its A query is failed too @@ -4750,7 +5355,7 @@ static int dns_sys_fallback_rcode_test(void) PJ_TEST_EQ(sys_srv.rec.entry[0].port, 5060, NULL, SYS_FAIL(-1300)); PJ_TEST_EQ(sys_srv.rec.entry[0].server.addr[0].ip.v4.s_addr, SYS_ADDR, NULL, SYS_FAIL(-1301)); - PJ_TEST_EQ(sys_state.count, 5, NULL, SYS_FAIL(-1302)); + PJ_TEST_EQ(sys_count(), 5, NULL, SYS_FAIL(-1302)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4775,7 +5380,7 @@ static int dns_sys_fallback_rcode_test(void) NULL, SYS_FAIL(-1307)); 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)); + PJ_TEST_EQ(sys_count(), 0, NULL, SYS_FAIL(-1310)); #endif pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -4837,7 +5442,7 @@ static int dns_sys_fallback_pool_test(void) } PJ_TEST_EQ(sys_cb[0].addr, SYS_ADDR, NULL, SYS_FAIL(-1329)); PJ_TEST_EQ(sys_cb[1].addr6_last, 1, NULL, SYS_FAIL(-1330)); - PJ_TEST_EQ(sys_state.count, 4, NULL, SYS_FAIL(-1331)); + PJ_TEST_EQ(sys_count(), 4, NULL, SYS_FAIL(-1331)); /* Cached meanwhile: answered at once, no lookup */ sys_cb[0].called = 0; @@ -4848,7 +5453,7 @@ static int dns_sys_fallback_pool_test(void) PJ_TEST_EQ(q, NULL, NULL, SYS_FAIL(-1333)); PJ_TEST_EQ(sys_cb[0].called, 1, NULL, SYS_FAIL(-1334)); PJ_TEST_EQ(sys_cb[0].addr, SYS_ADDR, NULL, SYS_FAIL(-1335)); - PJ_TEST_EQ(sys_state.count, 4, NULL, SYS_FAIL(-1336)); + PJ_TEST_EQ(sys_count(), 4, NULL, SYS_FAIL(-1336)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; pj_sem_destroy(sys_state.block); @@ -4876,7 +5481,7 @@ static int dns_sys_fallback_pool_test(void) NULL, SYS_FAIL(-1342)); PJ_TEST_EQ(wait_sys_count(3), 0, NULL, SYS_FAIL(-1343)); pj_thread_sleep(200); - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1344)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1344)); pj_sem_post(sys_state.block); PJ_TEST_EQ(wait_sys_count(4), 0, NULL, SYS_FAIL(-1345)); pj_sem_post(sys_state.block); @@ -4910,7 +5515,7 @@ static int dns_sys_fallback_pool_test(void) PJ_TEST_EQ(wait_sys_cb(0), 0, NULL, SYS_FAIL(-1351)); PJ_TEST_SUCCESS(sys_cb[0].status, NULL, SYS_FAIL(-1352)); } - PJ_TEST_EQ(sys_state.count, 3, NULL, SYS_FAIL(-1353)); + PJ_TEST_EQ(sys_count(), 3, NULL, SYS_FAIL(-1353)); lset.cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL; pj_dns_resolver_set_settings(res, &lset); sys_state.status = PJ_ERESOLVE; @@ -4925,7 +5530,7 @@ static int dns_sys_fallback_pool_test(void) PJ_TEST_EQ(sys_cb[0].status, PJLIB_UTIL_EDNSNOWORKINGNS, NULL, SYS_FAIL(-1356)); } - PJ_TEST_EQ(sys_state.count, 5, NULL, SYS_FAIL(-1357)); + PJ_TEST_EQ(sys_count(), 5, NULL, SYS_FAIL(-1357)); pj_dns_resolver_destroy(res, PJ_FALSE); res = NULL; @@ -5342,6 +5947,11 @@ int resolver_test(void) if (rc != 0) goto on_error; + PJ_LOG(3,(THIS_FILE, "dns_reset_join_test")); + rc = dns_reset_join_test(); + if (rc != 0) + goto on_error; + PJ_LOG(3,(THIS_FILE, "dns_cancel_pending_test")); rc = dns_cancel_pending_test(); if (rc != 0) @@ -5373,6 +5983,11 @@ int resolver_test(void) if (rc != 0) goto on_error; + PJ_LOG(3,(THIS_FILE, "dns_sys_fallback_reset_join_test")); + rc = dns_sys_fallback_reset_join_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 6dc7c7801b..9332079b68 100644 --- a/pjlib-util/src/pjlib-util/resolver.c +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -898,6 +898,14 @@ static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver, } +/* Whether the query started after the cache was last dropped */ +static pj_bool_t query_is_current(const pj_dns_resolver *resolver, + const pj_dns_async_query *q) +{ + return q->cache_gen == resolver->cache_gen; +} + + /* Generate an unpredictable transaction ID that is not currently in use. */ /* Returns a free transaction ID, or 0 if none could be found (ID space * exhausted), in which case the caller should fail the query. @@ -1166,7 +1174,8 @@ static void reset_entry(struct cached_res **p_cached) * A query which no nameserver answers is given to start_sys_fallback(). A DNS A * or AAAA query then waits on sys_jobs for the lookup thread, which looks * it up without the lock and gives it back on done_list, and stays in - * hquerybyres meanwhile, so that the queries started for the name join it. + * hquerybyres meanwhile, so that the queries started for the name join it, + * until the cache is dropped. * Another query goes to done_list at once, with the error of the * nameservers. The done_timer reports the queries of done_list as * on_timeout() does, without the lock, and recycles them. Without threads, @@ -1237,8 +1246,10 @@ static pj_bool_t query_has_cb(const pj_dns_async_query *q) } -/* No query joins it anymore */ -static void sys_unjoin(pj_dns_resolver *resolver, const pj_dns_async_query *q) +/* No query joins it anymore. By identity: after the cache is dropped, a + * newer query for the name may hold the key. + */ +static void unjoin(pj_dns_resolver *resolver, const pj_dns_async_query *q) { if (pj_hash_get(resolver->hquerybyres, &q->key, sizeof(q->key), NULL) == q) pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, @@ -1254,7 +1265,7 @@ static pj_status_t report_later(pj_dns_resolver *resolver, /* Without threads, joined until looked up from the done_timer */ if (!q->sys.pending) - sys_unjoin(resolver, q); + unjoin(resolver, q); pj_list_push_back(&resolver->done_list, q); /* Once destroy has started, it reports the queries itself */ @@ -1271,7 +1282,7 @@ static pj_status_t report_later(pj_dns_resolver *resolver, "Error scheduling the report of DNS %s query for %s", pj_dns_get_type_name(q->key.qtype), q->key.name)); pj_list_erase(q); - sys_unjoin(resolver, q); + unjoin(resolver, q); return status; } resolver->done_scheduled = PJ_TRUE; @@ -1435,9 +1446,13 @@ static pj_status_t start_sys_fallback(pj_dns_resolver *resolver, q->sys.lookup = PJ_TRUE; q->sys.pending = PJ_TRUE; - /* The queries started for the name join it meanwhile */ - pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key), 0, - q->hbufkey, q); + /* The queries started for the name join it meanwhile, unless the + * cache was dropped since it started: a newer query may hold the key + */ + if (query_is_current(resolver, q)) { + pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key), 0, + q->hbufkey, q); + } #if PJ_HAS_THREADS q->sys.status = sys_thread_start(resolver); if (q->sys.status == PJ_SUCCESS) { @@ -1534,7 +1549,7 @@ static void report_sys(pj_dns_resolver *resolver, pj_dns_async_query *q) question.name = pj_str(q->key.name); pj_grp_lock_acquire(resolver->grp_lock); if (!resolver->shutting_down && - q->cache_gen == resolver->cache_gen) + query_is_current(resolver, q)) { update_res_cache(resolver, &q->key, status, PJ_TRUE, &pkt, PJ_FALSE); @@ -1572,7 +1587,7 @@ static void report_sys(pj_dns_resolver *resolver, pj_dns_async_query *q) /* Cached as a nameserver's answer is, for bad_ns_ttl */ if (resp && !resolver->shutting_down && - q->cache_gen == resolver->cache_gen) + query_is_current(resolver, q)) { update_res_cache(resolver, &q->key, PJ_SUCCESS, PJ_TRUE, &pkt, PJ_TRUE); @@ -1614,6 +1629,7 @@ static void on_done_timer( pj_timer_heap_t *timer_heap, { q->sys.lookup = PJ_FALSE; q->sys.pending = PJ_FALSE; + unjoin(resolver, q); } pj_grp_lock_release(resolver->grp_lock); @@ -1621,7 +1637,7 @@ static void on_done_timer( pj_timer_heap_t *timer_heap, if (q->sys.pending) { sys_lookup_run(resolver, q); pj_grp_lock_acquire(resolver->grp_lock); - sys_unjoin(resolver, q); + unjoin(resolver, q); pj_grp_lock_release(resolver->grp_lock); } report_sys(resolver, q); @@ -1640,7 +1656,7 @@ static void sys_collect(pj_dns_resolver *resolver, struct query_head *list) while (!pj_list_empty(&resolver->sys_jobs)) { pj_dns_async_query *q = resolver->sys_jobs.next; pj_list_erase(q); - sys_unjoin(resolver, q); + unjoin(resolver, q); pj_list_push_back(list, q); } #if PJ_HAS_THREADS @@ -1649,7 +1665,7 @@ static void sys_collect(pj_dns_resolver *resolver, struct query_head *list) while (!pj_list_empty(&resolver->done_list)) { pj_dns_async_query *q = resolver->done_list.next; pj_list_erase(q); - sys_unjoin(resolver, q); + unjoin(resolver, q); pj_list_push_back(list, q); } } @@ -2474,7 +2490,7 @@ static void on_timeout( pj_timer_heap_t *timer_heap, /* Clear hash table entries */ pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); - pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + unjoin(resolver, q); /* Capture and clear the callback under the lock; invoke it unlocked. */ cb = q->cb; @@ -2669,9 +2685,11 @@ static void on_read_complete(pj_ioqueue_key_t *key, /* The response is now validated (matched an outstanding query). Only such a * response may mark the nameserver bad or bring it back from bad, so - * re-report it now that the match is confirmed. + * re-report it now that the match is confirmed. Not for a query started + * before the cache was dropped, e.g. on the previous network. */ - report_nameserver_status(resolver, src_addr, dns_pkt, PJ_TRUE); + report_nameserver_status(resolver, src_addr, dns_pkt, + query_is_current(resolver, q)); /* Cancel query timeout timer. */ pj_assert(q->timer_entry.id != 0); @@ -2680,7 +2698,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, /* Clear hash table entries */ pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); - pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + unjoin(resolver, q); /* A refusal, or a failed A query, says nothing about the name. A * failed AAAA query stands: some nameservers fail a missing AAAA. @@ -2738,7 +2756,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, * started: the response may be of the previous network. */ if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0 && !resolver->shutting_down && - q->cache_gen == resolver->cache_gen) + query_is_current(resolver, q)) { /* Save/update response cache. */ update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt, @@ -2848,9 +2866,10 @@ PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) /* 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. + * without TTL and, with keep_answers, the answers with records not from + * the system resolver. The responses of the pending queries won't be + * cached nor mark a nameserver bad, and the queries started from now on + * don't join them. Called with the lock held. */ static void drop_cache(pj_dns_resolver *resolver, pj_bool_t keep_answers) { @@ -2858,6 +2877,16 @@ static void drop_cache(pj_dns_resolver *resolver, pj_bool_t keep_answers) ++resolver->cache_gen; + it = pj_hash_first(resolver->hquerybyres, &it_buf); + while (it) { + pj_dns_async_query *q; + + q = (pj_dns_async_query*) pj_hash_this(resolver->hquerybyres, it); + it = pj_hash_next(resolver->hquerybyres, it); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, + NULL); + } + it = pj_hash_first(resolver->hrescache, &it_buf); while (it) { struct cached_res *cache;