Browse Source

feature: indtroduce DDR rfc9462 support.

Nick Peng 3 weeks ago
parent
commit
5f90115a10

+ 1 - 0
etc/smartdns/smartdns.conf

@@ -50,6 +50,7 @@
 #   -no-rules: skip all rules.
 #   -ipset ipsetname: use ipset rule.
 #   -nftset nftsetname: use nftset rule.
+#   -ddr: enable ddr.
 # example: 
 #  IPV4: 
 #    bind :53

+ 109 - 31
src/dns.c

@@ -626,6 +626,7 @@ struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_bu
 	int len = 0;
 	memset(rr_nested_buffer, 0, sizeof(*rr_nested_buffer));
 	rr_nested_buffer->type = type;
+	rr_nested_buffer->rtype = rtype;
 	int ret = 0;
 
 	/* resource record */
@@ -678,7 +679,7 @@ int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data,
 	return 0;
 }
 
-int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype)
+int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested)
 {
 	if (rr_nested == NULL || rr_nested->rr_start == NULL) {
 		return -1;
@@ -698,7 +699,7 @@ int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype)
 
 	_dns_write_short(&ptr, len - rr_nested->rr_head_len);
 
-	return _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rtype, len);
+	return _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rr_nested->rtype, len);
 }
 
 void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len)
@@ -1253,16 +1254,16 @@ int dns_get_SRV(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsign
 	return 0;
 }
 
-int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
-						const char *domain, int ttl, int priority, const char *target)
+static int _dns_add_SVCB_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
+									 dns_type_t rtype, const char *domain, int ttl, int priority, const char *target)
 {
-	svcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl);
+	svcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, rtype, domain, ttl);
 	if (svcparam_buffer == NULL) {
 		return -1;
 	}
 
 	int target_len = 0;
-	if (target == NULL) {
+	if (target == NULL || target[0] == '.') {
 		target = "";
 	}
 
@@ -1287,7 +1288,19 @@ int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet
 	return 0;
 }
 
-int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len)
+int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
+						const char *domain, int ttl, int priority, const char *target)
+{
+	return _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl, priority, target);
+}
+
+int dns_add_SVCB_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
+					   const char *domain, int ttl, int priority, const char *target)
+{
+	return _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_SVCB, domain, ttl, priority, target);
+}
+
+int dns_SVCB_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len)
 {
 	if (_dns_left_len(&svcparam->context) < 2 + len) {
 		return -1;
@@ -1307,7 +1320,7 @@ int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsign
 	return 0;
 }
 
-int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port)
+int dns_SVCB_add_port(struct dns_rr_nested *svcparam, unsigned short port)
 {
 	if (_dns_left_len(&svcparam->context) < 6) {
 		return -1;
@@ -1331,7 +1344,7 @@ int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port)
 	return 0;
 }
 
-int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len)
+int dns_SVCB_add_alpn(struct dns_rr_nested *svcparam, const unsigned char *alpn, int alpn_len)
 {
 	if (_dns_left_len(&svcparam->context) < 2 + 2 + alpn_len) {
 		return -1;
@@ -1354,7 +1367,26 @@ int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alp
 	return 0;
 }
 
-int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
+int dns_SVCB_add_no_default_alpn(struct dns_rr_nested *svcparam)
+{
+	if (_dns_left_len(&svcparam->context) < 4) {
+		return -1;
+	}
+
+	unsigned short value = DNS_HTTPS_T_NO_DEFAULT_ALPN;
+	if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {
+		return -1;
+	}
+
+	value = 0; /* no data for no-default-alpn */
+	if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+int dns_SVCB_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
 {
 	if (_dns_left_len(&svcparam->context) < 2 + 2 + ech_len) {
 		return -1;
@@ -1377,7 +1409,7 @@ int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
 	return 0;
 }
 
-int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
+int dns_SVCB_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
 {
 	if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_A_LEN) {
 		return -1;
@@ -1402,7 +1434,7 @@ int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[]
 	return 0;
 }
 
-int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
+int dns_SVCB_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
 {
 	if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_AAAA_LEN) {
 		return -1;
@@ -1427,12 +1459,54 @@ int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[]
 	return 0;
 }
 
+int dns_add_SVCB_end(struct dns_rr_nested *svcparam)
+{
+	return dns_add_rr_nested_end(svcparam);
+}
+
+/* Backward compatibility wrapper */
 int dns_add_HTTPS_end(struct dns_rr_nested *svcparam)
 {
-	return dns_add_rr_nested_end(svcparam, DNS_T_HTTPS);
+	return dns_add_SVCB_end(svcparam);
+}
+
+/* Backward compatibility wrappers for dns_HTTPS_add_* functions */
+int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len)
+{
+	return dns_SVCB_add_raw(svcparam, key, value, len);
+}
+
+int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len)
+{
+	return dns_SVCB_add_alpn(svcparam, (const unsigned char *)alpn, alpn_len);
+}
+
+int dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam)
+{
+	return dns_SVCB_add_no_default_alpn(svcparam);
 }
 
