Kaynağa Gözat

cache: support persist cache when restart smartdns

Nick Peng 5 yıl önce
ebeveyn
işleme
0b45da29c7

+ 2 - 0
ReadMe.md

@@ -504,6 +504,8 @@ https://github.com/pymumu/smartdns/releases
 |bind|DNS监听端口号|[::]:53|可绑定多个端口<br>`IP:PORT`: 服务器IP,端口号。<br>`[-group]`: 请求时使用的DNS服务器组。<br>`[-no-rule-addr]`:跳过address规则。<br>`[-no-rule-nameserver]`:跳过Nameserver规则。<br>`[-no-rule-ipset]`:跳过Ipset规则。<br>`[no-rule-soa]`:跳过SOA(#)规则.<br>`[no-dualstack-selection]`:停用双栈测速。<br>`[-no-speed-check]`:停用测速。<br>`[-no-cache]`:停止缓存|bind :53
 |bind-tcp|TCP DNS监听端口号|[::]:53|可绑定多个端口<br>`IP:PORT`: 服务器IP,端口号。<br>`[-group]`: 请求时使用的DNS服务器组。<br>`[-no-rule-addr]`:跳过address规则。<br>`[-no-rule-nameserver]`:跳过Nameserver规则。<br>`[-no-rule-ipset]`:跳过Ipset规则。<br>`[no-rule-soa]`:跳过SOA(#)规则.<br>`[no-dualstack-selection]`:停用双栈测速。<br>`[-no-speed-check]`:停用测速。<br>`[-no-cache]`:停止缓存|bind-tcp :53
 |cache-size|域名结果缓存个数|512|数字|cache-size 512
+|cache-persist|是否持久化缓存|no|[yes\|no]|cache-persist yes
+|cache-file|缓存持久化文件路径|/tmp/smartdns.cache|路径|cache-file /tmp/smartdns.cache
 |tcp-idle-time|TCP链接空闲超时时间|120|数字|tcp-idle-time 120
 |rr-ttl|域名结果TTL|远程查询结果|大于0的数字|rr-ttl 600
 |rr-ttl-min|允许的最小TTL值|远程查询结果|大于0的数字|rr-ttl-min 60

+ 2 - 0
ReadMe_en.md

@@ -498,6 +498,8 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |bind|DNS listening port number|[::]:53|Support binding multiple ports<br>`IP:PORT`: server IP, port number. <br>`[-group]`: The DNS server group used when requesting. <br>`[-no-rule-addr]`: Skip the address rule. <br>`[-no-rule-nameserver]`: Skip the Nameserver rule. <br>`[-no-rule-ipset]`: Skip the Ipset rule. <br>`[-no-rule-soa]`: Skip address SOA(#) rules.<br>`[-no-dualstack-selection]`: Disable dualstack ip selection.<br>`[-no-speed-check]`: Disable speed measurement. <br>`[-no-cache]`: stop caching |bind :53
 |bind-tcp|TCP mode DNS listening port number|[::]:53|Support binding multiple ports<br>`IP:PORT`: server IP, port number. <br>`[-group]`: The DNS server group used when requesting. <br>`[-no-rule-addr]`: Skip the address rule. <br>`[-no-rule-nameserver]`: Skip the Nameserver rule. <br>`[-no-rule-ipset]`: Skip the Ipset rule. <br>`[-no-rule-soa]`: Skip address SOA(#) rules.<br>`[-no-dualstack-selection]`: Disable dualstack ip selection.<br>`[-no-speed-check]`: Disable speed measurement. <br>`[-no-cache]`: stop caching |bind-tcp :53
 |cache-size|Domain name result cache number|512|integer|cache-size 512
+|cache-persist|enable persist cache|no|[yes\|no]|cache-persist yes
+|cache-file|cache persist file|/tmp/smartdns.cache|路径|cache-file /tmp/smartdns.cache
 |tcp-idle-time|TCP connection idle timeout|120|integer|tcp-idle-time 120
 |rr-ttl|Domain name TTL|Remote query result|number greater than 0|rr-ttl 600
 |rr-ttl-min|Domain name Minimum TTL|Remote query result|number greater than 0|rr-ttl-min 60

+ 7 - 1
etc/smartdns/smartdns.conf

@@ -38,7 +38,13 @@ bind [::]:53
 # dns cache size
 # cache-size [number]
 #   0: for no cache
-cache-size 512
+cache-size 4096
+
+# enable persist cache when restart
+# cache-persist yes
+
+# cache persist file
+# cache-file /tmp/smartdns.cache
 
 # prefetch domain
 # prefetch-domain [yes|no]

+ 21 - 2
package/optware/S50smartdns

@@ -332,9 +332,28 @@ case "$1" in
 		return 0
 	fi
 
-	if [ ! -d "/proc/$pid" ]; then
-		return 0;
+	kill -15 "$pid" 2>/dev/null
+	SLEEP=`which usleep`
+	SLEEPTIME=200000
+	if [ -z "$SLEEP" ]; then
+		SLEEP="sleep"
+		SLEEPTIME=0.2
 	fi
+	N=30
+	while [ $N -gt 0 ]
+	do
+		pid="$(cat "$SMARTDNS_PID" | head -n 1 2>/dev/null)"
+		if [ -z "$pid" ]; then
+			return 0
+		fi
+
+		if [ ! -d "/proc/$pid" ]; then
+			return 0;
+		fi
+
+		$SLEEP $SLEEPTIME 2>/dev/null
+		N=$((N-1))
+	done
 
 	kill -9 "$pid" 2>/dev/null
 	;;

+ 270 - 48
src/dns_cache.c

@@ -19,7 +19,11 @@
 #include "dns_cache.h"
 #include "stringutil.h"
 #include "tlog.h"
+#include <errno.h>
+#include <fcntl.h>
 #include <pthread.h>
+#include <string.h>
+#include <sys/types.h>
 
 #define DNS_CACHE_MAX_HITNUM 5000
 #define DNS_CACHE_HITNUM_STEP 2
@@ -65,7 +69,7 @@ static __attribute__((unused)) struct dns_cache *_dns_cache_last(void)
 	return list_last_entry(&dns_cache_head.cache_list, struct dns_cache, list);
 }
 
-static struct dns_cache *_dns_cache_first(void)
+static struct dns_cache *_dns_inactive_cache_first(void)
 {
 	struct dns_cache *dns_cache = NULL;
 
@@ -162,7 +166,7 @@ struct dns_cache_data *dns_cache_new_data_addr(uint32_t cache_flag, char *cname,
 
 	cache_addr->head.cache_flag = cache_flag;
 	cache_addr->head.cache_type = CACHE_TYPE_ADDR;
-	cache_addr->head.size = sizeof(struct dns_cache_addr) - sizeof(struct dns_cache_head);
+	cache_addr->head.size = sizeof(struct dns_cache_addr) - sizeof(struct dns_cache_data_head);
 
 	return (struct dns_cache_data *)cache_addr;
 
@@ -219,12 +223,12 @@ int dns_cache_replace(char *domain, int ttl, dns_type_t qtype, int speed, struct
 
 	/* update cache data */
 	pthread_mutex_lock(&dns_cache_head.lock);
-	dns_cache->ttl = ttl;
-	dns_cache->qtype = qtype;
-	dns_cache->ttl = ttl;
 	dns_cache->del_pending = 0;
-	dns_cache->speed = speed;
-	time(&dns_cache->insert_time);
+	dns_cache->info.ttl = ttl;
+	dns_cache->info.qtype = qtype;
+	dns_cache->info.ttl = ttl;
+	dns_cache->info.speed = speed;
+	time(&dns_cache->info.insert_time);
 	old_cache_data = dns_cache->cache_data;
 	dns_cache->cache_data = cache_data;
 	list_del_init(&dns_cache->list);
@@ -236,21 +240,13 @@ int dns_cache_replace(char *domain, int ttl, dns_type_t qtype, int speed, struct
 	return 0;
 }
 
-int dns_cache_insert(char *domain, int ttl, dns_type_t qtype, int speed, struct dns_cache_data *cache_data)
+int _dns_cache_insert(struct dns_cache_info *info, struct dns_cache_data *cache_data, struct list_head *head)
 {
 	uint32_t key = 0;
 	struct dns_cache *dns_cache = NULL;
 
-	if (cache_data == NULL || domain == NULL) {
-		return -1;
-	}
-
-	if (dns_cache_head.size <= 0) {
-		return 0;
-	}
-
 	/* if cache already exists, free */
-	dns_cache = dns_cache_lookup(domain, qtype);
+	dns_cache = dns_cache_lookup(info->domain, info->qtype);
 	if (dns_cache) {
 		dns_cache_delete(dns_cache);
 		dns_cache_release(dns_cache);
@@ -262,32 +258,22 @@ int dns_cache_insert(char *domain, int ttl, dns_type_t qtype, int speed, struct
 		goto errout;
 	}
 
-	if (ttl < DNS_CACHE_TTL_MIN) {
-		ttl = DNS_CACHE_TTL_MIN;
-	}
-
 	memset(dns_cache, 0, sizeof(*dns_cache));
-	key = hash_string(domain);
-	key = jhash(&qtype, sizeof(qtype), key);
-	safe_strncpy(dns_cache->domain, domain, DNS_MAX_CNAME_LEN);
-	atomic_set(&dns_cache->hitnum, 3);
+	key = hash_string(info->domain);
+	key = jhash(&info->qtype, sizeof(info->qtype), key);
 	atomic_set(&dns_cache->ref, 1);
-	dns_cache->qtype = qtype;
-	dns_cache->ttl = ttl;
-	dns_cache->hitnum_update_add = DNS_CACHE_HITNUM_STEP;
+	memcpy(&dns_cache->info, info, sizeof(*info));
 	dns_cache->del_pending = 0;
-	dns_cache->speed = speed;
 	dns_cache->cache_data = cache_data;
-	time(&dns_cache->insert_time);
 	pthread_mutex_lock(&dns_cache_head.lock);
 	hash_add(dns_cache_head.cache_hash, &dns_cache->node, key);
-	list_add_tail(&dns_cache->list, &dns_cache_head.cache_list);
+	list_add_tail(&dns_cache->list, head);
 	INIT_LIST_HEAD(&dns_cache->check_list);
 
 	/* Release extra cache, remove oldest cache record */
 	if (atomic_inc_return(&dns_cache_head.num) > dns_cache_head.size) {
 		struct dns_cache *del_cache;
-		del_cache = _dns_cache_first();
+		del_cache = _dns_inactive_cache_first();
 		if (del_cache) {
 			_dns_cache_remove(del_cache);
 		}
@@ -303,6 +289,33 @@ errout:
 	return -1;
 }
 
+int dns_cache_insert(char *domain, int ttl, dns_type_t qtype, int speed, struct dns_cache_data *cache_data)
+{
+	struct dns_cache_info info;
+
+	if (cache_data == NULL || domain == NULL) {
+		return -1;
+	}
+
+	if (dns_cache_head.size <= 0) {
+		return 0;
+	}
+
+	if (ttl < DNS_CACHE_TTL_MIN) {
+		ttl = DNS_CACHE_TTL_MIN;
+	}
+
+	info.hitnum = 3;
+	safe_strncpy(info.domain, domain, DNS_MAX_CNAME_LEN);
+	info.qtype = qtype;
+	info.ttl = ttl;
+	info.hitnum_update_add = DNS_CACHE_HITNUM_STEP;
+	info.speed = speed;
+	time(&info.insert_time);
+
+	return _dns_cache_insert(&info, cache_data, &dns_cache_head.cache_list);
+}
+
 struct dns_cache *dns_cache_lookup(char *domain, dns_type_t qtype)
 {
 	uint32_t key = 0;
@@ -322,11 +335,11 @@ struct dns_cache *dns_cache_lookup(char *domain, dns_type_t qtype)
 	pthread_mutex_lock(&dns_cache_head.lock);
 	hash_for_each_possible(dns_cache_head.cache_hash, dns_cache, node, key)
 	{
-		if (dns_cache->qtype != qtype) {
+		if (dns_cache->info.qtype != qtype) {
 			continue;
 		}
 
-		if (strncmp(domain, dns_cache->domain, DNS_MAX_CNAME_LEN) != 0) {
+		if (strncmp(domain, dns_cache->info.domain, DNS_MAX_CNAME_LEN) != 0) {
 			continue;
 		}
 
@@ -336,7 +349,7 @@ struct dns_cache *dns_cache_lookup(char *domain, dns_type_t qtype)
 
 	if (dns_cache_ret) {
 		/* Return NULL if the cache times out */
-		if (dns_cache_head.enable_inactive == 0 && (now - dns_cache_ret->insert_time > dns_cache_ret->ttl)) {
+		if (dns_cache_head.enable_inactive == 0 && (now - dns_cache_ret->info.insert_time > dns_cache_ret->info.ttl)) {
 			_dns_cache_remove(dns_cache_ret);
 			dns_cache_ret = NULL;
 		} else {
@@ -355,7 +368,7 @@ int dns_cache_get_ttl(struct dns_cache *dns_cache)
 	int ttl = 0;
 	time(&now);
 
-	ttl = dns_cache->insert_time + dns_cache->ttl - now;
+	ttl = dns_cache->info.insert_time + dns_cache->info.ttl - now;
 	if (ttl < 0) {
 		return 0;
 	}
@@ -377,15 +390,14 @@ void dns_cache_delete(struct dns_cache *dns_cache)
 
 int dns_cache_hitnum_dec_get(struct dns_cache *dns_cache)
 {
-	int hitnum = 0;
 	pthread_mutex_lock(&dns_cache_head.lock);
-	hitnum = atomic_dec_return(&dns_cache->hitnum);
-	if (dns_cache->hitnum_update_add > DNS_CACHE_HITNUM_STEP) {
-		dns_cache->hitnum_update_add--;
+	dns_cache->info.hitnum--;
+	if (dns_cache->info.hitnum_update_add > DNS_CACHE_HITNUM_STEP) {
+		dns_cache->info.hitnum_update_add--;
 	}
 	pthread_mutex_unlock(&dns_cache_head.lock);
 
-	return hitnum;
+	return dns_cache->info.hitnum;
 }
 
 void dns_cache_update(struct dns_cache *dns_cache)
@@ -394,13 +406,13 @@ void dns_cache_update(struct dns_cache *dns_cache)
 	if (!list_empty(&dns_cache->list)) {
 		list_del_init(&dns_cache->list);
 		list_add_tail(&dns_cache->list, &dns_cache_head.cache_list);
-		atomic_add(dns_cache->hitnum_update_add, &dns_cache->hitnum);
-		if (atomic_read(&dns_cache->hitnum) > DNS_CACHE_MAX_HITNUM) {
-			atomic_set(&dns_cache->hitnum, DNS_CACHE_MAX_HITNUM);
+		dns_cache->info.hitnum += dns_cache->info.hitnum_update_add;
+		if (dns_cache->info.hitnum > DNS_CACHE_MAX_HITNUM) {
+			dns_cache->info.hitnum = DNS_CACHE_MAX_HITNUM;
 		}
 
-		if (dns_cache->hitnum_update_add < DNS_CACHE_HITNUM_STEP_MAX) {
-			dns_cache->hitnum_update_add++;
+		if (dns_cache->info.hitnum_update_add < DNS_CACHE_HITNUM_STEP_MAX) {
+			dns_cache->info.hitnum_update_add++;
 		}
 	}
 	pthread_mutex_unlock(&dns_cache_head.lock);
@@ -414,7 +426,7 @@ void _dns_cache_remove_expired_ttl(time_t *now)
 
 	list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.inactive_list, list)
 	{
-		ttl = dns_cache->insert_time + dns_cache->ttl - *now;
+		ttl = dns_cache->info.insert_time + dns_cache->info.ttl - *now;
 		if (ttl > 0) {
 			continue;
 		}
@@ -443,7 +455,7 @@ void dns_cache_invalidate(dns_cache_preinvalid_callback callback, int ttl_pre)
 	pthread_mutex_lock(&dns_cache_head.lock);
 	list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.cache_list, list)
 	{
-		ttl = dns_cache->insert_time + dns_cache->ttl - now;
+		ttl = dns_cache->info.insert_time + dns_cache->info.ttl - now;
 		if (ttl > 0 && ttl < ttl_pre) {
 			/* If the TTL time is in the pre-timeout range, call callback function */
 			if (callback && dns_cache->del_pending == 0) {
@@ -479,6 +491,216 @@ void dns_cache_invalidate(dns_cache_preinvalid_callback callback, int ttl_pre)
 	}
 }
 
+static int _dns_cache_read_record(int fd, uint32_t cache_number)
+{
+
+	int i = 0;
+	int ret = 0;
+	struct dns_cache_record cache_record;
+	struct dns_cache_data_head data_head;
+	struct dns_cache_data *cache_data = NULL;
+	struct list_head *head = NULL;
+
+	for (i = 0; i < cache_number; i++) {
+		ret = read(fd, &cache_record, sizeof(cache_record));
+		if (ret != sizeof(cache_record)) {
+			tlog(TLOG_ERROR, "read cache failed, %s", strerror(errno));
+			goto errout;
+		}
+
+		if (cache_record.magic != MAGIC_CACHE_DATA) {
+			tlog(TLOG_ERROR, "magic is invalid.");
+			goto errout;
+		}
+
+		if (cache_record.type == CACHE_RECORD_TYPE_ACTIVE) {
+			head = &dns_cache_head.cache_list;
+		} else {
+			head = &dns_cache_head.inactive_list;
+		}
+
+		ret = read(fd, &data_head, sizeof(data_head));
+		if (ret != sizeof(data_head)) {
+			tlog(TLOG_ERROR, "read data head failed, %s", strerror(errno));
+			goto errout;
+		}
+
+		if (data_head.size > 1024 * 8) {
+			tlog(TLOG_ERROR, "data may invalid, skip load cache.");
+			goto errout;
+		}
+
+		cache_data = malloc(data_head.size + sizeof(data_head));
+		if (cache_data == NULL) {
+			tlog(TLOG_ERROR, "malloc cache data failed %s", strerror(errno));
+			goto errout;
+		}
+
+		memcpy(&cache_data->head, &data_head, sizeof(data_head));
+		ret = read(fd, cache_data->data, data_head.size);
+		if (ret != data_head.size) {
+			tlog(TLOG_ERROR, "read cache data failed, %s", strerror(errno));
+			goto errout;
+		}
+
+		if (_dns_cache_insert(&cache_record.info, cache_data, head) != 0) {
+			tlog(TLOG_ERROR, "insert cache data failed.");
+			cache_data = NULL;
+			goto errout;
+		}
+
+		cache_data = NULL;
+	}
+
+	return 0;
+errout:
+	if (cache_data) {
+		free(cache_data);
+	}
+	return -1;
+}
+
+int dns_cache_load(const char *file)
+{
+	int fd = -1;
+	int ret = 0;
+	fd = open(file, O_RDONLY);
+	if (fd < 0) {
+		return 0;
+	}
+
+	struct dns_cache_file cache_file;
+	ret = read(fd, &cache_file, sizeof(cache_file));
+	if (ret != sizeof(cache_file)) {
+		tlog(TLOG_ERROR, "read cache head failed.");
+		goto errout;
+	}
+
+	if (cache_file.magic != MAGIC_NUMBER) {
+		tlog(TLOG_ERROR, "cache file is invalid.");
+		goto errout;
+	}
+
+	if (strncmp(cache_file.version, __TIMESTAMP__, DNS_CACHE_VERSION_LEN) != 0) {
+		tlog(TLOG_WARN, "cache version is different, skip load cache.");
+		goto errout;
+	}
+
+	if (_dns_cache_read_record(fd, cache_file.cache_number) != 0) {
+		goto errout;
+	}
+
+	close(fd);
+	return 0;
+errout:
+	if (fd > 0) {
+		close(fd);
+	}
+
+	return -1;
+}
+
+static int _dns_cache_write_record(int fd, uint32_t *cache_number, enum CACHE_RECORD_TYPE type, struct list_head *head)
+{
+	struct dns_cache *dns_cache = NULL;
+	struct dns_cache *tmp = NULL;
+	struct dns_cache_record cache_record;
+
+	pthread_mutex_lock(&dns_cache_head.lock);
+	list_for_each_entry_safe_reverse(dns_cache, tmp, head, list)
+	{
+		cache_record.magic = MAGIC_CACHE_DATA;
+		cache_record.type = type;
+		memcpy(&cache_record.info, &dns_cache->info, sizeof(struct dns_cache_info));
+		int ret = write(fd, &cache_record, sizeof(cache_record));
+		if (ret != sizeof(cache_record)) {
+			tlog(TLOG_ERROR, "write cache failed, %s", strerror(errno));
+			goto errout;
+		}
+
+		struct dns_cache_data *cache_data = dns_cache->cache_data;
+		ret = write(fd, cache_data, sizeof(*cache_data) + cache_data->head.size);
+		if (ret != sizeof(*cache_data) + cache_data->head.size) {
+			tlog(TLOG_ERROR, "write cache data failed, %s", strerror(errno));
+			goto errout;
+		}
+
+		(*cache_number)++;
+	}
+
+	pthread_mutex_unlock(&dns_cache_head.lock);
+	return 0;
+
+errout:
+	pthread_mutex_unlock(&dns_cache_head.lock);
+	return -1;
+}
+
+static int _dns_cache_write_records(int fd, uint32_t *cache_number)
+{
+
+	if (_dns_cache_write_record(fd, cache_number, CACHE_RECORD_TYPE_ACTIVE, &dns_cache_head.cache_list) != 0) {
+		return -1;
+	}
+
+	if (_dns_cache_write_record(fd, cache_number, CACHE_RECORD_TYPE_INACTIVE, &dns_cache_head.inactive_list) != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+int dns_cache_save(const char *file)
+{
+	int fd = -1;
+	uint32_t cache_number = 0;
+	tlog(TLOG_DEBUG, "write cache file %s", file);
+
+	fd = open(file, O_TRUNC | O_CREAT | O_WRONLY | O_CLOEXEC, 0640);
+	if (fd < 0) {
+		tlog(TLOG_ERROR, "create file %s failed, %s", file, strerror(errno));
+		goto errout;
+	}
+
+	struct dns_cache_file cache_file;
+	memset(&cache_file, 0, sizeof(cache_file));
+	cache_file.magic = MAGIC_NUMBER;
+	safe_strncpy(cache_file.version, __TIMESTAMP__, DNS_CACHE_VERSION_LEN);
+	cache_file.cache_number = 0;
+
+	if (lseek(fd, sizeof(cache_file), SEEK_SET) < 0) {
+		tlog(TLOG_ERROR, "seek file %s failed, %s", file, strerror(errno));
+		goto errout;
+	}
+
+	if (_dns_cache_write_records(fd, &cache_number) != 0) {
+		tlog(TLOG_ERROR, "write record to file %s failed.", file);
+		goto errout;
+	}
+
+	if (lseek(fd, 0, SEEK_SET) < 0) {
+		tlog(TLOG_ERROR, "seek file %s failed, %s", file, strerror(errno));
+		goto errout;
+	}
+
+	cache_file.cache_number = cache_number;
+	if (write(fd, &cache_file, sizeof(cache_file)) != sizeof(cache_file)) {
+		tlog(TLOG_ERROR, "write file head %s failed, %s, %d", file, strerror(errno), fd);
+		goto errout;
+	}
+
+	tlog(TLOG_DEBUG, "wrote total %d records.", cache_number);
+
+	close(fd);
+	return 0;
+errout:
+	if (fd > 0) {
+		close(fd);
+	}
+
+	return -1;
+}
+
 void dns_cache_destroy(void)
 {
 	struct dns_cache *dns_cache = NULL;

+ 40 - 11
src/dns_cache.h

@@ -32,12 +32,21 @@ extern "C" {
 #endif
 
 #define DNS_CACHE_TTL_MIN 30
+#define DNS_CACHE_VERSION_LEN 32
+#define MAGIC_NUMBER 0x6548634163536e44
+#define MAGIC_CACHE_DATA 0x44615461
 
 enum CACHE_TYPE {
 	CACHE_TYPE_NONE,
 	CACHE_TYPE_ADDR,
 	CACHE_TYPE_PACKET,
 };
+
+enum CACHE_RECORD_TYPE {
+	CACHE_RECORD_TYPE_ACTIVE,
+	CACHE_RECORD_TYPE_INACTIVE,
+};
+
 struct dns_cache_data_head {
 	uint32_t cache_flag;
 	enum CACHE_TYPE cache_type;
@@ -67,32 +76,48 @@ struct dns_cache_packet {
 	unsigned char data[0];
 };
 
-struct dns_cache {
-	struct hlist_node node;
-	struct list_head list;
-	struct list_head check_list;
-	
-	atomic_t ref;
-	atomic_t hitnum;
-
+struct dns_cache_info {
 	char domain[DNS_MAX_CNAME_LEN];
 	int ttl;
+	int hitnum;
 	int speed;
 	int hitnum_update_add;
-	int del_pending;
 	time_t insert_time;
-	
 	dns_type_t qtype;
+};
+
+struct dns_cache_record {
+	uint32_t magic;
+	enum CACHE_RECORD_TYPE type;
+	struct dns_cache_info info;
+};
+
+struct dns_cache {
+	struct hlist_node node;
+	struct list_head list;
+	struct list_head check_list;
+
+	atomic_t ref;
+	int del_pending;
+
+	struct dns_cache_info info;
 	struct dns_cache_data *cache_data;
 };
 
+struct dns_cache_file {
+	uint64_t magic;
+	char version[DNS_CACHE_VERSION_LEN];
+	uint32_t cache_number;
+};
+
 enum CACHE_TYPE dns_cache_data_type(struct dns_cache_data *cache_data);
 
 uint32_t dns_cache_get_cache_flag(struct dns_cache_data *cache_data);
 
 void dns_cache_data_free(struct dns_cache_data *data);
 
-struct dns_cache_data *dns_cache_new_data_addr(uint32_t cache_flag, char *cname, int cname_ttl, unsigned char *addr, int addr_len);
+struct dns_cache_data *dns_cache_new_data_addr(uint32_t cache_flag, char *cname, int cname_ttl, unsigned char *addr,
+											   int addr_len);
 
 struct dns_cache_data *dns_cache_new_data_packet(uint32_t cache_flag, void *packet, size_t packet_len);
 
@@ -124,6 +149,10 @@ struct dns_cache_data *dns_cache_get_data(struct dns_cache *dns_cache);
 
 void dns_cache_destroy(void);
 
+int dns_cache_load(const char *file);
+
+int dns_cache_save(const char *file);
+
 #ifdef __cpluscplus
 }
 #endif

+ 9 - 0
src/dns_conf.c

@@ -72,6 +72,9 @@ int dns_conf_log_num = 8;
 char dns_conf_ca_file[DNS_MAX_PATH];
 char dns_conf_ca_path[DNS_MAX_PATH];
 
+char dns_conf_cache_file[DNS_MAX_PATH];
+int dns_conf_cache_persist = 2;
+
 /* auditing */
 int dns_conf_audit_enable = 0;
 int dns_conf_audit_log_SOA;
@@ -107,6 +110,10 @@ static int _get_domain(char *value, char *domain, int max_dmain_size, char **ptr
 	char *end = NULL;
 	int len = 0;
 
+	if (value == NULL || domain == NULL) {
+		goto errout;
+	}
+
 	/* first field */
 	begin = strstr(value, "/");
 	if (begin == NULL) {
@@ -1354,6 +1361,8 @@ static struct config_item _config_item[] = {
 	CONF_CUSTOM("speed-check-mode", _config_speed_check_mode, NULL),
 	CONF_INT("tcp-idle-time", &dns_conf_tcp_idle_time, 0, 3600),
 	CONF_INT("cache-size", &dns_conf_cachesize, 0, CONF_INT_MAX),
+	CONF_STRING("cache-file", (char *)&dns_conf_cache_file, DNS_MAX_PATH),
+	CONF_YESNO("cache-persist", &dns_conf_cache_persist),
 	CONF_YESNO("prefetch-domain", &dns_conf_prefetch),
 	CONF_YESNO("serve-expired", &dns_conf_serve_expired),
 	CONF_INT("serve-expired-ttl", &dns_conf_serve_expired_ttl, 0, CONF_INT_MAX),

+ 5 - 1
src/dns_conf.h

@@ -49,6 +49,7 @@ extern "C" {
 #define SMARTDNS_CONF_FILE "/etc/smartdns/smartdns.conf"
 #define SMARTDNS_LOG_FILE "/var/log/smartdns.log"
 #define SMARTDNS_AUDIT_FILE "/var/log/smartdns-audit.log"
+#define SMARTDNS_CACHE_FILE "/tmp/smartdns.cache"
 
 enum domain_rule {
 	DOMAIN_RULE_FLAGS = 0,
@@ -90,7 +91,7 @@ typedef enum {
 #define BIND_FLAG_NO_SPEED_CHECK (1 << 5)
 #define BIND_FLAG_NO_CACHE (1 << 6)
 #define BIND_FLAG_NO_DUALSTACK_SELECTION (1 << 7)
-#define BIND_FLAG_FORCE_AAAA_SOA (1 << 8) 
+#define BIND_FLAG_FORCE_AAAA_SOA (1 << 8)
 
 struct dns_rule_flags {
 	unsigned int flags;
@@ -215,6 +216,9 @@ extern int dns_conf_log_num;
 extern char dns_conf_ca_file[DNS_MAX_PATH];
 extern char dns_conf_ca_path[DNS_MAX_PATH];
 
+extern char dns_conf_cache_file[DNS_MAX_PATH];
+extern int dns_conf_cache_persist;
+
 extern struct dns_domain_check_order dns_conf_check_order;
 
 extern struct dns_server_groups dns_conf_server_groups[DNS_NAX_GROUP_NUMBER];

+ 71 - 15
src/dns_server.c

@@ -52,6 +52,7 @@
 #define DNS_PING_SECOND_TIMEOUT (DNS_REQUEST_MAX_TIMEOUT - DNS_TCPPING_START)
 #define SOCKET_IP_TOS (IPTOS_LOWDELAY | IPTOS_RELIABILITY)
 #define SOCKET_PRIORITY (6)
+#define CACHE_AUTO_ENABLE_SIZE (1024 * 1024 * 128)
 
 #define RECV_ERROR_AGAIN 1
 #define RECV_ERROR_OK 0
@@ -490,7 +491,7 @@ static int _dns_server_reply_udp(struct dns_request *request, struct dns_server_
 	send_len =
 		sendto(udpserver->head.fd, inpacket, inpacket_len, 0, (struct sockaddr *)&request->addr, request->addr_len);
 	if (send_len != inpacket_len) {
-		tlog(TLOG_ERROR, "send failed.");
+		tlog(TLOG_ERROR, "send failed, %s", strerror(errno));
 		return -1;
 	}
 
@@ -1883,7 +1884,7 @@ static int _dns_server_reply_passthrouth(struct dns_request *request, struct dns
 		}
 	}
 
-	if(_dns_server_setup_ipset_packet(request, packet) != 0) {
+	if (_dns_server_setup_ipset_packet(request, packet) != 0) {
 		tlog(TLOG_DEBUG, "setup ipset failed.");
 	}
 
@@ -2314,7 +2315,7 @@ static int _dns_server_process_cache_packet(struct dns_request *request, struct
 		goto errout;
 	}
 
-	if (dns_cache->qtype != request->qtype) {
+	if (dns_cache->info.qtype != request->qtype) {
 		goto errout;
 	}
 
@@ -2405,15 +2406,15 @@ static int _dns_server_process_cache(struct dns_request *request)
 		goto out;
 	}
 
-	if (request->qtype != dns_cache->qtype) {
+	if (request->qtype != dns_cache->info.qtype) {
 		goto out;
 	}
 
 	if (request->dualstack_selection && request->qtype == DNS_T_AAAA) {
 		dns_cache_A = dns_cache_lookup(request->domain, DNS_T_A);
-		if (dns_cache_A && (dns_cache_A->speed > 0)) {
-			if ((dns_cache_A->speed + (dns_conf_dualstack_ip_selection_threshold * 10)) < dns_cache->speed ||
-				dns_cache->speed < 0) {
+		if (dns_cache_A && (dns_cache_A->info.speed > 0)) {
+			if ((dns_cache_A->info.speed + (dns_conf_dualstack_ip_selection_threshold * 10)) < dns_cache->info.speed ||
+				dns_cache->info.speed < 0) {
 				tlog(TLOG_DEBUG, "Force IPV4 perfered.");
 				ret = _dns_server_reply_SOA(DNS_RC_NOERROR, request);
 				goto out_update_cache;
@@ -2430,7 +2431,7 @@ out_update_cache:
 	if (dns_cache_get_ttl(dns_cache) == 0) {
 		uint32_t server_flags = request->server_flags;
 		if (request->conn == NULL) {
-			server_flags = dns_cache_get_cache_flag(dns_cache_A->cache_data);
+			server_flags = dns_cache_get_cache_flag(dns_cache->cache_data);
 		}
 		_dns_server_prefetch_request(request->domain, request->qtype, server_flags);
 	} else {
@@ -3158,11 +3159,11 @@ static void _dns_server_prefetch_domain(struct dns_cache *dns_cache)
 	}
 
 	/* start prefetch domain */
-	tlog(TLOG_DEBUG, "prefetch by cache %s, qtype %d, ttl %d, hitnum %d", dns_cache->domain, dns_cache->qtype,
-		 dns_cache->ttl, hitnum);
-	if (_dns_server_prefetch_request(dns_cache->domain, dns_cache->qtype,
+	tlog(TLOG_DEBUG, "prefetch by cache %s, qtype %d, ttl %d, hitnum %d", dns_cache->info.domain, dns_cache->info.qtype,
+		 dns_cache->info.ttl, hitnum);
+	if (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype,
 									 dns_cache_get_cache_flag(dns_cache->cache_data)) != 0) {
-		tlog(TLOG_ERROR, "prefetch domain %s, qtype %d, failed.", dns_cache->domain, dns_cache->qtype);
+		tlog(TLOG_ERROR, "prefetch domain %s, qtype %d, failed.", dns_cache->info.domain, dns_cache->info.qtype);
 	}
 }
 
@@ -3568,6 +3569,60 @@ static int _dns_server_audit_init(void)
 	return 0;
 }
 
+static int _dns_server_cache_init(void)
+{
+	if (dns_cache_init(dns_conf_cachesize, dns_conf_serve_expired, dns_conf_serve_expired_ttl) != 0) {
+		tlog(TLOG_ERROR, "init cache failed.");
+		return -1;
+	}
+
+	char *dns_cache_file = SMARTDNS_CACHE_FILE;
+	if (dns_conf_cache_file[0] != 0) {
+		dns_cache_file = dns_conf_cache_file;
+	}
+
+	if (dns_conf_cache_persist == 2) {
+		uint64_t freespace = get_free_space(dns_cache_file);
+		if (freespace >= CACHE_AUTO_ENABLE_SIZE) {
+			tlog(TLOG_INFO, "auto enable cache persist.");
+			dns_conf_cache_persist = 1;
+		}
+	}
+
+	if (dns_conf_cachesize <= 0 || dns_conf_cache_persist == 0) {
+		return 0;
+	}
+
+	if (dns_cache_load(dns_cache_file) != 0) {
+		tlog(TLOG_WARN, "Load cache failed.");
+		return 0;
+	}
+
+	return 0;
+}
+
+static int _dns_server_cache_save(void)
+{
+	char *dns_cache_file = SMARTDNS_CACHE_FILE;
+	if (dns_conf_cache_file[0] != 0) {
+		dns_cache_file = dns_conf_cache_file;
+	}
+
+	if (dns_conf_cache_persist == 0 || dns_conf_cachesize <= 0) {
+		if (access(dns_cache_file, F_OK) == 0) {
+			unlink(dns_cache_file);
+		}
+		return 0;
+	}
+
+	if (dns_cache_save(dns_cache_file) != 0) {
+		tlog(TLOG_WARN, "save cache failed.");
+		return -1;
+	}
+
+	return 0;
+}
+
 int dns_server_init(void)
 {
 	pthread_attr_t attr;
@@ -3578,9 +3633,9 @@ int dns_server_init(void)
 		return -1;
 	}
 
-	if (dns_cache_init(dns_conf_cachesize, dns_conf_serve_expired, dns_conf_serve_expired_ttl) != 0) {
-		tlog(TLOG_ERROR, "init cache failed.");
-		return -1;
+	if (_dns_server_cache_init() != 0) {
+		tlog(TLOG_ERROR, "init dns cache filed.");
+		goto errout;
 	}
 
 	if (_dns_server_audit_init() != 0) {
@@ -3656,5 +3711,6 @@ void dns_server_exit(void)
 
 	pthread_mutex_destroy(&server.request_list_lock);
 
+	_dns_server_cache_save();
 	dns_cache_destroy();
 }

+ 1 - 0
src/smartdns.c

@@ -452,6 +452,7 @@ int main(int argc, char *argv[])
 	}
 
 	signal(SIGINT, _sig_exit);
+	signal(SIGTERM, _sig_exit);
 	atexit(_smartdns_exit);
 
 	return _smartdns_run();

+ 15 - 1
src/util.c

@@ -35,6 +35,7 @@
 #include <string.h>
 #include <sys/prctl.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
@@ -994,4 +995,17 @@ int set_sock_lingertime(int fd, int time)
 	}
 
 	return 0;
-}
+}
+
+uint64_t get_free_space(const char *path)
+{
+	uint64_t size = 0;
+	struct statvfs buf;
+	if (statvfs(path, &buf) != 0) {
+		return 0;
+	}
+
+	size = (uint64_t)buf.f_frsize * buf.f_bavail;
+
+	return size;
+}

+ 2 - 0
src/util.h

@@ -106,6 +106,8 @@ int set_sock_keepalive(int fd, int keepidle, int keepinterval, int keepcnt);
 
 int set_sock_lingertime(int fd, int time);
 
+uint64_t get_free_space(const char *path);
+
 #ifdef __cplusplus
 }
 #endif /*__cplusplus */

+ 2 - 1
systemd/smartdns.service.in

@@ -1,8 +1,9 @@
 [Unit]
-Description=smart dns server
+Description=smartdns server
 After=network.target 
 StartLimitBurst=0
 StartLimitIntervalSec=60
+TimeoutStopSec=5
 
 [Service]
 Type=forking