Kaynağa Gözat

feature: support HTTPS svcb record.

Nick Peng 1 yıl önce
ebeveyn
işleme
dfc7ebe289
11 değiştirilmiş dosya ile 1449 ekleme ve 88 silme
  1. 4 0
      etc/smartdns/smartdns.conf
  2. 1 1
      src/dns.h
  3. 216 1
      src/dns_conf.c
  4. 29 0
      src/dns_conf.h
  5. 466 86
      src/dns_server.c
  6. 2 0
      src/include/conf.h
  7. 93 0
      src/lib/conf.c
  8. 46 0
      src/util.c
  9. 2 0
      src/util.h
  10. 540 0
      test/cases/test-https.cc
  11. 50 0
      test/cases/test-qtype-soa.cc

+ 4 - 0
etc/smartdns/smartdns.conf

@@ -287,6 +287,10 @@ log-level info
 # srv-record /_ldap._tcp.example.com/ldapserver.example.com,389
 # srv-record /_ldap._tcp.example.com/
 
+# https-record /domain/[target=][,port=][,priority=][,alph=][,ech=][,ipv4hint=][,ipv6hint=]
+# https-record noipv4hint,noipv6hint
+# https-record /www.example.com/ipv4hint=192.168.1.2
+
 # enable DNS64 feature
 # dns64 [ip/subnet]
 # dns64 64:ff9b::/96

+ 1 - 1
src/dns.h

@@ -31,7 +31,7 @@ extern "C" {
 #define DNS_PACKSIZE (512 * 16)
 #define DNS_DEFAULT_PACKET_SIZE 512
 #define DNS_MAX_ALPN_LEN 32
-#define DNS_MAX_ECH_LEN 256
+#define DNS_MAX_ECH_LEN 512
 
 #define DNS_OPT_FLAG_DO 0x8000
 

+ 216 - 1
src/dns_conf.c

@@ -327,6 +327,9 @@ static void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size)
 	case DOMAIN_RULE_CNAME:
 		size = sizeof(struct dns_cname_rule);
 		break;
+	case DOMAIN_RULE_HTTPS:
+		size = sizeof(struct dns_https_record_rule);
+		break;
 	case DOMAIN_RULE_TTL:
 		size = sizeof(struct dns_ttl_rule);
 		break;
@@ -2535,6 +2538,203 @@ errout:
 	return -1;
 }
 
+static int _conf_domain_rule_https_copy_alpn(char *alpn_data, int max_alpn_len, const char *alpn_str)
+{
+	const char *ptr = NULL;
+	int alpn_len = 0;
+	char *alpn_len_ptr = NULL;
+	char *alpn_ptr = NULL;
+	int total_len = 0;
+
+	ptr = alpn_str;
+	alpn_len_ptr = alpn_data;
+	alpn_ptr = alpn_data + 1;
+	total_len++;
+
+	while (*ptr != '\0') {
+		total_len++;
+		if (total_len > max_alpn_len) {
+			return -1;
+		}
+
+		if (*ptr == ',') {
+			*alpn_len_ptr = alpn_len;
+			alpn_len = 0;
+			alpn_len_ptr = alpn_ptr;
+			ptr++;
+			alpn_ptr++;
+			continue;
+		}
+
+		*alpn_ptr = *ptr;
+		alpn_len++;
+		alpn_ptr++;
+		ptr++;
+	}
+
+	*alpn_len_ptr = alpn_len;
+	return total_len;
+}
+
+static int _conf_domain_rule_https_record(const char *domain, const char *host)
+{
+	struct dns_https_record_rule *https_record_rule = NULL;
+	enum domain_rule type = DOMAIN_RULE_HTTPS;
+	char buff[4096];
+	int key_num = 0;
+	char *keys[16];
+	char *value[16];
+	int priority = -1;
+	/*mode_type, 0: alias mode, 1: service mode */
+	int mode_type = 0;
+
+	safe_strncpy(buff, host, sizeof(buff));
+
+	https_record_rule = _new_dns_rule(type);
+	if (https_record_rule == NULL) {
+		goto errout;
+	}
+
+	if (conf_parse_key_values(buff, &key_num, keys, value) != 0) {
+		tlog(TLOG_ERROR, "input format error, don't have key-value.");
+		goto errout;
+	}
+
+	if (key_num < 1) {
+		tlog(TLOG_ERROR, "invalid parameter.");
+		goto errout;
+	}
+
+	for (int i = 0; i < key_num; i++) {
+		const char *key = keys[i];
+		const char *val = value[i];
+		if (strncmp(key, "#", sizeof("#")) == 0) {
+			if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_SOA, 0) != 0) {
+				goto errout;
+			}
+			break;
+		} else if (strncmp(key, "-", sizeof("-")) == 0) {
+			if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_IGN, 0) != 0) {
+				goto errout;
+			}
+		} else if (strncmp(key, "target", sizeof("target")) == 0) {
+			safe_strncpy(https_record_rule->record.target, val, DNS_MAX_CONF_CNAME_LEN);
+			https_record_rule->record.enable = 1;
+		} else if (strncmp(key, "noipv4hint", sizeof("noipv4hint")) == 0) {
+			https_record_rule->filter.no_ipv4hint = 1;
+		} else if (strncmp(key, "noipv6hint", sizeof("noipv6hint")) == 0) {
+			https_record_rule->filter.no_ipv6hint = 1;
+		} else {
+			mode_type = 1;
+			https_record_rule->record.enable = 1;
+			if (strncmp(key, "priority", sizeof("priority")) == 0) {
+				priority = atoi(val);
+			} else if (strncmp(key, "port", sizeof("port")) == 0) {
+				https_record_rule->record.port = atoi(val);
+
+			} else if (strncmp(key, "alpn", sizeof("alpn")) == 0) {
+				int alpn_len = _conf_domain_rule_https_copy_alpn(https_record_rule->record.alpn, DNS_MAX_ALPN_LEN, val);
+				if (alpn_len <= 0) {
+					tlog(TLOG_ERROR, "invalid option value for %s.", key);
+					goto errout;
+				}
+				https_record_rule->record.alpn_len = alpn_len;
+			} else if (strncmp(key, "ech", sizeof("ech")) == 0) {
+				int ech_len = SSL_base64_decode(val, https_record_rule->record.ech, DNS_MAX_ECH_LEN);
+				if (ech_len < 0) {
+					tlog(TLOG_ERROR, "invalid option value for %s.", key);
+					goto errout;
+				}
+				https_record_rule->record.ech_len = ech_len;
+			} else if (strncmp(key, "ipv4hint", sizeof("ipv4hint")) == 0) {
+				int addr_len = DNS_RR_A_LEN;
+				if (get_raw_addr_by_ip(val, https_record_rule->record.ipv4_addr, &addr_len) != 0) {
+					tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val);
+					goto errout;
+				}
+
+				if (addr_len != DNS_RR_A_LEN) {
+					tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val);
+					goto errout;
+				}
+				https_record_rule->record.has_ipv4 = 1;
+			} else if (strncmp(key, "ipv6hint", sizeof("ipv6hint")) == 0) {
+				int addr_len = DNS_RR_AAAA_LEN;
+				if (get_raw_addr_by_ip(val, https_record_rule->record.ipv6_addr, &addr_len) != 0) {
+					tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val);
+					goto errout;
+				}
+
+				if (addr_len != DNS_RR_AAAA_LEN) {
+					tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val);
+					goto errout;
+				}
+				https_record_rule->record.has_ipv6 = 1;
+			} else {
+				tlog(TLOG_WARN, "invalid parameter %s for https-record.", key);
+				continue;
+			}
+		}
+	}
+
+	if (mode_type == 0) {
+		if (priority < 0) {
+			priority = 0;
+		}
+	} else {
+		if (priority < 0) {
+			priority = 1;
+		} else if (priority == 0) {
+			tlog(TLOG_WARN, "invalid priority %d for https-record.", priority);
+			goto errout;
+		}
+	}
+
+	https_record_rule->record.priority = priority;
+
+	if (_config_domain_rule_add(domain, type, https_record_rule) != 0) {
+		goto errout;
+	}
+
+	_dns_rule_put(&https_record_rule->head);
+	https_record_rule = NULL;
+
+	return 0;
+errout:
+	if (https_record_rule) {
+		_dns_rule_put(&https_record_rule->head);
+	}
+
+	return -1;
+}
+
+static int _config_https_record(void *data, int argc, char *argv[])
+{
+	char *value = NULL;
+	char domain[DNS_MAX_CONF_CNAME_LEN];
+	int ret = -1;
+
+	if (argc < 2) {
+		goto errout;
+	}
+
+	value = argv[1];
+	if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {
+		goto errout;
+	}
+
+	ret = _conf_domain_rule_https_record(domain, value);
+	if (ret != 0) {
+		goto errout;
+	}
+
+	return 0;
+
+errout:
+	tlog(TLOG_ERROR, "add https-record %s:%s failed", domain, value);
+	return -1;
+}
+
 static void _config_speed_check_mode_clear(struct dns_domain_check_orders *check_orders)
 {
 	memset(check_orders->orders, 0, sizeof(check_orders->orders));
@@ -4624,6 +4824,7 @@ static int _conf_domain_rules(void *data, int argc, char *argv[])
 		{"speed-check-mode", required_argument, NULL, 'c'},
 		{"response-mode", required_argument, NULL, 'r'},
 		{"address", required_argument, NULL, 'a'},
+		{"https-record", required_argument, NULL, 'h'},
 		{"ipset", required_argument, NULL, 'p'},
 		{"nftset", required_argument, NULL, 't'},
 		{"nameserver", required_argument, NULL, 'n'},
@@ -4678,7 +4879,7 @@ static int _conf_domain_rules(void *data, int argc, char *argv[])
 	optind = 1;
 	optind_last = 1;
 	while (1) {
-		opt = getopt_long_only(argc, argv, "c:a:p:t:n:d:A:r:g:", long_options, NULL);
+		opt = getopt_long_only(argc, argv, "c:a:p:t:n:d:A:r:g:h:", long_options, NULL);
 		if (opt == -1) {
 			break;
 		}
@@ -4723,6 +4924,19 @@ static int _conf_domain_rules(void *data, int argc, char *argv[])
 
 			break;
 		}
+		case 'h': {
+			const char *https_record = optarg;
+			if (https_record == NULL) {
+				goto errout;
+			}
+
+			if (_conf_domain_rule_https_record(domain, https_record) != 0) {
+				tlog(TLOG_ERROR, "add https-record rule failed.");
+				goto errout;
+			}
+
+			break;
+		}
 		case 'p': {
 			const char *ipsetname = optarg;
 			if (ipsetname == NULL) {
@@ -5709,6 +5923,7 @@ static struct config_item _config_item[] = {
 	CONF_CUSTOM("address", _config_address, NULL),
 	CONF_CUSTOM("cname", _config_cname, NULL),
 	CONF_CUSTOM("srv-record", _config_srv_record, NULL),
+	CONF_CUSTOM("https-record", _config_https_record, NULL),
 	CONF_CUSTOM("proxy-server", _config_proxy_server, NULL),
 	CONF_YESNO_FUNC("ipset-timeout", _dns_conf_group_yesno,
 					(void *)offsetof(struct dns_conf_group, ipset_nftset.ipset_timeout_enable)),

+ 29 - 0
src/dns_conf.h

@@ -85,6 +85,7 @@ enum domain_rule {
 	DOMAIN_RULE_CHECKSPEED,
 	DOMAIN_RULE_RESPONSE_MODE,
 	DOMAIN_RULE_CNAME,
+	DOMAIN_RULE_HTTPS,
 	DOMAIN_RULE_TTL,
 	DOMAIN_RULE_MAX,
 };
@@ -136,6 +137,8 @@ typedef enum {
 #define DOMAIN_FLAG_NO_IPALIAS (1 << 18)
 #define DOMAIN_FLAG_GROUP_IGNORE (1 << 19)
 #define DOMAIN_FLAG_ENABLE_CACHE (1 << 20)
+#define DOMAIN_FLAG_ADDR_HTTPS_SOA (1 << 21)
+#define DOMAIN_FLAG_ADDR_HTTPS_IGN (1 << 22)
 
 #define IP_RULE_FLAG_BLACKLIST (1 << 0)
 #define IP_RULE_FLAG_WHITELIST (1 << 1)
@@ -288,6 +291,32 @@ struct dns_response_mode_rule {
 	enum response_mode_type mode;
 };
 
+struct dns_https_record {
+	int enable;
+	char target[DNS_MAX_CNAME_LEN];
+	int priority;
+	char alpn[DNS_MAX_ALPN_LEN];
+	int alpn_len;
+	int port;
+	unsigned char ech[DNS_MAX_ECH_LEN];
+	int ech_len;
+	int has_ipv4;
+	unsigned char ipv4_addr[DNS_RR_A_LEN];
+	int has_ipv6;
+	unsigned char ipv6_addr[DNS_RR_AAAA_LEN];
+};
+
+struct dns_https_filter {
+	int no_ipv4hint;
+	int no_ipv6hint;
+};
+
+struct dns_https_record_rule {
+	struct dns_rule head;
+	struct dns_https_record record;
+	struct dns_https_filter filter;
+};
+
 struct dns_group_table {
 	DECLARE_HASHTABLE(group, 8);
 };

+ 466 - 86
src/dns_server.c

@@ -266,6 +266,18 @@ struct dns_request_domain_rule {
 typedef DNS_CHILD_POST_RESULT (*child_request_callback)(struct dns_request *request, struct dns_request *child_request,
 														int is_first_resp);
 
+struct dns_request_https {
+	char domain[DNS_MAX_CNAME_LEN];
+	char target[DNS_MAX_CNAME_LEN];
+	int ttl;
+	int priority;
+	char alpn[DNS_MAX_ALPN_LEN];
+	int alpn_len;
+	int port;
+	char ech[DNS_MAX_ECH_LEN];
+	int ech_len;
+};
+
 struct dns_request {
 	atomic_t refcnt;
 
@@ -303,6 +315,8 @@ struct dns_request {
 	struct dns_opt_ecs ecs;
 	int edns0_do;
 
+	struct dns_request_https *https_svcb;
+
 	dns_result_callback result_callback;
 	void *user_ptr;
 
@@ -319,6 +333,7 @@ struct dns_request {
 	int ping_time;
 	int ip_ttl;
 	unsigned char ip_addr[DNS_RR_AAAA_LEN];
+	int ip_addr_type;
 
 	struct dns_soa soa;
 	int has_soa;
@@ -700,6 +715,16 @@ static int _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type
 				return 0;
 			}
 			break;
+		case DNS_T_HTTPS:
+			if (flags & DOMAIN_FLAG_ADDR_HTTPS_SOA) {
+				return 1;
+			}
+
+			if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {
+				request->skip_qtype_soa = 1;
+				return 0;
+			}
+			break;
 		default:
 			break;
 		}
@@ -1072,6 +1097,64 @@ static int _dns_server_add_srv(struct dns_server_post_context *context)
 	return 0;
 }
 
+static int _dns_add_rrs_HTTPS(struct dns_server_post_context *context)
+{
+	struct dns_request *request = context->request;
+	struct dns_request_https *https_svcb = request->https_svcb;
+	int ret = 0;
+	struct dns_rr_nested param;
+
+	if (https_svcb == NULL || request->qtype != DNS_T_HTTPS) {
+		return 0;
+	}
+
+	ret = dns_add_HTTPS_start(&param, context->packet, DNS_RRS_AN, https_svcb->domain, https_svcb->ttl,
+							  https_svcb->priority, https_svcb->target);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (https_svcb->alpn[0] != '\0' && https_svcb->alpn_len > 0) {
+		ret = dns_HTTPS_add_alpn(&param, https_svcb->alpn, https_svcb->alpn_len);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
+	if (https_svcb->port != 0) {
+		ret = dns_HTTPS_add_port(&param, https_svcb->port);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
+	if (request->has_ip) {
+		unsigned char *addr[1];
+		addr[0] = request->ip_addr;
+		if (request->ip_addr_type == DNS_T_A) {
+			ret = dns_HTTPS_add_ipv4hint(&param, addr, 1);
+		}
+	}
+
+	if (https_svcb->ech_len > 0) {
+		ret = dns_HTTPS_add_ech(&param, https_svcb->ech, https_svcb->ech_len);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
+	if (request->has_ip) {
+		unsigned char *addr[1];
+		addr[0] = request->ip_addr;
+		if (request->ip_addr_type == DNS_T_AAAA) {
+			ret = dns_HTTPS_add_ipv6hint(&param, addr, 1);
+		}
+	}
+
+	dns_add_HTTPS_end(&param);
+	return 0;
+}
+
 static int _dns_add_rrs(struct dns_server_post_context *context)
 {
 	struct dns_request *request = context->request;
@@ -1089,6 +1172,10 @@ static int _dns_add_rrs(struct dns_server_post_context *context)
 		domain = request->cname;
 	}
 
+	if (request->https_svcb != NULL) {
+		ret = _dns_add_rrs_HTTPS(context);
+	}
+
 	/* add A record */
 	if (request->has_ip && context->do_force_soa == 0) {
 		_dns_server_context_add_ip(context, request->ip_addr);
@@ -1561,7 +1648,7 @@ static int _dns_server_request_update_cache(struct dns_request *request, int spe
 	int ttl = 0;
 	int ret = -1;
 
-	if (qtype != DNS_T_A && qtype != DNS_T_AAAA) {
+	if (qtype != DNS_T_A && qtype != DNS_T_AAAA && qtype != DNS_T_HTTPS) {
 		goto errout;
 	}
 
@@ -1897,7 +1984,7 @@ static int _dns_cache_reply_packet(struct dns_server_post_context *context)
 		return _dns_cache_packet(context);
 	}
 
-	if (context->qtype != DNS_T_AAAA && context->qtype != DNS_T_A) {
+	if (context->qtype != DNS_T_AAAA && context->qtype != DNS_T_A && context->qtype != DNS_T_HTTPS) {
 		return _dns_cache_specify_packet(context);
 	}
 
@@ -1920,6 +2007,47 @@ static int _dns_cache_reply_packet(struct dns_server_post_context *context)
 	return 0;
 }
 
+static void _dns_server_add_ipset_nftset(struct dns_request *request, struct dns_ipset_rule *ipset_rule,
+										 struct dns_nftset_rule *nftset_rule, const unsigned char addr[], int addr_len,
+										 int ipset_timeout_value, int nftset_timeout_value)
+{
+	if (ipset_rule != NULL) {
+		/* add IPV4 to ipset */
+		if (addr_len == DNS_RR_A_LEN) {
+			tlog(TLOG_DEBUG, "IPSET-MATCH: domain: %s, ipset: %s, IP: %d.%d.%d.%d", request->domain,
+				 ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3]);
+			ipset_add(ipset_rule->ipsetname, addr, DNS_RR_A_LEN, ipset_timeout_value);
+		} else if (addr_len == DNS_RR_AAAA_LEN) {
+			tlog(TLOG_DEBUG,
+				 "IPSET-MATCH: domain: %s, ipset: %s, IP: "
+				 "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
+				 request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6],
+				 addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);
+			ipset_add(ipset_rule->ipsetname, addr, DNS_RR_AAAA_LEN, ipset_timeout_value);
+		}
+	}
+
+	if (nftset_rule != NULL) {
+		/* add IPV4 to ipset */
+		if (addr_len == DNS_RR_A_LEN) {
+			tlog(TLOG_DEBUG, "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: %d.%d.%d.%d", request->domain,
+				 nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0], addr[1], addr[2],
+				 addr[3]);
+			nftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr, DNS_RR_A_LEN,
+					   nftset_timeout_value);
+		} else if (addr_len == DNS_RR_AAAA_LEN) {
+			tlog(TLOG_DEBUG,
+				 "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: "
+				 "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
+				 request->domain, nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0],
+				 addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11],
+				 addr[12], addr[13], addr[14], addr[15]);
+			nftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr,
+					   DNS_RR_AAAA_LEN, nftset_timeout_value);
+		}
+	}
+}
+
 static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context *context)
 {
 	int ttl = 0;
@@ -2048,21 +2176,8 @@ static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context
 				dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
 
 				rule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule;
-				if (rule != NULL) {
-					/* add IPV4 to ipset */
-					tlog(TLOG_DEBUG, "IPSET-MATCH: domain: %s, ipset: %s, IP: %d.%d.%d.%d", request->domain,
-						 rule->ipsetname, addr[0], addr[1], addr[2], addr[3]);
-					ipset_add(rule->ipsetname, addr, DNS_RR_A_LEN, ipset_timeout_value);
-				}
-
-				if (nftset_ip != NULL) {
-					/* add IPV4 to ipset */
-					tlog(TLOG_DEBUG, "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: %d.%d.%d.%d", request->domain,
-						 nftset_ip->familyname, nftset_ip->nfttablename, nftset_ip->nftsetname, addr[0], addr[1],
-						 addr[2], addr[3]);
-					nftset_add(nftset_ip->familyname, nftset_ip->nfttablename, nftset_ip->nftsetname, addr,
-							   DNS_RR_A_LEN, nftset_timeout_value);
-				}
+				_dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN, ipset_timeout_value,
+											 nftset_timeout_value);
 			} break;
 			case DNS_T_AAAA: {
 				unsigned char addr[16];
@@ -2073,26 +2188,44 @@ static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context
 				dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
 
 				rule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule;
-				if (rule != NULL) {
-					tlog(TLOG_DEBUG,
-						 "IPSET-MATCH: domain: %s, ipset: %s, IP: "
-						 "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
-						 request->domain, rule->ipsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5],
-						 addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14],
-						 addr[15]);
-					ipset_add(rule->ipsetname, addr, DNS_RR_AAAA_LEN, ipset_timeout_value);
+				_dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN, ipset_timeout_value,
+											 nftset_timeout_value);
+			} break;
+			case DNS_T_HTTPS: {
+				char target[DNS_MAX_CNAME_LEN] = {0};
+				struct dns_https_param *p = NULL;
+				int priority = 0;
+
+				int ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
+													  DNS_MAX_CNAME_LEN);
+				if (ret != 0) {
+					tlog(TLOG_WARN, "get HTTPS svcparm failed");
+					return -1;
 				}
 
