Skip to content

Commit

Permalink
feature: support HTTPS svcb record.
Browse files Browse the repository at this point in the history
  • Loading branch information
pymumu committed Jan 29, 2024
1 parent 04972bd commit dfc7ebe
Show file tree
Hide file tree
Showing 11 changed files with 1,449 additions and 88 deletions.
4 changes: 4 additions & 0 deletions etc/smartdns/smartdns.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/dns.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
217 changes: 216 additions & 1 deletion src/dns_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2535,6 +2538,203 @@ static int _config_srv_record(void *data, int argc, char *argv[])
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));
Expand Down Expand Up @@ -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'},
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)),
Expand Down
29 changes: 29 additions & 0 deletions src/dns_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
};
Expand Down
Loading

1 comment on commit dfc7ebe

@qwerttvv
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-force-https-soa可以涵盖吗

Please sign in to comment.