浏览代码

feature: Simple add dns64 support.

Nick Peng 2 年之前
父节点
当前提交
24e1dac854
共有 6 个文件被更改,包括 230 次插入11 次删除
  1. 4 0
      ReadMe.md
  2. 4 0
      ReadMe_en.md
  3. 5 1
      etc/smartdns/smartdns.conf
  4. 41 0
      src/dns_conf.c
  5. 7 0
      src/dns_conf.h
  6. 169 10
      src/dns_server.c

+ 4 - 0
ReadMe.md

@@ -120,6 +120,9 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
 1. **支持 IPv4、IPv6 双栈**  
    支持 IPv4 和 IPV 6网络,支持查询 A 和 AAAA 记录,支持双栈 IP 速度优化,并支持完全禁用 IPv6 AAAA 解析。
 
+1. **支持DNS64**  
+   支持DNS64转换。
+
 1. **高性能、占用资源少**  
    多线程异步 IO 模式,cache 缓存查询结果。
 
@@ -603,6 +606,7 @@ entware|ipkg update<br />ipkg install smartdns|软件源路径:<https://bin.en
 | response-mode | 首次查询响应模式 | first-ping |模式:[first-ping\|fastest-ip\|fastest-response]<br /> [first-ping]: 最快ping响应地址模式,DNS上游最快查询时延+ping时延最短,查询等待与链接体验最佳;<br />[fastest-ip]: 最快IP地址模式,查询到的所有IP地址中ping最短的IP。需等待IP测速; <br />[fastest-response]: 最快响应的DNS结果,DNS查询等待时间最短,返回的IP地址可能不是最快。| response-mode first-ping |
 | address | 指定域名 IP 地址 | 无 | address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6] <br />- 表示忽略 <br /># 表示返回 SOA <br />4 表示 IPv4 <br />6 表示 IPv6 | address /www.example.com/1.2.3.4 |
 | cname | 指定域名别名 | 无 | cname /domain/target <br />- 表示忽略 <br />指定对应域名的cname | cname /www.example.com/cdn.example.com |
+| dns64 | DNS64转换 | 无 | dns64 ip-prefix/mask <br /> ipv6前缀和掩码 | dns64 64:ff9b::/96 |
 | nameserver | 指定域名使用 server 组解析 | 无 | nameserver /domain/[group\|-], group 为组名,- 表示忽略此规则,配套 server 中的 -group 参数使用 | nameserver /www.example.com/office |
 | ipset | 域名 ipset | 无 | ipset /domain/[ipset\|-\|#[4\|6]:[ipset\|-][,#[4\|6]:[ipset\|-]]],-表示忽略 | ipset /www.example.com/#4:dns4,#6:- <br />ipset /www.example.com/dns |
 | ipset-timeout | 设置 ipset 超时功能启用  | no | [yes\|no] | ipset-timeout yes |

+ 4 - 0
ReadMe_en.md

@@ -115,6 +115,9 @@ From the comparison, smartdns found the fastest IP address to visit www.baidu.co
 1. **Support IPV4, IPV6 dual stack**  
    Support IPV4, IPV6 network, support query A, AAAA record, dual-stack IP selection, and filter IPV6 AAAA record.
 
+1. **DNS64**  
+   Support DNS64 translation.
+
 1. **High performance, low resource consumption**  
    Multi-threaded asynchronous IO mode, cache cache query results.
 
@@ -566,6 +569,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |response-mode|First query response mode|first-ping|Mode: [first-ping\|fastest-ip\|fastest-response]<br /> [first-ping]: The fastest dns + ping response mode, DNS query delay + ping delay is the shortest;<br />[fastest-ip]: The fastest IP address mode, return the fastest ip address, may take some time to test speed. <br />[fastest-response]: The fastest response DNS result mode, the DNS query waiting time is the shortest. | response-mode first-ping |
 |address|Domain IP address|None|address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6], `-` for ignore, `#` for return SOA, `4` for IPV4, `6` for IPV6| address /www.example.com/1.2.3.4
 |cname|set cname to domain| None | cname /domain/target <br />- for ignore <br />set cname to domain. | cname /www.example.com/cdn.example.com |
+|dns64|dns64 translation | None | dns64 ip-prefix/mask <br /> ipv6 prefix and mask. | dns64 64:ff9b::/96 |
 |nameserver|To query domain with specific server group|None|nameserver /domain/[group\|-], `group` is the group name, `-` means ignore this rule, use the `-group` parameter in the related server|nameserver /www.example.com/office
 |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|no|[yes\|no]|ipset-timeout yes

+ 5 - 1
etc/smartdns/smartdns.conf

@@ -105,7 +105,7 @@ force-qtype-SOA 65
 # dualstack-ip-selection-threshold [num] (0~1000)
 # dualstack-ip-allow-force-AAAA [yes|no]
 # dualstack-ip-selection [yes|no]
-# dualstack-ip-selection yes
+# dualstack-ip-selection no
 
 # edns client subnet
 # edns-client-subnet [ip/subnet]
@@ -229,6 +229,10 @@ log-level info
 # specific cname to domain
 # cname /domain/target
 
+# enalbe DNS64 feature
+# dns64 [ip/subnet]
+# dns64 64:ff9b::/96
+
 # enable ipset timeout by ttl feature
 # ipset-timeout [yes]
 

+ 41 - 0
src/dns_conf.c

@@ -61,6 +61,9 @@ static time_t dns_conf_dnsmasq_lease_file_time;
 struct dns_hosts_table dns_hosts_table;
 int dns_hosts_record_num;
 
+/* DNS64 */
+struct dns_dns64 dns_conf_dns_dns64;
+
 /* server ip/port  */
 struct dns_bind_ip dns_conf_bind_ip[DNS_MAX_BIND_IP];
 int dns_conf_bind_ip_num = 0;
@@ -1690,6 +1693,43 @@ static int _config_speed_check_mode(void *data, int argc, char *argv[])
 	return _config_speed_check_mode_parser(&dns_conf_check_orders, mode);
 }
 
+static int _config_dns64(void *data, int argc, char *argv[])
+{
+	prefix_t prefix;
+	char *subnet = NULL;
+	const char *errmsg = NULL;
+	void *p = NULL;
+
+	if (argc <= 1) {
+		return -1;
+	}
+
+	subnet = argv[1];
+
+	p = prefix_pton(subnet, -1, &prefix, &errmsg);
+	if (p == NULL) {
+		goto errout;
+	}
+
+	if (prefix.family != AF_INET6) {
+		tlog(TLOG_ERROR, "dns64 subnet %s is not ipv6", subnet);
+		goto errout;
+	}
+
+	if (prefix.bitlen <= 0 || prefix.bitlen > 96) {
+		tlog(TLOG_ERROR, "dns64 subnet %s is not valid", subnet);
+		goto errout;
+	}
+
+	memcpy(&dns_conf_dns_dns64.prefix, &prefix.add.sin6.s6_addr, sizeof(dns_conf_dns_dns64.prefix));
+	dns_conf_dns_dns64.prefix_len = prefix.bitlen;
+
+	return 0;
+
+errout:
+	return -1;
+}
+
 static int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type)
 {
 	int index = dns_conf_bind_ip_num;
@@ -3021,6 +3061,7 @@ static struct config_item _config_item[] = {
 	CONF_YESNO("dualstack-ip-selection", &dns_conf_dualstack_ip_selection),
 	CONF_YESNO("dualstack-ip-allow-force-AAAA", &dns_conf_dualstack_ip_allow_force_AAAA),
 	CONF_INT("dualstack-ip-selection-threshold", &dns_conf_dualstack_ip_selection_threshold, 0, 1000),
+	CONF_CUSTOM("dns64", _config_dns64, NULL),
 	CONF_CUSTOM("log-level", _config_log_level, NULL),
 	CONF_STRING("log-file", (char *)dns_conf_log_file, DNS_MAX_PATH),
 	CONF_SIZE("log-size", &dns_conf_log_size, 0, 1024 * 1024 * 1024),

+ 7 - 0
src/dns_conf.h

@@ -393,6 +393,13 @@ struct dns_set_rule_flags_callback_args {
 	int is_clear_flag;
 };
 
+struct dns_dns64 {
+	unsigned char prefix[DNS_RR_AAAA_LEN];
+	uint32_t prefix_len;
+};
+
+extern struct dns_dns64 dns_conf_dns_dns64;
+
 extern struct dns_bind_ip dns_conf_bind_ip[DNS_MAX_BIND_IP];
 extern int dns_conf_bind_ip_num;
 

+ 169 - 10
src/dns_server.c

@@ -77,6 +77,13 @@ typedef enum {
 	DNS_CONN_TYPE_TLS_CLIENT,
 } DNS_CONN_TYPE;
 
+typedef enum DNS_CHILD_POST_RESULT {
+	DNS_CHILD_POST_SUCCESS = 0,
+	DNS_CHILD_POST_FAIL,
+	DNS_CHILD_POST_SKIP,
+	DNS_CHILD_POST_NO_RESPONSE,
+} DNS_CHILD_POST_RESULT;
+
 struct rule_walk_args {
 	void *args;
 	unsigned char *key[DOMAIN_RULE_MAX];
@@ -166,7 +173,8 @@ struct dns_request_pending_list {
 	struct hlist_node node;
 };
 
-typedef int (*child_request_callback)(struct dns_request *request, struct dns_request *child_request);
+typedef DNS_CHILD_POST_RESULT (*child_request_callback)(struct dns_request *request, struct dns_request *child_request,
+														int is_first_resp);
 
 struct dns_request {
 	atomic_t refcnt;
@@ -219,7 +227,6 @@ struct dns_request {
 	int ping_time;
 	int ip_ttl;
 	unsigned char ip_addr[DNS_RR_AAAA_LEN];
-	int ip_addr_len;
 
 	struct dns_soa soa;
 	int has_soa;
@@ -1587,7 +1594,7 @@ static int _dns_result_child_post(struct dns_server_post_context *context)
 {
 	struct dns_request *request = context->request;
 	struct dns_request *parent_request = request->parent_request;
-	int ret = 0;
+	DNS_CHILD_POST_RESULT child_ret = DNS_CHILD_POST_FAIL;
 
 	/* not a child request */
 	if (parent_request == NULL) {
@@ -1595,10 +1602,11 @@ static int _dns_result_child_post(struct dns_server_post_context *context)
 	}
 
 	if (request->child_callback) {
-		ret = request->child_callback(parent_request, request);
+		int is_first_resp = context->no_release_parent;
+		child_ret = request->child_callback(parent_request, request, is_first_resp);
 	}
 
-	if (context->do_reply == 1) {
+	if (context->do_reply == 1 && child_ret == DNS_CHILD_POST_SUCCESS) {
 		struct dns_server_post_context parent_context;
 		_dns_server_post_context_init(&parent_context, parent_request);
 		parent_context.do_cache = context->do_cache;
@@ -1612,17 +1620,21 @@ static int _dns_result_child_post(struct dns_server_post_context *context)
 		parent_context.no_release_parent = context->no_release_parent;
 
 		_dns_request_post(&parent_context);
-		ret = _dns_server_reply_all_pending_list(parent_request, &parent_context);
+		_dns_server_reply_all_pending_list(parent_request, &parent_context);
 	}
 
 	if (context->no_release_parent == 0) {
-		tlog(TLOG_INFO, "query %s with cname %s done", parent_request->domain, request->domain);
+		tlog(TLOG_INFO, "query %s with child %s done", parent_request->domain, request->domain);
 		request->parent_request = NULL;
 		parent_request->request_wait--;
 		_dns_server_request_release(parent_request);
 	}
 
-	return ret;
+	if (child_ret == DNS_CHILD_POST_FAIL) {
+		return -1;
+	}
+
+	return 0;
 }
 
 static int _dns_request_post(struct dns_server_post_context *context)
@@ -2787,6 +2799,12 @@ static int _dns_server_process_answer(struct dns_request *request, const char *d
 					 "%d, minimum: %d",
 					 domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial,
 					 request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);
+
+				/* if DNS64 enabled, skip check SOA. */
+				if (request->qtype == DNS_T_AAAA && dns_conf_dns_dns64.prefix_len > 0) {
+					break;
+				}
+
 				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;
@@ -3103,6 +3121,7 @@ static void _dns_server_query_end(struct dns_request *request)
 {
 	int ip_num = 0;
 	int request_wait = 0;
+
 	pthread_mutex_lock(&request->ip_map_lock);
 	ip_num = atomic_read(&request->ip_map_num);
 	/* if adblock ip address exist */
@@ -3838,6 +3857,10 @@ static struct dns_request *_dns_server_new_child_request(struct dns_request *req
 	child_request->prefetch_expired_domain = request->prefetch_expired_domain;
 	child_request->child_callback = child_callback;
 	child_request->parent_request = request;
+	if (request->has_ecs) {
+		memcpy(&child_request->ecs, &request->ecs, sizeof(child_request->ecs));
+		child_request->has_ecs = request->has_ecs;
+	}
 	_dns_server_request_get(request);
 	/* reference count is 1 hold by parent request */
 	request->child_request = child_request;
@@ -3919,12 +3942,13 @@ static int _dns_server_request_copy(struct dns_request *request, struct dns_requ
 	return 0;
 }
 
-static int _dns_server_process_cname_callback(struct dns_request *request, struct dns_request *child_request)
+static DNS_CHILD_POST_RESULT _dns_server_process_cname_callback(struct dns_request *request,
+																struct dns_request *child_request, int is_first_resp)
 {
 	_dns_server_request_copy(request, child_request);
 	safe_strncpy(request->cname, child_request->domain, sizeof(request->cname));
 
-	return 0;
+	return DNS_CHILD_POST_SUCCESS;
 }
 
 static int _dns_server_process_cname(struct dns_request *request)
@@ -3988,6 +4012,137 @@ 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, dns_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_ip == 0) {
+		if (child_request->has_soa) {
+			memcpy(&request->soa, &child_request->soa, sizeof(struct dns_soa));
+			request->has_soa = 1;
+			return DNS_CHILD_POST_SUCCESS;
+		}
+
+		if (request->has_soa == 0) {
+			_dns_server_setup_soa(request);
+			request->has_soa = 1;
+		}
+		return DNS_CHILD_POST_FAIL;
+	}
+
+	memcpy(request->ip_addr, dns_conf_dns_dns64.prefix, 16);
+	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;
+
+	request->rcode = child_request->rcode;
+	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;
+		}
+
+		new_addr_map->addr_type = DNS_T_AAAA;
+		addr_len = DNS_RR_AAAA_LEN;
+		memcpy(new_addr_map->ip_addr, dns_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_SUCCESS;
+}
+
+static int _dns_server_process_dns64(struct dns_request *request)
+{
+	if (request->qtype != DNS_T_AAAA) {
+		return 0;
+	}
+
+	if (dns_conf_dns_dns64.prefix_len <= 0) {
+		/* no dns64 prefix, no need to do dns64 */
+		return 0;
+	}
+
+	tlog(TLOG_DEBUG, "query %s with dns64", request->domain);
+
+	struct dns_request *child_request = _dns_server_new_child_request(request, _dns_server_process_dns64_callback);
+	if (child_request == NULL) {
+		tlog(TLOG_ERROR, "malloc failed.\n");
+		return -1;
+	}
+
+	child_request->qtype = DNS_T_A;
+	child_request->qclass = request->qclass;
+	safe_strncpy(child_request->domain, request->domain, sizeof(child_request->domain));
+
+	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 1;
+
+errout:
+
+	if (child_request) {
+		request->child_request = NULL;
+		_dns_server_request_release(child_request);
+	}
+
+	return -1;
+}
+
 static int _dns_server_qtype_soa(struct dns_request *request)
 {
 	struct dns_qtype_soa_list *soa_list = NULL;
@@ -4694,6 +4849,10 @@ static int _dns_server_do_query(struct dns_request *request, int skip_notify_eve
 	/* When the dual stack ip preference is enabled, both A and AAAA records are requested. */
 	_dns_server_query_dualstack(request);
 
+	if (_dns_server_process_dns64(request) != 0) {
+		goto clean_exit;
+	}
+
 clean_exit:
 	return 0;
 errout: