Browse Source

Feature: add domain-set option, simplify domain name rule settings

Nick Peng 3 years ago
parent
commit
1f1fd118de
7 changed files with 554 additions and 53 deletions
  1. 26 0
      ReadMe.md
  2. 32 6
      ReadMe_en.md
  3. 14 0
      etc/smartdns/smartdns.conf
  4. 396 25
      src/dns_conf.c
  5. 58 4
      src/dns_conf.h
  6. 27 17
      src/dns_server.c
  7. 1 1
      src/smartdns.c

+ 26 - 0
ReadMe.md

@@ -529,6 +529,7 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
 | ipset | 域名 ipset | 无 | ipset /domain/[ipset\|-\|#[4\|6]:[ipset\|-][,#[4\|6]:[ipset\|-]]],-表示忽略 | ipset /www.example.com/#4:dns4,#6:- |
 | ipset-timeout | 设置 ipset 超时功能启用  | 自动 | [yes] | ipset-timeout yes |
 | domain-rules | 设置域名规则 | 无 | domain-rules /domain/ [-rules...]<br>[-c\|-speed-check-mode]:测速模式,参考 speed-check-mode 配置<br>[-a\|-address]:参考 address 配置<br>[-n\|-nameserver]:参考 nameserver 配置<br>[-p\|-ipset]:参考ipset配置<br>[-d\|-dualstack-ip-selection]:参考 dualstack-ip-selection  | domain-rules /www.example.com/ -speed-check-mode none |
+| domain-set | 设置域名集合 | 无 | domain-set [options...]<br>[-n\|-name]:域名集合名称 <br>[-t\|-type]:域名集合类型,当前仅支持list,格式为域名列表,一行一个域名。<br>[-f\|-file]:域名集合文件路径。<br> 选项需要配合address, nameserver, ipset等需要指定域名的地方使用,使用方式为 /domain-set:[name]/| domain-set -name set -type list -file /path/to/list <br> address /domain-set:set/1.2.4.8 |
 | bogus-nxdomain | 假冒 IP 地址过滤 | 无 | [ip/subnet],可重复 | bogus-nxdomain 1.2.3.4/16 |
 | ignore-ip | 忽略 IP 地址 | 无 | [ip/subnet],可重复 | ignore-ip 1.2.3.4/16 |
 | whitelist-ip | 白名单 IP 地址 | 无 | [ip/subnet],可重复 | whitelist-ip 1.2.3.4/16 |
@@ -697,6 +698,31 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
 
     * Windows系统默认使用mDNS解析地址,如需要在windows下用使用smartdns解析,则需要在主机名后面增加`.`,表示使用DNS解析。如`ping smartdns.`
 
+13. 域名集合如何使用?  
+    为方便按集合配置域名,对于有/domain/的配置,可以指定域名集合,方便维护。具体方法为:
+    
+    * 使用`domain-set`配置集合文件,如
+    
+    ```sh
+    domain-set -name ad -file /etc/smartdns/ad-list.conf
+    ```
+
+    ad-list.conf的格式为一个域名一行,如
+    
+    ```
+    ad.com
+    site.com
+    ```
+
+    * 在有/domain/配置的选项使用域名集合,只需要将`/domain/`配置为`/domain-set:[集合名称]/`即可,如:
+
+    ```sh
+    address /domain-set:ad/#
+    domain-rules /domain-set:ad/ -a #
+    nameserver /domain-set:ad/server
+    ...
+    ```
+
 ## 编译
 
   SmartDNS 提供了编译软件包的脚本(`package/build-pkg.sh`),支持编译 LuCI、Debian、OpenWrt 和 Optware 安装包。

+ 32 - 6
ReadMe_en.md

@@ -487,6 +487,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |ipset|Domain IPSet|None|ipset /domain/[ipset\|-\|#[4\|6]:[ipset\|-][,#[4\|6]:[ipset\|-]]], `-` for ignore|ipset /www.example.com/#4:dns4,#6:-
 |ipset-timeout|ipset timeout enable|auto|[yes]|ipset-timeout yes
 |domain-rules|set domain rules|None|domain-rules /domain/ [-rules...]<br>`[-c\|-speed-check-mode]`: set speed check mode,same as parameter `speed-check-mode`<br>`[-a\|-address]`: same as  parameter `address` <br>`[-n\|-nameserver]`: same as parameter `nameserver`<br>`[-p\|-ipset]`: same as parameter `ipset`<br>`[-d\|-dualstack-ip-selection]`: same as parameter `dualstack-ip-selection`|domain-rules /www.example.com/ -speed-check-mode none
+| domain-set | collection of domains|None| domain-set [options...]<br>[-n\|-name]:name of set <br>[-t\|-type] [list]: set type, only support list, one domain per line <br>[-f\|-file]:file path of domain set<br> used with address, nameserver, ipset, example: /domain-set:[name]/ | domain-set -name set -type list -file /path/to/list <br> address /domain-set:set/1.2.4.8 |
 |bogus-nxdomain|bogus IP address|None|[IP/subnet], Repeatable| bogus-nxdomain 1.2.3.4/16
 |ignore-ip|ignore ip address|None|[ip/subnet], Repeatable| ignore-ip 1.2.3.4/16
 |whitelist-ip|ip whitelist|None|[ip/subnet], Repeatable,When the filtering server responds IPs in the IP whitelist, only result in whitelist will be accepted| whitelist-ip 1.2.3.4/16
@@ -534,13 +535,13 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 1. How to enable the audit log  
     The audit log records the domain name requested by the client. The record information includes the request time, the request IP address, the request domain name, and the request type. If you want to enable the audit log, configure `audit-enable yes` in the configuration file, `audit-size`, `Audit-file`, `audit-num` configure the audit log file size, the audit log file path, and the number of audit log files. The audit log file will be compressed to save space.
 
-1. How to avoid DNS privacy leaks
+1. How to avoid DNS privacy leaks  
     By default, smartdns will send requests to all configured DNS servers. If the upstream DNS servers record DNS logs, it will result in a DNS privacy leak. To avoid privacy leaks, try the following steps:
     * Use trusted DNS servers.
     * Use TLS servers.
     * Set up an upstream DNS server group.
 
-1. How to block ads
+1. How to block ads  
     Smartdns has a high-performance domain name matching algorithm. It is very efficient to filter advertisements by domain name. To block ads, you only need to configure records like the following configure. For example, if you block `*.ad.com`, configure as follows:
 
     ```sh
@@ -553,7 +554,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
     Address /pass.ad.com/-
     ```
 
-1. DNS query diversion
+1. DNS query diversion  
     In some cases, some domain names need to be queried using a specific DNS server to do DNS diversion. such as.
 
     ```sh
@@ -620,24 +621,49 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
     echo | openssl s_client -connect '1.0.0.1:853' 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
     ```
 
-1. How to solve the problem of slow DNS resolution in iOS system?  
+1. How to solve the problem of slow DNS resolution in iOS system?    
     Since iOS14, Apple has supported the resolution of DNS HTTPS (TYPE65) records. This function is used for solving problems related to HTTPS connections, but it is still a draft, and it will cause some functions such as adblocking fail. It is recommended to disable it through the following configuration.
 
     ```sh
     force-qtype-SOA 65
     ```
 
-1. How to resolve localhost ip by hostname?
+1. How to resolve localhost ip by hostname?  
     smartdns can cooperate with the dhcp server of DNSMASQ to support the resolution of local host name to IP address. You can configure smartdns to read the lease file of dnsmasq and support the resolution. The specific configuration parameters are as follows, (note that the DNSMASQ lease file may be different for each system and needs to be configured according to the actual situation)
 
     ```sh
     dnsmasq-lease-file /var/lib/misc/dnsmasq.leases
-    ````\
+    ```
 
     After the configuration is complete, you can directly use the host name to connect to the local machine. But need to pay attention:
 
     * Windows system uses mDNS to resolve addresses by default. If you need to use smartdns to resolve addresses under Windows, you need to add `.` after the host name, indicating that DNS resolution is used. Such as `ping smartdns.`
 
+1. How to use the domain set?  
+    To facilitate configuring domain names by set, for configurations with /domain/, you can specify a domain name set for easy maintenance. The specific method is:
+    
+    * Use `domain-set` configuration domain set file:
+    
+    ````sh
+    domain-set -name ad -file /etc/smartdns/ad-list.conf
+    ````
+
+    The format of ad-list.conf is one domain per line:
+    
+    ````
+    ad.com
+    site.com
+    ````
+
+    * To use the domain set, you only need to configure `/domain/` to `/domain-set:[collection name]/`, such as:
+
+    ````sh
+    address /domain-set:ad/#
+    domain-rules /domain-set:ad/ -a #
+    nameserver /domain-set:ad/server
+    ...
+    ````
+
 ## Compile
 
 smartdns contains scripts for compiling packages, supports compiling luci, debian, openwrt, opare installation packages, and can execute `package/build-pkg.sh` compilation.

+ 14 - 0
etc/smartdns/smartdns.conf

@@ -219,3 +219,17 @@ log-level info
 #   [-n] -nameserver [group|-]: same as nameserver option
 #   [-p] -ipset [ipset|-]: same as ipset option
 #   [-d] -dualstack-ip-selection [yes|no]: same as dualstack-ip-selection option
+
+# collection of domains 
+# the domain-set can be used with /domain/ for address, nameserver, ipset, etc.
+# domain-set -name [set-name] -type list -file [/path/to/file]
+#   [-n] -name [set name]: domain set name
+#   [-t] -type [list]: domain set type, list only now
+#   [-f] -file [path/to/set]: file path of domain set
+# 
+# example:
+# domain-set -name domain-list -type list -file /etc/smartdns/domain-list.conf
+# address /domain-set:domain-list/1.2.3.4
+# nameserver /domain-set:domain-list/server-group
+# ipset /domain-set:domain-list/ipset
+# domain-rules /domain-set:domain-list/ -speed-check-mode ping

+ 396 - 25
src/dns_conf.c

@@ -42,6 +42,9 @@ static struct dns_ipset_table dns_ipset_table;
 
 struct dns_qtype_soa_table dns_qtype_soa_table;
 
+struct dns_domain_set_rule_table dns_domain_set_rule_table;
+struct dns_domain_set_name_table dns_domain_set_name_table;
+
 /* dns groups */
 struct dns_group_table dns_group_table;
 
@@ -142,6 +145,62 @@ struct dns_edns_client_subnet dns_conf_ipv6_ecs;
 
 char dns_conf_sni_proxy_ip[DNS_MAX_IPLEN];
 
+static void *_new_dns_rule(enum domain_rule domain_rule)
+{
+	struct dns_rule *rule;
+	int size = 0;
+
+	if (domain_rule >= DOMAIN_RULE_MAX) {
+		return NULL;
+	}
+
+	switch (domain_rule) {
+	case DOMAIN_RULE_FLAGS:
+		size = sizeof(struct dns_rule_flags);
+		break;
+	case DOMAIN_RULE_ADDRESS_IPV4:
+		size = sizeof(struct dns_rule_address_IPV4);
+		break;
+	case DOMAIN_RULE_ADDRESS_IPV6:
+		size = sizeof(struct dns_rule_address_IPV6);
+		break;
+	case DOMAIN_RULE_IPSET:
+	case DOMAIN_RULE_IPSET_IPV4:
+	case DOMAIN_RULE_IPSET_IPV6:
+		size = sizeof(struct dns_ipset_rule);
+		break;
+	case DOMAIN_RULE_NAMESERVER:
+		size = sizeof(struct dns_nameserver_rule);
+		break;
+	case DOMAIN_RULE_CHECKSPEED:
+		size = sizeof(struct dns_domain_check_orders);
+		break;
+	default:
+		return NULL;
+	}
+
+	rule = malloc(size);
+	if (!rule) {
+		return NULL;
+	}
+	memset(rule, 0, size);
+	rule->rule = domain_rule;
+	atomic_set(&rule->refcnt, 1);
+	return rule;
+}
+
+static void _dns_rule_get(struct dns_rule *rule)
+{
+	atomic_inc(&rule->refcnt);
+}
+
+static void _dns_rule_put(struct dns_rule *rule)
+{
+	if (atomic_dec_and_test(&rule->refcnt)) {
+		free(rule);
+	}
+}
+
 static int _get_domain(char *value, char *domain, int max_dmain_size, char **ptr_after_domain)
 {
 	char *begin = NULL;
@@ -431,7 +490,8 @@ static int _config_domain_iter_free(void *data, const unsigned char *key, uint32
 			continue;
 		}
 
-		free(domain_rule->rules[i]);
+		_dns_rule_put(domain_rule->rules[i]);
+		domain_rule->rules[i] = NULL;
 	}
 
 	free(domain_rule);
@@ -458,6 +518,69 @@ static void _config_address_destroy(radix_node_t *node, void *cbctx)
 	node->data = NULL;
 }
 
+static int _config_domain_set_rule_add_ext(char *set_name, enum domain_rule type, void *rule, unsigned int flags, int is_clear_flag)
+{
+	struct dns_domain_set_rule *set_rule = NULL;
+	struct dns_domain_set_rule_list *set_rule_list = NULL;
+	uint32_t key = 0;
+
+	if (set_name == NULL) {
+		return -1;
+	}
+
+	set_rule = malloc(sizeof(struct dns_domain_set_rule));
+	if (set_rule == NULL) {
+		goto errout;
+	}
+	memset(set_rule, 0, sizeof(struct dns_domain_set_rule));
+
+	set_rule->type = type;
+	set_rule->rule = rule;
+	set_rule->flags = flags;
+	set_rule->is_clear_flag = is_clear_flag;
+
+	if (rule) {
+		_dns_rule_get(rule);
+	}
+
+	key = hash_string(set_name);
+	hash_for_each_possible(dns_domain_set_rule_table.rule_list, set_rule_list, node, key)
+	{
+		if (strncmp(set_rule_list->domain_set, set_name, DNS_MAX_CNAME_LEN) == 0) {
+			break;
+		}
+	}
+
+	if (set_rule_list == NULL) {
+		set_rule_list = malloc(sizeof(struct dns_domain_set_rule_list));
+		if (set_rule_list == NULL) {
+			goto errout;
+		}
+		memset(set_rule_list, 0, sizeof(struct dns_domain_set_rule_list));
+		INIT_LIST_HEAD(&set_rule_list->domain_ruls_list);
+		safe_strncpy(set_rule_list->domain_set, set_name, DNS_MAX_CNAME_LEN);
+		hash_add(dns_domain_set_rule_table.rule_list, &set_rule_list->node, key);
+	}
+
+	list_add_tail(&set_rule->list, &set_rule_list->domain_ruls_list);
+	return 0;
+errout:
+	if (set_rule) {
+		free(set_rule);
+	}
+	return -1;
+}
+
+static int _config_domian_set_rule_flags(char *set_name, unsigned int flags, int is_clear_flag)
+{
+	return _config_domain_set_rule_add_ext(set_name, DOMAIN_RULE_FLAGS, NULL, flags, is_clear_flag);
+}
+
+static int _config_domain_set_rule_add(char *set_name, enum domain_rule type, void *rule)
+{
+	return _config_domain_set_rule_add_ext(set_name, type, rule, 0, 0);
+}
+
 static int _config_domain_rule_add(char *domain, enum domain_rule type, void *rule)
 {
 	struct dns_domain_rule *domain_rule = NULL;
@@ -473,6 +596,11 @@ static int _config_domain_rule_add(char *domain, enum domain_rule type, void *ru
 		tlog(TLOG_ERROR, "domain name %s too long", domain);
 		goto errout;
 	}
+
+	if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) {
+		return _config_domain_set_rule_add(domain + sizeof("domain-set:") - 1, type, rule);
+	}
+
 	reverse_string(domain_key, domain, len, 1);
 	domain_key[len] = '.';
 	len++;
@@ -495,11 +623,12 @@ static int _config_domain_rule_add(char *domain, enum domain_rule type, void *ru
 
 	/* add new rule to domain */
 	if (domain_rule->rules[type]) {
-		free(domain_rule->rules[type]);
+		_dns_rule_put(domain_rule->rules[type]);
 		domain_rule->rules[type] = NULL;
 	}
 
 	domain_rule->rules[type] = rule;
+	_dns_rule_get(rule);
 
 	/* update domain rule */
 	if (add_domain_rule) {
@@ -529,6 +658,10 @@ static int _config_domain_rule_flag_set(char *domain, unsigned int flag, unsigne
 	char domain_key[DNS_MAX_CONF_CNAME_LEN];
 	int len = 0;
 
+	if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) {
+		return _config_domian_set_rule_flags(domain + sizeof("domain-set:") - 1, flag, is_clear);
+	}
+
 	len = strlen(domain);
 	if (len >= (int)sizeof(domain_key)) {
 		tlog(TLOG_ERROR, "domain %s too long", domain);
@@ -552,13 +685,12 @@ static int _config_domain_rule_flag_set(char *domain, unsigned int flag, unsigne
 
 	/* add new rule to domain */
 	if (domain_rule->rules[DOMAIN_RULE_FLAGS] == NULL) {
-		rule_flags = malloc(sizeof(*rule_flags));
-		memset(rule_flags, 0, sizeof(*rule_flags));
+		rule_flags = _new_dns_rule(DOMAIN_RULE_FLAGS);
 		rule_flags->flags = 0;
-		domain_rule->rules[DOMAIN_RULE_FLAGS] = rule_flags;
+		domain_rule->rules[DOMAIN_RULE_FLAGS] = (struct dns_rule *)rule_flags;
 	}
 
-	rule_flags = domain_rule->rules[DOMAIN_RULE_FLAGS];
+	rule_flags = (struct dns_rule_flags *)domain_rule->rules[DOMAIN_RULE_FLAGS];
 	if (is_clear == false) {
 		rule_flags->flags |= flag;
 	} else {
@@ -670,7 +802,7 @@ static int _conf_domain_rule_ipset(char *domain, const char *ipsetname)
 			goto errout;
 		}
 
-		ipset_rule = malloc(sizeof(*ipset_rule));
+		ipset_rule = _new_dns_rule(type);
 		if (ipset_rule == NULL) {
 			goto errout;
 		}
@@ -680,6 +812,7 @@ static int _conf_domain_rule_ipset(char *domain, const char *ipsetname)
 		if (_config_domain_rule_add(domain, type, ipset_rule) != 0) {
 			goto errout;
 		}
+		_dns_rule_put(&ipset_rule->head);
 	}
 
 	goto clear;
@@ -688,7 +821,7 @@ errout:
 	tlog(TLOG_ERROR, "add ipset %s failed", ipsetname);
 
 	if (ipset_rule) {
-		free(ipset_rule);
+		_dns_rule_put(&ipset_rule->head);
 	}
 
 clear:
@@ -720,9 +853,9 @@ errout:
 
 static int _conf_domain_rule_address(char *domain, const char *domain_address)
 {
-	struct dns_address_IPV4 *address_ipv4 = NULL;
-	struct dns_address_IPV6 *address_ipv6 = NULL;
-	void *address = NULL;
+	struct dns_rule_address_IPV4 *address_ipv4 = NULL;
+	struct dns_rule_address_IPV6 *address_ipv6 = NULL;
+	struct dns_rule *address = NULL;
 	char ip[MAX_IP_LEN];
 	int port = 0;
 	struct sockaddr_storage addr;
@@ -777,7 +910,7 @@ static int _conf_domain_rule_address(char *domain, const char *domain_address)
 		switch (addr.ss_family) {
 		case AF_INET: {
 			struct sockaddr_in *addr_in = NULL;
-			address_ipv4 = malloc(sizeof(*address_ipv4));
+			address_ipv4 = _new_dns_rule(DOMAIN_RULE_ADDRESS_IPV4);
 			if (address_ipv4 == NULL) {
 				goto errout;
 			}
@@ -785,27 +918,27 @@ static int _conf_domain_rule_address(char *domain, const char *domain_address)
 			addr_in = (struct sockaddr_in *)&addr;
 			memcpy(address_ipv4->ipv4_addr, &addr_in->sin_addr.s_addr, 4);
 			type = DOMAIN_RULE_ADDRESS_IPV4;
-			address = address_ipv4;
+			address = (struct dns_rule *)address_ipv4;
 		} break;
 		case AF_INET6: {
 			struct sockaddr_in6 *addr_in6 = NULL;
 			addr_in6 = (struct sockaddr_in6 *)&addr;
 			if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
-				address_ipv4 = malloc(sizeof(*address_ipv4));
+				address_ipv4 = _new_dns_rule(DOMAIN_RULE_ADDRESS_IPV4);
 				if (address_ipv4 == NULL) {
 					goto errout;
 				}
 				memcpy(address_ipv4->ipv4_addr, addr_in6->sin6_addr.s6_addr + 12, 4);
 				type = DOMAIN_RULE_ADDRESS_IPV4;
-				address = address_ipv4;
+				address = (struct dns_rule *)address_ipv4;
 			} else {
-				address_ipv6 = malloc(sizeof(*address_ipv6));
+				address_ipv6 = _new_dns_rule(DOMAIN_RULE_ADDRESS_IPV6);
 				if (address_ipv6 == NULL) {
 					goto errout;
 				}
 				memcpy(address_ipv6->ipv6_addr, addr_in6->sin6_addr.s6_addr, 16);
 				type = DOMAIN_RULE_ADDRESS_IPV6;
-				address = address_ipv6;
+				address = (struct dns_rule *)address_ipv6;
 			}
 		} break;
 		default:
@@ -818,10 +951,11 @@ static int _conf_domain_rule_address(char *domain, const char *domain_address)
 		goto errout;
 	}
 
+	_dns_rule_put(address);
 	return 0;
 errout:
 	if (address) {
-		free(address);
+		_dns_rule_put(address);
 	}
 
 	tlog(TLOG_ERROR, "add address %s, %s  failed", domain, domain_address);
@@ -857,7 +991,6 @@ static int _config_speed_check_mode_parser(struct dns_domain_check_orders *check
 	int i = 0;
 
 	safe_strncpy(tmpbuff, mode, DNS_MAX_OPT_LEN);
-	memset(check_orders, 0, sizeof(*check_orders));
 
 	ptr = tmpbuff;
 	do {
@@ -1088,7 +1221,7 @@ static int _conf_domain_rule_nameserver(char *domain, const char *group_name)
 			goto errout;
 		}
 
-		nameserver_rule = malloc(sizeof(*nameserver_rule));
+		nameserver_rule = _new_dns_rule(DOMAIN_RULE_NAMESERVER);
 		if (nameserver_rule == NULL) {
 			goto errout;
 		}
@@ -1107,10 +1240,12 @@ static int _conf_domain_rule_nameserver(char *domain, const char *group_name)
 		goto errout;
 	}
 
+	_dns_rule_put(&nameserver_rule->head);
+
 	return 0;
 errout:
 	if (nameserver_rule) {
-		free(nameserver_rule);
+		_dns_rule_put(&nameserver_rule->head);
 	}
 
 	tlog(TLOG_ERROR, "add nameserver %s, %s failed", domain, group_name);
@@ -1265,6 +1400,50 @@ static void _config_qtype_soa_table_destroy(void)
 	}
 }
 
+static void _config_domain_set_name_table_destroy(void)
+{
+	struct dns_domain_set_name_list *set_name_list = NULL;
+	struct hlist_node *tmp = NULL;
+	struct dns_domain_set_name *set_name = NULL;
+	struct dns_domain_set_name *tmp1 = NULL;
+	unsigned long i = 0;
+
+	hash_for_each_safe(dns_domain_set_name_table.names, i, tmp, set_name_list, node)
+	{
+		hlist_del_init(&set_name_list->node);
+		list_for_each_entry_safe(set_name, tmp1, &set_name_list->set_name_list, list)
+		{
+			list_del(&set_name->list);
+			free(set_name);
+		}
+
+		free(set_name_list);
+	}
+}
+
+static void _config_domain_set_rule_table_destroy(void)
+{
+	struct dns_domain_set_rule_list *set_rule_list = NULL;
+	struct hlist_node *tmp = NULL;
+	struct dns_domain_set_rule *set_rule = NULL;
+	struct dns_domain_set_rule *tmp1 = NULL;
+	unsigned long i = 0;
+
+	hash_for_each_safe(dns_domain_set_rule_table.rule_list, i, tmp, set_rule_list, node)
+	{
+		hlist_del_init(&set_rule_list->node);
+		list_for_each_entry_safe(set_rule, tmp1, &set_rule_list->domain_ruls_list, list)
+		{
+			list_del(&set_rule->list);
+			if (set_rule->rule) {
+				_dns_rule_put(set_rule->rule);
+			}
+			free(set_rule);
+		}
+		free(set_rule_list);
+	}
+}
+
 static int _config_blacklist_ip(void *data, int argc, char *argv[])
 {
 	if (argc <= 1) {
@@ -1355,11 +1534,10 @@ static int _conf_domain_rule_speed_check(char *domain, const char *mode)
 {
 	struct dns_domain_check_orders *check_orders = NULL;
 
-	check_orders = malloc(sizeof(*check_orders) * DOMAIN_CHECK_NUM);
+	check_orders = _new_dns_rule(DOMAIN_RULE_CHECKSPEED);
 	if (check_orders == NULL) {
 		goto errout;
 	}
-	memset(check_orders, 0, sizeof(*check_orders));
 
 	if (_config_speed_check_mode_parser(check_orders, mode) != 0) {
 		goto errout;
@@ -1369,12 +1547,109 @@ static int _conf_domain_rule_speed_check(char *domain, const char *mode)
 		goto errout;
 	}
 
+	_dns_rule_put(&check_orders->head);
 	return 0;
 errout:
 	if (check_orders) {
-		free(check_orders);
+		_dns_rule_put(&check_orders->head);
+	}
+	return 0;
+}
+
+static int _conf_domain_set(void *data, int argc, char *argv[])
+{
+	int opt = 0;
+	uint32_t key = 0;
+	struct dns_domain_set_name *domain_set = NULL;
+	struct dns_domain_set_name_list *domain_set_name_list = NULL;
+	char set_name[DNS_MAX_CNAME_LEN] = {0};
+
+	/* clang-format off */
+	static struct option long_options[] = {
+		{"name", required_argument, NULL, 'n'},
+		{"type", required_argument, NULL, 't'},
+		{"file", required_argument, NULL, 'f'},
+		{NULL, 0, NULL, 0}
+	};
+
+	if (argc <= 1) {
+		tlog(TLOG_ERROR, "invalid parameter.");
+		goto errout;
+	}
+
+	domain_set = malloc(sizeof(*domain_set));
+	if (domain_set == NULL) {
+		tlog(TLOG_ERROR, "cannot malloc memory.");
+		goto errout;
+	}
+	memset(domain_set, 0, sizeof(*domain_set));
+	INIT_LIST_HEAD(&domain_set->list);
+
+	optind = 1;
+	while (1) {
+		opt = getopt_long_only(argc, argv, "n:t:f:", long_options, NULL);
+		if (opt == -1) {
+			break;
+		}
+
+		switch (opt) {
+		case 'n':
+			safe_strncpy(set_name, optarg, DNS_MAX_CNAME_LEN);
+			break;
+		case 't': {
+			const char *type = optarg;
+			if (strncmp(type, "list", 5) == 0) {
+				domain_set->type = DNS_DOMAIN_SET_LIST;
+			} else if (strncmp(type, "geosite", 7) == 0) {
+				domain_set->type = DNS_DOMAIN_SET_GEOSITE;
+			} else {
+				tlog(TLOG_ERROR, "invalid domain set type.");
+				goto errout;
+			}
+			break;
+		}
+		case 'f':
+			conf_get_conf_fullpath(optarg, domain_set->file, DNS_MAX_PATH);
+			break;
+		default:
+			break;
+		}
 	}
+	/* clang-format on */
+
+	if (set_name[0] == 0 || domain_set->file[0] == 0) {
+		tlog(TLOG_ERROR, "invalid parameter.");
+		goto errout;
+	}
+
+	key = hash_string(set_name);
+	hash_for_each_possible(dns_domain_set_name_table.names, domain_set_name_list, node, key)
+	{
+		if (strcmp(domain_set_name_list->name, set_name) == 0) {
+			break;
+		}
+	}
+
+	if (domain_set_name_list == NULL) {
+		domain_set_name_list = malloc(sizeof(*domain_set_name_list));
+		if (domain_set_name_list == NULL) {
+			tlog(TLOG_ERROR, "cannot malloc memory.");
+			goto errout;
+		}
+		memset(domain_set_name_list, 0, sizeof(*domain_set_name_list));
+		INIT_LIST_HEAD(&domain_set_name_list->set_name_list);
+		safe_strncpy(domain_set_name_list->name, set_name, DNS_MAX_CNAME_LEN);
+		hash_add(dns_domain_set_name_table.names, &domain_set_name_list->node, key);
+	}
+
+	list_add_tail(&domain_set->list, &domain_set_name_list->set_name_list);
 	return 0;
+
+errout:
+	if (domain_set) {
+		free(domain_set);
+	}
+	return -1;
 }
 
 static int _conf_domain_rules(void *data, int argc, char *argv[])
@@ -1922,6 +2197,7 @@ static struct config_item _config_item[] = {
 	CONF_CUSTOM("ignore-ip", _conf_ip_ignore, NULL),
 	CONF_CUSTOM("edns-client-subnet", _conf_edns_client_subnet, NULL),
 	CONF_CUSTOM("domain-rules", _conf_domain_rules, NULL),
+	CONF_CUSTOM("domain-set", _conf_domain_set, NULL),
 	CONF_CUSTOM("dnsmasq-lease-file", _conf_dhcp_lease_dnsmasq_file, NULL),
 	CONF_CUSTOM("hosts-file", _conf_hosts_file, NULL),
 	CONF_STRING("ca-file", (char *)&dns_conf_ca_file, DNS_MAX_PATH),
@@ -1987,6 +2263,96 @@ int config_addtional_file(void *data, int argc, char *argv[])
 	return load_conf(file_path, _config_item, _conf_printf);
 }
 
+static int _update_domain_set_from_list(const char *file, struct dns_domain_set_rule_list *set_rule_list)
+{
+	FILE *fp = NULL;
+	char line[MAX_LINE_LEN];
+	char domain[DNS_MAX_CNAME_LEN];
+	int ret = 0;
+	int line_no = 0;
+	int filed_num = 0;
+	struct dns_domain_set_rule *set_rule = NULL;
+
+	fp = fopen(file, "r");
+	if (fp == NULL) {
+		tlog(TLOG_WARN, "open file %s error, %s", file, strerror(errno));
+		return 0;
+	}
+
+	line_no = 0;
+	while (fgets(line, MAX_LINE_LEN, fp)) {
+		line_no++;
+		filed_num = sscanf(line, "%256s", domain);
+		if (filed_num <= 0) {
+			continue;
+		}
+
+		if (domain[0] == '#' || domain[0] == '\n') {
+			continue;
+		}
+
+		list_for_each_entry(set_rule, &set_rule_list->domain_ruls_list, list)
+		{
+			if (set_rule->type == DOMAIN_RULE_FLAGS) {
+				ret = _config_domain_rule_flag_set(domain, set_rule->flags, set_rule->is_clear_flag);
+			} else {
+				ret = _config_domain_rule_add(domain, set_rule->type, set_rule->rule);
+			}
+
+			if (ret != 0) {
+				tlog(TLOG_WARN, "process file %s failed at line %d.", file, line_no);
+				continue;
+			}
+		}
+	}
+
+	fclose(fp);
+	return ret;
+}
+
+static int _update_domain_set(void)
+{
+	struct dns_domain_set_rule_list *set_rule_list = NULL;
+
+	struct dns_domain_set_name_list *set_name_list = NULL;
+	struct dns_domain_set_name *set_name_item = NULL;
+
+	unsigned long i = 0;
+	uint32_t key = 0;
+
+	hash_for_each(dns_domain_set_rule_table.rule_list, i, set_rule_list, node)
+	{
+		key = hash_string(set_rule_list->domain_set);
+		hash_for_each_possible(dns_domain_set_name_table.names, set_name_list, node, key)
+		{
+			if (strcmp(set_name_list->name, set_rule_list->domain_set) == 0) {
+				break;
+			}
+		}
+
+		if (set_name_list == NULL) {
+			tlog(TLOG_WARN, "domain set %s not found.", set_rule_list->domain_set);
+			continue;
+		}
+
+		list_for_each_entry(set_name_item, &set_name_list->set_name_list, list)
+		{
+			switch (set_name_item->type) {
+			case DNS_DOMAIN_SET_LIST:
+				_update_domain_set_from_list(set_name_item->file, set_rule_list);
+				break;
+			case DNS_DOMAIN_SET_GEOSITE:
+				break;
+			default:
+				tlog(TLOG_WARN, "domain set %s type %d not support.", set_name_list->name, set_name_item->type);
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
 static int _dns_server_load_conf_init(void)
 {
 	dns_conf_address_rule.ipv4 = New_Radix();
@@ -2003,6 +2369,8 @@ static int _dns_server_load_conf_init(void)
 	hash_init(dns_group_table.group);
 	hash_init(dns_hosts_table.hosts);
 	hash_init(dns_ptr_table.ptr);
+	hash_init(dns_domain_set_rule_table.rule_list);
+	hash_init(dns_domain_set_name_table.names);
 
 	_config_setup_smartdns_domain();
 
@@ -2096,7 +2464,6 @@ static int _dns_conf_load_post(void)
 			 dns_conf_response_mode_enum[dns_conf_response_mode].name);
 	}
 
-
 	if ((dns_conf_rr_ttl_min > dns_conf_rr_ttl_max) && dns_conf_rr_ttl_max > 0) {
 		dns_conf_rr_ttl_min = dns_conf_rr_ttl_max;
 	}
@@ -2113,6 +2480,10 @@ static int _dns_conf_load_post(void)
 		safe_strncpy(dns_resolv_file, DNS_RESOLV_FILE, sizeof(dns_resolv_file));
 	}
 
+	_update_domain_set();
+	_config_domain_set_name_table_destroy();
+	_config_domain_set_rule_table_destroy();
+
 	return 0;
 }
 

+ 58 - 4
src/dns_conf.h

@@ -20,6 +20,7 @@
 #define _DNS_CONF
 
 #include "art.h"
+#include "atomic.h"
 #include "conf.h"
 #include "dns.h"
 #include "dns_client.h"
@@ -102,16 +103,25 @@ typedef enum {
 #define BIND_FLAG_NO_DUALSTACK_SELECTION (1 << 7)
 #define BIND_FLAG_FORCE_AAAA_SOA (1 << 8)
 
+struct dns_rule {
+	atomic_t refcnt;
+	enum domain_rule rule;
+	char rule_data[];
+};
+
 struct dns_rule_flags {
+	struct dns_rule head;
 	unsigned int flags;
 	unsigned int is_flag_set;
 };
 
-struct dns_address_IPV4 {
+struct dns_rule_address_IPV4 {
+	struct dns_rule head;
 	unsigned char ipv4_addr[DNS_RR_A_LEN];
 };
 
-struct dns_address_IPV6 {
+struct dns_rule_address_IPV6 {
+	struct dns_rule head;
 	unsigned char ipv6_addr[DNS_RR_AAAA_LEN];
 };
 
@@ -121,14 +131,17 @@ struct dns_ipset_name {
 };
 
 struct dns_ipset_rule {
+	struct dns_rule head;
 	const char *ipsetname;
 };
 
 struct dns_domain_rule {
-	void *rules[DOMAIN_RULE_MAX];
+	struct dns_rule head;
+	struct dns_rule *rules[DOMAIN_RULE_MAX];
 };
 
 struct dns_nameserver_rule {
+	struct dns_rule head;
 	const char *group_name;
 };
 
@@ -145,6 +158,7 @@ struct dns_domain_check_order {
 };
 
 struct dns_domain_check_orders {
+	struct dns_rule head;
 	struct dns_domain_check_order orders[DOMAIN_CHECK_NUM];
 };
 
@@ -174,7 +188,7 @@ struct dns_hosts {
 	char domain[DNS_MAX_CNAME_LEN];
 	dns_hosts_type host_type;
 	int dns_type;
-	int is_soa;	
+	int is_soa;
 	union {
 		unsigned char ipv4_addr[DNS_RR_A_LEN];
 		unsigned char ipv6_addr[DNS_RR_AAAA_LEN];
@@ -255,6 +269,46 @@ struct dns_qtype_soa_table {
 };
 extern struct dns_qtype_soa_table dns_qtype_soa_table;
 
+struct dns_domain_set_rule {
+	struct list_head list;
+	enum domain_rule type;
+	void *rule;
+	unsigned int flags;
+	unsigned int is_clear_flag;
+};
+
+struct dns_domain_set_rule_list {
+	struct hlist_node node;
+	char domain_set[DNS_MAX_CNAME_LEN];
+	struct list_head domain_ruls_list;
+};
+
+struct dns_domain_set_rule_table {
+	DECLARE_HASHTABLE(rule_list, 4);
+};
+extern struct dns_domain_set_rule_table dns_domain_set_rule_table;
+
+enum dns_domain_set_type {
+	DNS_DOMAIN_SET_LIST = 0,
+	DNS_DOMAIN_SET_GEOSITE = 1,
+};
+
+struct dns_domain_set_name {
+	struct list_head list;
+	enum dns_domain_set_type type;
+	char file[DNS_MAX_PATH];
+};
+
+struct dns_domain_set_name_list {
+	struct hlist_node node;
+	char name[DNS_MAX_CNAME_LEN];
+	struct list_head set_name_list;
+};
+struct dns_domain_set_name_table {
+	DECLARE_HASHTABLE(names, 4);
+};
+extern struct dns_domain_set_name_table dns_domain_set_name_table;
+
 extern struct dns_bind_ip dns_conf_bind_ip[DNS_MAX_BIND_IP];
 extern int dns_conf_bind_ip_num;
 

+ 27 - 17
src/dns_server.c

@@ -316,6 +316,15 @@ static int _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint
 	return 0;
 }
 
+static void *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule)
+{
+	if (rule >= DOMAIN_RULE_MAX || request == NULL) {
+		return NULL;
+	}
+
+	return request->domain_rule.rules[rule];
+}
+
 static void _dns_server_set_dualstack_selection(struct dns_request *request)
 {
 	struct dns_rule_flags *rule_flag = NULL;
@@ -325,7 +334,7 @@ static void _dns_server_set_dualstack_selection(struct dns_request *request)
 		return;
 	}
 
-	rule_flag = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
+	rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);
 	if (rule_flag) {
 		if (rule_flag->flags & DOMAIN_FLAG_DUALSTACK_SELECT) {
 			request->dualstack_selection = 1;
@@ -361,7 +370,7 @@ static int _dns_server_is_return_soa(struct dns_request *request)
 		}
 	}
 
-	rule_flag = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
+	rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);
 	if (rule_flag) {
 		flags = rule_flag->flags;
 		if (flags & DOMAIN_FLAG_ADDR_SOA) {
@@ -733,11 +742,11 @@ static int _dns_add_rrs(struct dns_server_post_context *context)
 	}
 	/* add SOA record */
 	if (has_soa) {
-		ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, 0, &request->soa);
+		ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa);
 		tlog(TLOG_DEBUG, "result: %s, qtype: %d, return SOA", request->domain, context->qtype);
 	} else if (context->do_force_soa == 1) {
 		_dns_server_setup_soa(request);
-		ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, 0, &request->soa);
+		ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa);
 	}
 
 	if (request->has_ecs) {
@@ -1317,15 +1326,15 @@ static int _dns_server_setup_ipset_packet(struct dns_server_post_context *contex
 	}
 
 	/* check ipset rule */
-	rule_flags = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
+	rule_flags = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);
 	if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IGN) == 0) {
-		ipset_rule = request->domain_rule.rules[DOMAIN_RULE_IPSET];
+		ipset_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET);
 	}
 	if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV4_IGN) == 0) {
-		ipset_rule_v4 = request->domain_rule.rules[DOMAIN_RULE_IPSET_IPV4];
+		ipset_rule_v4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV4);
 	}
 	if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV6_IGN) == 0) {
-		ipset_rule_v6 = request->domain_rule.rules[DOMAIN_RULE_IPSET_IPV6];
+		ipset_rule_v6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV6);
 	}
 
 	if (!(ipset_rule || ipset_rule_v4 || ipset_rule_v6)) {
@@ -2501,6 +2510,7 @@ static int _dns_server_process_answer(struct dns_request *request, const char *d
 					 request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);
 				int soa_num = atomic_inc_return(&request->soa_num);
 				if ((soa_num >= (dns_server_num() / 3) + 1 || soa_num > 4) && atomic_read(&request->ip_map_num) <= 0) {
+					request->ip_ttl = ttl;
 					_dns_server_request_complete(request);
 				}
 			} break;
@@ -3340,7 +3350,7 @@ static int _dns_server_pre_process_rule_flags(struct dns_request *request)
 	unsigned int flags = 0;
 
 	/* get domain rule flag */
-	rule_flag = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
+	rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);
 	if (rule_flag == NULL) {
 		goto out;
 	}
@@ -3394,14 +3404,15 @@ out:
 
 soa:
 	/* return SOA */
+	request->ip_ttl = 30;
 	_dns_server_reply_SOA(DNS_RC_NOERROR, request);
 	return 0;
 }
 
 static int _dns_server_process_address(struct dns_request *request)
 {
-	struct dns_address_IPV4 *address_ipv4 = NULL;
-	struct dns_address_IPV6 *address_ipv6 = NULL;
+	struct dns_rule_address_IPV4 *address_ipv4 = NULL;
+	struct dns_rule_address_IPV6 *address_ipv6 = NULL;
 
 	if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {
 		goto errout;
@@ -3413,14 +3424,14 @@ static int _dns_server_process_address(struct dns_request *request)
 		if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] == NULL) {
 			goto errout;
 		}
-		address_ipv4 = request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4];
+		address_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4);
 		memcpy(request->ip_addr, address_ipv4->ipv4_addr, DNS_RR_A_LEN);
 		break;
 	case DNS_T_AAAA:
 		if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] == NULL) {
 			goto errout;
 		}