-				if (nftset_ip6 != NULL) {
-					/* add IPV6 to ipset */
-					tlog(TLOG_DEBUG,
-						 "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: "
-						 "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
-						 request->domain, nftset_ip6->familyname, nftset_ip6->nfttablename, nftset_ip6->nftsetname,
-						 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9],
-						 addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);
-					nftset_add(nftset_ip6->familyname, nftset_ip6->nfttablename, nftset_ip6->nftsetname, addr,
-							   DNS_RR_AAAA_LEN, nftset_timeout_value);
+				for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) {
+					switch (p->key) {
+					case DNS_HTTPS_T_IPV4HINT: {
+						unsigned char *addr;
+						for (int k = 0; k < p->len / 4; k++) {
+							addr = p->value + k * 4;
+							rule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule;
+							_dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN,
+														 ipset_timeout_value, nftset_timeout_value);
+						}
+					} break;
+					case DNS_HTTPS_T_IPV6HINT: {
+						unsigned char *addr;
+						for (int k = 0; k < p->len / 16; k++) {
+							addr = p->value + k * 16;
+							rule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule;
+							_dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN,
+														 ipset_timeout_value, nftset_timeout_value);
+						}
+					} break;
+					default:
+						break;
+					}
 				}
 			} break;
 			default:
