diff --git a/ReadMe.md b/ReadMe.md index ad1a7e3..0c7bd45 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -602,6 +602,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 | | 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 91a499c..413bdb1 100644 --- a/ReadMe_en.md +++ b/ReadMe_en.md @@ -565,6 +565,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use |speed-check-mode|Speed ​​mode|None|[ping\|tcp:[80]\|none]|speed-check-mode ping,tcp:80,tcp:443 |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 | |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 765203a..2ceb4f9 100644 --- a/etc/smartdns/smartdns.conf +++ b/etc/smartdns/smartdns.conf @@ -226,6 +226,9 @@ log-level info # address /www.example.com/-, ignore address, query from upstream, suffix 4, for ipv4, 6 for ipv6, none for all # address /www.example.com/#, return SOA to client, suffix 4, for ipv4, 6 for ipv6, none for all +# specific cname to domain +# cname /domain/target + # enable ipset timeout by ttl feature # ipset-timeout [yes] diff --git a/src/Makefile b/src/Makefile index f44a666..76652de 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,7 +20,7 @@ OBJS=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_co # cflags ifndef CFLAGS -CFLAGS =-O2 -g -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough +CFLAGS =-g -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough endif override CFLAGS +=-Iinclude override CFLAGS += -DBASE_FILE_NAME='"$(notdir $<)"' diff --git a/src/dns_conf.c b/src/dns_conf.c index c8e6cbe..7f77960 100644 --- a/src/dns_conf.c +++ b/src/dns_conf.c @@ -191,6 +191,9 @@ static void *_new_dns_rule(enum domain_rule domain_rule) case DOMAIN_RULE_CHECKSPEED: size = sizeof(struct dns_domain_check_orders); break; + case DOMAIN_RULE_CNAME: + size = sizeof(struct dns_cname_rule); + break; default: return NULL; } @@ -1547,6 +1550,64 @@ errout: return 0; } +static int _conf_domain_rule_cname(const char *domain, const char *cname) +{ + struct dns_cname_rule *cname_rule = NULL; + enum domain_rule type = DOMAIN_RULE_CNAME; + + cname_rule = _new_dns_rule(type); + if (cname_rule == NULL) { + goto errout; + } + + /* ignore this domain */ + if (*cname == '-') { + if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_CNAME_IGN, 0) != 0) { + goto errout; + } + + return 0; + } + + safe_strncpy(cname_rule->cname, cname, DNS_MAX_CONF_CNAME_LEN); + + if (_config_domain_rule_add(domain, type, cname_rule) != 0) { + goto errout; + } + _dns_rule_put(&cname_rule->head); + cname_rule = NULL; + + return 0; + +errout: + tlog(TLOG_ERROR, "add cname %s:%s failed", domain, cname); + + if (cname_rule) { + _dns_rule_put(&cname_rule->head); + } + + return 0; +} + +static int _config_cname(void *data, int argc, char *argv[]) +{ + char *value = argv[1]; + char domain[DNS_MAX_CONF_CNAME_LEN]; + + if (argc <= 1) { + goto errout; + } + + if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { + goto errout; + } + + return _conf_domain_rule_cname(domain, value); +errout: + tlog(TLOG_ERROR, "add cname %s:%s failed", domain, value); + return 0; +} + static void _config_speed_check_mode_clear(struct dns_domain_check_orders *check_orders) { memset(check_orders->orders, 0, sizeof(check_orders->orders)); @@ -2326,6 +2387,7 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) {"nftset", required_argument, NULL, 't'}, {"nameserver", required_argument, NULL, 'n'}, {"dualstack-ip-selection", required_argument, NULL, 'd'}, + {"cname", required_argument, NULL, 'A'}, {"no-serve-expired", no_argument, NULL, 254}, {"delete", no_argument, NULL, 255}, {NULL, no_argument, NULL, 0} @@ -2344,7 +2406,7 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) /* process extra options */ optind = 1; while (1) { - opt = getopt_long_only(argc, argv, "c:a:p:t:n:d:", long_options, NULL); + opt = getopt_long_only(argc, argv, "c:a:p:t:n:d:A:", long_options, NULL); if (opt == -1) { break; } @@ -2402,6 +2464,16 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) break; } + case 'A': { + const char *cname = optarg; + + if (_conf_domain_rule_cname(domain, cname) != 0) { + tlog(TLOG_ERROR, "add cname rule failed."); + goto errout; + } + + break; + } case 'd': { const char *yesno = optarg; if (_conf_domain_rule_dualstack_selection(domain, yesno) != 0) { @@ -2866,6 +2938,7 @@ static struct config_item _config_item[] = { CONF_CUSTOM("server-https", _config_server_https, NULL), CONF_CUSTOM("nameserver", _config_nameserver, NULL), CONF_CUSTOM("address", _config_address, NULL), + CONF_CUSTOM("cname", _config_cname, NULL), CONF_CUSTOM("proxy-server", _config_proxy_server, NULL), CONF_YESNO("ipset-timeout", &dns_conf_ipset_timeout_enable), CONF_CUSTOM("ipset", _config_ipset, NULL), diff --git a/src/dns_conf.h b/src/dns_conf.h index af54a6e..0f40e0b 100644 --- a/src/dns_conf.h +++ b/src/dns_conf.h @@ -74,6 +74,7 @@ enum domain_rule { DOMAIN_RULE_NFTSET_IP6, DOMAIN_RULE_NAMESERVER, DOMAIN_RULE_CHECKSPEED, + DOMAIN_RULE_CNAME, DOMAIN_RULE_MAX, }; @@ -104,6 +105,7 @@ typedef enum { #define DOMAIN_FLAG_NFTSET_IP_IGN (1 << 13) #define DOMAIN_FLAG_NFTSET_IP6_IGN (1 << 14) #define DOMAIN_FLAG_NO_SERVE_EXPIRED (1 << 15) +#define DOMAIN_FLAG_CNAME_IGN (1 << 16) #define SERVER_FLAG_EXCLUDE_DEFAULT (1 << 0) @@ -116,6 +118,7 @@ typedef enum { #define BIND_FLAG_NO_CACHE (1 << 6) #define BIND_FLAG_NO_DUALSTACK_SELECTION (1 << 7) #define BIND_FLAG_FORCE_AAAA_SOA (1 << 8) +#define BIND_FLAG_NO_RULE_CNAME (1 << 9) struct dns_rule { atomic_t refcnt; @@ -156,6 +159,11 @@ struct dns_ipset_names { }; extern struct dns_ipset_names dns_conf_ipset_no_speed; +struct dns_cname_rule { + struct dns_rule head; + char cname[DNS_MAX_CNAME_LEN]; +}; + struct dns_nftset_name { struct hlist_node node; char nftfamilyname[DNS_MAX_NFTSET_FAMILYLEN]; diff --git a/src/dns_server.c b/src/dns_server.c index e4f1ccb..ee30694 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -121,6 +121,7 @@ struct dns_server_post_context { int do_force_soa; int skip_notify_count; int select_all_best_ip; + int no_release_parent; }; struct dns_server_conn_udp { @@ -165,6 +166,8 @@ struct dns_request_pending_list { struct hlist_node node; }; +typedef int (*child_request_callback)(struct dns_request *request, struct dns_request *child_request); + struct dns_request { atomic_t refcnt; @@ -242,6 +245,10 @@ struct dns_request { pthread_mutex_t ip_map_lock; + struct dns_request *child_request; + struct dns_request *parent_request; + child_request_callback child_callback; + atomic_t ip_map_num; DECLARE_HASHTABLE(ip_map, 4); @@ -281,6 +288,8 @@ static void _dns_server_request_release(struct dns_request *request); static void _dns_server_request_release_complete(struct dns_request *request, int do_complete); static int _dns_server_reply_passthrough(struct dns_server_post_context *context); static int _dns_server_do_query(struct dns_request *request, int skip_notify_event); +static int _dns_request_post(struct dns_server_post_context *context); +static int _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context); static void _dns_server_wakeup_thread(void) { @@ -1553,6 +1562,47 @@ static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context return 0; } +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; + + /* not a child request */ + if (parent_request == NULL) { + return 0; + } + + if (request->child_callback) { + ret = request->child_callback(parent_request, request); + } + + if (context->do_reply == 1) { + struct dns_server_post_context parent_context; + _dns_server_post_context_init(&parent_context, parent_request); + parent_context.do_cache = context->do_cache; + parent_context.do_ipset = context->do_ipset; + parent_context.do_force_soa = context->do_force_soa; + parent_context.do_audit = context->do_audit; + parent_context.do_reply = context->do_reply; + parent_context.reply_ttl = context->reply_ttl; + parent_context.skip_notify_count = context->skip_notify_count; + parent_context.select_all_best_ip = 1; + + _dns_request_post(&parent_context); + ret = _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); + request->parent_request = NULL; + parent_request->request_wait--; + _dns_server_request_release(parent_request); + } + + return ret; +} + static int _dns_request_post(struct dns_server_post_context *context) { struct dns_request *request = context->request; @@ -1583,6 +1633,9 @@ static int _dns_request_post(struct dns_server_post_context *context) /* setup ipset */ _dns_server_setup_ipset_nftset_packet(context); + /* reply child request */ + _dns_result_child_post(context); + if (context->do_reply == 0) { return 0; } @@ -1811,6 +1864,7 @@ out: context.reply_ttl = reply_ttl; context.skip_notify_count = 1; context.select_all_best_ip = with_all_ips; + context.no_release_parent = 1; _dns_request_post(&context); return _dns_server_reply_all_pending_list(request, &context); @@ -1822,12 +1876,16 @@ static int _dns_server_request_complete(struct dns_request *request) } static int _dns_ip_address_check_add(struct dns_request *request, char *cname, unsigned char *addr, - dns_type_t addr_type) + dns_type_t addr_type, int ping_time) { uint32_t key = 0; struct dns_ip_address *addr_map = NULL; int addr_len = 0; + if (ping_time == 0) { + ping_time = -1; + } + if (addr_type == DNS_T_A) { addr_len = DNS_RR_A_LEN; } else if (addr_type == DNS_T_AAAA) { @@ -1868,7 +1926,7 @@ static int _dns_ip_address_check_add(struct dns_request *request, char *cname, u addr_map->addr_type = addr_type; addr_map->hitnum = 1; addr_map->recv_tick = get_tick_count(); - addr_map->ping_time = -1; + addr_map->ping_time = ping_time; memcpy(addr_map->ip_addr, addr, addr_len); if (dns_conf_force_no_cname == 0) { safe_strncpy(addr_map->cname, cname, DNS_MAX_CNAME_LEN); @@ -2050,6 +2108,11 @@ static void _dns_server_request_release_complete(struct dns_request *request, in _dns_server_complete_with_multi_ipaddress(request); } + if (request->parent_request != NULL) { + _dns_server_request_release(request->parent_request); + request->parent_request = NULL; + } + pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { @@ -2530,7 +2593,7 @@ static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request } /* add this ip to request */ - if (_dns_ip_address_check_add(request, cname, addr, DNS_T_A) != 0) { + if (_dns_ip_address_check_add(request, cname, addr, DNS_T_A, 0) != 0) { _dns_server_request_release(request); return -1; } @@ -2607,7 +2670,7 @@ static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_reque } /* add this ip to request */ - if (_dns_ip_address_check_add(request, cname, addr, DNS_T_AAAA) != 0) { + if (_dns_ip_address_check_add(request, cname, addr, DNS_T_AAAA, 0) != 0) { _dns_server_request_release(request); return -1; } @@ -2883,7 +2946,8 @@ static int _dns_server_get_answer(struct dns_server_post_context *context) continue; } - if (context->no_check_add_ip == 0 && _dns_ip_address_check_add(request, name, addr, DNS_T_A) != 0) { + if (context->no_check_add_ip == 0 && + _dns_ip_address_check_add(request, name, addr, DNS_T_A, request->ping_time) != 0) { continue; } @@ -2913,7 +2977,8 @@ static int _dns_server_get_answer(struct dns_server_post_context *context) continue; } - if (context->no_check_add_ip == 0 && _dns_ip_address_check_add(request, name, addr, DNS_T_AAAA) != 0) { + if (context->no_check_add_ip == 0 && + _dns_ip_address_check_add(request, name, addr, DNS_T_AAAA, request->ping_time) != 0) { continue; } @@ -2983,16 +3048,19 @@ static int _dns_server_reply_passthrough(struct dns_server_post_context *context _dns_server_get_answer(context); - _dns_result_callback(context); - _dns_cache_reply_packet(context); if (_dns_server_setup_ipset_nftset_packet(context) != 0) { tlog(TLOG_DEBUG, "setup ipset failed."); } + _dns_result_callback(context); + _dns_server_audit_log(context); + /* reply child request */ + _dns_result_child_post(context); + if (request->conn && context->do_reply == 1) { /* When passthrough, modify the id to be the id of the client request. */ struct dns_update_param param; @@ -3730,6 +3798,173 @@ errout: return -1; } +static struct dns_request *_dns_server_new_child_request(struct dns_request *request, + child_request_callback child_callback) +{ + struct dns_request *child_request = NULL; + + child_request = _dns_server_new_request(); + if (child_request == NULL) { + tlog(TLOG_ERROR, "malloc failed.\n"); + goto errout; + } + + child_request->server_flags = request->server_flags; + safe_strncpy(child_request->dns_group_name, request->dns_group_name, sizeof(request->dns_group_name)); + child_request->prefetch = request->prefetch; + child_request->prefetch_expired_domain = request->prefetch_expired_domain; + child_request->child_callback = child_callback; + child_request->parent_request = request; + _dns_server_request_get(request); + /* reference count is 1 hold by parent request */ + request->child_request = child_request; + return child_request; +errout: + if (child_request) { + _dns_server_request_release(child_request); + } + + return NULL; +} + +static int _dns_server_request_copy(struct dns_request *request, struct dns_request *from) +{ + unsigned long bucket = 0; + struct dns_ip_address *addr_map = NULL; + struct hlist_node *tmp = NULL; + uint32_t key = 0; + int addr_len = 0; + + request->rcode = from->rcode; + + if (from->has_ip) { + request->has_ip = 1; + request->ip_ttl = from->ip_ttl; + request->ping_time = from->ping_time; + memcpy(request->ip_addr, from->ip_addr, sizeof(request->ip_addr)); + } + + if (from->has_cname) { + request->has_cname = 1; + request->ttl_cname = from->ttl_cname; + safe_strncpy(request->cname, from->cname, sizeof(request->cname)); + } + + if (from->has_soa) { + request->has_soa = 1; + memcpy(&request->soa, &from->soa, sizeof(request->soa)); + } + + 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(&from->ip_map_lock); + hash_for_each_safe(from->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 if (addr_map->addr_type == DNS_T_AAAA) { + addr_len = DNS_RR_AAAA_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(&from->ip_map_lock); + return -1; + } + + memcpy(new_addr_map, addr_map, sizeof(struct dns_ip_address)); + new_addr_map->ping_time = addr_map->ping_time; + key = jhash(new_addr_map->ip_addr, addr_len, 0); + key = jhash(&addr_map->addr_type, sizeof(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(&from->ip_map_lock); + + return 0; +} + +static int _dns_server_process_cname_callback(struct dns_request *request, struct dns_request *child_request) +{ + _dns_server_request_copy(request, child_request); + safe_strncpy(request->cname, child_request->domain, sizeof(request->cname)); + + return 0; +} + +static int _dns_server_process_cname(struct dns_request *request) +{ + struct dns_cname_rule *cname = NULL; + int ret = 0; + struct dns_rule_flags *rule_flag = NULL; + + if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_CNAME) == 0) { + return 0; + } + + /* get domain rule flag */ + rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); + if (rule_flag != NULL) { + if (rule_flag->flags & DOMAIN_FLAG_CNAME_IGN) { + return 0; + } + } + + /* cname /domain/ rule */ + if (request->domain_rule.rules[DOMAIN_RULE_CNAME] == NULL) { + return 0; + } + + cname = _dns_server_get_dns_rule(request, DOMAIN_RULE_CNAME); + if (cname == NULL) { + return 0; + } + + tlog(TLOG_INFO, "query %s with cname %s", request->domain, cname->cname); + + struct dns_request *child_request = _dns_server_new_child_request(request, _dns_server_process_cname_callback); + if (child_request == NULL) { + tlog(TLOG_ERROR, "malloc failed.\n"); + return -1; + } + + child_request->qtype = request->qtype; + child_request->qclass = request->qclass; + safe_strncpy(child_request->domain, cname->cname, sizeof(child_request->cname)); + + request->request_wait++; + 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; @@ -4408,6 +4643,10 @@ static int _dns_server_do_query(struct dns_request *request, int skip_notify_eve goto clean_exit; } + if (_dns_server_process_cname(request) != 0) { + goto clean_exit; + } + // setup options _dns_server_setup_query_option(request, &options);