diff --git a/ReadMe.md b/ReadMe.md
index 6b8c993..5176e3c 100644
--- a/ReadMe.md
+++ b/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
ipkg install smartdns|软件源路径: [first-ping]: 最快ping响应地址模式,DNS上游最快查询时延+ping时延最短,查询等待与链接体验最佳;
[fastest-ip]: 最快IP地址模式,查询到的所有IP地址中ping最短的IP。需等待IP测速;
[fastest-response]: 最快响应的DNS结果,DNS查询等待时间最短,返回的IP地址可能不是最快。| response-mode first-ping |
| address | 指定域名 IP 地址 | 无 | address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6]
- 表示忽略
# 表示返回 SOA
4 表示 IPv4
6 表示 IPv6 | address /www.example.com/1.2.3.4 |
| cname | 指定域名别名 | 无 | cname /domain/target
- 表示忽略
指定对应域名的cname | cname /www.example.com/cdn.example.com |
+| dns64 | DNS64转换 | 无 | dns64 ip-prefix/mask
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:-
ipset /www.example.com/dns |
| ipset-timeout | 设置 ipset 超时功能启用 | no | [yes\|no] | ipset-timeout yes |
diff --git a/ReadMe_en.md b/ReadMe_en.md
index 14f2bcb..aab93f2 100644
--- a/ReadMe_en.md
+++ b/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]
[first-ping]: The fastest dns + ping response mode, DNS query delay + ping delay is the shortest;
[fastest-ip]: The fastest IP address mode, return the fastest ip address, may take some time to test speed.
[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
- for ignore
set cname to domain. | cname /www.example.com/cdn.example.com |
+|dns64|dns64 translation | None | dns64 ip-prefix/mask
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
diff --git a/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf
index 2ceb4f9..8c7754a 100644
--- a/etc/smartdns/smartdns.conf
+++ b/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]
diff --git a/src/dns_conf.c b/src/dns_conf.c
index a0bf002..8a5b647 100644
--- a/src/dns_conf.c
+++ b/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),
diff --git a/src/dns_conf.h b/src/dns_conf.h
index 51789ec..586b60e 100644
--- a/src/dns_conf.h
+++ b/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;
diff --git a/src/dns_server.c b/src/dns_server.c
index 492f8c9..8a511b7 100644
--- a/src/dns_server.c
+++ b/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: