Преглед изворни кода

feature: add group-match option.

Nick Peng пре 1 година
родитељ
комит
a75495b8a2
5 измењених фајлова са 399 додато и 42 уклоњено
  1. 5 0
      etc/smartdns/smartdns.conf
  2. 128 4
      src/dns_conf.c
  3. 7 0
      src/dns_conf.h
  4. 123 38
      src/dns_server.c
  5. 136 0
      test/cases/test-group.cc

+ 5 - 0
etc/smartdns/smartdns.conf

@@ -387,3 +387,8 @@ log-level info
 # set client rules
 # client-rules ip-cidr [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
 # client-rules option is same as bind option, please see bind option for detail.
+
+# set group rules
+# group-begin [group-name]
+# group-match [-g|group group-name] [-domain domain] [-client-ip client]
+# group-end

+ 128 - 4
src/dns_conf.c

@@ -192,7 +192,8 @@ struct dns_edns_client_subnet dns_conf_ipv6_ecs;
 
 char dns_conf_sni_proxy_ip[DNS_MAX_IPLEN];
 
-static int _conf_domain_rule_nameserver(char *domain, const char *group_name);
+static int _conf_domain_rule_nameserver(const char *domain, const char *group_name);
+static int _conf_domain_rule_group(const char *domain, const char *group_name);
 static int _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic);
 static int _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs,
 							   struct dns_edns_client_subnet *ipv6_ecs);
@@ -205,6 +206,8 @@ static struct dns_conf_group *_config_rule_group_new(const char *group_name);
 static struct dns_conf_group *_config_current_rule_group(void);
 static void _config_ip_iter_free(radix_node_t *node, void *cbctx);
 static int _config_nftset_setvalue(struct dns_nftset_names *nftsets, const char *nftsetvalue);
+static int _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear);
+static int _config_client_rule_group_add(const char *client, const char *group_name);
 
 static void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size)
 {
@@ -237,6 +240,9 @@ static void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size)
 	case DOMAIN_RULE_NAMESERVER:
 		size = sizeof(struct dns_nameserver_rule);
 		break;
+	case DOMAIN_RULE_GROUP:
+		size = sizeof(struct dns_group_rule);
+		break;
 	case DOMAIN_RULE_CHECKSPEED:
 		size = sizeof(struct dns_domain_check_orders);
 		break;
@@ -623,6 +629,81 @@ static int _config_group_end(void *data, int argc, char *argv[])
 	return 0;
 }
 
+static int _config_group_match(void *data, int argc, char *argv[])
+{
+	int opt = 0;
+	struct dns_conf_group_info *saved_group_info = dns_conf_current_group_info;
+	const char *group_name = saved_group_info->group_name;
+	char group_name_buf[DNS_MAX_CONF_CNAME_LEN];
+
+	/* clang-format off */
+	static struct option long_options[] = {
+		{"domain", required_argument, NULL, 'd'},
+		{"client-ip", required_argument, NULL, 'c'},
+		{"group", required_argument, NULL, 'g'},
+		{NULL, no_argument, NULL, 0}
+	};
+	/* clang-format on */
+
+	if (argc <= 1 || group_name == NULL) {
+		tlog(TLOG_ERROR, "invalid parameter.");
+		goto errout;
+	}
+
+	dns_conf_current_group_info = dns_conf_default_group_info;
+
+	for (int i = 1; i < argc - 1; i++) {
+		if (strncmp(argv[i], "-g", sizeof("-g")) == 0 || strncmp(argv[i], "--group", sizeof("--group")) == 0 ||
+			strncmp(argv[i], "-group", sizeof("-group")) == 0) {
+			safe_strncpy(group_name_buf, argv[i + 1], DNS_MAX_CONF_CNAME_LEN);
+			group_name = group_name_buf;
+			break;
+		}
+	}
+
+	while (1) {
+		opt = getopt_long_only(argc, argv, "g:", long_options, NULL);
+		if (opt == -1) {
+			break;
+		}
+
+		switch (opt) {
+		case 'g': {
+			group_name = optarg;
+			break;
+		}
+		case 'd': {
+			const char *domain = optarg;
+
+			if (_conf_domain_rule_group(domain, group_name) != 0) {
+				tlog(TLOG_ERROR, "set group match for domain %s failed.", optarg);
+				goto errout;
+			}
+			break;
+		}
+		case 'c': {
+			char *client_ip = optarg;
+			if (_config_client_rule_group_add(client_ip, group_name) != 0) {
+				tlog(TLOG_ERROR, "add group rule failed.");
+				goto errout;
+			}
+			break;
+		}
+		default:
+			tlog(TLOG_WARN, "unknown group-match option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(),
+				 conf_get_current_lineno());
+			break;
+		}
+	}
+
+	dns_conf_current_group_info = saved_group_info;
+
+	return 0;
+errout:
+	dns_conf_current_group_info = saved_group_info;
+	return -1;
+}
+
 /* create and get dns server group */
 static struct dns_proxy_names *_dns_conf_get_proxy(const char *proxy_name)
 {
@@ -2854,7 +2935,7 @@ static int _config_server_https(void *data, int argc, char *argv[])
 	return ret;
 }
 
