From 24e1dac854ebb838fbff97215f964df6ac01595f Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Fri, 17 Feb 2023 20:51:48 +0800 Subject: [PATCH] feature: Simple add dns64 support. --- ReadMe.md | 4 + ReadMe_en.md | 4 + etc/smartdns/smartdns.conf | 6 +- src/dns_conf.c | 41 +++++++++ src/dns_conf.h | 7 ++ src/dns_server.c | 179 ++++++++++++++++++++++++++++++++++--- 6 files changed, 230 insertions(+), 11 deletions(-) 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: