From 499ab1b64f2de4985eb4283d992dee01fa2ca05c Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Tue, 14 Feb 2023 22:46:15 +0800 Subject: [PATCH] feature: support set ttl, ttl-min, ttl-max to domain. --- ReadMe.md | 2 +- ReadMe_en.md | 2 +- src/dns.c | 35 +++++++++++++++++---------- src/dns_conf.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ src/dns_conf.h | 8 +++++++ src/dns_server.c | 59 +++++++++++++++++++++++++++++++--------------- 6 files changed, 134 insertions(+), 33 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 0c7bd45..6b8c993 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -611,7 +611,7 @@ entware|ipkg update
ipkg install smartdns|软件源路径:ipv4 地址的 family 只支持 inet 和 ip
ipv6 地址的 family 只支持 inet 和 ip6
由于 nft 限制,两种地址只能分开存放于两个 set 中。| nftset-no-speed #4:inet#tab#set4| | nftset-debug | 设置 nftset 调试功能启用 | no | [yes\|no] | nftset-debug yes | -| domain-rules | 设置域名规则 | 无 | domain-rules /domain/ [-rules...]
[-c\|-speed-check-mode]:测速模式,参考 speed-check-mode 配置
[-a\|-address]:参考 address 配置
[-n\|-nameserver]:参考 nameserver 配置
[-p\|-ipset]:参考ipset配置
[-t\|-nftset]:参考nftset配置
[-d\|-dualstack-ip-selection]:参考 dualstack-ip-selection
[-no-serve-expired]:禁用过期缓存
[-delete]:删除对应的规则 | domain-rules /www.example.com/ -speed-check-mode none | +| domain-rules | 设置域名规则 | 无 | domain-rules /domain/ [-rules...]
[-c\|-speed-check-mode]:测速模式,参考 speed-check-mode 配置
[-a\|-address]:参考 address 配置
[-n\|-nameserver]:参考 nameserver 配置
[-p\|-ipset]:参考ipset配置
[-t\|-nftset]:参考nftset配置
[-d\|-dualstack-ip-selection]:参考 dualstack-ip-selection
[-no-serve-expired]:禁用过期缓存
[-rr-ttl\|-rr-ttl-min\|-rr-ttl-max]: 参考配置rr-ttl, rr-ttl-min, rr-ttl-max
[-delete]:删除对应的规则 | domain-rules /www.example.com/ -speed-check-mode none | | domain-set | 设置域名集合 | 无 | domain-set [options...]
[-n\|-name]:域名集合名称
[-t\|-type]:域名集合类型,当前仅支持list,格式为域名列表,一行一个域名。
[-f\|-file]:域名集合文件路径。
选项需要配合address, nameserver, ipset, nftset等需要指定域名的地方使用,使用方式为 /domain-set:[name]/| domain-set -name set -type list -file /path/to/list
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 | diff --git a/ReadMe_en.md b/ReadMe_en.md index 413bdb1..14f2bcb 100644 --- a/ReadMe_en.md +++ b/ReadMe_en.md @@ -574,7 +574,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use |nftset-timeout|nftset timeout enable|no|[yes\|no]|nftset-timeout yes |nftset-no-speed|When speed check fails, set the ip address of the domain name to the nftset | None | nftset-no-speed [#4\|#6]:[family#nftable#nftset][,#[4\|6]:[family#nftable#nftset]]]
the valid families are inet and ip for ipv4 addresses while the valid ones are inet and ip6 for ipv6 addresses
due to the limitation of nftable
two types of addresses have to be stored in two sets| nftset-no-speed #4:inet#tab#set4| |nftset-debug|nftset debug enable|no|[yes\|no]|nftset-debug yes -|domain-rules|set domain rules|None|domain-rules /domain/ [-rules...]
[-c\|-speed-check-mode]: set speed check mode,same as parameter speed-check-mode
[-a\|-address]: same as parameter `address`
[-n\|-nameserver]: same as parameter `nameserver`
[-p\|-ipset]: same as parameter `nftset`
[-t\|-nftset]: same as parameter `nftset`
[-d\|-dualstack-ip-selection]: same as parameter `dualstack-ip-selection`
[-no-serve-expired]:disable serve expired
[-delete]:delete rule|domain-rules /www.example.com/ -speed-check-mode none +|domain-rules|set domain rules|None|domain-rules /domain/ [-rules...]
[-c\|-speed-check-mode]: set speed check mode,same as parameter speed-check-mode
[-a\|-address]: same as parameter `address`
[-n\|-nameserver]: same as parameter `nameserver`
[-p\|-ipset]: same as parameter `nftset`
[-t\|-nftset]: same as parameter `nftset`
[-d\|-dualstack-ip-selection]: same as parameter `dualstack-ip-selection`
[-no-serve-expired]:disable serve expired
[-rr-ttl\|-rr-ttl-min\|-rr-ttl-max]: same as parameter: rr-ttl, rr-ttl-min, rr-ttl-max
[-delete]:delete rule|domain-rules /www.example.com/ -speed-check-mode none | domain-set | collection of domains|None| domain-set [options...]
[-n\|-name]:name of set
[-t\|-type] [list]: set type, only support list, one domain per line
[-f\|-file]:file path of domain set
used with address, nameserver, ipset, nftset, example: /domain-set:[name]/ | domain-set -name set -type list -file /path/to/list
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 diff --git a/src/dns.c b/src/dns.c index 0031f90..7184ab5 100644 --- a/src/dns.c +++ b/src/dns.c @@ -42,6 +42,8 @@ (void)(expr); \ } while (0) +#define member_size(type, member) sizeof(((type *)0)->member) + /* read short and move pointer */ static unsigned short _dns_read_short(unsigned char **buffer) { @@ -111,7 +113,7 @@ static int _dns_get_domain_from_packet(unsigned char *packet, int packet_size, u /*[len]string[len]string...[0]0 */ while (1) { - if (ptr >= packet + packet_size || ptr < packet || output_len >= size - 1 || ptr_jump > 4) { + if (ptr >= packet + packet_size || ptr < packet || output_len >= size - 1 || ptr_jump > 32) { return -1; } @@ -1639,12 +1641,12 @@ static int _dns_encode_SOA(struct dns_context *context, struct dns_rrs *rrs) return 0; } -static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs) +static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs, int opt_len) { // TODO int len = 0; - if (_dns_left_len(context) < 4) { + if (opt_len < 4) { return -1; } @@ -1668,25 +1670,24 @@ static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs * return 0; } -static int _dns_decode_opt_cookie(struct dns_context *context, struct dns_opt_cookie *cookie) +static int _dns_decode_opt_cookie(struct dns_context *context, struct dns_opt_cookie *cookie, int opt_len) { // TODO - int len = _dns_left_len(context); - if (len < 8) { + if (opt_len < (int)member_size(struct dns_opt_cookie, client_cookie)) { return -1; } - len = 8; + int len = 8; memcpy(cookie->client_cookie, context->ptr, len); context->ptr += len; - len = _dns_left_len(context); - if (len == 0) { + opt_len -= len; + if (opt_len <= 0) { cookie->server_cookie_len = 0; return 0; } - if (len < 8) { + if (opt_len < (int)member_size(struct dns_opt_cookie, server_cookie)) { return -1; } @@ -1881,7 +1882,7 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign switch (opt_code) { case DNS_OPT_T_ECS: { struct dns_opt_ecs ecs; - ret = _dns_decode_opt_ecs(context, &ecs); + ret = _dns_decode_opt_ecs(context, &ecs, opt_len); if (ret != 0) { tlog(TLOG_ERROR, "decode ecs failed."); return -1; @@ -1895,7 +1896,7 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign } break; case DNS_OPT_T_COOKIE: { struct dns_opt_cookie cookie; - ret = _dns_decode_opt_cookie(context, &cookie); + ret = _dns_decode_opt_cookie(context, &cookie, opt_len); if (ret != 0) { tlog(TLOG_ERROR, "decode cookie failed."); return -1; @@ -2254,6 +2255,16 @@ static int _dns_encode_qd(struct dns_context *context, struct dns_rrs *rrs) return -1; } + if (domain[0] == '-') { + /* for google and cloudflare */ + unsigned char *ptr = context->ptr - 7; + memcpy(ptr, "\xC0\x12", 2); + ptr += 2; + _dns_write_short(&ptr, qtype); + _dns_write_short(&ptr, qclass); + context->ptr = ptr; + } + return 0; } diff --git a/src/dns_conf.c b/src/dns_conf.c index 7f77960..a0bf002 100644 --- a/src/dns_conf.c +++ b/src/dns_conf.c @@ -194,6 +194,9 @@ static void *_new_dns_rule(enum domain_rule domain_rule) case DOMAIN_RULE_CNAME: size = sizeof(struct dns_cname_rule); break; + case DOMAIN_RULE_TTL: + size = sizeof(struct dns_ttl_rule); + break; default: return NULL; } @@ -2363,6 +2366,39 @@ errout: return -1; } +static int _conf_domain_rule_rr_ttl(const char *domain, int ttl, int ttl_min, int ttl_max) +{ + struct dns_ttl_rule *rr_ttl = NULL; + + if (ttl < 0 || ttl_min < 0 || ttl_max < 0) { + tlog(TLOG_ERROR, "invalid ttl value."); + goto errout; + } + + rr_ttl = _new_dns_rule(DOMAIN_RULE_TTL); + if (rr_ttl == NULL) { + goto errout; + } + + rr_ttl->ttl = ttl; + rr_ttl->ttl_min = ttl_min; + rr_ttl->ttl_max = ttl_max; + + if (_config_domain_rule_add(domain, DOMAIN_RULE_TTL, rr_ttl) != 0) { + goto errout; + } + + _dns_rule_put(&rr_ttl->head); + + return 0; +errout: + if (rr_ttl != NULL) { + _dns_rule_put(&rr_ttl->head); + } + + return -1; +} + static int _conf_domain_rule_no_serve_expired(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_SERVE_EXPIRED, 0); @@ -2378,6 +2414,9 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) int opt = 0; char domain[DNS_MAX_CONF_CNAME_LEN]; char *value = argv[1]; + int rr_ttl = 0; + int rr_ttl_min = 0; + int rr_ttl_max = 0; /* clang-format off */ static struct option long_options[] = { @@ -2388,6 +2427,9 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) {"nameserver", required_argument, NULL, 'n'}, {"dualstack-ip-selection", required_argument, NULL, 'd'}, {"cname", required_argument, NULL, 'A'}, + {"rr-ttl", required_argument, NULL, 251}, + {"rr-ttl-min", required_argument, NULL, 252}, + {"rr-ttl-max", required_argument, NULL, 253}, {"no-serve-expired", no_argument, NULL, 254}, {"delete", no_argument, NULL, 255}, {NULL, no_argument, NULL, 0} @@ -2496,6 +2538,18 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) break; } + case 251: { + rr_ttl = atoi(optarg); + break; + } + case 252: { + rr_ttl_min = atoi(optarg); + break; + } + case 253: { + rr_ttl_max = atoi(optarg); + break; + } case 254: { if (_conf_domain_rule_no_serve_expired(domain) != 0) { tlog(TLOG_ERROR, "set no-serve-expired rule failed."); @@ -2517,6 +2571,13 @@ static int _conf_domain_rules(void *data, int argc, char *argv[]) } } + if (rr_ttl > 0 || rr_ttl_min > 0 || rr_ttl_max > 0) { + if (_conf_domain_rule_rr_ttl(domain, rr_ttl, rr_ttl_min, rr_ttl_max) != 0) { + tlog(TLOG_ERROR, "set rr-ttl rule failed."); + goto errout; + } + } + return 0; errout: return -1; diff --git a/src/dns_conf.h b/src/dns_conf.h index 0f40e0b..51789ec 100644 --- a/src/dns_conf.h +++ b/src/dns_conf.h @@ -75,6 +75,7 @@ enum domain_rule { DOMAIN_RULE_NAMESERVER, DOMAIN_RULE_CHECKSPEED, DOMAIN_RULE_CNAME, + DOMAIN_RULE_TTL, DOMAIN_RULE_MAX, }; @@ -164,6 +165,13 @@ struct dns_cname_rule { char cname[DNS_MAX_CNAME_LEN]; }; +struct dns_ttl_rule { + struct dns_rule head; + int ttl; + int ttl_max; + int ttl_min; +}; + 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 87d9165..492f8c9 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -290,6 +290,7 @@ 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_get_dns_rule(struct dns_request *request, enum domain_rule rule); static void _dns_server_wakeup_thread(void) { @@ -313,17 +314,37 @@ static int _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag) return -1; } -static int _dns_server_get_conf_ttl(int ttl) +static int _dns_server_get_conf_ttl(struct dns_request *request, int ttl) { - if (dns_conf_rr_ttl > 0) { - return dns_conf_rr_ttl; + int rr_ttl = dns_conf_rr_ttl; + int rr_ttl_min = dns_conf_rr_ttl_min; + int rr_ttl_max = dns_conf_rr_ttl_max; + + struct dns_ttl_rule *ttl_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_TTL); + if (ttl_rule != NULL) { + if (ttl_rule->ttl > 0) { + rr_ttl = ttl_rule->ttl; + } + + if (ttl_rule->ttl_min > 0) { + rr_ttl_min = ttl_rule->ttl_min; + } + + if (ttl_rule->ttl_max > 0) { + rr_ttl_max = ttl_rule->ttl_max; + } } - if (dns_conf_rr_ttl_max > 0 && ttl > dns_conf_rr_ttl_max) { - ttl = dns_conf_rr_ttl_max; - } else if (dns_conf_rr_ttl_min > 0 && ttl < dns_conf_rr_ttl_min) { - ttl = dns_conf_rr_ttl_min; + if (rr_ttl > 0) { + return rr_ttl; } + + if (rr_ttl_min > 0 && ttl > rr_ttl_min) { + ttl = rr_ttl_min; + } else if (rr_ttl_max > 0 && ttl < rr_ttl_max) { + ttl = rr_ttl_max; + } + return ttl; } @@ -1058,13 +1079,13 @@ static int _dns_server_request_update_cache(struct dns_request *request, dns_typ if (cache_ttl > 0) { ttl = cache_ttl; } else { - ttl = _dns_server_get_conf_ttl(request->ip_ttl); + ttl = _dns_server_get_conf_ttl(request, request->ip_ttl); } speed = request->ping_time; if (has_soa) { if (request->dualstack_selection && request->has_ip && request->qtype == DNS_T_AAAA) { - ttl = _dns_server_get_conf_ttl(request->ip_ttl); + ttl = _dns_server_get_conf_ttl(request, request->ip_ttl); } else { ttl = dns_conf_rr_ttl; if (ttl == 0) { @@ -1220,7 +1241,7 @@ static int _dns_cache_cname_packet(struct dns_server_post_context *context) return -1; } - ttl = _dns_server_get_conf_ttl(request->ip_ttl); + ttl = _dns_server_get_conf_ttl(request, request->ip_ttl); speed = request->ping_time; tlog(TLOG_DEBUG, "Cache CNAME: %s, qtype: %d, speed: %d", request->cname, request->qtype, speed); @@ -2573,14 +2594,14 @@ static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request if (request->has_ip == 0) { request->has_ip = 1; memcpy(request->ip_addr, addr, DNS_RR_A_LEN); - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); if (cname[0] != 0 && request->has_cname == 0 && dns_conf_force_no_cname == 0) { request->has_cname = 1; safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); } } else { if (ttl < request->ip_ttl) { - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } } @@ -2650,14 +2671,14 @@ static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_reque if (request->has_ip == 0) { request->has_ip = 1; memcpy(request->ip_addr, addr, DNS_RR_AAAA_LEN); - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); if (cname[0] != 0 && request->has_cname == 0 && dns_conf_force_no_cname == 0) { request->has_cname = 1; safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); } } else { if (ttl < request->ip_ttl) { - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } } @@ -2752,7 +2773,7 @@ static int _dns_server_process_answer(struct dns_request *request, const char *d continue; } safe_strncpy(cname, domain_cname, DNS_MAX_CNAME_LEN); - request->ttl_cname = _dns_server_get_conf_ttl(ttl); + request->ttl_cname = _dns_server_get_conf_ttl(request, ttl); tlog(TLOG_DEBUG, "name: %s ttl: %d cname: %s\n", name, ttl, cname); } break; case DNS_T_SOA: { @@ -2960,7 +2981,7 @@ static int _dns_server_get_answer(struct dns_server_post_context *context) memcpy(request->ip_addr, addr, DNS_RR_A_LEN); /* add this ip to request */ - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); request->has_ip = 1; request->rcode = packet->head.rcode; } break; @@ -2990,7 +3011,7 @@ static int _dns_server_get_answer(struct dns_server_post_context *context) } memcpy(request->ip_addr, addr, DNS_RR_AAAA_LEN); - request->ip_ttl = _dns_server_get_conf_ttl(ttl); + request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); request->has_ip = 1; request->rcode = packet->head.rcode; } break; @@ -3015,7 +3036,7 @@ static int _dns_server_get_answer(struct dns_server_post_context *context) } safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); - request->ttl_cname = _dns_server_get_conf_ttl(ttl); + request->ttl_cname = _dns_server_get_conf_ttl(request, ttl); request->has_cname = 1; } break; case DNS_T_SOA: { @@ -3188,7 +3209,7 @@ static int dns_server_resolve_callback(const char *domain, dns_result_type rtype return 0; } - ttl = _dns_server_get_conf_ttl(ttl); + ttl = _dns_server_get_conf_ttl(request, ttl); if (ttl > dns_conf_rr_ttl_reply_max && dns_conf_rr_ttl_reply_max > 0) { ttl = dns_conf_rr_ttl_reply_max; }