@@ -2620,7 +2753,8 @@ static void _dns_server_select_possible_ipaddress(struct dns_request *request)
 	}
 
 	/* Return the most likely correct IP address */
-	/* Returns the IP with the most hits, or the last returned record is considered to be the most likely correct. */
+	/* Returns the IP with the most hits, or the last returned record is considered to be the most likely
+	 * correct. */
 	pthread_mutex_lock(&request->ip_map_lock);
 	hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)
 	{
@@ -2685,6 +2819,9 @@ static void _dns_server_delete_request(struct dns_request *request)
 		_dns_server_conn_release(request->conn);
 	}
 	pthread_mutex_destroy(&request->ip_map_lock);
+	if (request->https_svcb) {
+		free(request->https_svcb);
+	}
 	memset(request, 0, sizeof(*request));
 	free(request);
 	atomic_dec(&server.request_num);
@@ -3014,6 +3151,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 
 		if (request->ping_time > rtt || request->ping_time == -1) {
 			memcpy(request->ip_addr, &addr_in->sin_addr.s_addr, 4);
+			request->ip_addr_type = DNS_T_A;
 			request->ping_time = rtt;
 			request->has_cname = 0;
 			request->has_ip = 1;
@@ -3031,7 +3169,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 			}
 		}
 
-		if (request->qtype == DNS_T_A) {
+		if (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) {
 			request->has_ping_result = 1;
 		}
 	} break;
@@ -3049,6 +3187,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 				request->has_cname = 0;
 				request->has_ip = 1;
 				memcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr + 12, 4);
+				request->ip_addr_type = DNS_T_A;
 				if (addr_map && addr_map->cname[0] != 0) {
 					request->has_cname = 1;
 					safe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN);
@@ -3057,7 +3196,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 				}
 			}
 
-			if (request->qtype == DNS_T_A) {
+			if (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) {
 				request->has_ping_result = 1;
 			}
 		} else {
@@ -3071,6 +3210,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 				request->has_cname = 0;
 				request->has_ip = 1;
 				memcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr, 16);
+				request->ip_addr_type = DNS_T_AAAA;
 				if (addr_map && addr_map->cname[0] != 0) {
 					request->has_cname = 1;
 					safe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN);
@@ -3079,7 +3219,7 @@ static void _dns_server_ping_result(struct ping_host_struct *ping_host, const ch
 				}
 			}
 
-			if (request->qtype == DNS_T_AAAA) {
+			if (request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) {
 				request->has_ping_result = 1;
 			}
 		}
@@ -3556,35 +3696,18 @@ static int _dns_server_is_adblock_ipv6(const unsigned char addr[16])
 	return -1;
 }
 
-static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request *request, const char *domain,
-										char *cname, unsigned int result_flag)
+static int _dns_server_process_answer_A_IP(struct dns_request *request, char *cname, unsigned char addr[4], int ttl,
+										   unsigned int result_flag)
 {
-	int ttl = 0;
+	char ip[DNS_MAX_CNAME_LEN] = {0};
 	int ip_check_result = 0;
-	unsigned char addr[4];
 	unsigned char *paddrs[MAX_IP_NUM];
 	int paddr_num = 0;
-	char name[DNS_MAX_CNAME_LEN] = {0};
-	char ip[DNS_MAX_CNAME_LEN] = {0};
 	struct dns_iplist_ip_addresses *alias = NULL;
 
-	if (request->qtype != DNS_T_A) {
-		/* ignore non-matched query type */
-		return 0;
-	}
-
-	/* get A result */
-	dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
 	paddrs[paddr_num] = addr;
 	paddr_num = 1;
 
-	tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %d.%d.%d.%d", name, ttl, addr[0], addr[1], addr[2], addr[3]);
-
-	/* if domain is not match */
-	if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {
-		return -1;
-	}
-
 	/* ip rule check */
 	ip_check_result = _dns_server_process_ip_rule(request, addr, 4, DNS_T_A, result_flag, &alias);
 	if (ip_check_result == 0) {
@@ -3604,6 +3727,7 @@ static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request
 		unsigned char *paddr = paddrs[i];
 		if (atomic_read(&request->ip_map_num) == 0) {
 			request->has_ip = 1;
+			request->ip_addr_type = DNS_T_A;
 			memcpy(request->ip_addr, paddr, DNS_RR_A_LEN);
 			request->ip_ttl = _dns_server_get_conf_ttl(request, ttl);
 			if (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) {
@@ -3642,36 +3766,18 @@ static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request
 	return 0;
 }
 
-static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_request *request, const char *domain,
-										   char *cname, unsigned int result_flag)
+static int _dns_server_process_answer_AAAA_IP(struct dns_request *request, char *cname, unsigned char addr[16], int ttl,
+											  unsigned int result_flag)
 {
-	unsigned char addr[16];
-	unsigned char *paddrs[MAX_IP_NUM];
-	int paddr_num = 0;
-	char name[DNS_MAX_CNAME_LEN] = {0};
 	char ip[DNS_MAX_CNAME_LEN] = {0};
-	int ttl = 0;
 	int ip_check_result = 0;
+	unsigned char *paddrs[MAX_IP_NUM];
 	struct dns_iplist_ip_addresses *alias = NULL;
+	int paddr_num = 0;
 
-	if (request->qtype != DNS_T_AAAA) {
-		/* ignore non-matched query type */
-		return -1;
-	}
-
-	dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
 	paddrs[paddr_num] = addr;
 	paddr_num = 1;
 
-	tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
-		 name, ttl, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
-		 addr[11], addr[12], addr[13], addr[14], addr[15]);
-
-	/* if domain is not match */
-	if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {
-		return -1;
-	}
-
 	ip_check_result = _dns_server_process_ip_rule(request, addr, 16, DNS_T_AAAA, result_flag, &alias);
 	if (ip_check_result == 0) {
 		/* match */
@@ -3690,6 +3796,7 @@ static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_reque
 		unsigned char *paddr = paddrs[i];
 		if (atomic_read(&request->ip_map_num) == 0) {
 			request->has_ip = 1;
+			request->ip_addr_type = DNS_T_AAAA;
 			memcpy(request->ip_addr, paddr, DNS_RR_AAAA_LEN);
 			request->ip_ttl = _dns_server_get_conf_ttl(request, ttl);
 			if (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) {
@@ -3730,6 +3837,185 @@ static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_reque
 	return 0;
 }
 
+static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request *request, const char *domain,
+										char *cname, unsigned int result_flag)
+{
+	int ttl = 0;
+	unsigned char addr[4];
+	char name[DNS_MAX_CNAME_LEN] = {0};
+
+	if (request->qtype != DNS_T_A) {
+		return -1;
+	}
+
+	/* get A result */
+	dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
+
+	tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %d.%d.%d.%d", name, ttl, addr[0], addr[1], addr[2], addr[3]);
+
+	/* if domain is not match */
+	if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {
+		return -1;
+	}
+
+	_dns_server_request_get(request);
+	int ret = _dns_server_process_answer_A_IP(request, cname, addr, ttl, result_flag);
+	_dns_server_request_release(request);
+
+	return ret;
+}
+
+static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_request *request, const char *domain,
+										   char *cname, unsigned int result_flag)
+{
+	unsigned char addr[16];
+
+	char name[DNS_MAX_CNAME_LEN] = {0};
+
+	int ttl = 0;
+
+	if (request->qtype != DNS_T_AAAA) {
+		/* ignore non-matched query type */
+		return -1;
+	}
+
+	dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);
+
+	tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
+		 name, ttl, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
+		 addr[11], addr[12], addr[13], addr[14], addr[15]);
+
+	/* if domain is not match */
+	if (strncmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {
+		return -1;
+	}
+
+	_dns_server_request_get(request);
+	int ret = _dns_server_process_answer_AAAA_IP(request, cname, addr, ttl, result_flag);
+	_dns_server_request_release(request);
+
+	return ret;
+}
+
+static int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_request *request, const char *domain,
+											char *cname, unsigned int result_flag)
+{
+	int ttl = 0;
+	int ret = -1;
+	char name[DNS_MAX_CNAME_LEN] = {0};
+	char target[DNS_MAX_CNAME_LEN] = {0};
+	struct dns_https_param *p = NULL;
+	int priority = 0;
+	struct dns_request_https *https_svcb;
+	int no_ipv4 = 0;
+	int no_ipv6 = 0;
+	struct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS);
+	if (https_record_rule) {
+		if (https_record_rule->filter.no_ipv4hint) {
+			no_ipv4 = 1;
+		}
+
+		if (https_record_rule->filter.no_ipv6hint) {
+			no_ipv6 = 1;
+		}
+	}
+
+	ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
+	if (ret != 0) {
+		tlog(TLOG_WARN, "get HTTPS svcparm failed");
+		return -1;
+	}
+
+	https_svcb = request->https_svcb;
+	if (https_svcb == 0) {
+		/* ignore non-matched query type */
+		tlog(TLOG_WARN, "https svcb not set");
+		return -1;
+	}
+
+	tlog(TLOG_DEBUG, "domain: %s HTTPS: %s TTL: %d priority: %d", name, target, ttl, priority);
+	https_svcb->ttl = ttl;
+	https_svcb->priority = priority;
+	safe_strncpy(https_svcb->target, target, sizeof(https_svcb->target));
+	safe_strncpy(https_svcb->domain, name, sizeof(https_svcb->domain));
+	request->ip_ttl = ttl;
+
+	_dns_server_request_get(request);
+	for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) {
+		switch (p->key) {
+		case DNS_HTTPS_T_MANDATORY: {
+		} break;
+		case DNS_HTTPS_T_ALPN: {
+			memcpy(https_svcb->alpn, p->value, sizeof(https_svcb->alpn));
+			https_svcb->alpn_len = p->len;
+		} break;
+		case DNS_HTTPS_T_NO_DEFAULT_ALPN: {
+		} break;
+		case DNS_HTTPS_T_PORT: {
+			int port = *(unsigned short *)(p->value);
+			https_svcb->port = ntohs(port);
+		} break;
+		case DNS_HTTPS_T_IPV4HINT: {
+			struct dns_rule_address_IPV4 *address_ipv4 = NULL;
+			if (_dns_server_is_return_soa_qtype(request, DNS_T_A) || no_ipv4 == 1) {
+				break;
+			}
+
+			if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {
+				break;
+			}
+
+			address_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4);
+			if (address_ipv4 != NULL) {
+				memcpy(request->ip_addr, address_ipv4->ipv4_addr, DNS_RR_A_LEN);
+				request->has_ip = 1;
+				request->ip_addr_type = DNS_T_A;
+				break;
+			}
+
+			for (int k = 0; k < p->len / 4; k++) {
+				_dns_server_process_answer_A_IP(request, cname, p->value + k * 4, ttl, result_flag);
+			}
+		} break;
+		case DNS_HTTPS_T_ECH: {
+			if (p->len > sizeof(https_svcb->ech)) {
+				tlog(TLOG_WARN, "ech too long");
+				break;
+			}
+			memcpy(https_svcb->ech, p->value, p->len);
+			https_svcb->ech_len = p->len;
+		} break;
+		case DNS_HTTPS_T_IPV6HINT: {
+			struct dns_rule_address_IPV6 *address_ipv6 = NULL;
+
+			if (_dns_server_is_return_soa_qtype(request, DNS_T_AAAA) || no_ipv6 == 1) {
+				break;
+			}
+
+			if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {
+				break;
+			}
+
+			address_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6);
+			if (address_ipv6 != NULL) {
+				memcpy(request->ip_addr, address_ipv6->ipv6_addr, DNS_RR_AAAA_LEN);
+				request->has_ip = 1;
+				request->ip_addr_type = DNS_T_AAAA;
+				break;
+			}
+
+			for (int k = 0; k < p->len / 16; k++) {
+				_dns_server_process_answer_AAAA_IP(request, cname, p->value + k * 16, ttl, result_flag);
+			}
+		} break;
+		}
+	}
+
+	_dns_server_request_release(request);
+
+	return 0;
+}
+
 static int _dns_server_process_answer(struct dns_request *request, const char *domain, struct dns_packet *packet,
 									  unsigned int result_flag)
 {
@@ -3805,6 +4091,15 @@ static int _dns_server_process_answer(struct dns_request *request, const char *d
 				request->ttl_cname = _dns_server_get_conf_ttl(request, ttl);
 				tlog(TLOG_DEBUG, "name: %s ttl: %d cname: %s\n", domain_name, ttl, cname);
 			} break;
+			case DNS_T_HTTPS: {
+				ret = _dns_server_process_answer_HTTPS(rrs, request, domain, cname, result_flag);
+				if (ret == -1) {
+					break;
+				} else if (ret == -2) {
+					continue;
+				}
+				request->rcode = packet->head.rcode;
+			} break;
 			case DNS_T_SOA: {
 				/* if DNS64 enabled, skip check SOA. */
 				if (_dns_server_is_dns64_request(request)) {
@@ -3817,7 +4112,8 @@ static int _dns_server_process_answer(struct dns_request *request, const char *d
 				}
 				dns_get_SOA(rrs, name, 128, &ttl, &request->soa);
 				tlog(TLOG_DEBUG,
-					 "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, expire: "
+					 "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, "
+					 "expire: "
 					 "%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);
@@ -3938,7 +4234,8 @@ static int _dns_server_passthrough_rule_check(struct dns_request *request, const
 				}
 
 				tlog(TLOG_DEBUG,
-					 "domain: %s TTL: %d IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
+					 "domain: %s TTL: %d IP: "
+					 "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
 					 name, ttl_tmp, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8],
 					 addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);
 
@@ -4098,7 +4395,8 @@ static int _dns_server_get_answer(struct dns_server_post_context *context)
 				}
 				dns_get_SOA(rrs, name, 128, &ttl, &request->soa);
 				tlog(TLOG_DEBUG,
-					 "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, expire: "
+					 "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, "
+					 "expire: "
 					 "%d, minimum: %d",
 					 request->domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial,
 					 request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);
@@ -4920,6 +5218,10 @@ static void _dns_server_update_rule_by_flags(struct dns_request_domain_rule *req
 		request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;
 	}
 
+	if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {
+		request_domain_rule->rules[DOMAIN_RULE_HTTPS] = NULL;
+	}
+
 	if (flags & DOMAIN_FLAG_IPSET_IGN) {
 		request_domain_rule->rules[DOMAIN_RULE_IPSET] = NULL;
 	}
@@ -5200,6 +5502,28 @@ static int _dns_server_pre_process_rule_flags(struct dns_request *request)
 			request->dualstack_selection = 0;
 		}
 		break;
+	case DNS_T_HTTPS:
+		if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {
+			/* ignore this domain for A request */
+			goto skip_soa_out;
+		}
+
+		if (_dns_server_is_return_soa(request)) {
+			/* if HTTPS exists, return SOA with NOERROR*/
+			if (request->domain_rule.rules[DOMAIN_RULE_HTTPS] != NULL) {
+				goto soa;
+			}
+
+			if (_dns_server_is_return_soa_qtype(request, DNS_T_A) &&
+				_dns_server_is_return_soa_qtype(request, DNS_T_AAAA)) {
+				/* return SOA for HTTPS request */
+				rcode = DNS_RC_NXDOMAIN;
+				goto soa;
+			}
+		}
+
+		goto out;
+		break;
 	default:
 		goto out;
 		break;
@@ -5703,6 +6027,55 @@ errout:
 	return -1;
 }
 
+static int _dns_server_process_https_svcb(struct dns_request *request)
+{
+	struct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS);
+
+	if (request->qtype != DNS_T_HTTPS) {
+		return 0;
+	}
+
+	if (request->https_svcb != NULL) {
+		return 0;
+	}
+
+	request->https_svcb = malloc(sizeof(*request->https_svcb));
+	if (request->https_svcb == NULL) {
+		return -1;
+	}
+	memset(request->https_svcb, 0, sizeof(*request->https_svcb));
+
+	if (https_record_rule == NULL) {
+		return 0;
+	}
+
+	if (https_record_rule->record.enable == 0) {
+		return 0;
+	}
+
+	safe_strncpy(request->https_svcb->domain, request->domain, sizeof(request->https_svcb->domain));
+	safe_strncpy(request->https_svcb->target, https_record_rule->record.target, sizeof(request->https_svcb->target));
+	request->https_svcb->priority = https_record_rule->record.priority;
+	request->https_svcb->port = https_record_rule->record.port;
+	memcpy(request->https_svcb->ech, https_record_rule->record.ech, https_record_rule->record.ech_len);
+	request->https_svcb->ech_len = https_record_rule->record.ech_len;
+	memcpy(request->https_svcb->alpn, https_record_rule->record.alpn, sizeof(request->https_svcb->alpn));
+	request->https_svcb->alpn_len = https_record_rule->record.alpn_len;
+	if (https_record_rule->record.has_ipv4) {
+		memcpy(request->ip_addr, https_record_rule->record.ipv4_addr, DNS_RR_A_LEN);
+		request->ip_addr_type = DNS_T_A;
+		request->has_ip = 1;
+	} else if (https_record_rule->record.has_ipv6) {
+		memcpy(request->ip_addr, https_record_rule->record.ipv6_addr, DNS_RR_AAAA_LEN);
+		request->ip_addr_type = DNS_T_AAAA;
+		request->has_ip = 1;
+	}
+
+	request->rcode = DNS_RC_NOERROR;
+
+	return -1;
+}
+
 static int _dns_server_qtype_soa(struct dns_request *request)
 {
 	if (request->skip_qtype_soa || request->conf->soa_table == NULL) {
@@ -6104,6 +6477,7 @@ static int _dns_server_process_special_query(struct dns_request *request)
 			/* pass to upstream server */
 			request->passthrough = 1;
 		}
+	case DNS_T_HTTPS:
 		break;
 	case DNS_T_SVCB:
 		ret = _dns_server_process_svcb(request);
@@ -6163,7 +6537,8 @@ static void _dns_server_check_set_passthrough(struct dns_request *request)
 		request->dualstack_selection = 0;
 	}
 