-		address_ipv6 = request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6];
+		address_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6);
 		memcpy(request->ip_addr, address_ipv6->ipv6_addr, DNS_RR_AAAA_LEN);
 		break;
 	default:
@@ -3468,7 +3479,7 @@ static void _dns_server_process_speed_check_rule(struct dns_request *request)
 	struct dns_domain_check_orders *check_order = NULL;
 
 	/* get domain rule flag */
-	check_order = request->domain_rule.rules[DOMAIN_RULE_CHECKSPEED];
+	check_order = _dns_server_get_dns_rule(request, DOMAIN_RULE_CHECKSPEED);
 	if (check_order == NULL) {
 		return;
 	}
@@ -3812,7 +3823,7 @@ static int _dns_server_process_smartdns_domain(struct dns_request *request)
 	unsigned int flags = 0;
 
 	/* get domain rule flag */
-	rule_flag = request->domain_rule.rules[DOMAIN_RULE_FLAGS];
+	rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);
 	if (rule_flag == NULL) {
 		return -1;
 	}
@@ -3870,7 +3881,7 @@ static const char *_dns_server_get_request_groupname(struct dns_request *request
 
 	/* Get the nameserver rule */
 	if (request->domain_rule.rules[DOMAIN_RULE_NAMESERVER]) {
-		struct dns_nameserver_rule *nameserver_rule = request->domain_rule.rules[DOMAIN_RULE_NAMESERVER];
+		struct dns_nameserver_rule *nameserver_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_NAMESERVER);
 		return nameserver_rule->group_name;
 	}
 
@@ -4052,7 +4063,6 @@ static int _dns_server_do_query(struct dns_request *request)
 		safe_strncpy(request->dns_group_name, group_name, DNS_GROUP_NAME_LEN);
 	}
 
-
 	_dns_server_set_dualstack_selection(request);
 
 	if (_dns_server_process_special_query(request) == 0) {

+ 1 - 1
src/smartdns.c

@@ -495,7 +495,7 @@ static int _smartdns_create_logdir(void)
 		return -1;
 	}
 
-	chown(logdir, uid, gid);
+	unused = chown(logdir, uid, gid);
 	return 0;
 }