diff --git a/ReadMe.md b/ReadMe.md
index e99df86..b0bfc95 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -568,6 +568,7 @@ https://github.com/pymumu/smartdns/releases
|nameserver|指定域名使用server组解析|无|nameserver /domain/[group\|-], `group`为组名,`-`表示忽略此规则,配套server中的`-group`参数使用| nameserver /www.example.com/office
|ipset|域名IPSET|None|ipset /domain/[ipset\|-], `-`表示忽略|ipset /www.example.com/pass
|ipset-timeout|设置IPSET超时功能启用|auto|[yes]|ipset-timeout yes
+|domain-rules|设置域名规则|无|domain-rules /domain/ [-rules...]
`[-speed-check-mode]`: 测速模式,参考`speed-check-mode`配置
`[-address]`: 参考`address`配置
`[-nameserver]`: 参考`nameserver`配置
`[-ipset]`:参考`ipset`配置|domain-rules /www.example.com/ -speed-check-mode none
|bogus-nxdomain|假冒IP地址过滤|无|[ip/subnet],可重复| bogus-nxdomain 1.2.3.4/16
|ignore-ip|忽略IP地址|无|[ip/subnet],可重复| ignore-ip 1.2.3.4/16
|whitelist-ip|白名单IP地址|无|[ip/subnet],可重复| whitelist-ip 1.2.3.4/16
diff --git a/ReadMe_en.md b/ReadMe_en.md
index dbceea5..087c36e 100755
--- a/ReadMe_en.md
+++ b/ReadMe_en.md
@@ -563,6 +563,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
|nameserver|To query domain with specific server group|None|nameserver /domain/[group\|-], `group` is the group name, `-` means ignore this rule, use the `-group` parameter in the related server|nameserver /www.example.com/office
|ipset|Domain IPSet|None|ipset /domain/[ipset\|-], `-` for ignore|ipset /www.example.com/pass
|ipset-timeout|ipset timeout enable|auto|[yes]|ipset-timeout yes
+|domain-rules|set domain rules|None|domain-rules /domain/ [-rules...]
`[-speed-check-mode]`: set speed check mode,same as parameter `speed-check-mode`
`[-address]`: same as parameter `address`
`[-nameserver]`: same as parameter `nameserver`
`[-ipset]`: same as parameter `ipset`|domain-rules /www.example.com/ -speed-check-mode none
|bogus-nxdomain|bogus IP address|None|[IP/subnet], Repeatable| bogus-nxdomain 1.2.3.4/16
|ignore-ip|ignore ip address|None|[ip/subnet], Repeatable| ignore-ip 1.2.3.4/16
|whitelist-ip|ip whitelist|None|[ip/subnet], Repeatable,When the filtering server responds IPs in the IP whitelist, only result in whitelist will be accepted| whitelist-ip 1.2.3.4/16
diff --git a/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf
index 27b2212..5717dac 100644
--- a/etc/smartdns/smartdns.conf
+++ b/etc/smartdns/smartdns.conf
@@ -156,3 +156,12 @@ log-level info
# ipset /domain/[ipset|-]
# ipset /www.example.com/block, set ipset with ipset name of block
# ipset /www.example.com/-, ignore this domain
+
+# set domain rules
+# domain-rules /domain/ [-speed-check-mode [...]]
+# rules:
+# -speed-check-mode [mode]: speed check mode
+# speed-check-mode [ping|tcp:port|none|,]
+# -address [address|-]: same as address option
+# -nameserver [group|-]: same as nameserver option
+# -ipset [ipset|-]: same as ipset option
\ No newline at end of file
diff --git a/src/dns_conf.c b/src/dns_conf.c
index 2eb770c..13600b5 100644
--- a/src/dns_conf.c
+++ b/src/dns_conf.c
@@ -58,6 +58,7 @@ struct dns_domain_check_order dns_conf_check_order = {
.order = {DOMAIN_CHECK_ICMP, DOMAIN_CHECK_TCP},
.tcp_port = 80,
};
+int dns_has_cap_ping = 0;
/* logging */
int dns_conf_log_level = TLOG_ERROR;
@@ -94,6 +95,49 @@ struct dns_edns_client_subnet dns_conf_ipv6_ecs;
char dns_conf_sni_proxy_ip[DNS_MAX_IPLEN];
+static int _get_domain(char *value, char *domain, int max_dmain_size, char **ptr_after_domain)
+{
+ char *begin = NULL;
+ char *end = NULL;
+ int len = 0;
+
+ /* first field */
+ begin = strstr(value, "/");
+ if (begin == NULL) {
+ goto errout;
+ }
+
+ /* second field */
+ begin++;
+ end = strstr(begin, "/");
+ if (end == NULL) {
+ goto errout;
+ }
+
+ /* remove prefix . */
+ while (*begin == '.') {
+ begin++;
+ }
+
+ /* Get domain */
+ len = end - begin;
+ if (len >= max_dmain_size) {
+ tlog(TLOG_ERROR, "domain name %s too long", value);
+ goto errout;
+ }
+
+ memcpy(domain, begin, len);
+ domain[len] = '\0';
+
+ if (ptr_after_domain) {
+ *ptr_after_domain = end + 1;
+ }
+
+ return 0;
+errout:
+ return -1;
+}
+
/* create and get dns server group */
static struct dns_server_groups *_dns_conf_get_group(const char *group_name)
{
@@ -523,58 +567,14 @@ errout:
return NULL;
}
-static int _config_ipset(void *data, int argc, char *argv[])
+static int _conf_domain_rule_ipset(char *domain, const char *ipsetname)
{
struct dns_ipset_rule *ipset_rule = NULL;
- char domain[DNS_MAX_CONF_CNAME_LEN];
- char ipsetname[DNS_MAX_IPSET_NAMELEN];
const char *ipset = NULL;
- char *begin = NULL;
- char *end = NULL;
- int len = 0;
- char *value = argv[1];
-
- if (argc <= 1) {
- goto errout;
- }
-
- /* first field */
- begin = strstr(value, "/");
- if (begin == NULL) {
- goto errout;
- }
-
- /* second field */
- begin++;
- end = strstr(begin, "/");
- if (end == NULL) {
- goto errout;
- }
-
- /* remove prefix . */
- while (*begin == '.') {
- begin++;
- }
-
- /* Get domain */
- len = end - begin;
- if (len >= sizeof(domain)) {
- tlog(TLOG_ERROR, "domain name %s too long", value);
- goto errout;
- }
-
- memcpy(domain, begin, len);
- domain[len] = '\0';
-
- len = strlen(end + 1);
- if (len <= 0) {
- goto errout;
- }
/* Process domain option */
- if (strncmp(end + 1, "-", sizeof("-")) != 0) {
+ if (strncmp(ipsetname, "-", sizeof("-")) != 0) {
/* new ipset domain */
- safe_strncpy(ipsetname, end + 1, DNS_MAX_IPSET_NAMELEN);
ipset = _dns_conf_get_ipset(ipsetname);
if (ipset == NULL) {
goto errout;
@@ -605,66 +605,47 @@ errout:
free(ipset_rule);
}
+ tlog(TLOG_ERROR, "add ipset %s failed", ipsetname);
+ return 0;
+}
+
+static int _config_ipset(void *data, int argc, char *argv[])
+{
+ char domain[DNS_MAX_CONF_CNAME_LEN];
+ char *value = argv[1];
+
+ if (argc <= 1) {
+ goto errout;
+ }
+
+ if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {
+ goto errout;
+ }
+
+ return _conf_domain_rule_ipset(domain, value);
+errout:
tlog(TLOG_ERROR, "add ipset %s failed", value);
return 0;
}
-static int _config_address(void *data, int argc, char *argv[])
+static int _conf_domain_rule_address(char *domain, const char *domain_address)
{
struct dns_address_IPV4 *address_ipv4 = NULL;
struct dns_address_IPV6 *address_ipv6 = NULL;
void *address = NULL;
- char *value = argv[1];
char ip[MAX_IP_LEN];
- char domain[DNS_MAX_CONF_CNAME_LEN];
- char *begin = NULL;
- char *end = NULL;
- int len = 0;
int port;
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
enum domain_rule type = 0;
unsigned int flag = 0;
- if (argc <= 1) {
- goto errout;
- }
-
- /* first field */
- begin = strstr(value, "/");
- if (begin == NULL) {
- goto errout;
- }
-
- /* second field */
- begin++;
- end = strstr(begin, "/");
- if (end == NULL) {
- goto errout;
- }
-
- /* remove prefix . */
- while (*begin == '.') {
- begin++;
- }
-
- /* get domain */
- len = end - begin;
-
- if (len >= sizeof(domain)) {
- tlog(TLOG_ERROR, "domain name %s too long", value);
- goto errout;
- }
-
- memcpy(domain, begin, len);
- domain[len] = 0;
-
- if (*(end + 1) == '#') {
- if (strncmp(end + 1, "#4", sizeof("#4")) == 0) {
+ if (*(domain_address) == '#') {
+ if (strncmp(domain_address, "#4", sizeof("#4")) == 0) {
flag = DOMAIN_FLAG_ADDR_IPV4_SOA;
- } else if (strncmp(end + 1, "#6", sizeof("#6")) == 0) {
+ } else if (strncmp(domain_address, "#6", sizeof("#6")) == 0) {
flag = DOMAIN_FLAG_ADDR_IPV6_SOA;
- } else if (strncmp(end + 1, "#", sizeof("#")) == 0) {
+ } else if (strncmp(domain_address, "#", sizeof("#")) == 0) {
flag = DOMAIN_FLAG_ADDR_SOA;
} else {
goto errout;
@@ -676,12 +657,12 @@ static int _config_address(void *data, int argc, char *argv[])
}
return 0;
- } else if (*(end + 1) == '-') {
- if (strncmp(end + 1, "-4", sizeof("-4")) == 0) {
+ } else if (*(domain_address) == '-') {
+ if (strncmp(domain_address, "-4", sizeof("-4")) == 0) {
flag = DOMAIN_FLAG_ADDR_IPV4_IGN;
- } else if (strncmp(end + 1, "-6", sizeof("-6")) == 0) {
+ } else if (strncmp(domain_address, "-6", sizeof("-6")) == 0) {
flag = DOMAIN_FLAG_ADDR_IPV6_IGN;
- } else if (strncmp(end + 1, "-", sizeof("-")) == 0) {
+ } else if (strncmp(domain_address, "-", sizeof("-")) == 0) {
flag = DOMAIN_FLAG_ADDR_IGN;
} else {
goto errout;
@@ -695,7 +676,7 @@ static int _config_address(void *data, int argc, char *argv[])
return 0;
} else {
/* set address to domain */
- if (parse_ip(end + 1, ip, &port) != 0) {
+ if (parse_ip(domain_address, ip, &port) != 0) {
goto errout;
}
@@ -753,28 +734,45 @@ errout:
free(address);
}
+ tlog(TLOG_ERROR, "add address %s, %s failed", domain, domain_address);
+ return 0;
+}
+
+static int _config_address(void *data, int argc, char *argv[])
+{
+ char *value = argv[1];
+ char domain[DNS_MAX_CONF_CNAME_LEN];
+
+ if (argc <= 1) {
+ goto errout;
+ }
+
+ if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {
+ goto errout;
+ }
+
+ return _conf_domain_rule_address(domain, value);
+errout:
tlog(TLOG_ERROR, "add address %s failed", value);
return 0;
}
-static int _config_speed_check_mode(void *data, int argc, char *argv[])
+static int _config_speed_check_mode_parser(struct dns_domain_check_order *check_order, const char *mode)
{
- char mode[DNS_MAX_OPT_LEN];
+ char tmpbuff[DNS_MAX_OPT_LEN];
char *field;
char *ptr;
int order = 0;
int port = 80;
int i = 0;
- if (argc <= 1) {
- return -1;
- }
+ safe_strncpy(tmpbuff, mode, DNS_MAX_OPT_LEN);
+ memset(check_order, 0, sizeof(*check_order));
- safe_strncpy(mode, argv[1], sizeof(mode));
- ptr = mode;
+ ptr = tmpbuff;
do {
field = ptr;
- ptr = strstr(mode, ",");
+ ptr = strstr(ptr, ",");
if (field == NULL || order >= DOMAIN_CHECK_NUM) {
return 0;
}
@@ -784,7 +782,13 @@ static int _config_speed_check_mode(void *data, int argc, char *argv[])
}
if (strncmp(field, "ping", sizeof("ping")) == 0) {
- dns_conf_check_order.order[order] = DOMAIN_CHECK_ICMP;
+ if (dns_has_cap_ping == 0) {
+ if (ptr) {
+ ptr++;
+ }
+ continue;
+ }
+ check_order->order[order] = DOMAIN_CHECK_ICMP;
} else if (strstr(field, "tcp") == field) {
char *port_str = strstr(field, ":");
if (port_str) {
@@ -794,12 +798,12 @@ static int _config_speed_check_mode(void *data, int argc, char *argv[])
}
}
- dns_conf_check_order.order[order] = DOMAIN_CHECK_TCP;
- dns_conf_check_order.tcp_port = port;
+ check_order->order[order] = DOMAIN_CHECK_TCP;
+ check_order->tcp_port = port;
} else if (strncmp(field, "none", sizeof("none")) == 0) {
- dns_conf_check_order.order[order] = DOMAIN_CHECK_NONE;
+ check_order->order[order] = DOMAIN_CHECK_NONE;
for (i = order + 1; i < DOMAIN_CHECK_NUM; i++) {
- dns_conf_check_order.order[i] = DOMAIN_CHECK_NONE;
+ check_order->order[i] = DOMAIN_CHECK_NONE;
}
return 0;
@@ -809,11 +813,23 @@ static int _config_speed_check_mode(void *data, int argc, char *argv[])
ptr++;
}
- } while (1);
+ } while (ptr);
return 0;
}
+static int _config_speed_check_mode(void *data, int argc, char *argv[])
+{
+ char mode[DNS_MAX_OPT_LEN];
+
+ if (argc <= 1) {
+ return -1;
+ }
+
+ safe_strncpy(mode, argv[1], sizeof(mode));
+ return _config_speed_check_mode_parser(&dns_conf_check_order, mode);
+}
+
static int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type)
{
int index = dns_conf_bind_ip_num;
@@ -950,56 +966,12 @@ static int _config_server_https(void *data, int argc, char *argv[])
return ret;
}
-static int _config_nameserver(void *data, int argc, char *argv[])
+static int _conf_domain_rule_nameserver(char *domain, const char *group_name)
{
struct dns_nameserver_rule *nameserver_rule = NULL;
- char domain[DNS_MAX_CONF_CNAME_LEN];
- char group_name[DNS_GROUP_NAME_LEN];
const char *group = NULL;
- char *begin = NULL;
- char *end = NULL;
- int len = 0;
- char *value = argv[1];
- if (argc <= 1) {
- goto errout;
- }
-
- /* first field */
- begin = strstr(value, "/");
- if (begin == NULL) {
- goto errout;
- }
-
- /* second field */
- begin++;
- end = strstr(begin, "/");
- if (end == NULL) {
- goto errout;
- }
-
- /* remove prefix . */
- while (*begin == '.') {
- begin++;
- }
-
- len = end - begin;
-
- if (len >= sizeof(domain)) {
- tlog(TLOG_ERROR, "domain name %s too long", value);
- goto errout;
- }
-
- memcpy(domain, begin, len);
- domain[len] = '\0';
-
- len = strlen(end + 1);
- if (len <= 0) {
- goto errout;
- }
-
- if (strncmp(end + 1, "-", sizeof("-")) != 0) {
- safe_strncpy(group_name, end + 1, DNS_GROUP_NAME_LEN);
+ if (strncmp(group_name, "-", sizeof("-")) != 0) {
group = _dns_conf_get_group_name(group_name);
if (group == NULL) {
goto errout;
@@ -1030,6 +1002,25 @@ errout:
free(nameserver_rule);
}
+ tlog(TLOG_ERROR, "add nameserver %s, %s failed", domain, group_name);
+ return 0;
+}
+
+static int _config_nameserver(void *data, int argc, char *argv[])
+{
+ char domain[DNS_MAX_CONF_CNAME_LEN];
+ char *value = argv[1];
+
+ if (argc <= 1) {
+ goto errout;
+ }
+
+ if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {
+ goto errout;
+ }
+
+ return _conf_domain_rule_nameserver(domain, value);
+errout:
tlog(TLOG_ERROR, "add nameserver %s failed", value);
return 0;
}
@@ -1188,6 +1179,127 @@ errout:
return -1;
}
+static int _conf_domain_rule_speed_check(char *domain, const char *mode)
+{
+ struct dns_domain_check_order *check_order;
+
+ check_order = malloc(sizeof(*check_order));
+ if (check_order == NULL) {
+ goto errout;
+ }
+
+ if (_config_speed_check_mode_parser(check_order, mode) != 0) {
+ goto errout;
+ }
+
+ if (_config_domain_rule_add(domain, DOMAIN_RULE_CHECKSPEED, check_order) != 0) {
+ goto errout;
+ }
+
+ return 0;
+errout:
+ if (check_order) {
+ free(check_order);
+ }
+ return 0;
+}
+
+static int _conf_domain_rules(void *data, int argc, char *argv[])
+{
+ int opt = 0;
+ char domain[DNS_MAX_CONF_CNAME_LEN];
+ char *value = argv[1];
+
+ /* clang-format off */
+ static struct option long_options[] = {
+ {"speed-check-mode", required_argument, NULL, 'c'},
+ {"address", required_argument, NULL, 'a'},
+ {"ipset", required_argument, NULL, 'p'},
+ {"nameserver", required_argument, NULL, 'n'},
+ {NULL, no_argument, NULL, 0}
+ };
+ /* clang-format on */
+
+ if (argc <= 1) {
+ tlog(TLOG_ERROR, "invalid parameter.");
+ goto errout;
+ }
+
+ if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {
+ goto errout;
+ }
+
+ /* process extra options */
+ optind = 1;
+ while (1) {
+ opt = getopt_long_only(argc, argv, "", long_options, NULL);
+ if (opt == -1) {
+ break;
+ }
+
+ switch (opt) {
+ case 'c': {
+ const char *check_mode = optarg;
+ if (check_mode == NULL) {
+ goto errout;
+ }
+
+ if (_conf_domain_rule_speed_check(domain, check_mode) != 0) {
+ tlog(TLOG_ERROR, "add check-speed-rule rule failed.");
+ goto errout;
+ }
+
+ break;
+ }
+ case 'a': {
+ const char *address = optarg;
+ if (address == NULL) {
+ goto errout;
+ }
+
+ if (_conf_domain_rule_address(domain, address) != 0) {
+ tlog(TLOG_ERROR, "add address rule failed.");
+ goto errout;
+ }
+
+ break;
+ }
+ case 'p': {
+ const char *ipsetname = optarg;
+ if (ipsetname == NULL) {
+ goto errout;
+ }
+
+ if (_conf_domain_rule_ipset(domain, ipsetname) != 0) {
+ tlog(TLOG_ERROR, "add ipset rule failed.");
+ goto errout;
+ }
+
+ break;
+ }
+ case 'n': {
+ const char *nameserver_group = optarg;
+ if (nameserver_group == NULL) {
+ goto errout;
+ }
+
+ if (_conf_domain_rule_nameserver(domain, nameserver_group) != 0) {
+ tlog(TLOG_ERROR, "add nameserver rule failed.");
+ goto errout;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return 0;
+errout:
+ return -1;
+}
+
static int _config_log_level(void *data, int argc, char *argv[])
{
/* read log level and set */
@@ -1248,6 +1360,7 @@ static struct config_item _config_item[] = {
CONF_CUSTOM("bogus-nxdomain", _conf_bogus_nxdomain, NULL),
CONF_CUSTOM("ignore-ip", _conf_ip_ignore, NULL),
CONF_CUSTOM("edns-client-subnet", _conf_edns_client_subnet, NULL),
+ CONF_CUSTOM("domain-rules", _conf_domain_rules, NULL),
CONF_CUSTOM("conf-file", config_addtional_file, NULL),
CONF_END(),
};
@@ -1323,6 +1436,8 @@ static int _dns_conf_speed_check_mode_verify(void)
int i, j;
int has_cap = has_network_raw_cap();
int print_log = 0;
+
+ dns_has_cap_ping = has_cap;
if (has_cap == 1) {
return 0;
}
diff --git a/src/dns_conf.h b/src/dns_conf.h
index 1ed6350..280b280 100644
--- a/src/dns_conf.h
+++ b/src/dns_conf.h
@@ -56,6 +56,7 @@ enum domain_rule {
DOMAIN_RULE_ADDRESS_IPV6,
DOMAIN_RULE_IPSET,
DOMAIN_RULE_NAMESERVER,
+ DOMAIN_RULE_CHECKSPEED,
DOMAIN_RULE_MAX,
};
diff --git a/src/dns_server.c b/src/dns_server.c
index f0e76cf..81f9f69 100644
--- a/src/dns_server.c
+++ b/src/dns_server.c
@@ -1935,6 +1935,19 @@ errout:
return -1;
}
+static void _dns_server_prolcess_speed_check_rule(struct dns_request *request)
+{
+ struct dns_domain_check_order *check_order = NULL;
+
+ /* get domain rule flag */
+ check_order = request->domain_rule.rules[DOMAIN_RULE_CHECKSPEED];
+ if (check_order == NULL) {
+ return;
+ }
+
+ request->check_order_list = check_order;
+}
+
static int _dns_server_process_cache(struct dns_request *request)
{
struct dns_cache *dns_cache = NULL;
@@ -2109,6 +2122,17 @@ static const char *_dns_server_get_request_groupname(struct dns_request *request
return NULL;
}
+static void _dns_server_check_set_passthrough(struct dns_request *request)
+{
+ if (request->check_order_list->order[0] == DOMAIN_CHECK_NONE) {
+ request->passthrough = 1;
+ }
+
+ if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SPEED_CHECK) == 0) {
+ request->passthrough = 1;
+ }
+}
+
static int _dns_server_do_query(struct dns_request *request, const char *domain, int qtype)
{
int ret = -1;
@@ -2143,6 +2167,12 @@ static int _dns_server_do_query(struct dns_request *request, const char *domain,
goto clean_exit;
}
+ /* process speed check rule */
+ _dns_server_prolcess_speed_check_rule(request);
+
+ /* check and set passthrough */
+ _dns_server_check_set_passthrough(request);
+
/* process cache */
if (request->prefetch == 0) {
if (_dns_server_process_cache(request) == 0) {
@@ -2186,17 +2216,6 @@ errout:
return ret;
}
-static void _dns_server_check_set_passthrough(struct dns_request *request, struct dns_server_conn_head *conn)
-{
- if (request->check_order_list->order[0] == DOMAIN_CHECK_NONE) {
- request->passthrough = 1;
- }
-
- if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SPEED_CHECK) == 0) {
- request->passthrough = 1;
- }
-}
-
static int _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *local, socklen_t local_len,
struct sockaddr_storage *from, socklen_t from_len)
{
@@ -2254,7 +2273,6 @@ static int _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *in
memcpy(&request->localaddr, local, local_len);
_dns_server_request_set_client(request, conn);
- _dns_server_check_set_passthrough(request, conn);
_dns_server_request_set_client_addr(request, from, from_len);
_dns_server_request_set_id(request, packet->head.id);
_dns_server_set_dualstack_selection(request);