dns_cache: Replace cache timeout mechanism with time wheel algorithm to reduce CPU usage

This commit is contained in:
Nick Peng
2023-09-23 23:31:19 +08:00
parent b7fb501be9
commit bfacad33ae
10 changed files with 760 additions and 356 deletions

View File

@@ -15,8 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
BIN=smartdns
OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o
OBJS_MAIN=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o lib/conf.o lib/nftset.o
OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/timer_wheel.o
OBJS_MAIN=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o timer.o lib/conf.o lib/nftset.o
OBJS=$(OBJS_MAIN) $(OBJS_LIB)
# cflags

View File

@@ -18,6 +18,7 @@
#include "dns_cache.h"
#include "stringutil.h"
#include "timer.h"
#include "tlog.h"
#include "util.h"
#include <errno.h>
@@ -27,31 +28,28 @@
#include <string.h>
#include <sys/types.h>
#define DNS_CACHE_MAX_HITNUM 5000
#define DNS_CACHE_HITNUM_STEP 2
#define DNS_CACHE_MAX_HITNUM 6000
#define DNS_CACHE_HITNUM_STEP 3
#define DNS_CACHE_HITNUM_STEP_MAX 6
#define DNS_CACHE_READ_TIMEOUT 60
struct dns_cache_head {
struct hash_table cache_hash;
struct list_head cache_list;
struct list_head inactive_list;
atomic_t num;
int size;
int enable_inactive;
int inactive_list_expired;
pthread_mutex_t lock;
struct dns_cache *last_active_inserted;
dns_cache_callback timeout_callback;
};
typedef int (*dns_cache_read_callback)(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data);
static struct dns_cache_head dns_cache_head;
int dns_cache_init(int size, int enable_inactive, int inactive_list_expired)
int dns_cache_init(int size, dns_cache_callback timeout_callback)
{
int bits = 0;
INIT_LIST_HEAD(&dns_cache_head.cache_list);
INIT_LIST_HEAD(&dns_cache_head.inactive_list);
bits = ilog2(size) - 1;
if (bits >= 20) {
@@ -63,35 +61,14 @@ int dns_cache_init(int size, int enable_inactive, int inactive_list_expired)
hash_table_init(dns_cache_head.cache_hash, bits, malloc);
atomic_set(&dns_cache_head.num, 0);
dns_cache_head.size = size;
dns_cache_head.enable_inactive = enable_inactive;
dns_cache_head.inactive_list_expired = inactive_list_expired;
dns_cache_head.last_active_inserted = NULL;
dns_cache_head.timeout_callback = timeout_callback;
pthread_mutex_init(&dns_cache_head.lock, NULL);
return 0;
}
static __attribute__((unused)) struct dns_cache *_dns_cache_last(void)
static struct dns_cache *_dns_cache_first(void)
{
struct dns_cache *dns_cache = NULL;
dns_cache = list_last_entry(&dns_cache_head.inactive_list, struct dns_cache, list);
if (dns_cache) {
return dns_cache;
}
return list_last_entry(&dns_cache_head.cache_list, struct dns_cache, list);
}
static struct dns_cache *_dns_inactive_cache_first(void)
{
struct dns_cache *dns_cache = NULL;
dns_cache = list_first_entry_or_null(&dns_cache_head.inactive_list, struct dns_cache, list);
if (dns_cache) {
return dns_cache;
}
return list_first_entry_or_null(&dns_cache_head.cache_list, struct dns_cache, list);
}
@@ -117,6 +94,7 @@ void dns_cache_release(struct dns_cache *dns_cache)
if (dns_cache == NULL) {
return;
}
if (!atomic_dec_and_test(&dns_cache->ref)) {
return;
}
@@ -128,23 +106,8 @@ static void _dns_cache_remove(struct dns_cache *dns_cache)
{
hash_del(&dns_cache->node);
list_del_init(&dns_cache->list);
dns_timer_del(&dns_cache->timer);
dns_cache_release(dns_cache);
if (dns_cache == dns_cache_head.last_active_inserted) {
dns_cache_release(dns_cache_head.last_active_inserted);
dns_cache_head.last_active_inserted = NULL;
}
}
static void _dns_cache_move_inactive(struct dns_cache *dns_cache)
{
list_del(&dns_cache->list);
list_add_tail(&dns_cache->list, &dns_cache_head.inactive_list);
if (dns_cache == dns_cache_head.last_active_inserted) {
dns_cache_release(dns_cache_head.last_active_inserted);
dns_cache_head.last_active_inserted = NULL;
}
time(&dns_cache->info.replace_time);
}
enum CACHE_TYPE dns_cache_data_type(struct dns_cache_data *cache_data)
@@ -271,41 +234,33 @@ struct dns_cache_data *dns_cache_new_data_packet(void *packet, size_t packet_len
return (struct dns_cache_data *)cache_packet;
}
static void _dns_cache_insert_sorted(struct dns_cache *dns_cache, struct list_head *head)
static void dns_cache_timer_relase(struct tw_timer_list *timer, void *data)
{
time_t ttl;
struct dns_cache *tmp = NULL;
struct list_head *insert_head = head;
ttl = dns_cache->info.insert_time + dns_cache->info.ttl;
if (dns_cache_head.last_active_inserted && dns_cache != dns_cache_head.last_active_inserted) {
time_t ttl_last =
dns_cache_head.last_active_inserted->info.insert_time + dns_cache_head.last_active_inserted->info.ttl;
if (ttl == ttl_last) {
insert_head = &(dns_cache_head.last_active_inserted->list);
goto out;
}
}
/* ascending order */
list_for_each_entry_reverse(tmp, head, list)
{
if ((tmp->info.insert_time + tmp->info.ttl) <= ttl) {
insert_head = &tmp->list;
break;
}
}
out:
if (dns_cache_head.last_active_inserted) {
dns_cache_release(dns_cache_head.last_active_inserted);
}
list_add(&dns_cache->list, insert_head);
dns_cache_head.last_active_inserted = dns_cache;
dns_cache_get(dns_cache);
struct dns_cache *dns_cache = data;
dns_cache_release(dns_cache);
}
static int _dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int speed, int no_inactive, int inactive,
static void dns_cache_expired(struct tw_timer_list *timer, void *data, unsigned long timestamp)
{
struct dns_cache *dns_cache = data;
if (dns_cache->del_pending == 1) {
dns_cache_release(dns_cache);
return;
}
if (dns_cache_head.timeout_callback) {
if (dns_cache_head.timeout_callback(dns_cache) != 0) {
dns_cache_release(dns_cache);
return;
}
}
dns_cache->del_pending = 1;
dns_timer_mod(&dns_cache->timer, 3);
}
static int _dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int speed, int timeout, int update_time,
struct dns_cache_data *cache_data)
{
struct dns_cache *dns_cache = NULL;
@@ -318,7 +273,7 @@ static int _dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int spee
/* lookup existing cache */
dns_cache = dns_cache_lookup(cache_key);
if (dns_cache == NULL) {
return dns_cache_insert(cache_key, ttl, speed, no_inactive, cache_data);
return dns_cache_insert(cache_key, ttl, speed, timeout, cache_data);
}
if (ttl < DNS_CACHE_TTL_MIN) {
@@ -333,38 +288,35 @@ static int _dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int spee
dns_cache->info.query_flag = cache_key->query_flag;
dns_cache->info.ttl = ttl;
dns_cache->info.speed = speed;
dns_cache->info.no_inactive = no_inactive;
dns_cache->info.timeout = timeout;
dns_cache->info.is_visited = 1;
old_cache_data = dns_cache->cache_data;
dns_cache->cache_data = cache_data;
list_del(&dns_cache->list);
if (inactive == 0) {
time(&dns_cache->info.insert_time);
time(&dns_cache->info.replace_time);
_dns_cache_insert_sorted(dns_cache, &dns_cache_head.cache_list);
} else {
time(&dns_cache->info.replace_time);
list_add_tail(&dns_cache->list, &dns_cache_head.inactive_list);
if (cache_data) {
old_cache_data = dns_cache->cache_data;
dns_cache->cache_data = cache_data;
if (old_cache_data == cache_data) {
old_cache_data = NULL;
}
}
if (update_time) {
time(&dns_cache->info.insert_time);
}
time(&dns_cache->info.replace_time);
list_del(&dns_cache->list);
list_add_tail(&dns_cache->list, &dns_cache_head.cache_list);
dns_timer_mod(&dns_cache->timer, timeout);
pthread_mutex_unlock(&dns_cache_head.lock);
dns_cache_data_free(old_cache_data);
if (old_cache_data) {
dns_cache_data_free(old_cache_data);
}
dns_cache_release(dns_cache);
return 0;
}
int dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int speed, int no_inactive,
int dns_cache_replace(struct dns_cache_key *cache_key, int ttl, int speed, int timeout, int update_time,
struct dns_cache_data *cache_data)
{
return _dns_cache_replace(cache_key, ttl, speed, no_inactive, 0, cache_data);
}
int dns_cache_replace_inactive(struct dns_cache_key *cache_key, int ttl, int speed, int no_inactive,
struct dns_cache_data *cache_data)
{
return _dns_cache_replace(cache_key, ttl, speed, no_inactive, 1, cache_data);
return _dns_cache_replace(cache_key, ttl, speed, timeout, update_time, cache_data);
}
static void _dns_cache_remove_by_domain(struct dns_cache_key *cache_key)
@@ -430,24 +382,26 @@ static int _dns_cache_insert(struct dns_cache_info *info, struct dns_cache_data
memcpy(&dns_cache->info, info, sizeof(*info));
dns_cache->del_pending = 0;
dns_cache->cache_data = cache_data;
dns_cache->timer.function = dns_cache_expired;
dns_cache->timer.del_function = dns_cache_timer_relase;
dns_cache->timer.expires = info->timeout;
dns_cache->timer.data = dns_cache;
pthread_mutex_lock(&dns_cache_head.lock);
hash_table_add(dns_cache_head.cache_hash, &dns_cache->node, key);
if (head == &dns_cache_head.inactive_list) {
list_add_tail(&dns_cache->list, head);
} else {
_dns_cache_insert_sorted(dns_cache, head);
}
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 = NULL;
del_cache = _dns_inactive_cache_first();
del_cache = _dns_cache_first();
if (del_cache) {
_dns_cache_remove(del_cache);
}
}
pthread_mutex_unlock(&dns_cache_head.lock);
dns_cache_get(dns_cache);
dns_timer_add(&dns_cache->timer);
return 0;
errout:
@@ -458,7 +412,7 @@ errout:
return -1;
}
int dns_cache_insert(struct dns_cache_key *cache_key, int ttl, int speed, int no_inactive,
int dns_cache_insert(struct dns_cache_key *cache_key, int ttl, int speed, int timeout,
struct dns_cache_data *cache_data)
{
struct dns_cache_info info;
@@ -485,7 +439,7 @@ int dns_cache_insert(struct dns_cache_key *cache_key, int ttl, int speed, int no
info.ttl = ttl;
info.hitnum_update_add = DNS_CACHE_HITNUM_STEP;
info.speed = speed;
info.no_inactive = no_inactive;
info.timeout = timeout;
info.is_visited = 1;
time(&info.insert_time);
time(&info.replace_time);
@@ -535,13 +489,7 @@ struct dns_cache *dns_cache_lookup(struct dns_cache_key *cache_key)
}
if (dns_cache_ret) {
/* Return NULL if the cache times out */
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 {
dns_cache_get(dns_cache_ret);
}
dns_cache_get(dns_cache_ret);
}
pthread_mutex_unlock(&dns_cache_head.lock);
@@ -638,6 +586,8 @@ void dns_cache_update(struct dns_cache *dns_cache)
{
pthread_mutex_lock(&dns_cache_head.lock);
if (!list_empty(&dns_cache->list)) {
list_del_init(&dns_cache->list);
list_add_tail(&dns_cache->list, &dns_cache_head.cache_list);
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;
@@ -651,141 +601,20 @@ void dns_cache_update(struct dns_cache *dns_cache)
pthread_mutex_unlock(&dns_cache_head.lock);
}
static void _dns_cache_remove_expired_ttl(dns_cache_callback inactive_precallback, int ttl_inactive_pre,
unsigned int max_callback_num, const time_t *now)
{
struct dns_cache *dns_cache = NULL;
struct dns_cache *tmp = NULL;
unsigned int callback_num = 0;
int ttl = 0;
LIST_HEAD(checklist);
pthread_mutex_lock(&dns_cache_head.lock);
list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.inactive_list, list)
{
ttl = dns_cache->info.insert_time + dns_cache->info.ttl - *now;
if (ttl > 0) {
continue;
}
if (dns_cache_head.inactive_list_expired + ttl < 0) {
_dns_cache_remove(dns_cache);
continue;
}
if (inactive_precallback == NULL) {
if (dns_cache_head.inactive_list_expired + ttl > 0) {
break;
}
continue;
}
ttl = *now - dns_cache->info.replace_time;
if (ttl < ttl_inactive_pre) {
break;
}
if (callback_num >= max_callback_num) {
break;
}
if (dns_cache->del_pending == 1) {
continue;
}
/* If the TTL time is in the pre-timeout range, call callback function */
dns_cache_get(dns_cache);
list_add_tail(&dns_cache->check_list, &checklist);
dns_cache->del_pending = 1;
callback_num++;
}
pthread_mutex_unlock(&dns_cache_head.lock);
list_for_each_entry_safe(dns_cache, tmp, &checklist, check_list)
{
/* run inactive_precallback */
if (inactive_precallback) {
inactive_precallback(dns_cache);
}
dns_cache_release(dns_cache);
}
}
void dns_cache_invalidate(dns_cache_callback precallback, int ttl_pre, unsigned int max_callback_num,
dns_cache_callback inactive_precallback, int ttl_inactive_pre)
{
struct dns_cache *dns_cache = NULL;
struct dns_cache *tmp = NULL;
time_t now = 0;
int ttl = 0;
LIST_HEAD(checklist);
unsigned int callback_num = 0;
if (max_callback_num <= 0) {
max_callback_num = -1;
}
if (dns_cache_head.size <= 0) {
return;
}
time(&now);
pthread_mutex_lock(&dns_cache_head.lock);
list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.cache_list, list)
{
ttl = dns_cache->info.insert_time + dns_cache->info.ttl - now;
if (ttl > ttl_pre) {
break;
}
if (ttl > 0 && ttl < ttl_pre) {
/* If the TTL time is in the pre-timeout range, call callback function */
if (precallback && dns_cache->del_pending == 0 && callback_num < max_callback_num) {
list_add_tail(&dns_cache->check_list, &checklist);
dns_cache_get(dns_cache);
dns_cache->del_pending = 1;
callback_num++;
continue;
}
}
if (ttl <= 0) {
if (dns_cache_head.enable_inactive && dns_cache->info.no_inactive == 0) {
_dns_cache_move_inactive(dns_cache);
} else {
_dns_cache_remove(dns_cache);
}
}
}
pthread_mutex_unlock(&dns_cache_head.lock);
if (dns_cache_head.enable_inactive && dns_cache_head.inactive_list_expired != 0) {
_dns_cache_remove_expired_ttl(inactive_precallback, ttl_inactive_pre, max_callback_num, &now);
}
list_for_each_entry_safe(dns_cache, tmp, &checklist, check_list)
{
/* run callback */
if (precallback) {
precallback(dns_cache);
}
list_del(&dns_cache->check_list);
dns_cache_release(dns_cache);
}
}
static int _dns_cache_read_to_cache(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data)
{
struct list_head *head = NULL;
head = &dns_cache_head.cache_list;
struct dns_cache_info *info = &cache_record->info;
if (cache_record->type == CACHE_RECORD_TYPE_ACTIVE) {
head = &dns_cache_head.cache_list;
} else if (cache_record->type == CACHE_RECORD_TYPE_INACTIVE) {
head = &dns_cache_head.inactive_list;
} else {
tlog(TLOG_ERROR, "read cache record type is invalid.");
goto errout;
time_t now = time(NULL);
unsigned int seed_tmp = now;
int passed_time = now - info->replace_time;
int timeout = info->timeout - passed_time;
if (timeout < DNS_CACHE_READ_TIMEOUT * 2) {
timeout = DNS_CACHE_READ_TIMEOUT + (rand_r(&seed_tmp) % DNS_CACHE_READ_TIMEOUT);
}
info->timeout = timeout;
if (_dns_cache_insert(&cache_record->info, cache_data, head) != 0) {
tlog(TLOG_ERROR, "insert cache data failed.");
@@ -854,11 +683,6 @@ static int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_
cache_record.info.is_visited = 0;
cache_record.info.domain[DNS_MAX_CNAME_LEN - 1] = '\0';
cache_record.info.dns_group_name[DNS_GROUP_NAME_LEN - 1] = '\0';
if (cache_record.type >= CACHE_RECORD_TYPE_END) {
tlog(TLOG_ERROR, "read cache record type is invalid.");
goto errout;
}
ret = callback(&cache_record, cache_data);
if (ret == -2) {
cache_data = NULL;
@@ -930,7 +754,7 @@ int dns_cache_load(const char *file)
return _dns_cache_file_read(file, _dns_cache_read_to_cache);
}
static int _dns_cache_write_record(int fd, uint32_t *cache_number, enum CACHE_RECORD_TYPE type, struct list_head *head)
static int _dns_cache_write_record(int fd, uint32_t *cache_number, struct list_head *head)
{
struct dns_cache *dns_cache = NULL;
struct dns_cache *tmp = NULL;
@@ -940,7 +764,6 @@ static int _dns_cache_write_record(int fd, uint32_t *cache_number, enum CACHE_RE
list_for_each_entry_safe(dns_cache, tmp, head, list)
{
cache_record.magic = MAGIC_RECORD;
cache_record.type = type;
memcpy(&cache_record.info, &dns_cache->info, sizeof(struct dns_cache_info));
ssize_t ret = write(fd, &cache_record, sizeof(cache_record));
if (ret != sizeof(cache_record)) {
@@ -968,11 +791,7 @@ errout:
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) {
if (_dns_cache_write_record(fd, cache_number, &dns_cache_head.cache_list) != 0) {
return -1;
}
@@ -1060,17 +879,7 @@ void dns_cache_destroy(void)
struct dns_cache *dns_cache = NULL;
struct dns_cache *tmp = NULL;
if (dns_cache_head.last_active_inserted) {
dns_cache_release(dns_cache_head.last_active_inserted);
dns_cache_head.last_active_inserted = NULL;
}
pthread_mutex_lock(&dns_cache_head.lock);
list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.inactive_list, list)
{
_dns_cache_delete(dns_cache);
}
list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.cache_list, list)
{
_dns_cache_delete(dns_cache);
@@ -1083,6 +892,6 @@ void dns_cache_destroy(void)
const char *dns_cache_file_version(void)
{
const char *version = "cache ver 1.0";
const char *version = "cache ver 1.2";
return version;
}

View File

@@ -25,6 +25,7 @@
#include "hash.h"
#include "hashtable.h"
#include "list.h"
#include "timer.h"
#include <stdlib.h>
#include <time.h>
@@ -45,12 +46,6 @@ enum CACHE_TYPE {
CACHE_TYPE_PACKET,
};
enum CACHE_RECORD_TYPE {
CACHE_RECORD_TYPE_ACTIVE,
CACHE_RECORD_TYPE_INACTIVE,
CACHE_RECORD_TYPE_END,
};
struct dns_cache_data_head {
enum CACHE_TYPE cache_type;
int is_soa;
@@ -90,7 +85,7 @@ struct dns_cache_info {
int ttl;
int hitnum;
int speed;
int no_inactive;
int timeout;
int hitnum_update_add;
int is_visited;
time_t insert_time;
@@ -99,7 +94,6 @@ struct dns_cache_info {
struct dns_cache_record {
uint32_t magic;
enum CACHE_RECORD_TYPE type;
struct dns_cache_info info;
};
@@ -113,6 +107,8 @@ struct dns_cache {
struct dns_cache_info info;
struct dns_cache_data *cache_data;
struct tw_timer_list timer;
};
struct dns_cache_file {
@@ -138,15 +134,14 @@ void dns_cache_data_free(struct dns_cache_data *data);
struct dns_cache_data *dns_cache_new_data_packet(void *packet, size_t packet_len);
int dns_cache_init(int size, int enable_inactive, int inactive_list_expired);
typedef int (*dns_cache_callback)(struct dns_cache *dns_cache);
int dns_cache_replace(struct dns_cache_key *key, int ttl, int speed, int no_inactive,
int dns_cache_init(int size, dns_cache_callback timeout_callback);
int dns_cache_replace(struct dns_cache_key *key, int ttl, int speed, int tiemout, int update_time,
struct dns_cache_data *cache_data);
int dns_cache_replace_inactive(struct dns_cache_key *key, int ttl, int speed, int no_inactive,
struct dns_cache_data *cache_data);
int dns_cache_insert(struct dns_cache_key *key, int ttl, int speed, int no_inactive, struct dns_cache_data *cache_data);
int dns_cache_insert(struct dns_cache_key *key, int ttl, int speed, int timeout, struct dns_cache_data *cache_data);
struct dns_cache *dns_cache_lookup(struct dns_cache_key *key);
@@ -162,11 +157,6 @@ int dns_cache_is_visited(struct dns_cache *dns_cache);
void dns_cache_update(struct dns_cache *dns_cache);
typedef void dns_cache_callback(struct dns_cache *dns_cache);
void dns_cache_invalidate(dns_cache_callback precallback, int ttl_pre, unsigned int max_callback_num,
dns_cache_callback inactive_precallback, int ttl_inactive_pre);
int dns_cache_get_ttl(struct dns_cache *dns_cache);
int dns_cache_get_cname_ttl(struct dns_cache *dns_cache);

View File

@@ -1253,6 +1253,43 @@ static int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpac
return ret;
}
static int _dns_server_get_cache_timeout(struct dns_request *request, int ttl)
{
int timeout = 0;
if (dns_conf_prefetch) {
if (dns_conf_serve_expired) {
timeout = dns_conf_serve_expired_prefetch_time;
if (timeout == 0) {
timeout = dns_conf_serve_expired_ttl / 2;
if (timeout == 0 || timeout > EXPIRED_DOMAIN_PREFETCH_TIME) {
timeout = EXPIRED_DOMAIN_PREFETCH_TIME;
}
}
if (request->prefetch == 0) {
timeout += ttl;
}
} else {
timeout = ttl - 3;
}
} else {
timeout = ttl;
if (dns_conf_serve_expired) {
timeout += dns_conf_serve_expired_ttl;
}
}
if (request->prefetch) {
timeout -= 1;
}
if (timeout <= 0) {
timeout = 1;
}
return timeout;
}
static int _dns_server_request_update_cache(struct dns_request *request, dns_type_t qtype,
struct dns_cache_data *cache_data, int has_soa, int cache_ttl)
{
@@ -1293,18 +1330,13 @@ static int _dns_server_request_update_cache(struct dns_request *request, dns_typ
cache_key.query_flag = request->server_flags;
if (request->prefetch) {
if (request->prefetch_expired_domain == 0) {
if (dns_cache_replace(&cache_key, ttl, speed, request->no_serve_expired, cache_data) != 0) {
goto errout;
}
} else {
if (dns_cache_replace_inactive(&cache_key, ttl, speed, request->no_serve_expired, cache_data) != 0) {
goto errout;
}
if (dns_cache_replace(&cache_key, ttl, speed, _dns_server_get_cache_timeout(request, ttl),
!request->prefetch_expired_domain, cache_data) != 0) {
goto errout;
}
} else {
/* insert result to cache */
if (dns_cache_insert(&cache_key, ttl, speed, request->no_serve_expired, cache_data) != 0) {
if (dns_cache_insert(&cache_key, ttl, speed, _dns_server_get_cache_timeout(request, ttl), cache_data) != 0) {
goto errout;
}
}
@@ -1441,18 +1473,13 @@ static int _dns_cache_cname_packet(struct dns_server_post_context *context)
cache_key.query_flag = request->server_flags;
if (request->prefetch) {
if (request->prefetch_expired_domain == 0) {
if (dns_cache_replace(&cache_key, ttl, speed, request->no_serve_expired, cache_packet) != 0) {
goto errout;
}
} else {
if (dns_cache_replace_inactive(&cache_key, ttl, speed, request->no_serve_expired, cache_packet) != 0) {
goto errout;
}
if (dns_cache_replace(&cache_key, ttl, speed, _dns_server_get_cache_timeout(request, ttl),
!request->prefetch_expired_domain, cache_packet) != 0) {
goto errout;
}
} else {
/* insert result to cache */
if (dns_cache_insert(&cache_key, ttl, speed, request->no_serve_expired, cache_packet) != 0) {
if (dns_cache_insert(&cache_key, ttl, speed, _dns_server_get_cache_timeout(request, ttl), cache_packet) != 0) {
goto errout;
}
}
@@ -1484,12 +1511,15 @@ static int _dns_cache_packet(struct dns_server_post_context *context)
cache_key.query_flag = request->server_flags;
if (request->prefetch) {
if (dns_cache_replace(&cache_key, context->reply_ttl, -1, request->no_serve_expired, cache_packet) != 0) {
if (dns_cache_replace(&cache_key, context->reply_ttl, -1,
_dns_server_get_cache_timeout(request, context->reply_ttl),
!request->prefetch_expired_domain, cache_packet) != 0) {
goto errout;
}
} else {
/* insert result to cache */
if (dns_cache_insert(&cache_key, context->reply_ttl, -1, request->no_serve_expired, cache_packet) != 0) {
if (dns_cache_insert(&cache_key, context->reply_ttl, -1,
_dns_server_get_cache_timeout(request, context->reply_ttl), cache_packet) != 0) {
goto errout;
}
}
@@ -3061,9 +3091,9 @@ static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_reque
return -1;
}
snprintf(ip, sizeof(ip), "[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]", paddr[0], paddr[1],
paddr[2], paddr[3], paddr[4], paddr[5], paddr[6], paddr[7], paddr[8], paddr[9], paddr[10], paddr[11],
paddr[12], paddr[13], paddr[14], paddr[15]);
snprintf(ip, sizeof(ip), "[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]", paddr[0],
paddr[1], paddr[2], paddr[3], paddr[4], paddr[5], paddr[6], paddr[7], paddr[8], paddr[9], paddr[10],
paddr[11], paddr[12], paddr[13], paddr[14], paddr[15]);
/* start ping */
_dns_server_request_get(request);
@@ -6450,13 +6480,13 @@ static int _dns_server_second_ping_check(struct dns_request *request)
return ret;
}
static void _dns_server_prefetch_domain(struct dns_cache *dns_cache)
static int _dns_server_prefetch_domain(struct dns_cache *dns_cache)
{
/* If there are still hits, continue pre-fetching */
struct dns_server_query_option server_query_option;
int hitnum = dns_cache_hitnum_dec_get(dns_cache);
if (hitnum <= 0) {
return;
return -1;
}
/* start prefetch domain */
@@ -6467,11 +6497,19 @@ static void _dns_server_prefetch_domain(struct dns_cache *dns_cache)
server_query_option.ecs_enable_flag = 0;
if (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, 0, &server_query_option) != 0) {
tlog(TLOG_ERROR, "prefetch domain %s, qtype %d, failed.", dns_cache->info.domain, dns_cache->info.qtype);
return -1;
}
return 0;
}
static void _dns_server_prefetch_expired_domain(struct dns_cache *dns_cache)
static int _dns_server_prefetch_expired_domain(struct dns_cache *dns_cache)
{
time_t ttl = dns_cache->info.insert_time + dns_cache->info.ttl + dns_conf_serve_expired_ttl - time(NULL);
if (ttl < 0) {
return -1;
}
/* start prefetch domain */
tlog(TLOG_DEBUG, "expired domain, prefetch by cache %s, qtype %d, ttl %d", dns_cache->info.domain,
dns_cache->info.qtype, dns_cache->info.ttl);
@@ -6483,7 +6521,23 @@ static void _dns_server_prefetch_expired_domain(struct dns_cache *dns_cache)
if (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, 1, &server_query_option) != 0) {
tlog(TLOG_DEBUG, "prefetch domain %s, qtype %d, failed.", dns_cache->info.domain, dns_cache->info.qtype);
return -1;
}
return 0;
}
static int _dns_server_cache_expired(struct dns_cache *dns_cache)
{
if (dns_conf_prefetch == 1) {
if (dns_conf_serve_expired == 1) {
return _dns_server_prefetch_expired_domain(dns_cache);
} else {
return _dns_server_prefetch_domain(dns_cache);
}
}
return -1;
}
static void _dns_server_tcp_idle_check(void)
@@ -6592,44 +6646,8 @@ static void _dns_server_save_cache_to_file(void)
static void _dns_server_period_run_second(void)
{
static unsigned int sec = 0;
static time_t last = 0;
time_t now = 0;
sec++;
time(&now);
if (last == 0) {
last = now;
}
if (now - 180 > last) {
dns_cache_invalidate(NULL, 0, 0, NULL, 0);
tlog(TLOG_WARN, "Service paused for 180s, force invalidate cache.");
}
last = now;
if (sec % 2 == 0) {
if (dns_conf_prefetch) {
/* do pre-fetching */
if (dns_conf_serve_expired) {
int prefetch_time = dns_conf_serve_expired_prefetch_time;
if (prefetch_time == 0) {
prefetch_time = dns_conf_serve_expired_ttl / 2;
if (prefetch_time == 0 || prefetch_time > EXPIRED_DOMAIN_PREFETCH_TIME) {
prefetch_time = EXPIRED_DOMAIN_PREFETCH_TIME;
}
}
dns_cache_invalidate(NULL, 0, DNS_MAX_DOMAIN_REFETCH_NUM, _dns_server_prefetch_expired_domain,
prefetch_time);
} else {
dns_cache_invalidate(_dns_server_prefetch_domain, 3, DNS_MAX_DOMAIN_REFETCH_NUM, NULL, 0);
}
} else {
dns_cache_invalidate(NULL, 0, 0, NULL, 0);
}
}
_dns_server_tcp_idle_check();
_dns_server_check_need_exit();
@@ -7212,7 +7230,7 @@ static int _dns_server_audit_init(void)
static int _dns_server_cache_init(void)
{
if (dns_cache_init(dns_conf_cachesize, dns_conf_serve_expired, dns_conf_serve_expired_ttl) != 0) {
if (dns_cache_init(dns_conf_cachesize, _dns_server_cache_expired) != 0) {
tlog(TLOG_ERROR, "init cache failed.");
return -1;
}

50
src/include/timer_wheel.h Normal file
View File

@@ -0,0 +1,50 @@
/*************************************************************************
*
* Copyright (C) 2018-2023 Ruilin Peng (Nick) <pymumu@gmail.com>.
*
* 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 __TIMER_WHEEL_H
#define __TIMER_WHEEL_H
#include "list.h"
struct tw_base;
struct tw_timer_list;
typedef void (*tw_func)(struct tw_timer_list *, void *, unsigned long);
typedef void (*tw_del_func)(struct tw_timer_list *, void *);
struct tw_timer_list {
void *data;
unsigned long expires;
tw_func function;
tw_del_func del_function;
struct list_head entry;
};
struct tw_base *tw_init_timers(void);
int tw_cleanup_timers(struct tw_base *);
void tw_add_timer(struct tw_base *, struct tw_timer_list *);
int tw_del_timer(struct tw_base *, struct tw_timer_list *);
int tw_mod_timer_pending(struct tw_base *, struct tw_timer_list *, unsigned long);
int tw_mod_timer(struct tw_base *, struct tw_timer_list *, unsigned long);
#endif

419
src/lib/timer_wheel.c Normal file
View File

@@ -0,0 +1,419 @@
/*************************************************************************
*
* Copyright (C) 2018-2023 Ruilin Peng (Nick) <pymumu@gmail.com>.
*
* 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 "bitops.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "timer_wheel.h"
#define TVR_BITS 8
#define TVN_BITS 6
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_MASK (TVR_SIZE - 1)
#define TVN_MASK (TVN_SIZE - 1)
#define INDEX(N) ((base->jiffies >> (TVR_BITS + N * TVN_BITS)) & TVN_MASK)
struct tvec {
struct list_head vec[TVN_SIZE];
};
struct tvec_root {
struct list_head vec[TVR_SIZE];
};
struct tw_base {
pthread_spinlock_t lock;
pthread_t runner;
unsigned long jiffies;
struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
};
static inline void _tw_add_timer(struct tw_base *base, struct tw_timer_list *timer)
{
int i;
unsigned long idx;
unsigned long expires;
struct list_head *vec;
expires = timer->expires;
idx = expires - base->jiffies;
if (idx < TVR_SIZE) {
i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((long)idx < 0) {
vec = base->tv1.vec + (base->jiffies & TVR_MASK);
} else {
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
}
list_add_tail(&timer->entry, vec);
}
static inline unsigned long _apply_slack(struct tw_base *base, struct tw_timer_list *timer)
{
long delta;
unsigned long mask, expires, expires_limit;
expires = timer->expires;
delta = expires - base->jiffies;
if (delta < 256) {
return expires;
}
expires_limit = expires + delta / 256;
mask = expires ^ expires_limit;
if (mask == 0) {
return expires;
}
int bit = fls_long(mask);
mask = (1UL << bit) - 1;
expires_limit = expires_limit & ~(mask);
return expires_limit;
}
static inline void _tw_detach_timer(struct tw_timer_list *timer)
{
struct list_head *entry = &timer->entry;
list_del(entry);
entry->next = NULL;
}
static inline int _tw_cascade(struct tw_base *base, struct tvec *tv, int index)
{
struct tw_timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec + index, &tv_list);
list_for_each_entry_safe(timer, tmp, &tv_list, entry)
{
_tw_add_timer(base, timer);
}
return index;
}
static inline int timer_pending(struct tw_timer_list *timer)
{
struct list_head *entry = &timer->entry;
return (entry->next != NULL);
}
static inline int __detach_if_pending(struct tw_timer_list *timer)
{
if (!timer_pending(timer)) {
return 0;
}
_tw_detach_timer(timer);
return 1;
}
static inline int __mod_timer(struct tw_base *base, struct tw_timer_list *timer, int pending_only)
{
int ret = 0;
ret = __detach_if_pending(timer);
if (!ret && pending_only) {
goto done;
}
ret = 1;
_tw_add_timer(base, timer);
done:
return ret;
}
void tw_add_timer(struct tw_base *base, struct tw_timer_list *timer)
{
if (timer->function == NULL) {
return;
}
pthread_spin_lock(&base->lock);
{
timer->expires += base->jiffies;
timer->expires = _apply_slack(base, timer);
_tw_add_timer(base, timer);
}
pthread_spin_unlock(&base->lock);
}
int tw_del_timer(struct tw_base *base, struct tw_timer_list *timer)
{
int ret = 0;
pthread_spin_lock(&base->lock);
{
if (timer_pending(timer)) {
ret = 1;
_tw_detach_timer(timer);
}
}
pthread_spin_unlock(&base->lock);
if (ret == 1 && timer->del_function) {
timer->del_function(timer, timer->data);
}
return ret;
}
int tw_mod_timer_pending(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires)
{
int ret = 1;
pthread_spin_lock(&base->lock);
{
timer->expires = expires + base->jiffies;
timer->expires = _apply_slack(base, timer);
ret = __mod_timer(base, timer, 1);
}
pthread_spin_unlock(&base->lock);
return ret;
}
int tw_mod_timer(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires)
{
int ret = 1;
pthread_spin_lock(&base->lock);
{
if (timer_pending(timer) && timer->expires == expires) {
goto unblock;
}
timer->expires = expires + base->jiffies;
timer->expires = _apply_slack(base, timer);
ret = __mod_timer(base, timer, 0);
}
unblock:
pthread_spin_unlock(&base->lock);
return ret;
}
int tw_cleanup_timers(struct tw_base *base)
{
int ret = 0;
void *res = NULL;
ret = pthread_cancel(base->runner);
if (ret != 0) {
goto errout;
}
ret = pthread_join(base->runner, &res);
if (ret != 0) {
goto errout;
}
if (res != PTHREAD_CANCELED) {
goto errout;
}
ret = pthread_spin_destroy(&base->lock);
if (ret != 0) {
goto errout;
}
free(base);
return 0;
errout:
return -1;
}
static inline void run_timers(struct tw_base *base)
{
unsigned long index, call_time;
struct tw_timer_list *timer;
struct list_head work_list;
struct list_head *head = &work_list;
pthread_spin_lock(&base->lock);
{
index = base->jiffies & TVR_MASK;
if (!index && (!_tw_cascade(base, &base->tv2, INDEX(0))) && (!_tw_cascade(base, &base->tv3, INDEX(1))) &&
(!_tw_cascade(base, &base->tv4, INDEX(2))))
_tw_cascade(base, &base->tv5, INDEX(3));
call_time = base->jiffies++;
list_replace_init(base->tv1.vec + index, head);
while (!list_empty(head)) {
tw_func fn;
void *data;
timer = list_first_entry(head, struct tw_timer_list, entry);
fn = timer->function;
data = timer->data;
_tw_detach_timer(timer);
pthread_spin_unlock(&base->lock);
{
fn(timer, data, call_time);
}
pthread_spin_lock(&base->lock);
if ( (timer_pending(timer) == 0 && timer->del_function) ) {
pthread_spin_unlock(&base->lock);
timer->del_function(timer, timer->data);
pthread_spin_lock(&base->lock);
}
}
}
pthread_spin_unlock(&base->lock);
}
static unsigned long _tw_tick_count(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
static void *timer_work(void *arg)
{
struct tw_base *base = arg;
int sleep = 1000;
int sleep_time = 0;
unsigned long now = {0};
unsigned long last = {0};
unsigned long expect_time = 0;
sleep_time = sleep;
now = _tw_tick_count() - sleep;
last = now;
expect_time = now + sleep;
while (1) {
run_timers(base);
now = _tw_tick_count();
if (sleep_time > 0) {
sleep_time -= now - last;
if (sleep_time <= 0) {
sleep_time = 0;
}
int cnt = sleep_time / sleep;
expect_time -= cnt * sleep;
sleep_time -= cnt * sleep;
}
if (now >= expect_time) {
sleep_time = sleep - (now - expect_time);
if (sleep_time < 0) {
sleep_time = 0;
expect_time = now;
}
expect_time += sleep;
}
last = now;
usleep(sleep_time * 1000);
}
return NULL;
}
struct tw_base *tw_init_timers(void)
{
int j = 0;
int ret = 0;
struct timeval tv = {
0,
};
struct tw_base *base = NULL;
base = malloc(sizeof(*base));
if (!base) {
goto errout;
}
ret = pthread_spin_init(&base->lock, 0);
if (ret != 0) {
goto errout2;
}
for (j = 0; j < TVN_SIZE; j++) {
INIT_LIST_HEAD(base->tv5.vec + j);
INIT_LIST_HEAD(base->tv4.vec + j);
INIT_LIST_HEAD(base->tv3.vec + j);
INIT_LIST_HEAD(base->tv2.vec + j);
}
for (j = 0; j < TVR_SIZE; j++) {
INIT_LIST_HEAD(base->tv1.vec + j);
}
ret = gettimeofday(&tv, 0);
if (ret < 0) {
goto errout1;
}
base->jiffies = tv.tv_sec;
ret = pthread_create(&base->runner, NULL, timer_work, base);
if (ret != 0) {
goto errout1;
}
return base;
errout1:
(void)pthread_spin_destroy(&base->lock);
errout2:
free(base);
errout:
return NULL;
}

View File

@@ -30,6 +30,7 @@
#include "rbtree.h"
#include "tlog.h"
#include "util.h"
#include "timer.h"
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
@@ -486,6 +487,11 @@ static int _smartdns_init(void)
tlog(TLOG_NOTICE, "smartdns starting...(Copyright (C) Nick Peng <pymumu@gmail.com>, build: %s %s)", __DATE__,
__TIME__);
if (dns_timer_init() != 0) {
tlog(TLOG_ERROR, "init timer failed.");
goto errout;
}
if (_smartdns_init_ssl() != 0) {
tlog(TLOG_ERROR, "init ssl failed.");
goto errout;
@@ -573,6 +579,7 @@ static void _smartdns_exit(void)
fast_ping_exit();
dns_server_exit();
_smartdns_destroy_ssl();
dns_timer_destroy();
tlog_exit();
dns_server_load_exit();
}

70
src/timer.c Normal file
View File

@@ -0,0 +1,70 @@
/*************************************************************************
*
* Copyright (C) 2018-2023 Ruilin Peng (Nick) <pymumu@gmail.com>.
*
* 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 "timer.h"
#include "timer_wheel.h"
static struct tw_base *dns_timer_base = NULL;
int dns_timer_init(void)
{
struct tw_base *tw = tw_init_timers();
if (tw == NULL) {
return -1;
}
dns_timer_base = tw;
return 0;
}
void dns_timer_destroy(void)
{
if (dns_timer_base != NULL) {
tw_cleanup_timers(dns_timer_base);
dns_timer_base = NULL;
}
}
void dns_timer_add(struct tw_timer_list *timer)
{
if (dns_timer_base == NULL) {
return;
}
tw_add_timer(dns_timer_base, timer);
}
int dns_timer_del(struct tw_timer_list *timer)
{
if (dns_timer_base == NULL) {
return 0;
}
return tw_del_timer(dns_timer_base, timer);
}
int dns_timer_mod(struct tw_timer_list *timer, unsigned long expires)
{
if (dns_timer_base == NULL) {
return 0;
}
return tw_mod_timer(dns_timer_base, timer, expires);
}

41
src/timer.h Normal file
View File

@@ -0,0 +1,41 @@
/*************************************************************************
*
* Copyright (C) 2018-2023 Ruilin Peng (Nick) <pymumu@gmail.com>.
*
* 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 SMART_DNS_TIMER_H
#define SMART_DNS_TIMER_H
#include "timer_wheel.h"
#ifdef __cplusplus
extern "C" {
#endif /*__cplusplus */
int dns_timer_init(void);
void dns_timer_add(struct tw_timer_list *timer);
int dns_timer_del(struct tw_timer_list *timer);
int dns_timer_mod(struct tw_timer_list *timer, unsigned long expires);
void dns_timer_destroy(void);
#ifdef __cplusplus
}
#endif /*__cplusplus */
#endif

View File

@@ -23,8 +23,8 @@ CXXFLAGS += -g
CXXFLAGS += -DTEST
CXXFLAGS += -I./ -I../src -I../src/include
SMARTDNS_OBJS = lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.o lib/nftset.o
SMARTDNS_OBJS += smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o
SMARTDNS_OBJS = lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.o lib/nftset.o lib/timer_wheel.o
SMARTDNS_OBJS += smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o timer.o
OBJS = $(addprefix ../src/, $(SMARTDNS_OBJS))
TEST_SOURCES := $(wildcard *.cc) $(wildcard */*.cc) $(wildcard */*/*.cc)