-	if (request->passthrough == 1 && (request->qtype == DNS_T_A || request->qtype == DNS_T_AAAA) &&
+	if (request->passthrough == 1 &&
+		(request->qtype == DNS_T_A || request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) &&
 		request->edns0_do == 0) {
 		request->passthrough = 2;
 	}
@@ -6462,6 +6837,10 @@ static int _dns_server_do_query(struct dns_request *request, int skip_notify_eve
 		goto clean_exit;
 	}
 
+	if (_dns_server_process_https_svcb(request) != 0) {
+		goto clean_exit;
+	}
+
 	if (_dns_server_process_smartdns_domain(request) == 0) {
 		goto clean_exit;
 	}
@@ -6700,7 +7079,8 @@ static int _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *in
 	}
 
 	tlog(TLOG_DEBUG,
-		 "request qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, ra = "
+		 "request qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, "
+		 "ra = "
 		 "%d, rcode = %d\n",
 		 packet->head.qdcount, packet->head.ancount, packet->head.nscount, packet->head.nrcount, inpacket_len,
 		 packet->head.id, packet->head.tc, packet->head.rd, packet->head.ra, packet->head.rcode);

+ 2 - 0
src/include/conf.h

@@ -206,6 +206,8 @@ extern int conf_enum(const char *item, void *data, int argc, char *argv[]);
 
 typedef int(conf_error_handler)(const char *file, int lineno, int ret);
 
+int conf_parse_key_values(char *line, int *key_num, char **keys, char **values);
+
 int load_conf(const char *file, struct config_item items[], conf_error_handler handler);
 
 void load_exit(void);

+ 93 - 0
src/lib/conf.c

@@ -296,6 +296,98 @@ void conf_getopt_reset(void)
 	optopt = 0;
 }
 
+int conf_parse_key_values(char *line, int *key_num, char **keys, char **values)
+{
+	int count = 0;
+	char *ptr = line;
+	char *key = NULL;
+	char *value = NULL;
+	char *field_start = NULL;
+	int filed_stage = 0;
+	int inquote = 0;
+	int end = 0;
+
+	if (line == NULL || key_num == NULL || keys == NULL || values == NULL) {
+		return -1;
+	}
+
+	while (1) {
+		if (*ptr == '\'' || *ptr == '"') {
+			if (inquote == 0) {
+				inquote = *ptr;
+				ptr++;
+				continue;
+			} else if (inquote == *ptr) {
+				inquote = 0;
+				*ptr = '\0';
+			}
+		}
+
+		if (field_start == NULL) {
+			field_start = ptr;
+		}
+
+		if (inquote != 0) {
+			ptr++;
+			continue;
+		}
+
+		if (*ptr == ',' || *ptr == '=' || *ptr == '\0') {
+			if (filed_stage == 0) {
+				key = field_start;
+				if (*key == '\0' || *key == ',') {
+					field_start = NULL;
+					if (end == 1) {
+						break;
+					}
+					ptr++;
+					continue;
+				}
+				value = ptr;
+				filed_stage = 1;
+				keys[count] = key;
+				values[count] = value;
+				if (*ptr == '\0' || *ptr == ',') {
+					count++;
+					key = NULL;
+					value = NULL;
+					filed_stage = 0;
+				}
+				*ptr = '\0';
+			} else if (filed_stage == 1) {
+				value = field_start;
+				if (*ptr == '=') {
+					goto errout;
+				}
+				filed_stage = 0;
+				keys[count] = key;
+				values[count] = value;
+				count++;
+				*ptr = '\0';
+				key = NULL;
+				value = NULL;
+			}
+
+			field_start = NULL;
+		}
+
+		if (end == 1) {
+			break;
+		}
+
+		ptr++;
+		if (*ptr == '\0') {
+			end = 1;
+		}
+	}
+
+	*key_num = count;
+
+	return 0;
+errout:
+	return -1;
+}
+
 static int conf_parse_args(char *key, char *value, int *argc, char **argv)
 {
 	char *start = NULL;
@@ -313,6 +405,7 @@ static int conf_parse_args(char *key, char *value, int *argc, char **argv)
 				*(tmp - 1) = *tmp;
 				tmp++;
 			}