-int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
+int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port)
+{
+	return dns_SVCB_add_port(svcparam, port);
+}
+
+int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
+{
+	return dns_SVCB_add_ipv4hint(svcparam, addr, addr_num);
+}
+
+int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
+{
+	return dns_SVCB_add_ech(svcparam, ech, ech_len);
+}
+
+int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
+{
+	return dns_SVCB_add_ipv6hint(svcparam, addr, addr_num);
+}
+
+int dns_svcparm_start(struct dns_rrs *rrs, struct dns_svcparam **https_param, char *domain, int maxsize,
 								int *ttl, int *priority, char *target, int target_size)
 {
 	int qtype = 0;
@@ -1444,7 +1518,7 @@ int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **ht
 		return -1;
 	}
 
-	if (qtype != DNS_T_HTTPS) {
+	if (qtype != DNS_T_HTTPS && qtype != DNS_T_SVCB) {
 		return -1;
 	}
 
@@ -1477,14 +1551,14 @@ int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **ht
 		return 0;
 	}
 
-	*https_param = (struct dns_https_param *)data;
+	*https_param = (struct dns_svcparam *)data;
 
 	return 0;
 }
 
-struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *param)
+struct dns_svcparam *dns_svcparm_next(struct dns_rrs *rrs, struct dns_svcparam *param)
 {
-	return dns_get_rr_nested_next(rrs, param, sizeof(struct dns_https_param) + param->len);
+	return dns_get_rr_nested_next(rrs, param, sizeof(struct dns_svcparam) + param->len);
 }
 
 /*
@@ -2284,7 +2358,7 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign
 	return 0;
 }
 
-static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
+static int _dns_encode_svcparam(struct dns_context *context, struct dns_rrs *rrs)
 {
 	int ret = 0;
 	int qtype = 0;
@@ -2296,16 +2370,16 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 	unsigned char *rr_start = NULL;
 	int ttl = 0;
 	int priority = 0;
-	struct dns_https_param *param = NULL;
+	struct dns_svcparam *param = NULL;
 
 	ret =
-		dns_get_HTTPS_svcparm_start(rrs, &param, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
+		dns_svcparm_start(rrs, &param, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
 	if (ret < 0) {
 		tlog(TLOG_DEBUG, "get https param failed.");
 		return 0;
 	}
 
-	qtype = DNS_T_HTTPS;
+	qtype = rrs->type;
 	qclass = DNS_C_IN;
 
 	ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, 0, &rr_len_ptr);
@@ -2326,7 +2400,7 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 	}
 
 	start = context->ptr;
-	for (; param != NULL; param = dns_get_HTTPS_svcparm_next(rrs, param)) {
+	for (; param != NULL; param = dns_svcparm_next(rrs, param)) {
 		if (context->ptr - start > rrs->len || _dns_left_len(context) <= 0) {
 			return -1;
 		}
@@ -2364,8 +2438,8 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 	return 0;
 }
 
-static int _dns_decode_HTTPS(struct dns_context *context, const char *domain, dns_rr_type type, unsigned int ttl,
-							 int rr_len)
+static int _dns_decode_SVCB_HTTPS(struct dns_context *context, const char *domain, dns_rr_type type, dns_type_t qtype,
+							 unsigned int ttl, int rr_len)
 {
 	unsigned char *start = context->ptr;
 
@@ -2389,7 +2463,8 @@ static int _dns_decode_HTTPS(struct dns_context *context, const char *domain, dn
 		return -1;
 	}
 
-	dns_add_HTTPS_start(&param, packet, DNS_RRS_AN, domain, ttl, priority, target);
+	/* Use unified function with qtype parameter */
+	_dns_add_SVCB_HTTPS_start(&param, packet, DNS_RRS_AN, qtype, domain, ttl, priority, target);
 
 	while (context->ptr - start < rr_len) {
 		if (_dns_left_len(context) < 4) {
@@ -2414,7 +2489,7 @@ static int _dns_decode_HTTPS(struct dns_context *context, const char *domain, dn
 		case DNS_HTTPS_T_IPV4HINT:
 		case DNS_HTTPS_T_ECH:
 		case DNS_HTTPS_T_IPV6HINT: {
-			dns_HTTPS_add_raw(&param, key, value, value_len);
+			dns_SVCB_add_raw(&param, key, value, value_len);
 		} break;
 		default:
 			tlog(TLOG_DEBUG, "DNS HTTPS key = %d not supported", key);
@@ -2424,7 +2499,8 @@ static int _dns_decode_HTTPS(struct dns_context *context, const char *domain, dn
 		context->ptr += value_len;
 	}
 
-	dns_add_HTTPS_end(&param);
+	/* Use unified end function */
+	dns_add_SVCB_end(&param);
 
 	return 0;
 }
@@ -2590,9 +2666,10 @@ static int _dns_decode_an(struct dns_context *context, dns_rr_type type)
 		dns_set_OPT_option(packet, ttl);
 		dns_set_OPT_payload_size(packet, qclass);
 	} break;
-	case DNS_T_HTTPS: {
+	case DNS_T_HTTPS:
+	case DNS_T_SVCB: {
 		unsigned char *https_start = context->ptr;
-		ret = _dns_decode_HTTPS(context, domain, type, ttl, rr_len);
+		ret = _dns_decode_SVCB_HTTPS(context, domain, type, qtype, ttl, rr_len);
 		if (ret < 0) {
 			tlog(TLOG_DEBUG, "decode HTTPS failed, %s", domain);
 			return -1;
@@ -2698,7 +2775,8 @@ static int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs)
 		}
 		break;
 	case DNS_T_HTTPS:
-		ret = _dns_encode_HTTPS(context, rrs);
+	case DNS_T_SVCB:
+		ret = _dns_encode_svcparam(context, rrs);
 		if (ret < 0) {
 			return -1;
 		}

+ 1 - 58
src/dns_client/client_tls.c

@@ -353,63 +353,6 @@ errout:
 	return NULL;
 }
 
-/**
- * Encode ALPN protocol string into wire format
- * @param alpn Comma-separated protocol string (e.g., "h2,http/1.1")
- * @param alpn_data Output buffer for encoded data
- * @param alpn_data_max Maximum size of output buffer
- * @return Length of encoded data, or -1 on error
- *
- * Wire format: each protocol is length-prefixed: [len1]proto1[len2]proto2...
- * Example: "h2,http/1.1" -> [2]h2[8]http/1.1
- */
-static int _dns_client_encode_alpn_protos(const char *alpn, uint8_t *alpn_data, int alpn_data_max)
-{
-	int alpn_data_len = 0;
-	const char *alpn_str = alpn;
-
-	if (alpn == NULL || alpn[0] == 0 || alpn_data == NULL || alpn_data_max <= 0) {
-		return 0;
-	}
-
-	/* Parse comma-separated ALPN protocols and encode in wire format */
-	while (*alpn_str && alpn_data_len < alpn_data_max - 1) {
-		const char *comma = strchr(alpn_str, ',');
-		int proto_len;
-
-		if (comma) {
-			proto_len = comma - alpn_str;
-		} else {
-			proto_len = strnlen(alpn_str, alpn_data_max - alpn_data_len - 1);
-		}
-
-		/* Skip empty protocols */
-		if (proto_len == 0) {
-			alpn_str = comma ? comma + 1 : alpn_str + proto_len;
-			continue;
-		}
-
-		/* Check if we have space for length byte + protocol */
-		if (alpn_data_len + 1 + proto_len > alpn_data_max) {
-			tlog(TLOG_WARN, "ALPN string too long, truncating.");
-			break;
-		}
-
-		/* Write length-prefixed protocol */
-		alpn_data[alpn_data_len++] = (uint8_t)proto_len;
-		memcpy(alpn_data + alpn_data_len, alpn_str, proto_len);
-		alpn_data_len += proto_len;
-
-		/* Move to next protocol */
-		alpn_str = comma ? comma + 1 : alpn_str + proto_len;
-		if (!comma) {
-			break;
-		}
-	}
-
-	return alpn_data_len;
-}
-
 int _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn)
 {
 	int fd = -1;
@@ -519,7 +462,7 @@ int _dns_client_create_socket_tls(struct dns_server_info *server_info, const cha
 
 	if (alpn && alpn[0] != 0) {
 		uint8_t alpn_data[DNS_MAX_ALPN_LEN];
-		int alpn_data_len = _dns_client_encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data));
+		int alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data));
 
 		if (alpn_data_len > 0) {
 			if (SSL_set_alpn_protos(ssl, alpn_data, alpn_data_len)) {

+ 5 - 0
src/dns_conf/bind.c

@@ -216,6 +216,7 @@ static int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type)
 		{"ipset", required_argument, NULL, 255},
 		{"nftset", required_argument, NULL, 256},
 		{"alpn", required_argument, NULL, 257},
+		{"ddr", no_argument, NULL, 258},
 		{NULL, no_argument, NULL, 0}
 	};
 	/* clang-format on */
@@ -347,6 +348,10 @@ static int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type)
 			safe_strncpy(bind_ip->alpn, optarg, DNS_MAX_ALPN_LEN);
 			break;
 		}
+		case 258: {
+			server_flag |= BIND_FLAG_DDR;
+			break;
+		}
 		default:
 			if (optind > optind_last) {
 				tlog(TLOG_WARN, "unknown bind option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(),

+ 3 - 3
src/dns_server/answer.c

@@ -237,7 +237,7 @@ static int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_requ
 	int ret = -1;
 	char name[DNS_MAX_CNAME_LEN] = {0};
 	char target[DNS_MAX_CNAME_LEN] = {0};
-	struct dns_https_param *p = NULL;
+	struct dns_svcparam *p = NULL;
 	int priority = 0;
 	struct dns_request_https *https_svcb;
 	int no_ipv4 = 0;
@@ -251,7 +251,7 @@ static int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_requ
 		no_ech = https_record_rule->filter.no_ech;
 	}
 
-	ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
+	ret = dns_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;
@@ -272,7 +272,7 @@ static int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_requ
 	request->ip_ttl = ttl;
 
 	_dns_server_request_get(request);
-	for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) {
+	for (; p; p = dns_svcparm_next(rrs, p)) {
 		switch (p->key) {
 		case DNS_HTTPS_T_MANDATORY: {
 		} break;

+ 3 - 3
src/dns_server/context.c

@@ -636,17 +636,17 @@ static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context
 			} break;
 			case DNS_T_HTTPS: {
 				char target[DNS_MAX_CNAME_LEN] = {0};
-				struct dns_https_param *p = NULL;
+				struct dns_svcparam *p = NULL;
 				int priority = 0;
 
-				int ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
+				int ret = dns_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;
 				}
 
-				for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) {
+				for (; p; p = dns_svcparm_next(rrs, p)) {
 					switch (p->key) {
 					case DNS_HTTPS_T_IPV4HINT: {
 						unsigned char *addr;

+ 207 - 0
src/dns_server/ddr.c

@@ -0,0 +1,207 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2025 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 "ddr.h"
+#include "context.h"
+#include "dns_server.h"
+#include "request.h"
+#include "smartdns/dns.h"
+#include "smartdns/util.h"
+#include "soa.h"
+#include <netinet/in.h>
+
+static const char *_ddr_get_alpn(const struct dns_bind_ip *bind_ip)
+{
+	if (bind_ip->alpn[0] != '\0') {
+		return bind_ip->alpn;
+	}
+
+	switch (bind_ip->type) {
+	case DNS_BIND_TYPE_TLS:
+		return "dot";
+	case DNS_BIND_TYPE_HTTPS:
+		return "h2,http/1.1";
+	default:
+		return NULL;
+	}
+}
+
+static void _ddr_extract_local_addresses(const struct sockaddr_storage *addr, unsigned char *ipv4_addr, int *ipv4_num,
+										 unsigned char *ipv6_addr, int *ipv6_num)
+{
+	*ipv4_num = 0;
+	*ipv6_num = 0;
+
+	if (addr == NULL) {
+		return;
+	}
+
+	switch (addr->ss_family) {
+	case AF_INET: {
+		const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr;
+		memcpy(ipv4_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);
+		*ipv4_num = 1;
+	} break;
+	case AF_INET6: {
+		const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr;
+		memcpy(ipv6_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);
+		*ipv6_num = 1;
+	} break;
+	default:
+		break;
+	}
+}
+
+static int _ddr_build_svcb_record(struct dns_packet *packet, const char *domain, int ttl, int priority,
+								  const char *alpn, int port, unsigned char *ipv4_addr, int ipv4_num,
+								  unsigned char *ipv6_addr, int ipv6_num)
+{
+	struct dns_rr_nested svcparam_buffer;
+
+	if (dns_add_SVCB_start(&svcparam_buffer, packet, DNS_RRS_AN, domain, ttl, priority, NULL) != 0) {
+		return -1;
+	}
+
+	/* Add ALPN parameter */
+	if (alpn != NULL) {
+		uint8_t alpn_data[DNS_MAX_ALPN_LEN];
+		int alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data));
+		if (alpn_data_len > 0) {
+			dns_SVCB_add_alpn(&svcparam_buffer, alpn_data, alpn_data_len);
+		}
+	}
+
+	/* Add port parameter */
+	if (port > 0) {
+		dns_SVCB_add_port(&svcparam_buffer, port);
+	}
+
+	/* Add IPv4 hint */
+	if (ipv4_num > 0 && ipv4_addr != NULL) {
+		unsigned char *ip_addr[1] = {ipv4_addr};
+		dns_SVCB_add_ipv4hint(&svcparam_buffer, ip_addr, ipv4_num);
+	}
+
+	/* Add IPv6 hint */
+	if (ipv6_num > 0 && ipv6_addr != NULL) {
+		unsigned char *ip_addr[1] = {ipv6_addr};
+		dns_SVCB_add_ipv6hint(&svcparam_buffer, ip_addr, ipv6_num);
+	}
+
+	dns_add_SVCB_end(&svcparam_buffer);
+	return 0;
+}
+
+int _dns_server_process_DDR(struct dns_request *request)
+{
+	struct dns_server_post_context context;
+	int ret = 0;
+	int added_svcb = 0;
+	int ttl = request->ip_ttl;
+
+	_dns_server_post_context_init(&context, request);
+	context.do_reply = 1;
+
+	/* Initialize DNS response head */
+	struct dns_head head;
+	memset(&head, 0, sizeof(head));
+	head.id = request->id;
+	head.qr = DNS_QR_ANSWER;
+	head.opcode = DNS_OP_QUERY;
+	head.aa = 0;
+	head.rd = 0;
+	head.ra = 1;
+	head.rcode = DNS_RC_NOERROR;
+
+	/* Initialize DNS packet */
+	ret = dns_packet_init(context.packet, context.packet_maxlen, &head);
+	if (ret != 0) {
+		return _dns_server_reply_SOA(DNS_RC_NOERROR, request);
+	}
+
+	/* Add request domain */
+	ret = dns_add_domain(context.packet, request->domain, request->qtype, request->qclass);
+	if (ret != 0) {
+		return _dns_server_reply_SOA(DNS_RC_NOERROR, request);
+	}
+
+	/* Set default TTL if not set */
+	if (ttl <= 0) {
+		ttl = 60;
+	}
+
+	/* Get local address for IP hints */
+	struct sockaddr_storage *local_addr = (struct sockaddr_storage *)dns_server_request_get_local_addr(request);
+
+	/* Iterate through all bind IPs and create SVCB records for DDR-enabled bindings */
+	int priority = 1;
+	for (int i = 0; i < dns_conf.bind_ip_num; i++) {
+		struct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i];
+		const char *alpn = NULL;
+		int port = 0;
+		char ip[DNS_MAX_IPLEN];
+		unsigned char ipv4_addr[DNS_RR_A_LEN];
+		int ipv4_num = 0;
+		unsigned char ipv6_addr[DNS_RR_AAAA_LEN];
+		int ipv6_num = 0;
+
+		/* Skip if DDR is not enabled for this binding */
+		if ((bind_ip->flags & BIND_FLAG_DDR) == 0) {
+			continue;
+		}
+
+		/* Determine ALPN */
+		alpn = _ddr_get_alpn(bind_ip);
+		if (alpn == NULL) {
+			continue;
+		}
+
+		/* Extract port from IP string */
+		if (parse_ip(bind_ip->ip, ip, &port) != 0) {
+			continue;
+		}
+
+		/* Extract local addresses for IP hints */
+		_ddr_extract_local_addresses(local_addr, ipv4_addr, &ipv4_num, ipv6_addr, &ipv6_num);
+
+		/* Build SVCB record */
+		ret = _ddr_build_svcb_record(context.packet, request->domain, ttl, priority, alpn, port, ipv4_addr, ipv4_num,
+									 ipv6_addr, ipv6_num);
+		if (ret == 0) {
+			added_svcb++;
+			priority++;
+		}
+	}
+
+	/* If no SVCB records were added, return SOA */
+	if (added_svcb == 0) {
+		return _dns_server_reply_SOA(DNS_RC_NOERROR, request);
+	}
+
+	/* Encode to binary data */
+	int encode_len = dns_encode(context.inpacket, context.inpacket_maxlen, context.packet);
+	if (encode_len <= 0) {
+		return _dns_server_reply_SOA(DNS_RC_NOERROR, request);
+	}
+
+	context.inpacket_len = encode_len;
+	context.do_cache = 0;
+	context.do_ipset = 0;
+	_dns_server_reply_passthrough(&context);
+	return 0;
+}

+ 34 - 0
src/dns_server/ddr.h

@@ -0,0 +1,34 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2025 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/>.
+ */
+
+#ifndef _DNS_SERVER_DDR_H
+#define _DNS_SERVER_DDR_H
+
+#include "dns_server.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /*__cplusplus */
+
+int _dns_server_process_DDR(struct dns_request *request);
+
+#ifdef __cplusplus
+}
+#endif /*__cplusplus */
+
+#endif // _DNS_SERVER_DDR_H

+ 1 - 5
src/dns_server/request.c

@@ -20,6 +20,7 @@
 #include "address.h"
 #include "connection.h"
 #include "context.h"
+#include "ddr.h"
 #include "dns64.h"
 #include "dns_server.h"
 #include "dualstack.h"
@@ -655,11 +656,6 @@ static int _dns_server_process_local_SOA(struct dns_request *request)
 	return _dns_server_reply_SOA_ext(DNS_RC_NOERROR, request);
 }
 
-int _dns_server_process_DDR(struct dns_request *request)
-{
-	return _dns_server_reply_SOA(DNS_RC_NOERROR, request);
-}
-
 int _dns_server_process_srv(struct dns_request *request)
 {
 	struct dns_srv_records *srv_records = dns_server_get_srv_record(request->domain);

+ 0 - 4
src/dns_server/request.h

@@ -31,8 +31,6 @@ void _dns_server_request_release_complete(struct dns_request *request, int do_co
 
 void _dns_server_query_end(struct dns_request *request);
 
-int _dns_server_process_DDR(struct dns_request *request);
-
 void *dns_server_request_get_private(struct dns_request *request);
 
 struct dns_request *_dns_server_new_request(void);
@@ -65,8 +63,6 @@ void _dns_server_set_request_mdns(struct dns_request *request);
 
 int _dns_server_process_svcb(struct dns_request *request);
 
-int _dns_server_process_DDR(struct dns_request *request);
-
 int _dns_server_process_srv(struct dns_request *request);
 
 int _dns_server_process_host(struct dns_request *request);

+ 28 - 11
src/include/smartdns/dns.h

@@ -233,9 +233,10 @@ struct dns_rr_nested {
 	unsigned char *rr_len_ptr;
 	unsigned short rr_head_len;
 	dns_rr_type type;
+	dns_type_t rtype;
 };
 
-struct dns_https_param {
+struct dns_svcparam {
 	unsigned short key;
 	unsigned short len;
 	unsigned char value[0];
@@ -246,7 +247,7 @@ struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, i
 
 struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet,
 											  dns_rr_type type, dns_type_t rtype, const char *domain, int ttl);
-int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype);
+int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested);
 int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len);
 
 void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len);
@@ -304,24 +305,40 @@ int dns_get_SRV(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsign
 /* the key must be added in orders, or dig will report FORMERR */
 int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
 						const char *domain, int ttl, int priority, const char *target);
+int dns_add_SVCB_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
+					   const char *domain, int ttl, int priority, const char *target);
+
+/* SVCB parameter functions */
+int dns_SVCB_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len);
+/* key 1, alpn */
+int dns_SVCB_add_alpn(struct dns_rr_nested *svcparam, const unsigned char *alpn, int alpn_len);
+/* key 2, no default alpn */
+int dns_SVCB_add_no_default_alpn(struct dns_rr_nested *svcparam);
+/* key 3, port */
+int dns_SVCB_add_port(struct dns_rr_nested *svcparam, unsigned short port);
+/* key 4, ipv4 */
+int dns_SVCB_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
+/* key 5, ech */
+int dns_SVCB_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len);
+/* key 6, ipv6*/
+int dns_SVCB_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
+
+/* End functions */
+int dns_add_SVCB_end(struct dns_rr_nested *svcparam);
+int dns_add_HTTPS_end(struct dns_rr_nested *svcparam);
+
+/* Backward compatibility - HTTPS parameter functions */
 int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len);
-/* key 1, alph */
 int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len);
-/* key 2, no default alph */
 int dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam);
