Nick Peng 2 месяцев назад
Родитель
Сommit
30992f22a9

+ 13 - 0
src/dns_conf/dns64.c

@@ -18,6 +18,7 @@
 
 #include "dns64.h"
 #include "dns_conf_group.h"
+#include "domain_rule.h"
 
 int _config_dns64(void *data, int argc, char *argv[])
 {
@@ -60,4 +61,16 @@ int _config_dns64(void *data, int argc, char *argv[])
 
 errout:
 	return -1;
+}
+
+static void _dns_conf_dns64_setup_ipv4only_arpa_rule(void)
+{
+	_config_domain_rule_flag_set(DNS64_IPV4ONLY_APRA_DOMAIN, DOMAIN_FLAG_DUALSTACK_SELECT, 0);
+	_conf_domain_rule_speed_check(DNS64_IPV4ONLY_APRA_DOMAIN, "none");
+	_conf_domain_rule_response_mode(DNS64_IPV4ONLY_APRA_DOMAIN, "fastest-response");
+}
+
+void _dns_conf_dns64_post(void)
+{
+	_dns_conf_dns64_setup_ipv4only_arpa_rule();
 }

+ 2 - 0
src/dns_conf/dns64.h

@@ -28,6 +28,8 @@ extern "C" {
 
 int _config_dns64(void *data, int argc, char *argv[]);
 
+void _dns_conf_dns64_post(void);
+
 #ifdef __cplusplus
 }
 #endif /*__cplusplus */

+ 2 - 0
src/dns_conf/dns_conf.c

@@ -435,6 +435,8 @@ static int _dns_conf_load_post(void)
 
 	_dns_conf_group_post();
 
+	_dns_conf_dns64_post();
+
 	_config_domain_set_name_table_destroy();
 
 	_config_ip_set_name_table_destroy();

+ 2 - 2
src/dns_conf/domain_rule.c

@@ -539,7 +539,7 @@ static int _conf_domain_rule_no_ipalias(const char *domain)
 	return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_IPALIAS, 0);
 }
 
-static int _conf_domain_rule_response_mode(char *domain, const char *mode)
+int _conf_domain_rule_response_mode(char *domain, const char *mode)
 {
 	enum response_mode_type response_mode_type = DNS_RESPONSE_MODE_FIRST_PING_IP;
 	struct dns_response_mode_rule *response_mode = NULL;
@@ -571,7 +571,7 @@ errout:
 	return 0;
 }
 
-static int _conf_domain_rule_speed_check(char *domain, const char *mode)
+int _conf_domain_rule_speed_check(char *domain, const char *mode)
 {
 	struct dns_domain_check_orders *check_orders = NULL;
 

+ 3 - 0
src/dns_conf/domain_rule.h

@@ -40,6 +40,9 @@ int _config_domain_rules(void *data, int argc, char *argv[]);
 int _config_domain_rule_delete(const char *domain);
 int _conf_domain_rule_group(const char *domain, const char *group_name);
 
+int _conf_domain_rule_speed_check(char *domain, const char *mode);
+int _conf_domain_rule_response_mode(char *domain, const char *mode);
+
 #ifdef __cplusplus
 }
 #endif /*__cplusplus */

+ 236 - 0
src/dns_server/dns64.c

@@ -0,0 +1,236 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2025 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dns64.h"
+#include "address.h"
+#include "context.h"
+#include "dns_server.h"
+#include "ptr.h"
+#include "request.h"
+#include "request_pending.h"
+#include "rules.h"
+#include "soa.h"
+
+#include "smartdns/dns_conf.h"
+
+#include <errno.h>
+#include <string.h>
+
+int _dns_server_is_dns64_request(struct dns_request *request)
+{
+	if (request->conf->dns_dns64.prefix_len <= 0) {
+		return 0;
+	}
+
+	if (request->dualstack_selection_query == 1) {
+		return 0;
+	}
+
+	if (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) == 0) {
+		return 1;
+	}
+
+	if (request->qtype != DNS_T_AAAA) {
+		return 0;
+	}
+
+	return 1;
+}
+
+static int _dns_server_process_ipv4only_arpa(struct dns_request *request)
+{
+	if (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) != 0) {
+		return -1;
+	}
+
+	/* address /domain/ rule */
+	switch (request->qtype) {
+	case DNS_T_A: {
+		u_int8_t *ipv4_addr1 = (u_int8_t[]){192, 0, 0, 170};
+		u_int8_t *ipv4_addr2 = (u_int8_t[]){192, 0, 0, 171};
+		memcpy(request->ip_addr, ipv4_addr1, DNS_RR_A_LEN);
+		_dns_ip_address_check_add(request, request->cname, ipv4_addr1, DNS_T_A, 1, NULL);
+		_dns_ip_address_check_add(request, request->cname, ipv4_addr2, DNS_T_A, 1, NULL);
+		request->has_ip = 1;
+	} break;
+	case DNS_T_AAAA:
+		/* no AAAA record for ipv4only.arpa */
+		request->has_ip = 0;
+		break;
+	default:
+		goto errout;
+		break;
+	}
+
+	request->rcode = DNS_RC_NOERROR;
+	request->ip_ttl = _dns_server_get_local_ttl(request);
+	request->dualstack_selection = 0;
+
+	struct dns_server_post_context context;
+	_dns_server_post_context_init(&context, request);
+	context.do_reply = 1;
+	context.do_audit = 1;
+	context.do_ipset = 0;
+	context.do_cache = 0;
+	context.select_all_best_ip = 1;
+	_dns_request_post(&context);
+	_dns_server_reply_all_pending_list(request, &context);
+
+	return 0;
+errout:
+	return -1;
+}
+
+static enum DNS_CHILD_POST_RESULT
+_dns_server_process_dns64_callback(struct dns_request *request, struct dns_request *child_request, int is_first_resp)
+{
+	unsigned long bucket = 0;
+	struct dns_ip_address *addr_map = NULL;
+	struct hlist_node *tmp = NULL;
+	uint32_t key = 0;
+	int addr_len = 0;
+
+	if (request->has_ip == 1) {
+		if (memcmp(request->ip_addr, request->conf->dns_dns64.prefix, 12) != 0) {
+			return DNS_CHILD_POST_SKIP;
+		}
+	}
+
+	if (child_request->qtype != DNS_T_A) {
+		return DNS_CHILD_POST_FAIL;
+	}
+
+	if (child_request->has_cname == 1) {
+		safe_strncpy(request->cname, child_request->cname, sizeof(request->cname));
+		request->has_cname = 1;
+		request->ttl_cname = child_request->ttl_cname;
+	}
+
+	if (child_request->has_ip == 0 && request->has_ip == 0) {
+		request->rcode = child_request->rcode;
+		if (child_request->has_soa) {
+			memcpy(&request->soa, &child_request->soa, sizeof(struct dns_soa));
+			request->has_soa = 1;
+			return DNS_CHILD_POST_SKIP;
+		}
+
+		if (request->has_soa == 0) {
+			_dns_server_setup_soa(request);
+			request->has_soa = 1;
+		}
+		return DNS_CHILD_POST_FAIL;
+	}
+
+	if (request->has_ip == 0 && child_request->has_ip == 1) {
+		request->rcode = child_request->rcode;
+		memcpy(request->ip_addr, request->conf->dns_dns64.prefix, 12);
+		memcpy(request->ip_addr + 12, child_request->ip_addr, 4);
+		request->ip_ttl = child_request->ip_ttl;
+		request->has_ip = 1;
+		request->has_soa = 0;
+	}
+
+	pthread_mutex_lock(&request->ip_map_lock);
+	hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)
+	{
+		hash_del(&addr_map->node);
+		free(addr_map);
+	}
+	pthread_mutex_unlock(&request->ip_map_lock);
+
+	pthread_mutex_lock(&child_request->ip_map_lock);
+	hash_for_each_safe(child_request->ip_map, bucket, tmp, addr_map, node)
+	{
+		struct dns_ip_address *new_addr_map = NULL;
+
+		if (addr_map->addr_type == DNS_T_A) {
+			addr_len = DNS_RR_A_LEN;
+		} else {
+			continue;
+		}
+
+		new_addr_map = malloc(sizeof(struct dns_ip_address));
+		if (new_addr_map == NULL) {
+			tlog(TLOG_ERROR, "malloc failed.\n");
+			pthread_mutex_unlock(&child_request->ip_map_lock);
+			return DNS_CHILD_POST_FAIL;
+		}
+		memset(new_addr_map, 0, sizeof(struct dns_ip_address));
+
+		new_addr_map->addr_type = DNS_T_AAAA;
+		addr_len = DNS_RR_AAAA_LEN;
+		memcpy(new_addr_map->ip_addr, request->conf->dns_dns64.prefix, 16);
+		memcpy(new_addr_map->ip_addr + 12, addr_map->ip_addr, 4);
+
+		new_addr_map->ping_time = addr_map->ping_time;
+		key = jhash(new_addr_map->ip_addr, addr_len, 0);
+		key = jhash(&new_addr_map->addr_type, sizeof(new_addr_map->addr_type), key);
+		pthread_mutex_lock(&request->ip_map_lock);
+		hash_add(request->ip_map, &new_addr_map->node, key);
+		pthread_mutex_unlock(&request->ip_map_lock);
+	}
+	pthread_mutex_unlock(&child_request->ip_map_lock);
+
+	if (request->dualstack_selection == 1) {
+		return DNS_CHILD_POST_NO_RESPONSE;
+	}
+
+	return DNS_CHILD_POST_SKIP;
+}
+
+int _dns_server_process_dns64(struct dns_request *request)
+{
+	if (_dns_server_is_dns64_request(request) == 0) {
+		return 0;
+	}
+
+	tlog(TLOG_DEBUG, "query %s with dns64", request->domain);
+
+	if (_dns_server_process_ipv4only_arpa(request) == 0) {
+		return 2;
+	}
+
+	struct dns_request *child_request =
+		_dns_server_new_child_request(request, request->domain, DNS_T_A, _dns_server_process_dns64_callback);
+	if (child_request == NULL) {
+		tlog(TLOG_ERROR, "malloc failed.\n");
+		return -1;
+	}
+
+	request->dualstack_selection = 0;
+	child_request->prefetch_flags |= PREFETCH_FLAGS_NO_DUALSTACK;
+	request->request_wait++;
+	int ret = _dns_server_do_query(child_request, 0);
+	if (ret != 0) {
+		request->request_wait--;
+		tlog(TLOG_ERROR, "do query %s type %d failed.\n", request->domain, request->qtype);
+		goto errout;
+	}
+
+	_dns_server_request_release_complete(child_request, 0);
+	return 0;
+
+errout:
+
+	if (child_request) {
+		request->child_request = NULL;
+		_dns_server_request_release(child_request);
+	}
+
+	return -1;
+}

+ 33 - 0
src/dns_server/dns64.h

@@ -0,0 +1,33 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2025 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _DNS_SERVER_DNS64_
+#define _DNS_SERVER_DNS64_
+
+#include "dns_server.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /*__cplusplus */
+
+int _dns_server_process_dns64(struct dns_request *request);
+
+#ifdef __cplusplus
+}
+#endif /*__cplusplus */
+#endif

+ 7 - 1
src/dns_server/dns_server.c

@@ -344,7 +344,13 @@ int _dns_server_do_query(struct dns_request *request, int skip_notify_event)
 	list_add_tail(&request->list, &server.request_list);
 	pthread_mutex_unlock(&server.request_list_lock);
 
-	if (_dns_server_process_dns64(request) != 0) {
+	ret = _dns_server_process_dns64(request);
+	if (ret != 0) {
+		if (ret == 2) {
+			/* dns64 processing, return success */
+			goto clean_exit;
+		}
+
 		goto errout;
 	}
 

+ 3 - 152
src/dns_server/request.c

@@ -20,6 +20,7 @@
 #include "address.h"
 #include "connection.h"
 #include "context.h"
+#include "dns64.h"
 #include "dns_server.h"
 #include "dualstack.h"
 #include "mdns.h"
@@ -41,23 +42,6 @@ int _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag)
 	return -1;
 }
 