+			*(tmp - 1) = '\0';
 			ptr++;
 			continue;
 		}

+ 46 - 0
src/util.c

@@ -279,6 +279,52 @@ errout:
 	return -1;
 }
 
+int get_raw_addr_by_ip(const char *ip, unsigned char *raw_addr, int *raw_addr_len)
+{
+	struct sockaddr_storage addr;
+	socklen_t addr_len = sizeof(addr);
+
+	if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {
+		goto errout;
+	}
+
+	switch (addr.ss_family) {
+	case AF_INET: {
+		struct sockaddr_in *addr_in = NULL;
+		addr_in = (struct sockaddr_in *)&addr;
+		if (*raw_addr_len < DNS_RR_A_LEN) {
+			goto errout;
+		}
+		memcpy(raw_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);
+		*raw_addr_len = DNS_RR_A_LEN;
+	} break;
+	case AF_INET6: {
+		struct sockaddr_in6 *addr_in6 = NULL;
+		addr_in6 = (struct sockaddr_in6 *)&addr;
+		if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
+			if (*raw_addr_len < DNS_RR_A_LEN) {
+				goto errout;
+			}
+			memcpy(raw_addr, addr_in6->sin6_addr.s6_addr + 12, DNS_RR_A_LEN);
+			*raw_addr_len = DNS_RR_A_LEN;
+		} else {
+			if (*raw_addr_len < DNS_RR_AAAA_LEN) {
+				goto errout;
+			}
+			memcpy(raw_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);
+			*raw_addr_len = DNS_RR_AAAA_LEN;
+		}
+	} break;
+	default:
+		goto errout;
+		break;
+	}
+
+	return 0;
+errout:
+	return -1;
+}
+
 int getsocket_inet(int fd, struct sockaddr *addr, socklen_t *addr_len)
 {
 	struct sockaddr_storage addr_store;

+ 2 - 0
src/util.h

@@ -68,6 +68,8 @@ int is_private_addr(const unsigned char *addr, int addr_len);
 
 int getaddr_by_host(const char *host, struct sockaddr *addr, socklen_t *addr_len);
 
+int get_raw_addr_by_ip(const char *ip, unsigned char *raw_addr, int *raw_addr_len);
+
 int getsocket_inet(int fd, struct sockaddr *addr, socklen_t *addr_len);
 
 int fill_sockaddr_by_ip(unsigned char *ip, int ip_len, int port, struct sockaddr *addr, socklen_t *addr_len);

+ 540 - 0
test/cases/test-https.cc

@@ -0,0 +1,540 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "client.h"
+#include "dns.h"
+#include "include/utils.h"
+#include "server.h"
+#include "util.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+class HTTPS : public ::testing::Test
+{
+  protected:
+	virtual void SetUp() {}
+	virtual void TearDown() {}
+};
+
+TEST_F(HTTPS, ipv4_speed_prefer)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10);
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=");
+}
+
+TEST_F(HTTPS, ipv6_speed_prefer)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "102:304:506:708:90a:b0c:d0e:f10", 60, 10);
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(),
+			  "1 b.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10");
+}
+
+TEST_F(HTTPS, ipv4_SOA)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+address /a.com/#4
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 61053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	auto result_check = client.GetAnswer()[0].GetData();
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(),
+			  "1 a.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10");
+}
+
+TEST_F(HTTPS, ipv6_SOA)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+address /a.com/#6
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 61053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	auto result_check = client.GetAnswer()[0].GetData();
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=");
+}
+
+TEST_F(HTTPS, UPSTREAM_SOA)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053",
+						  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+address /a.com/#6
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NXDOMAIN");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 60);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+}
+
+TEST_F(HTTPS, HTTPS_SOA)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611);
+			return smartdns::SERVER_REQUEST_OK;
+		}
+
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100);
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+https-record /a.com/#
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("a.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4");
+}
+
+TEST_F(HTTPS, HTTPS_IGN)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+force-qtype-SOA 65
+https-record /a.com/-
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+
+	ASSERT_TRUE(client.Query("b.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 61053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	auto result_check = client.GetAnswer()[0].GetData();
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=");
+}
+
+TEST_F(HTTPS, HTTPS_DOMAIN_RULE_IGN)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+address #
+domain-rules /a.com/ -https-record -
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+
+	ASSERT_TRUE(client.Query("b.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NXDOMAIN");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 61053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	auto result_check = client.GetAnswer()[0].GetData();
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=");
+}
+
+TEST_F(HTTPS, https_record)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053",
+						  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+force-qtype-SOA 65
+https-record /a.com/target=b.com,port=1443,alpn=\"h2,h3-19\",ech=\"AEX+DQA=\",ipv4hint=1.2.3.4
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+
+	ASSERT_TRUE(client.Query("b.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=1443 ipv4hint=1.2.3.4 ech=AEX+DQA=");
+}
+
+TEST_F(HTTPS, filter_ip)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10);
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+https-record noipv4hint,noipv6hint
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA=");
+}

+ 50 - 0
test/cases/test-qtype-soa.cc

@@ -240,3 +240,53 @@ speed-check-mode none
 	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
 	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
 }
+
+TEST_F(QtypeSOA, HTTPS_SOA)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+	std::map<int, int> qid_map;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype != DNS_T_HTTPS) {
+			return smartdns::SERVER_REQUEST_SOA;
+		}
+
+		struct dns_packet *packet = request->response_packet;
+		struct dns_rr_nested svcparam_buffer;
+
+		dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com");
+		const char alph[] = "\x02h2\x05h3-19";
+		int alph_len = sizeof(alph) - 1;
+		dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);
+		dns_HTTPS_add_port(&svcparam_buffer, 443);
+		unsigned char add_v4[] = {1, 2, 3, 4};
+		unsigned char *addr[1] = {add_v4};
+		dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);
+		unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};
+		dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));
+		unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+		addr[0] = add_v6;
+		dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);
+		dns_add_HTTPS_end(&svcparam_buffer);
+
+		return smartdns::SERVER_REQUEST_OK;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-console yes
+dualstack-ip-selection no
+speed-check-mode none
+address /a.com/#
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+    ASSERT_TRUE(client.Query("a.com HTTPS", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NXDOMAIN");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com");
+	EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+}