-/* key 3, port */
 int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port);
-/* key 4, ipv4 */
 int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
-/* key 5, ech */
 int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len);
-/* key 6, ipv6*/
 int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
-int dns_add_HTTPS_end(struct dns_rr_nested *svcparam);
 
-int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
+int dns_svcparm_start(struct dns_rrs *rrs, struct dns_svcparam **https_param, char *domain, int maxsize,
 								int *ttl, int *priority, char *target, int target_size);
-struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *param);
+struct dns_svcparam *dns_svcparm_next(struct dns_rrs *rrs, struct dns_svcparam *param);
 
 /*
  * Packet operation

+ 24 - 23
src/include/smartdns/dns_conf.h

@@ -78,27 +78,27 @@ extern "C" {
 
 /* Domain rule types, ordered by usage frequency for memory optimization */
 enum domain_rule {
-	DOMAIN_RULE_FLAGS = 0,               /* Flags (block, ignore, cache, etc.) */
-	
-	DOMAIN_RULE_ADDRESS_IPV4,            /* IPv4 address rule (ad-block, custom DNS) */
-	DOMAIN_RULE_ADDRESS_IPV6,            /* IPv6 address rule */
-	DOMAIN_RULE_NAMESERVER,              /* Nameserver group (domain routing) */
-	
-	DOMAIN_RULE_CHECKSPEED,              /* Speed check mode */
-	DOMAIN_RULE_IPSET,                   /* IPSet rule for traffic routing */
-	DOMAIN_RULE_NFTSET_IP,               /* NFTSet IPv4 */
-	DOMAIN_RULE_IPSET_IPV4,              /* IPv4 IPSet */
-	
-	DOMAIN_RULE_GROUP,                   /* Group rule */
-
-	DOMAIN_RULE_NFTSET_IP6,              /* NFTSet IPv6 */
-	DOMAIN_RULE_IPSET_IPV6,              /* IPv6 IPSet */
-	
-	DOMAIN_RULE_HTTPS,                   /* HTTPS record */
-	DOMAIN_RULE_RESPONSE_MODE,           /* Response mode */
-	DOMAIN_RULE_CNAME,                   /* CNAME rule */
-	DOMAIN_RULE_TTL,                     /* TTL control */
-	
+	DOMAIN_RULE_FLAGS = 0, /* Flags (block, ignore, cache, etc.) */
+
+	DOMAIN_RULE_ADDRESS_IPV4, /* IPv4 address rule (ad-block, custom DNS) */
+	DOMAIN_RULE_ADDRESS_IPV6, /* IPv6 address rule */
+	DOMAIN_RULE_NAMESERVER,   /* Nameserver group (domain routing) */
+
+	DOMAIN_RULE_CHECKSPEED, /* Speed check mode */
+	DOMAIN_RULE_IPSET,      /* IPSet rule for traffic routing */
+	DOMAIN_RULE_NFTSET_IP,  /* NFTSet IPv4 */
+	DOMAIN_RULE_IPSET_IPV4, /* IPv4 IPSet */
+
+	DOMAIN_RULE_GROUP, /* Group rule */
+
+	DOMAIN_RULE_NFTSET_IP6, /* NFTSet IPv6 */
+	DOMAIN_RULE_IPSET_IPV6, /* IPv6 IPSet */
+
+	DOMAIN_RULE_HTTPS,         /* HTTPS record */
+	DOMAIN_RULE_RESPONSE_MODE, /* Response mode */
+	DOMAIN_RULE_CNAME,         /* CNAME rule */
+	DOMAIN_RULE_TTL,           /* TTL control */
+
 	DOMAIN_RULE_MAX,
 };
 
