浏览代码

feature: add hosts-file option to read hosts to resolve host names

Nick Peng 1 年之前
父节点
当前提交
c95efa5ccf

+ 3 - 0
etc/smartdns/smartdns.conf

@@ -316,6 +316,9 @@ log-level info
 # mdns-lookup [yes|no]
 # mdns-lookup no
 
+# set hosts file
+# hosts-file [file]
+
 # set domain rules
 # domain-rules /domain/ [-speed-check-mode [...]]
 # rules:

+ 16 - 1
package/luci-compat/files/luci/i18n/smartdns.zh-cn.po

@@ -329,6 +329,9 @@ msgstr ""
 msgid "Grant access to LuCI app smartdns"
 msgstr "授予访问 LuCI 应用 smartdns 的权限"
 
+msgid "Hosts File"
+msgstr "Hosts文件"
+
 msgid "HTTP Host"
 msgstr "HTTP主机"
 
@@ -383,6 +386,9 @@ msgstr "如果本软件对你有帮助,请给作者加个蛋。"
 msgid "Include Config Files<br>/etc/smartdns/conf.d"
 msgstr "包含配置文件<br>/etc/smartdns/conf.d"
 
+msgid "Include hosts file."
+msgstr "包含hosts文件。"
+
 msgid ""
 "Include other config files from /etc/smartdns/conf.d or custom path, can be "
 "downloaded from the download page."
@@ -501,7 +507,7 @@ msgstr "报告BUG"
 msgid "Return SOA when the requested result contains a specified IP address."
 msgstr "当结果包含对应范围的IP时,返回SOA。"
 
-msgid "Resolve Local Hostnames"
+msgid "Resolve Local HostnaFmes"
 msgstr "解析本地主机名"
 
 msgid "Resolve local hostnames by reading Dnsmasq lease file."
@@ -729,6 +735,9 @@ msgstr "上传域名列表文件,或在下载文件设置页面设置自动下
 msgid "Upload domain list file."
 msgstr "上传域名列表文件"
 
+msgid "Upload File"
+msgstr "上传文件"
+
 msgid "Upload IP set file."
 msgstr "上传IP集合列表文件。"
 
@@ -780,12 +789,18 @@ msgstr "默认"
 msgid "domain list (/etc/smartdns/domain-set)"
 msgstr "域名列表(/etc/smartdns/domain-set)"
 
+msgid "other file (/etc/smartdns/download)"
+msgstr "其它文件(/etc/smartdns/download)"
+
 msgid "https"
 msgstr "https"
 
 msgid "ip"
 msgstr "ip"
 
+msgid "ip-set file (/etc/smartdns/ip-set)"
+msgstr "IP集合列表文件(/etc/smartdns/ip-set)"
+
 msgid "ipset name format error, format: [#[4|6]:]ipsetname"
 msgstr "IPset名称格式错误,格式:[#[4|6]:]ipsetname"
 

+ 22 - 1
package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua

@@ -316,7 +316,8 @@ o = s:taboption("advanced", Value, "rr_ttl_reply_max", translate("Reply Domain T
 o.rempty      = true
 
 o = s:taboption("advanced", DynamicList, "conf_files", translate("Include Config Files<br>/etc/smartdns/conf.d"),
-    translate("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."));
+    translate("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."))
+o.rmempty = true
 uci:foreach("smartdns", "download-file", function(section)
     local filetype = section.type
     if (filetype ~= 'config') then
@@ -326,6 +327,17 @@ uci:foreach("smartdns", "download-file", function(section)
     o:value(section.name);
 end)
 
+o = s:taboption("advanced", DynamicList, "hosts_files", translate("Hosts File"), translate("Include hosts file."))
+o.rmempty = true
+uci:foreach("smartdns", "download-file", function(section)
+    local filetype = section.type
+    if (filetype ~= 'other') then
+        return
+    end
+
+    o:value(section.name);
+end)
+
 ---- other args
 o = s:taboption("advanced", Value, "server_flags", translate("Additional Server Args"), translate("Additional server args, refer to the help description of the bind option."))
 o.default     = ""
@@ -870,6 +882,13 @@ o.rempty = true
 o.editable = true
 o.root_directory = "/etc/smartdns/domain-set"
 
+o = s:option(FileUpload, "upload_other_file", translate("Upload File"))
+o.rmempty = true
+o.datatype = "file"
+o.rempty = true
+o.editable = true
+o.root_directory = "/etc/smartdns/download"
+
 o = s:option(Button, "_updateate")
 o.title = translate("Update Files")
 o.inputtitle = translate("Update Files")
@@ -913,6 +932,8 @@ end
 o = s:option(ListValue, "type", translate("type"), translate("File Type"))
 o:value("list", translate("domain list (/etc/smartdns/domain-set)"))
 o:value("config", translate("smartdns config (/etc/smartdns/conf.d)"))
+o:value("ip-set", translate("ip-set file (/etc/smartdns/ip-set)"))
+o:value("other", translate("other file (/etc/smartdns/download)"))
 o.default = "list"
 o.rempty = false
 

+ 15 - 0
package/luci/files/luci/i18n/smartdns.zh-cn.po

@@ -332,6 +332,9 @@ msgstr ""
 msgid "Grant access to LuCI app smartdns"
 msgstr "授予访问 LuCI 应用 smartdns 的权限"
 
+msgid "Hosts File"
+msgstr "Hosts文件"
+
 msgid "HTTP Host"
 msgstr "HTTP主机"
 
@@ -386,6 +389,9 @@ msgstr "如果本软件对你有帮助,请给作者加个蛋。"
 msgid "Include Config Files<br>/etc/smartdns/conf.d"
 msgstr "包含配置文件<br>/etc/smartdns/conf.d"
 
+msgid "Include hosts file."
+msgstr "包含hosts文件。"
+
 msgid ""
 "Include other config files from /etc/smartdns/conf.d or custom path, can be "
 "downloaded from the download page."
@@ -738,6 +744,9 @@ msgstr "上传域名列表文件,或在下载文件设置页面设置自动下
 msgid "Upload domain list file."
 msgstr "上传域名列表文件"
 
+msgid "Upload File"
+msgstr "上传文件"
+
 msgid "Upload IP set file."
 msgstr "上传IP集合列表文件。"
 
@@ -786,12 +795,18 @@ msgstr "默认"
 msgid "domain list (/etc/smartdns/domain-set)"
 msgstr "域名列表(/etc/smartdns/domain-set)"
 
+msgid "other file (/etc/smartdns/download)"
+msgstr "其它文件(/etc/smartdns/download)"
+
 msgid "https"
 msgstr "https"
 
 msgid "ip"
 msgstr "ip"
 
+msgid "ip-set file (/etc/smartdns/ip-set)"
+msgstr "IP集合列表文件(/etc/smartdns/ip-set)"
+
 msgid "ipset name format error, format: [#[4|6]:]ipsetname"
 msgstr "IPset名称格式错误,格式:[#[4|6]:]ipsetname"
 

+ 22 - 0
package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js

@@ -417,6 +417,20 @@ return view.extend({
 			o.value(download_files[i].name);
 		}
 
+		o = s.taboption("advanced", form.DynamicList, "hosts_files", _("Hosts File"), _("Include hosts file."));
+		o.rmempty = true;
+		for (var i = 0; i < download_files.length; i++) {
+			if (download_files[i].type == undefined) {
+				continue;
+			}
+
+			if (download_files[i].type != 'other') {
+				continue
+			}
+
+			o.value(download_files[i].name);
+		}
+	
 		///////////////////////////////////////
 		// second dns server;
 		///////////////////////////////////////
@@ -586,6 +600,12 @@ return view.extend({
 		o.rempty = true
 		o.root_directory = "/etc/smartdns/domain-set"
 
+		o = s.taboption("files", form.FileUpload, "upload_other_file", _("Upload File"));
+		o.rmempty = true
+		o.datatype = "file"
+		o.rempty = true
+		o.root_directory = "/etc/smartdns/download"
+
 		o = s.taboption('files', form.DummyValue, "_update", _("Update Files"));
 		o.renderWidget = function () {
 			return E('button', {
@@ -629,6 +649,8 @@ return view.extend({
 		so = ss.option(form.ListValue, "type", _("type"), _("File Type"));
 		so.value("list", _("domain list (/etc/smartdns/domain-set)"));
 		so.value("config", _("smartdns config (/etc/smartdns/conf.d)"));
+		so.value("ip-set", _("ip-set file (/etc/smartdns/ip-set)"));
+		so.value("other", _("other file (/etc/smartdns/download)"));
 		so.default = "list";
 		so.rempty = false;
 

+ 2 - 1
package/openwrt/Makefile

@@ -50,7 +50,8 @@ endef
 
 define Package/smartdns/install
 	$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d 
-	$(INSTALL_DIR) $(1)/etc/smartdns $(1)/etc/smartdns/domain-set $(1)/etc/smartdns/conf.d/ $(1)/etc/smartdns/ip-set
+	$(INSTALL_DIR) $(1)/etc/smartdns $(1)/etc/smartdns/domain-set $(1)/etc/smartdns/conf.d/
+	$(INSTALL_DIR) $(1)/etc/smartdns/ip-set $(1)/etc/smartdns/download
 	$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/smartdns $(1)/usr/sbin/smartdns
 	$(INSTALL_BIN) $(PKG_BUILD_DIR)/package/openwrt/files/etc/init.d/smartdns $(1)/etc/init.d/smartdns
 	$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/address.conf $(1)/etc/smartdns/address.conf

+ 31 - 0
package/openwrt/files/etc/init.d/smartdns

@@ -31,7 +31,9 @@ SMARTDNS_DEFAULT_FORWARDING_FILE="/etc/smartdns/domain-forwarding.list"
 SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE="/etc/smartdns/domain-block.list"
 SMARTDNS_CONF_DIR="/etc/smartdns"
 SMARTDNS_CONF_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/conf.d"
+SMARTDNS_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/download"
 SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/domain-set"
+SMARTDNS_IP_SET_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/ip-set"
 SMARTDNS_VAR_CONF_DIR="/var/etc/smartdns"
 SMARTDNS_CONF="$SMARTDNS_VAR_CONF_DIR/smartdns.conf"
 ADDRESS_CONF="$SMARTDNS_CONF_DIR/address.conf"
@@ -520,6 +522,21 @@ conf_append_conf_files()
 	}
 }
 
+conf_append_hosts_files()
+{
+	local hosts_file="$1"
+
+	if [ "$1" != "${1#/}" ]; then
+		fullpath="$1"
+	else 
+		fullpath="$SMARTDNS_DOWNLOAD_DIR/$hosts_file"
+	fi
+
+	[ -f "$fullpath" ] && {
+		conf_append "hosts-file" "'$fullpath'"
+	}
+}
+
 load_service()
 {
 	local section="$1"
@@ -749,6 +766,8 @@ load_service()
 
 	config_list_foreach "$section" "conf_files" conf_append_conf_files
 
+	config_list_foreach "$section" "hosts_files" conf_append_hosts_files
+
 	config_foreach load_domain_rules "domain-rule"
 
 	config_foreach load_domain_rule_list "domain-rule-list"
@@ -808,12 +827,20 @@ download_file() {
 	config_get url "$section" "url" ""
 	config_get name "$section" "name" ""
 	config_get filetype "$section" "type" ""
+	config_get_bool use_proxy "$section" "use_proxy" "0"
 
 	[ -z "$url" ] && return 0
 	[ -z "$name" ] && return 0
 	[ -z "$filetype" ] && return 0
 
 	echo "download $filetype file $name from $url"
+	[ "$use_proxy" = "1" ] && {
+		proxy="$(uci -q get smartdns.@smartdns[0].proxy_server)"
+		[ ! -z "$proxy" ] && {
+			export http_proxy="$proxy"
+			export https_proxy="$proxy"
+		}
+	}
 	wget --timeout 120 -q -O "/tmp/$name" "$url"
 	if [ $? -ne 0 ]; then
 		echo "download file $name failed"
@@ -825,6 +852,10 @@ download_file() {
 		mv "/tmp/$name" "$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR/$name"	
 	elif [ "$filetype" = "config" ]; then
 		mv "/tmp/$name" "$SMARTDNS_CONF_DOWNLOAD_DIR/$name"	
+	elif [ "$filetype" = "ip-set" ]; then
+		mv "/tmp/$name" "$SMARTDNS_IP_SET_DOWNLOAD_DIR/$name"
+	else 
+		mv "/tmp/$name" "$SMARTDNS_DOWNLOAD_DIR/$name"
 	fi
 }
 

+ 1 - 0
package/openwrt/make.sh

@@ -50,6 +50,7 @@ build()
 	mkdir $ROOT/root/etc/smartdns/domain-set/ -p 
 	mkdir $ROOT/root/etc/smartdns/ip-set/ -p 
 	mkdir $ROOT/root/etc/smartdns/conf.d/ -p 
+	mkdir $ROOT/root/etc/smartdns/download/ -p 
 
 	cp $SMARTDNS_CONF  $ROOT/root/etc/smartdns/
 	cp $ADDRESS_CONF $ROOT/root/etc/smartdns/

+ 150 - 53
src/dns_conf.c

@@ -4149,11 +4149,154 @@ static int _conf_dhcp_lease_dnsmasq_file(void *data, int argc, char *argv[])
 	return 0;
 }
 
-static int _conf_hosts_file(void *data, int argc, char *argv[])
+static int _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv)
 {
+	char file_path[DNS_MAX_PATH];
+	char file_path_dir[DNS_MAX_PATH];
+	glob_t globbuf = {0};
+
+	if (file_pattern == NULL) {
+		return -1;
+	}
+
+	if (file_pattern[0] != '/') {
+		safe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH);
+		dir_name(file_path_dir);
+		if (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) {
+			if (snprintf(file_path, DNS_MAX_PATH, "%s", file_pattern) < 0) {
+				return -1;
+			}
+		} else {
+			if (snprintf(file_path, DNS_MAX_PATH, "%s/%s", file_path_dir, file_pattern) < 0) {
+				return -1;
+			}
+		}
+	} else {
+		safe_strncpy(file_path, file_pattern, DNS_MAX_PATH);
+	}
+
+	errno = 0;
+	if (glob(file_path, 0, NULL, &globbuf) != 0) {
+		if (errno == 0) {
+			return 0;
+		}
+
+		tlog(TLOG_ERROR, "open config file '%s' failed, %s", file_path, strerror(errno));
+		return -1;
+	}
+
+	for (size_t i = 0; i != globbuf.gl_pathc; ++i) {
+		const char *file = globbuf.gl_pathv[i];
+		struct stat statbuf;
+
+		if (stat(file, &statbuf) != 0) {
+			continue;
+		}
+
+		if (!S_ISREG(statbuf.st_mode)) {
+			continue;
+		}
+
+		if (callback(file, priv) != 0) {
+			tlog(TLOG_ERROR, "load config file '%s' failed.", file);
+			globfree(&globbuf);
+			return -1;
+		}
+	}
+
+	globfree(&globbuf);
+
 	return 0;
 }
 
+static int _conf_hosts_file_add(const char *file, void *priv)
+{
+	FILE *fp = NULL;
+	char line[MAX_LINE_LEN];
+	char ip[DNS_MAX_IPLEN];
+	char hostname[DNS_MAX_CNAME_LEN];
+	int ret = 0;
+	int line_no = 0;
+
+	fp = fopen(file, "r");
+	if (fp == NULL) {
+		tlog(TLOG_WARN, "open file %s error, %s", file, strerror(errno));
+		return -1;
+	}
+
+	line_no = 0;
+	while (fgets(line, MAX_LINE_LEN, fp)) {
+		line_no++;
+		int is_ptr_add = 0;
+
+		char *token = strtok(line, " \t\n");
+		if (token == NULL) {
+			continue;
+		}
+
+		safe_strncpy(ip, token, sizeof(ip) - 1);
+		if (ip[0] == '#') {
+			continue;
+		}
+
+		while ((token = strtok(NULL, " \t\n")) != NULL) {
+			safe_strncpy(hostname, token, sizeof(hostname) - 1);
+			char *skip_hostnames[] = {
+				"*",
+			};
+
+			int skip = 0;
+			for (size_t i = 0; i < sizeof(skip_hostnames) / sizeof(skip_hostnames[0]); i++) {
+				if (strncmp(hostname, skip_hostnames[i], DNS_MAX_CNAME_LEN - 1) == 0) {
+					skip = 1;
+					break;
+				}
+			}
+
+			if (skip == 1) {
+				continue;
+			}
+
+			ret = _conf_host_add(hostname, ip, DNS_HOST_TYPE_HOST, 0);
+			if (ret != 0) {
+				tlog(TLOG_WARN, "add hosts-file failed at '%s:%d'.", file, line_no);
+				continue;
+			}
+
+			if (is_ptr_add == 1) {
+				continue;
+			}
+
+			ret = _conf_ptr_add(hostname, ip, 0);
+			if (ret != 0) {
+				tlog(TLOG_WARN, "add hosts-file failed at '%s:%d'.", file, line_no);
+				continue;
+			}
+
+			is_ptr_add = 1;
+		}
+	}
+
+	fclose(fp);
+
+	return 0;
+}
+
+static int _conf_hosts_file(void *data, int argc, char *argv[])
+{
+	const char *file_pattern = NULL;
+	if (argc < 1) {
+		return -1;
+	}
+
+	file_pattern = argv[1];
+	if (file_pattern == NULL) {
+		return -1;
+	}
+
+	return _config_foreach_file(file_pattern, _conf_hosts_file_add, NULL);
+}
+
 static void _config_host_table_destroy(int only_dynamic)
 {
 	struct dns_hosts *host = NULL;
@@ -4464,13 +4607,14 @@ static int conf_additional_file(const char *conf_file)
 	return load_conf(file_path, _config_item, _conf_printf);
 }
 
+static int _config_additional_file_callback(const char *file, void *priv)
+{
+	return conf_additional_file(file);
+}
+
 int config_additional_file(void *data, int argc, char *argv[])
 {
 	const char *conf_pattern = NULL;
-	char file_path[DNS_MAX_PATH];
-	char file_path_dir[DNS_MAX_PATH];
-	glob_t globbuf = {0};
-
 	if (argc < 1) {
 		return -1;
 	}
@@ -4480,54 +4624,7 @@ int config_additional_file(void *data, int argc, char *argv[])
 		return -1;
 	}
 
-	if (conf_pattern[0] != '/') {
-		safe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH);
-		dir_name(file_path_dir);
-		if (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) {
-			if (snprintf(file_path, DNS_MAX_PATH, "%s", conf_pattern) < 0) {
-				return -1;
-			}
-		} else {
-			if (snprintf(file_path, DNS_MAX_PATH, "%s/%s", file_path_dir, conf_pattern) < 0) {
-				return -1;
-			}
-		}
-	} else {
-		safe_strncpy(file_path, conf_pattern, DNS_MAX_PATH);
-	}
-
-	errno = 0;
-	if (glob(file_path, 0, NULL, &globbuf) != 0) {
-		if (errno == 0) {
-			return 0;
-		}
-
-		tlog(TLOG_ERROR, "open config file '%s' failed, %s", file_path, strerror(errno));
-		return -1;
-	}
-
-	for (size_t i = 0; i != globbuf.gl_pathc; ++i) {
-		const char *file = globbuf.gl_pathv[i];
-		struct stat statbuf;
-
-		if (stat(file, &statbuf) != 0) {
-			continue;
-		}
-
-		if (!S_ISREG(statbuf.st_mode)) {
-			continue;
-		}
-
-		if (conf_additional_file(file) != 0) {
-			tlog(TLOG_ERROR, "load config file '%s' failed.", file);
-			globfree(&globbuf);
-			return -1;
-		}
-	}
-
-	globfree(&globbuf);
-
-	return 0;
+	return _config_foreach_file(conf_pattern, _config_additional_file_callback, NULL);
 }
 
 const char *dns_conf_get_cache_dir(void)

+ 187 - 0
test/cases/test-hosts.cc

@@ -0,0 +1,187 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 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 "client.h"
+#include "dns.h"
+#include "include/utils.h"
+#include "server.h"
+#include "util.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+class Hosts : public ::testing::Test
+{
+  protected:
+	virtual void SetUp() {}
+	virtual void TearDown() {}
+};
+
+TEST_F(Hosts, read)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+	std::string file = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts";
+	std::string file2 = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts";
+	std::string file3 = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts";
+	std::ofstream ofs(file);
+	std::ofstream ofs2(file2);
+	std::ofstream ofs3(file3);
+	ASSERT_TRUE(ofs.is_open());
+	ASSERT_TRUE(ofs2.is_open());
+	ASSERT_TRUE(ofs3.is_open());
+	Defer
+	{
+		ofs.close();
+		unlink(file.c_str());
+
+		ofs2.close();
+		unlink(file2.c_str());
+
+		ofs3.close();
+		unlink(file3.c_str());
+	};
+
+	ofs << "1.2.3.4   b.com\n";
+	ofs << "64:ff9b::102:304   b.com\n";
+	ofs.flush();
+
+	ofs2 << "1.1.1.1 a.com 1.a.com 2.a.com\n";
+	ofs2 << "# 4.5.6.7 c.com\n";
+	ofs2.flush();
+
+	ofs3 << "127.0.0.1 d.com\n";
+	ofs3 << "::1 d.com\n";
+	ofs3 << "0.0.0.0 e.com\n";
+	ofs3 << ":: e.com\n";
+	ofs3.flush();
+
+	server_upstream.Start("udp://0.0.0.0:61053",
+						  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+speed-check-mode none
+hosts-file /tmp/*.hosts
+)""");
+	smartdns::Client client;
+
+	ASSERT_TRUE(client.Query("b.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+
+	ASSERT_TRUE(client.Query("b.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304");
+
+	ASSERT_TRUE(client.Query("-x 64:ff9b::102:304", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(),
+			  "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "b.com.");
+
+	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_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1");
+
+	ASSERT_TRUE(client.Query("-x 1.1.1.1", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.1.1.1.in-addr.arpa");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "a.com.");
+
+	ASSERT_TRUE(client.Query("1.a.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1");
+
+	ASSERT_TRUE(client.Query("2.a.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "2.a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1");
+
+	ASSERT_TRUE(client.Query("c.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 0);
+	EXPECT_EQ(client.GetStatus(), "NXDOMAIN");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "c.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 60);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("d.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "d.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "127.0.0.1");
+
+	ASSERT_TRUE(client.Query("d.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "d.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "::1");
+
+	ASSERT_TRUE(client.Query("e.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "e.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "0.0.0.0");
+
+	ASSERT_TRUE(client.Query("e.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "e.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "::");
+}