浏览代码

feature: support set ttl, ttl-min, ttl-max to domain.

Nick Peng 2 年之前
父节点
当前提交
499ab1b64f
共有 6 个文件被更改,包括 134 次插入33 次删除
  1. 1 1
      ReadMe.md
  2. 1 1
      ReadMe_en.md
  3. 23 12
      src/dns.c
  4. 61 0
      src/dns_conf.c
  5. 8 0
      src/dns_conf.h
  6. 40 19
      src/dns_server.c

+ 1 - 1
ReadMe.md

@@ -611,7 +611,7 @@ entware|ipkg update<br />ipkg install smartdns|软件源路径:<https://bin.en
 | nftset-timeout | 设置 nftset 超时功能启用  | no | [yes\|no] | nftset-timeout yes |
 | nftset-no-speed | 当测速失败时,将域名结果设置到nftset集合中 | 无 | nftset-no-speed [#4\|#6]:[family#nftable#nftset][,#[4\|6]:[family#nftable#nftset]]] <br />ipv4 地址的 family 只支持 inet 和 ip <br />ipv6 地址的 family 只支持 inet 和 ip6 <br />由于 nft 限制,两种地址只能分开存放于两个 set 中。| nftset-no-speed #4:inet#tab#set4|
 | nftset-debug | 设置 nftset 调试功能启用  | no | [yes\|no] | nftset-debug yes |
-| domain-rules | 设置域名规则 | 无 | domain-rules /domain/ [-rules...]<br />[-c\|-speed-check-mode]:测速模式,参考 speed-check-mode 配置<br />[-a\|-address]:参考 address 配置<br />[-n\|-nameserver]:参考 nameserver 配置<br />[-p\|-ipset]:参考ipset配置<br />[-t\|-nftset]:参考nftset配置<br />[-d\|-dualstack-ip-selection]:参考 dualstack-ip-selection<br /> [-no-serve-expired]:禁用过期缓存<br />[-delete]:删除对应的规则 | domain-rules /www.example.com/ -speed-check-mode none |
+| domain-rules | 设置域名规则 | 无 | domain-rules /domain/ [-rules...]<br />[-c\|-speed-check-mode]:测速模式,参考 speed-check-mode 配置<br />[-a\|-address]:参考 address 配置<br />[-n\|-nameserver]:参考 nameserver 配置<br />[-p\|-ipset]:参考ipset配置<br />[-t\|-nftset]:参考nftset配置<br />[-d\|-dualstack-ip-selection]:参考 dualstack-ip-selection<br /> [-no-serve-expired]:禁用过期缓存<br />[-rr-ttl\|-rr-ttl-min\|-rr-ttl-max]: 参考配置rr-ttl, rr-ttl-min, rr-ttl-max<br />[-delete]:删除对应的规则 | domain-rules /www.example.com/ -speed-check-mode none |
 | domain-set | 设置域名集合 | 无 | domain-set [options...]<br />[-n\|-name]:域名集合名称 <br />[-t\|-type]:域名集合类型,当前仅支持list,格式为域名列表,一行一个域名。<br />[-f\|-file]:域名集合文件路径。<br /> 选项需要配合address, nameserver, ipset, nftset等需要指定域名的地方使用,使用方式为 /domain-set:[name]/| domain-set -name set -type list -file /path/to/list <br /> 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 |

+ 1 - 1
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]]] <br />the valid families are inet and ip for ipv4 addresses while the valid ones are inet and ip6 for ipv6 addresses <br />due to the limitation of nftable <br />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...]<br />[-c\|-speed-check-mode]: set speed check mode,same as parameter speed-check-mode<br />[-a\|-address]: same as  parameter `address` <br />[-n\|-nameserver]: same as parameter `nameserver`<br />[-p\|-ipset]: same as parameter `nftset`<br />[-t\|-nftset]: same as parameter `nftset`<br />[-d\|-dualstack-ip-selection]: same as parameter `dualstack-ip-selection`<br />  [-no-serve-expired]:disable serve expired<br />[-delete]:delete rule|domain-rules /www.example.com/ -speed-check-mode none
+|domain-rules|set domain rules|None|domain-rules /domain/ [-rules...]<br />[-c\|-speed-check-mode]: set speed check mode,same as parameter speed-check-mode<br />[-a\|-address]: same as  parameter `address` <br />[-n\|-nameserver]: same as parameter `nameserver`<br />[-p\|-ipset]: same as parameter `nftset`<br />[-t\|-nftset]: same as parameter `nftset`<br />[-d\|-dualstack-ip-selection]: same as parameter `dualstack-ip-selection`<br />  [-no-serve-expired]:disable serve expired<br />[-rr-ttl\|-rr-ttl-min\|-rr-ttl-max]: same as parameter: rr-ttl, rr-ttl-min, rr-ttl-max<br />[-delete]:delete rule|domain-rules /www.example.com/ -speed-check-mode none
 | domain-set | collection of domains|None| domain-set [options...]<br />[-n\|-name]:name of set <br />[-t\|-type] [list]: set type, only support list, one domain per line <br />[-f\|-file]:file path of domain set<br /> used with address, nameserver, ipset, nftset, example: /domain-set:[name]/ | domain-set -name set -type list -file /path/to/list <br /> 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

+ 23 - 12
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;
 }
 

+ 61 - 0
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;

+ 8 - 0
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];

+ 40 - 19
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 (rr_ttl > 0) {
+		return rr_ttl;
 	}
 
-	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_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;
 			}