-int _dns_server_is_dns64_request(struct dns_request *request)
-{
-	if (request->qtype != DNS_T_AAAA) {
-		return 0;
-	}
-
-	if (request->dualstack_selection_query == 1) {
-		return 0;
-	}
-
-	if (request->conf->dns_dns64.prefix_len <= 0) {
-		return 0;
-	}
-
-	return 1;
-}
-
 static int _dns_server_request_complete_with_all_IPs(struct dns_request *request, int with_all_ips)
 {
 	int ttl = 0;
@@ -166,7 +150,9 @@ static void _dns_server_delete_request(struct dns_request *request)
 	if (request->conn) {
 		_dns_server_conn_release(request->conn);
 	}
+
 	pthread_mutex_destroy(&request->ip_map_lock);
+
 	if (request->https_svcb) {
 		free(request->https_svcb);
 	}
@@ -824,141 +810,6 @@ const char *_dns_server_get_request_server_groupname(struct dns_request *request
 	return NULL;
 }
 
-static enum DNS_CHILD_POST_RESULT
-_dns_server_process_dns64_callback(struct dns_request *request, struct dns_request *child_request, int is_first_resp)
-{
-	unsigned long bucket = 0;
-	struct dns_ip_address *addr_map = NULL;
-	struct hlist_node *tmp = NULL;
-	uint32_t key = 0;
-	int addr_len = 0;
-
-	if (request->has_ip == 1) {
-		if (memcmp(request->ip_addr, request->conf->dns_dns64.prefix, 12) != 0) {
-			return DNS_CHILD_POST_SKIP;
-		}
-	}
-
-	if (child_request->qtype != DNS_T_A) {
-		return DNS_CHILD_POST_FAIL;
-	}
-
-	if (child_request->has_cname == 1) {
-		safe_strncpy(request->cname, child_request->cname, sizeof(request->cname));
-		request->has_cname = 1;
-		request->ttl_cname = child_request->ttl_cname;
-	}
-
-	if (child_request->has_ip == 0 && request->has_ip == 0) {
-		request->rcode = child_request->rcode;
-		if (child_request->has_soa) {
-			memcpy(&request->soa, &child_request->soa, sizeof(struct dns_soa));
-			request->has_soa = 1;
-			return DNS_CHILD_POST_SKIP;
-		}
-
-		if (request->has_soa == 0) {
-			_dns_server_setup_soa(request);
-			request->has_soa = 1;
-		}
-		return DNS_CHILD_POST_FAIL;
-	}
-
-	if (request->has_ip == 0 && child_request->has_ip == 1) {
-		request->rcode = child_request->rcode;
-		memcpy(request->ip_addr, request->conf->dns_dns64.prefix, 12);
-		memcpy(request->ip_addr + 12, child_request->ip_addr, 4);
-		request->ip_ttl = child_request->ip_ttl;
-		request->has_ip = 1;
-		request->has_soa = 0;
-	}
-
-	pthread_mutex_lock(&request->ip_map_lock);
-	hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)
-	{
-		hash_del(&addr_map->node);
-		free(addr_map);
-	}
-	pthread_mutex_unlock(&request->ip_map_lock);
-
-	pthread_mutex_lock(&child_request->ip_map_lock);
-	hash_for_each_safe(child_request->ip_map, bucket, tmp, addr_map, node)
-	{
-		struct dns_ip_address *new_addr_map = NULL;
-
-		if (addr_map->addr_type == DNS_T_A) {
-			addr_len = DNS_RR_A_LEN;
-		} else {
-			continue;
-		}
-
-		new_addr_map = malloc(sizeof(struct dns_ip_address));
-		if (new_addr_map == NULL) {
-			tlog(TLOG_ERROR, "malloc failed.\n");
-			pthread_mutex_unlock(&child_request->ip_map_lock);
-			return DNS_CHILD_POST_FAIL;
-		}
-		memset(new_addr_map, 0, sizeof(struct dns_ip_address));
-
-		new_addr_map->addr_type = DNS_T_AAAA;
-		addr_len = DNS_RR_AAAA_LEN;
-		memcpy(new_addr_map->ip_addr, request->conf->dns_dns64.prefix, 16);
-		memcpy(new_addr_map->ip_addr + 12, addr_map->ip_addr, 4);
-
-		new_addr_map->ping_time = addr_map->ping_time;
-		key = jhash(new_addr_map->ip_addr, addr_len, 0);
-		key = jhash(&new_addr_map->addr_type, sizeof(new_addr_map->addr_type), key);
-		pthread_mutex_lock(&request->ip_map_lock);
-		hash_add(request->ip_map, &new_addr_map->node, key);
-		pthread_mutex_unlock(&request->ip_map_lock);
-	}
-	pthread_mutex_unlock(&child_request->ip_map_lock);
-
-	if (request->dualstack_selection == 1) {
-		return DNS_CHILD_POST_NO_RESPONSE;
-	}
-
-	return DNS_CHILD_POST_SKIP;
-}
-
-int _dns_server_process_dns64(struct dns_request *request)
-{
-	if (_dns_server_is_dns64_request(request) == 0) {
-		return 0;
-	}
-
-	tlog(TLOG_DEBUG, "query %s with dns64", request->domain);
-
-	struct dns_request *child_request =
-		_dns_server_new_child_request(request, request->domain, DNS_T_A, _dns_server_process_dns64_callback);
-	if (child_request == NULL) {
-		tlog(TLOG_ERROR, "malloc failed.\n");
-		return -1;
-	}
-
-	request->dualstack_selection = 0;
-	child_request->prefetch_flags |= PREFETCH_FLAGS_NO_DUALSTACK;
-	request->request_wait++;
-	int ret = _dns_server_do_query(child_request, 0);
-	if (ret != 0) {
-		request->request_wait--;
-		tlog(TLOG_ERROR, "do query %s type %d failed.\n", request->domain, request->qtype);
-		goto errout;
-	}
-
-	_dns_server_request_release_complete(child_request, 0);
-	return 0;
-
-errout:
-
-	if (child_request) {
-		request->child_request = NULL;
-		_dns_server_request_release(child_request);
-	}
-
-	return -1;
-}
-
 int _dns_server_get_expired_ttl_reply(struct dns_request *request, struct dns_cache *dns_cache)
 {
 	int ttl = dns_cache_get_ttl(dns_cache);

+ 0 - 2
src/dns_server/request.h

@@ -103,8 +103,6 @@ int _dns_server_is_dns64_request(struct dns_request *request);
 
 void _dns_server_request_release(struct dns_request *request);
 
-void _dns_server_request_release(struct dns_request *request);
-
 void _dns_server_request_get(struct dns_request *request);
 
 void _dns_server_request_remove_all(void);

+ 2 - 0
src/include/smartdns/dns_conf.h

@@ -74,6 +74,8 @@ extern "C" {
 #define SMARTDNS_DEBUG_DIR "/tmp/smartdns"
 #define DNS_RESOLV_FILE "/etc/resolv.conf"
 
+#define DNS64_IPV4ONLY_APRA_DOMAIN "ipv4only.arpa"
+
 enum domain_rule {
 	DOMAIN_RULE_FLAGS = 0,
 	DOMAIN_RULE_ADDRESS_IPV4,

+ 87 - 5
test/cases/test-dns64.cc

@@ -17,9 +17,9 @@
  */
 
 #include "client.h"
-#include "smartdns/dns.h"
 #include "include/utils.h"
 #include "server.h"
+#include "smartdns/dns.h"
 #include "smartdns/util.h"
 #include "gtest/gtest.h"
 #include <fstream>
@@ -102,7 +102,6 @@ dns64 64:ff9b::/96
 	EXPECT_LT(client.GetQueryTime(), 100);
 }
 
-
 TEST_F(DNS64, with_AAAA_result)
 {
 	smartdns::MockServer server_upstream;
@@ -112,7 +111,7 @@ TEST_F(DNS64, with_AAAA_result)
 		if (request->qtype == DNS_T_A) {
 			smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4");
 			return smartdns::SERVER_REQUEST_OK;
-		} 
+		}
 
 		if (request->qtype == DNS_T_AAAA) {
 			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1");
@@ -141,7 +140,6 @@ dns64 64:ff9b::/96
 	EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1");
 }
 
-
 TEST_F(DNS64, ipv4_in_ipv6)
 {
 	smartdns::MockServer server_upstream;
@@ -150,7 +148,7 @@ TEST_F(DNS64, ipv4_in_ipv6)
 	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
 		if (request->qtype == DNS_T_A) {
 			return smartdns::SERVER_REQUEST_SOA;
-		} 
+		}
 
 		if (request->qtype == DNS_T_AAAA) {
 			smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4");
@@ -177,3 +175,87 @@ server 127.0.0.1:61053
 	EXPECT_EQ(client.GetAnswer()[0].GetData(), "::ffff:1.2.3.4");
 }
 
+TEST_F(DNS64, ipv4only_arpa)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype == DNS_T_A) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		if (request->qtype == DNS_T_AAAA) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1");
+			return smartdns::SERVER_REQUEST_OK;
+		}
+		return smartdns::SERVER_REQUEST_SOA;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+dns64 64:ff9b::/96
+)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("ipv4only.arpa AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 0);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+
+	ASSERT_TRUE(client.Query("ipv4only.arpa A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 2);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "ipv4only.arpa");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.0.0.170");
+	EXPECT_EQ(client.GetAnswer()[1].GetName(), "ipv4only.arpa");
+	EXPECT_EQ(client.GetAnswer()[1].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[1].GetData(), "192.0.0.171");
+}
+
+TEST_F(DNS64, ipv4only_arpa_nodns64)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype == DNS_T_A) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4");
+			return smartdns::SERVER_REQUEST_OK;
+		}
+
+		if (request->qtype == DNS_T_AAAA) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1");
+			return smartdns::SERVER_REQUEST_OK;
+		}
+		return smartdns::SERVER_REQUEST_SOA;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "::ffff:1.2.3.4", 60, 10);
+	server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 90);
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_LT(client.GetQueryTime(), 1200);
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "::ffff:1.2.3.4");
+
+	ASSERT_TRUE(client.Query("a.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_LT(client.GetQueryTime(), 1200);
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+}