@@ -178,6 +178,7 @@ typedef enum {
 #define BIND_FLAG_NO_SERVE_EXPIRED (1 << 14)
 #define BIND_FLAG_NO_RULES (1 << 15)
 #define BIND_FLAG_ACL (1 << 16)
+#define BIND_FLAG_DDR (1 << 17)
 
 enum response_mode_type {
 	DNS_RESPONSE_MODE_FIRST_PING_IP = 0,
@@ -269,8 +270,8 @@ extern struct dns_nftset_names dns_conf_nftset;
 struct dns_domain_rule {
 	unsigned char sub_rule_only : 1;
 	unsigned char root_rule_only : 1;
-	unsigned char capacity : 6;          /* Current allocated capacity (max 63) */
-	struct dns_rule *rules[];            /* Flexible array member */
+	unsigned char capacity : 6; /* Current allocated capacity (max 63) */
+	struct dns_rule *rules[];   /* Flexible array member */
 };
 
 struct dns_nameserver_rule {

+ 2 - 0
src/include/smartdns/util.h

@@ -213,6 +213,8 @@ static inline void *zalloc(size_t count, size_t size)
 	return calloc(count, size);
 }
 
+int encode_alpn_protos(const char *alpn, uint8_t *alpn_data, int alpn_data_max);
+
 #ifdef __cplusplus
 }
 #endif /*__cplusplus */

+ 71 - 0
src/utils/alpn.c

@@ -0,0 +1,71 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2025 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 "smartdns/tlog.h"
+#include "smartdns/util.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+int encode_alpn_protos(const char *alpn, uint8_t *alpn_data, int alpn_data_max)
+{
+	int alpn_data_len = 0;
+	const char *alpn_str = alpn;
+
+	if (alpn == NULL || alpn[0] == 0 || alpn_data == NULL || alpn_data_max <= 0) {
+		return 0;
+	}
+
+	/* Parse comma-separated ALPN protocols and encode in wire format */
+	while (*alpn_str && alpn_data_len < alpn_data_max - 1) {
+		const char *comma = strchr(alpn_str, ',');
+		int proto_len;
+
+		if (comma) {
+			proto_len = comma - alpn_str;
+		} else {
+			proto_len = strnlen(alpn_str, alpn_data_max - alpn_data_len - 1);
+		}
+
+		/* Skip empty protocols */
+		if (proto_len == 0) {
+			alpn_str = comma ? comma + 1 : alpn_str + proto_len;
+			continue;
+		}
+
+		/* Check if we have space for length byte + protocol */
+		if (alpn_data_len + 1 + proto_len > alpn_data_max) {
+			tlog(TLOG_WARN, "ALPN string too long, truncating.");
+			break;
+		}
+
+		/* Write length-prefixed protocol */
+		alpn_data[alpn_data_len++] = (uint8_t)proto_len;
+		memcpy(alpn_data + alpn_data_len, alpn_str, proto_len);
+		alpn_data_len += proto_len;
+
+		/* Move to next protocol */
+		alpn_str = comma ? comma + 1 : alpn_str + proto_len;
+		if (!comma) {
+			break;
+		}
+	}
+
+	return alpn_data_len;
+}

+ 4 - 3
src/utils/dns_debug.c

@@ -264,13 +264,14 @@ static int _dns_debug_display(struct dns_packet *packet)
 				printf("domain: %s SRV: %s TTL: %d priority: %d weight: %d port: %d\n", name, target, ttl, priority,
 					   weight, port);
 			} break;
+			case DNS_T_SVCB:
 			case DNS_T_HTTPS: {
 				char name[DNS_MAX_CNAME_LEN] = {0};
 				char target[DNS_MAX_CNAME_LEN] = {0};
-				struct dns_https_param *p = NULL;
+				struct dns_svcparam *p = NULL;
 				int priority = 0;
 
-				ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
+				ret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
 												  DNS_MAX_CNAME_LEN);
 				if (ret != 0) {
 					printf("get HTTPS svcparm failed\n");
@@ -279,7 +280,7 @@ static int _dns_debug_display(struct dns_packet *packet)
 
 				printf("domain: %s HTTPS: %s TTL: %d priority: %d\n", name, target, ttl, priority);
 
-				for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) {
+				for (; p; p = dns_svcparm_next(rrs, p)) {
 					switch (p->key) {
 					case DNS_HTTPS_T_MANDATORY: {
 						printf("  HTTPS: mandatory: %s\n", p->value);

+ 76 - 0
test/cases/test-ddr.cc

@@ -0,0 +1,76 @@
+#include "client.h"
+#include "include/utils.h"
+#include "server.h"
+#include "smartdns/dns.h"
+#include "smartdns/util.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+class DDR : public ::testing::Test
+{
+  protected:
+	virtual void SetUp() {}
+	virtual void TearDown() {}
+};
+
+TEST_F(DDR, DDR_RESPONSE)
+{
+	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 -ddr
+bind-tls [::]:60153 -ddr
+bind-https [::]:60253 -ddr -alpn h2
+bind-tcp [::]:60353
+server 127.0.0.1:61053
+log-console yes
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("_dns.resolver.arpa SVCB", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 2);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+
+	bool has_doq = false;
+	bool has_dot = false;
+
+	for (auto &ans : client.GetAnswer()) {
+		EXPECT_EQ(ans.GetName(), "_dns.resolver.arpa");
+		EXPECT_EQ(ans.GetType(), "SVCB");
+		if (ans.GetData().find("alpn=\"h2\"") != std::string::npos) {
+			has_doq = true;
+		}
+		if (ans.GetData().find("alpn=\"dot\"") != std::string::npos) {
+			has_dot = true;
+		}
+	}
+	EXPECT_TRUE(has_doq);
+	EXPECT_TRUE(has_dot);
+}
+
+TEST_F(DDR, DDR_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
+bind-tls [::]:60053
+server 127.0.0.1:61053
+log-console yes
+log-level debug
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("_dns.resolver.arpa SVCB", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAuthorityNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAuthority()[0].GetName(), "_dns.resolver.arpa");
+	EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA");
+}