-static int _conf_domain_rule_nameserver(char *domain, const char *group_name)
+static int _conf_domain_rule_nameserver(const char *domain, const char *group_name)
 {
 	struct dns_nameserver_rule *nameserver_rule = NULL;
 	const char *group = NULL;
@@ -2896,6 +2977,48 @@ errout:
 	return 0;
 }
 
+static int _conf_domain_rule_group(const char *domain, const char *group_name)
+{
+	struct dns_group_rule *group_rule = NULL;
+	const char *group = NULL;
+
+	if (strncmp(group_name, "-", sizeof("-")) != 0) {
+		group = _dns_conf_get_group_name(group_name);
+		if (group == NULL) {
+			goto errout;
+		}
+
+		group_rule = _new_dns_rule(DOMAIN_RULE_GROUP);
+		if (group_rule == NULL) {
+			goto errout;
+		}
+
+		group_rule->group_name = group;
+	} else {
+		/* ignore this domain */
+		if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_GROUP_IGNORE, 0) != 0) {
+			goto errout;
+		}
+
+		return 0;
+	}
+
+	if (_config_domain_rule_add(domain, DOMAIN_RULE_GROUP, group_rule) != 0) {
+		goto errout;
+	}
+
+	_dns_rule_put(&group_rule->head);
+
+	return 0;
+errout:
+	if (group_rule) {
+		_dns_rule_put(&group_rule->head);
+	}
+
+	tlog(TLOG_ERROR, "add group %s, %s failed", domain, group_name);
+	return 0;
+}
+
 static int _conf_domain_rule_dualstack_selection(char *domain, const char *yesno)
 {
 	if (strncmp(yesno, "yes", sizeof("yes")) == 0 || strncmp(yesno, "Yes", sizeof("Yes")) == 0) {
@@ -3206,7 +3329,6 @@ static int _config_client_rules_free(struct dns_client_rules *client_rules)
 	return 0;
 }
 
-static int _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear);
 static int _config_client_rule_flag_callback(const char *ip_cidr, void *priv)
 {
 	struct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv;
@@ -4329,7 +4451,8 @@ static int _conf_domain_rules(void *data, int argc, char *argv[])
 	}
 
 	for (int i = 2; i < argc - 1; i++) {
-		if (strncmp(argv[i], "-g", sizeof("-g")) == 0 || strncmp(argv[i], "--group", sizeof("--group")) == 0) {
+		if (strncmp(argv[i], "-g", sizeof("-g")) == 0 || strncmp(argv[i], "--group", sizeof("--group")) == 0 ||
+			strncmp(argv[i], "-group", sizeof("-group")) == 0) {
 			safe_strncpy(group_name, argv[i + 1], DNS_MAX_CONF_CNAME_LEN);
 			group = group_name;
 			break;
@@ -5345,6 +5468,7 @@ static struct config_item _config_item[] = {
 	CONF_CUSTOM("hosts-file", _conf_hosts_file, NULL),
 	CONF_CUSTOM("group-begin", _config_group_begin, NULL),
 	CONF_CUSTOM("group-end", _config_group_end, NULL),
+	CONF_CUSTOM("group-match", _config_group_match, NULL),
 	CONF_CUSTOM("client-rules", _config_client_rules, NULL),
 	CONF_STRING("ca-file", (char *)&dns_conf_ca_file, DNS_MAX_PATH),
 	CONF_STRING("ca-path", (char *)&dns_conf_ca_path, DNS_MAX_PATH),

+ 7 - 0
src/dns_conf.h

@@ -81,6 +81,7 @@ enum domain_rule {
 	DOMAIN_RULE_NFTSET_IP,
 	DOMAIN_RULE_NFTSET_IP6,
 	DOMAIN_RULE_NAMESERVER,
+	DOMAIN_RULE_GROUP,
 	DOMAIN_RULE_CHECKSPEED,
 	DOMAIN_RULE_RESPONSE_MODE,
 	DOMAIN_RULE_CNAME,
@@ -133,6 +134,7 @@ typedef enum {
 #define DOMAIN_FLAG_CNAME_IGN (1 << 16)
 #define DOMAIN_FLAG_NO_CACHE (1 << 17)
 #define DOMAIN_FLAG_NO_IPALIAS (1 << 18)
+#define DOMAIN_FLAG_GROUP_IGNORE (1 << 19)
 
 #define IP_RULE_FLAG_BLACKLIST (1 << 0)
 #define IP_RULE_FLAG_WHITELIST (1 << 1)
@@ -258,6 +260,11 @@ struct dns_nameserver_rule {
 	const char *group_name;
 };
 
+struct dns_group_rule {
+	struct dns_rule head;
+	const char *group_name;
+};
+
 struct dns_server_groups {
 	struct hlist_node node;
 	char group_name[DNS_GROUP_NAME_LEN];

+ 123 - 38
src/dns_server.c

@@ -104,6 +104,7 @@ typedef enum DNS_CHILD_POST_RESULT {
 
 struct rule_walk_args {
 	void *args;
+	int rule_index;
 	unsigned char *key[DOMAIN_RULE_MAX];
 	uint32_t key_len[DOMAIN_RULE_MAX];
 };
@@ -515,22 +516,40 @@ static int _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint
 	return 0;
 }
 
+static void *_dns_server_get_dns_rule_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule)
+{
+	if (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) {
+		return NULL;
+	}
+
+	return domain_rule->rules[rule];
+}
+
+static int _dns_server_is_dns_rule_extract_match_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule)
+{
+	if (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) {
+		return 0;
+	}
+
+	return domain_rule->is_sub_rule[rule] == 0;
+}
+
 static void *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule)
 {
-	if (rule >= DOMAIN_RULE_MAX || request == NULL) {
+	if (request == NULL) {
 		return NULL;
 	}
 
-	return request->domain_rule.rules[rule];
+	return _dns_server_get_dns_rule_ext(&request->domain_rule, rule);
 }
 
 static int _dns_server_is_dns_rule_extract_match(struct dns_request *request, enum domain_rule rule)
 {
-	if (rule >= DOMAIN_RULE_MAX || request == NULL) {
+	if (request == NULL) {
 		return 0;
 	}
 
-	return request->domain_rule.is_sub_rule[rule] == 0;
+	return _dns_server_is_dns_rule_extract_match_ext(&request->domain_rule, rule);
 }
 
 static int _dns_server_is_dns64_request(struct dns_request *request)
@@ -4535,9 +4554,9 @@ static void _dns_server_log_rule(const char *domain, enum domain_rule rule_type,
 	tlog(TLOG_INFO, "RULE-MATCH, type: %d, domain: %s, rule: %s", rule_type, domain, rule_name);
 }
 
-static void _dns_server_update_rule_by_flags(struct dns_request *request)
+static void _dns_server_update_rule_by_flags(struct dns_request_domain_rule *request_domain_rule)
 {
-	struct dns_rule_flags *rule_flag = (struct dns_rule_flags *)request->domain_rule.rules[0];
+	struct dns_rule_flags *rule_flag = (struct dns_rule_flags *)request_domain_rule->rules[0];
 	unsigned int flags = 0;
 
 	if (rule_flag == NULL) {
@@ -4546,47 +4565,47 @@ static void _dns_server_update_rule_by_flags(struct dns_request *request)
 	flags = rule_flag->flags;
 
 	if (flags & DOMAIN_FLAG_ADDR_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;
-		request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_IPSET_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_IPSET] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_IPSET] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_IPSET_IPV4_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_IPSET_IPV4] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_IPSET_IPV4] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_IPSET_IPV6_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_IPSET_IPV6] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_IPSET_IPV6] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_NFTSET_IP_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_NFTSET_IP] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_NFTSET_IP] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_NFTSET_IP6_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) {
-		request->domain_rule.rules[DOMAIN_RULE_NFTSET_IP6] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_NFTSET_IP6] = NULL;
 	}
 
 	if (flags & DOMAIN_FLAG_NAMESERVER_IGNORE) {
-		request->domain_rule.rules[DOMAIN_RULE_NAMESERVER] = NULL;
+		request_domain_rule->rules[DOMAIN_RULE_NAMESERVER] = NULL;
 	}
 }
 
 static int _dns_server_get_rules(unsigned char *key, uint32_t key_len, int is_subkey, void *value, void *arg)
 {
 	struct rule_walk_args *walk_args = arg;
-	struct dns_request *request = walk_args->args;
+	struct dns_request_domain_rule *request_domain_rule = walk_args->args;
 	struct dns_domain_rule *domain_rule = value;
 	int i = 0;
 	if (domain_rule == NULL) {
@@ -4605,42 +4624,49 @@ static int _dns_server_get_rules(unsigned char *key, uint32_t key_len, int is_su
 		}
 	}
 
-	for (i = 0; i < DOMAIN_RULE_MAX; i++) {
+	if (walk_args->rule_index >= 0) {
+		i = walk_args->rule_index;
+	} else {
+		i = 0;
+	}
+
+	for (; i < DOMAIN_RULE_MAX; i++) {
 		if (domain_rule->rules[i] == NULL) {
+			if (walk_args->rule_index >= 0) {
+				break;
+			}
 			continue;
 		}
 
-		request->domain_rule.rules[i] = domain_rule->rules[i];
-		request->domain_rule.is_sub_rule[i] = is_subkey;
+		request_domain_rule->rules[i] = domain_rule->rules[i];
+		request_domain_rule->is_sub_rule[i] = is_subkey;
 		walk_args->key[i] = key;
 		walk_args->key_len[i] = key_len;
+		if (walk_args->rule_index >= 0) {
+			break;
+		}
 	}
 
 	/* update rules by flags */
-	_dns_server_update_rule_by_flags(request);
+	_dns_server_update_rule_by_flags(request_domain_rule);
 
 	return 0;
 }
 
-static void _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log)
+static void _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf,
+													  struct dns_request_domain_rule *request_domain_rule,
+													  int rule_index, const char *domain, int out_log)
 {
 	int domain_len = 0;
 	char domain_key[DNS_MAX_CNAME_LEN];
+	struct rule_walk_args walk_args;
 	int matched_key_len = DNS_MAX_CNAME_LEN;
 	unsigned char matched_key[DNS_MAX_CNAME_LEN];
-	struct rule_walk_args walk_args;
 	int i = 0;
 
-	if (request->skip_domain_rule != 0) {
-		return;
-	}
-
-	if (request->conf == NULL) {
-		return;
-	}
-
 	memset(&walk_args, 0, sizeof(walk_args));
-	walk_args.args = request;
+	walk_args.args = request_domain_rule;
+	walk_args.rule_index = rule_index;
 
 	/* reverse domain string */
 	domain_len = strlen(domain);
@@ -4655,15 +4681,24 @@ static void _dns_server_get_domain_rule_by_domain(struct dns_request *request, c
 	domain_key[domain_len] = 0;
 
 	/* find domain rule */
-	art_substring_walk(&request->conf->domain_rule.tree, (unsigned char *)domain_key, domain_len, _dns_server_get_rules,
+	art_substring_walk(&conf->domain_rule.tree, (unsigned char *)domain_key, domain_len, _dns_server_get_rules,
 					   &walk_args);
-	if (likely(dns_conf_log_level > TLOG_DEBUG)) {
+	if (likely(dns_conf_log_level > TLOG_DEBUG) || out_log == 0) {
 		return;
 	}
 
+	if (walk_args.rule_index >= 0) {
+		i = walk_args.rule_index;
+	} else {
+		i = 0;
+	}
+
 	/* output log rule */
-	for (i = 0; i < DOMAIN_RULE_MAX; i++) {
+	for (; i < DOMAIN_RULE_MAX; i++) {
 		if (walk_args.key[i] == NULL) {
+			if (walk_args.rule_index >= 0) {
+				break;
+			}
 			continue;
 		}
 
@@ -4676,11 +4711,25 @@ static void _dns_server_get_domain_rule_by_domain(struct dns_request *request, c
 
 		matched_key_len--;
 		matched_key[matched_key_len] = 0;
-		if (out_log != 0) {
-			_dns_server_log_rule(request->domain, i, matched_key, matched_key_len);
+		_dns_server_log_rule(domain, i, matched_key, matched_key_len);
+
+		if (walk_args.rule_index >= 0) {
+			break;
 		}
 	}
+}
+
+static void _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log)
+{
+	if (request->skip_domain_rule != 0) {
+		return;
+	}
+
+	if (request->conf == NULL) {
+		return;
+	}
 
+	_dns_server_get_domain_rule_by_domain_ext(request->conf, &request->domain_rule, -1, domain, out_log);
 	request->skip_domain_rule = 1;
 }
 
@@ -5945,6 +5994,42 @@ errout:
 	return ret;
 }
 
+static int _dns_server_setup_request_conf_pre(struct dns_request *request)
+{
+	struct dns_conf_group *rule_group = NULL;
+	struct dns_request_domain_rule domain_rule;
+
+	if (request->skip_domain_rule != 0 && request->conf) {
+		return 0;
+	}
+
+	rule_group = dns_server_get_rule_group(request->dns_group_name);
+	if (rule_group == NULL) {
+		return -1;
+	}
+
+	request->conf = rule_group;
+	memset(&domain_rule, 0, sizeof(domain_rule));
+	_dns_server_get_domain_rule_by_domain_ext(rule_group, &domain_rule, DOMAIN_RULE_GROUP, request->domain, 1);
+	if (domain_rule.rules[DOMAIN_RULE_GROUP] == NULL) {
+		return 0;
+	}
+
+	struct dns_group_rule *group_rule = _dns_server_get_dns_rule_ext(&domain_rule, DOMAIN_RULE_GROUP);
+	if (group_rule == NULL) {
+		return 0;
+	}
+	rule_group = dns_server_get_rule_group(group_rule->group_name);
+	if (rule_group == NULL) {
+		return 0;
+	}
+
+	request->conf = rule_group;
+	tlog(TLOG_DEBUG, "domain %s match group %s", request->domain, rule_group->group_name);
+
+	return 0;
+}
+
 static int _dns_server_setup_request_conf(struct dns_request *request)
 {
 	struct dns_conf_group *rule_group = NULL;
@@ -5975,7 +6060,7 @@ static int _dns_server_do_query(struct dns_request *request, int skip_notify_eve
 
 	request->send_tick = get_tick_count();
 
-	if (_dns_server_setup_request_conf(request) != 0) {
+	if (_dns_server_setup_request_conf_pre(request) != 0) {
 		goto errout;
 	}
 

+ 136 - 0
test/cases/test-group.cc

@@ -385,3 +385,139 @@ client-rules 127.0.0.1
 		EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
 	}
 }
+
+TEST_F(Group, group_match_client_ip)
+{
+	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");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8");
+			return smartdns::SERVER_REQUEST_OK;
+		} else if (request->qtype == DNS_T_AAAA) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2");
+			return smartdns::SERVER_REQUEST_OK;
+		}
+		return smartdns::SERVER_REQUEST_SOA;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80);
+	server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110);
+	server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150);
+	server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200);
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+speed-check-mode ping
+group-begin client
+dualstack-ip-selection no
+group-match -client-ip 127.0.0.1
+)""");
+	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_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1");
+
+	ASSERT_TRUE(client.Query("a.com", 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(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+
+	auto ipaddr = smartdns::GetAvailableIPAddresses();
+	if (ipaddr.size() > 0) {
+		ASSERT_TRUE(client.Query("a.com AAAA", 60053, ipaddr[0]));
+		std::cout << client.GetResult() << std::endl;
+		ASSERT_EQ(client.GetAuthorityNum(), 1);
+		EXPECT_EQ(client.GetStatus(), "NOERROR");
+		EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com");
+		EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);
+		EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+		ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0]));
+		std::cout << client.GetResult() << std::endl;
+		ASSERT_EQ(client.GetAnswerNum(), 1);
+		EXPECT_EQ(client.GetStatus(), "NOERROR");
+		EXPECT_LT(client.GetQueryTime(), 20);
+		EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+		EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);
+		EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+	}
+}
+
+TEST_F(Group, group_match_domain)
+{
+	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");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8");
+			return smartdns::SERVER_REQUEST_OK;
+		} else if (request->qtype == DNS_T_AAAA) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1");
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2");
+			return smartdns::SERVER_REQUEST_OK;
+		}
+		return smartdns::SERVER_REQUEST_SOA;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80);
+	server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110);
+	server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150);
+	server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200);
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+speed-check-mode none
+group-begin client
+address #6
+group-match -domain a.com
+)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("a.com", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 2);
+	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].GetData(), "1.2.3.4");
+	EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8");
+
+	ASSERT_TRUE(client.Query("b.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 2);
+	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(), "2001:db8::1");
+	EXPECT_EQ(client.GetAnswer()[1].GetData(), "2001:db8::2");
+
+	ASSERT_TRUE(client.Query("b.com", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 2);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_LT(client.GetQueryTime(), 20);
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com");
+	EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+	EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8");
+}