Skip to content

Commit 82c86a7

Browse files
committed
gwproxy/dns: Replace the old DNS cache system with a new one.
The new DNS cache library uses a hashmap data structure, which has an average O(1) time complexity for lookup. It's also cleaner to split the caching logic into a smaller, more manageable C files. Signed-off-by: Ammar Faizi <[email protected]>
1 parent daacf53 commit 82c86a7

File tree

1 file changed

+80
-195
lines changed

1 file changed

+80
-195
lines changed

src/gwproxy/dns.c

Lines changed: 80 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#endif
88

99
#include "dns.h"
10+
#include "dns_cache.h"
1011

1112
#include <assert.h>
1213
#include <stdlib.h>
@@ -31,21 +32,6 @@ struct gwp_dns_wrk {
3132
pthread_t thread;
3233
};
3334

34-
struct cache_entry {
35-
char *name;
36-
char *service;
37-
struct addrinfo *res; /* Cached result. */
38-
time_t expired_at; /* Expiration time. */
39-
};
40-
41-
struct gwp_dns_cache {
42-
pthread_rwlock_t lock; /* Lock for the cache. */
43-
struct cache_entry *entries; /* Array of entries. */
44-
uint32_t nr; /* Number of entries. */
45-
uint32_t cap; /* Capacity of the array. */
46-
time_t last_scan; /* Last scan time. */
47-
};
48-
4935
struct gwp_dns_ctx {
5036
volatile bool should_stop;
5137
pthread_mutex_t lock;
@@ -56,6 +42,7 @@ struct gwp_dns_ctx {
5642
struct gwp_dns_entry *tail;
5743
struct gwp_dns_wrk *workers;
5844
struct gwp_dns_cache *cache;
45+
time_t last_scan;
5946
struct gwp_dns_cfg cfg;
6047
};
6148

@@ -160,125 +147,16 @@ static void prep_hints(struct addrinfo *hints, uint32_t restyp)
160147
hints->ai_family = AF_INET6;
161148
}
162149

163-
static void free_cache_entry(struct cache_entry *e)
164-
{
165-
if (!e)
166-
return;
167-
168-
free(e->name);
169-
if (e->res)
170-
freeaddrinfo(e->res);
171-
}
172-
173-
static void gwp_dns_cache_scan_and_delete_expired_entries(struct gwp_dns_cache *cache)
174-
{
175-
uint32_t i;
176-
177-
pthread_rwlock_wrlock(&cache->lock);
178-
for (i = 0; i < cache->nr; i++) {
179-
struct cache_entry *last, *e = &cache->entries[i];
180-
time_t now = time(NULL);
181-
182-
if (e->expired_at > now)
183-
continue;
184-
185-
free_cache_entry(e);
186-
last = &cache->entries[--cache->nr];
187-
if (e != last) {
188-
/*
189-
* Move the last entry to the current position
190-
* to avoid holes in the array.
191-
*/
192-
*e = *last;
193-
} else {
194-
/*
195-
* If the last entry is the one we are deleting,
196-
* just clear it.
197-
*/
198-
memset(e, 0, sizeof(*e));
199-
}
200-
201-
/*
202-
* The next iteration will check the current index
203-
* again as it now contains an untraversed entry
204-
* that may also be expired.
205-
*/
206-
i--;
207-
}
208-
pthread_rwlock_unlock(&cache->lock);
209-
}
210-
211-
static int gwp_dns_cache_insert(struct gwp_dns_ctx *ctx, const char *name,
212-
const char *service, struct addrinfo *res)
150+
static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
151+
const struct addrinfo *ai)
213152
{
214-
struct gwp_dns_cache *cache = ctx->cache;
215-
char *cname, *cservice;
216-
struct cache_entry *e;
217-
size_t nl, sl;
218-
int r;
219-
220-
if (!cache)
221-
return -ENOSYS;
222-
223-
pthread_rwlock_wrlock(&cache->lock);
224-
if (cache->nr >= cache->cap) {
225-
size_t new_cap = cache->cap ? cache->cap * 2 : 16;
226-
struct cache_entry *nentries;
227-
228-
nentries = realloc(cache->entries, new_cap * sizeof(*nentries));
229-
if (!nentries) {
230-
r = -ENOMEM;
231-
goto out;
232-
}
233-
234-
cache->entries = nentries;
235-
cache->cap = new_cap;
236-
}
237-
238-
nl = strlen(name);
239-
sl = service ? strlen(service) : 0;
240-
cname = malloc(nl + 1 + sl + 1);
241-
if (!cname) {
242-
r = -ENOMEM;
243-
goto out;
244-
}
245-
246-
cservice = cname + nl + 1;
247-
memcpy(cname, name, nl + 1);
248-
if (service)
249-
memcpy(cservice, service, sl + 1);
250-
else
251-
cservice[0] = '\0';
252-
253-
e = &cache->entries[cache->nr++];
254-
e->name = cname;
255-
e->service = cservice;
256-
e->res = res;
257-
e->expired_at = time(NULL) + ctx->cfg.cache_expiry;
258-
r = 0;
259-
out:
260-
pthread_rwlock_unlock(&cache->lock);
261-
return r;
262-
}
153+
int x;
263154

264-
/**
265-
* Try to pass the result of the DNS resolution to the cache. If successful,
266-
* the `ai` pointer is owned by the cache and the caller MUST not free it.
267-
*
268-
* @param ctx The DNS context.
269-
* @param name Name of the DNS entry.
270-
* @param service Service of the DNS entry.
271-
* @param ai The addrinfo result to be cached.
272-
* @return True if the result was successfully passed to the
273-
* cache, false otherwise.
274-
*/
275-
static bool try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
276-
const char *service, struct addrinfo *ai)
277-
{
278155
if (!ctx->cache)
279-
return false;
156+
return;
280157

281-
return gwp_dns_cache_insert(ctx, name, service, ai) == 0;
158+
x = ctx->cfg.cache_expiry;
159+
gwp_dns_cache_insert(ctx->cache, name, ai, time(NULL) + x);
282160
}
283161

284162
int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name,
@@ -295,10 +173,8 @@ int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name,
295173
return -r;
296174

297175
found = iterate_addr_list(res, addr, restyp);
298-
if (found) {
299-
if (try_pass_result_to_cache(ctx, name, service, res))
300-
res = NULL;
301-
}
176+
if (found)
177+
try_pass_result_to_cache(ctx, name, res);
302178

303179
if (res)
304180
freeaddrinfo(res);
@@ -332,19 +208,19 @@ static void cond_scan_cache(struct gwp_dns_ctx *ctx)
332208
if (!ctx->cache)
333209
return;
334210

335-
if (time(NULL) - ctx->cache->last_scan < ctx->cfg.cache_expiry)
211+
if (time(NULL) - ctx->last_scan < ctx->cfg.cache_expiry)
336212
return;
337213

338214
pthread_mutex_unlock(&ctx->lock);
339-
gwp_dns_cache_scan_and_delete_expired_entries(ctx->cache);
215+
gwp_dns_cache_housekeep(ctx->cache);
340216
pthread_mutex_lock(&ctx->lock);
341217

342218
/*
343219
* Call time(NULL) again because the scan might have taken
344220
* several seconds if the number of entries is large or
345221
* it got contended by other threads.
346222
*/
347-
ctx->cache->last_scan = time(NULL);
223+
ctx->last_scan = time(NULL);
348224
}
349225

350226
/*
@@ -499,10 +375,8 @@ static void dispatch_batch_result(int r, struct gwp_dns_ctx *ctx,
499375
}
500376

501377
eventfd_write(e->ev_fd, 1);
502-
if (!e->res) {
503-
if (try_pass_result_to_cache(ctx, e->name, e->service, ai))
504-
dbq->reqs[i]->ar_result = NULL;
505-
}
378+
if (!e->res)
379+
try_pass_result_to_cache(ctx, e->name, ai);
506380
}
507381
}
508382

@@ -703,49 +577,75 @@ static int init_workers(struct gwp_dns_ctx *ctx)
703577
return r;
704578
}
705579

706-
int gwp_dns_cache_lookup(struct gwp_dns_ctx *ctx, const char *name,
707-
const char *service, struct gwp_sockaddr *addr)
580+
static bool fetch_i4(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr,
581+
uint16_t port)
708582
{
709-
struct gwp_dns_cache *cache;
710-
bool found_expired;
711-
time_t now;
712-
uint32_t i;
713-
int r;
583+
uint8_t *b = gwp_dns_cache_entget_i4(e);
584+
if (!b)
585+
return false;
714586

715-
if (!ctx->cache)
716-
return -ENOSYS;
587+
memset(addr, 0, sizeof(*addr));
588+
addr->i4.sin_family = AF_INET;
589+
addr->i4.sin_port = htons(port);
590+
memcpy(&addr->i4.sin_addr, b, 4);
591+
return true;
592+
}
717593

718-
cache = ctx->cache;
719-
pthread_rwlock_rdlock(&cache->lock);
720-
r = -ENOENT;
721-
now = time(NULL);
722-
found_expired = false;
723-
for (i = 0; i < cache->nr; i++) {
724-
struct cache_entry *e = &cache->entries[i];
725-
bool exp = (e->expired_at <= now);
594+
static bool fetch_i6(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr,
595+
uint16_t port)
596+
{
597+
uint8_t *b = gwp_dns_cache_entget_i6(e);
598+
if (!b)
599+
return false;
726600

727-
found_expired |= exp;
601+
memset(addr, 0, sizeof(*addr));
602+
addr->i6.sin6_family = AF_INET6;
603+
addr->i6.sin6_port = htons(port);
604+
memcpy(&addr->i6.sin6_addr, b, 16);
605+
return true;
606+
}
607+
608+
static int fetch_addr(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr,
609+
uint16_t port, uint32_t restyp)
610+
{
611+
if (restyp == GWP_DNS_RESTYP_IPV4_ONLY) {
612+
if (!fetch_i4(e, addr, port))
613+
return -EHOSTUNREACH;
614+
} else if (restyp == GWP_DNS_RESTYP_IPV6_ONLY) {
615+
if (!fetch_i6(e, addr, port))
616+
return -EHOSTUNREACH;
617+
} else if (restyp == GWP_DNS_RESTYP_PREFER_IPV6) {
618+
if (!fetch_i6(e, addr, port)) {
619+
if (!fetch_i4(e, addr, port))
620+
return -EHOSTUNREACH;
621+
}
622+
} else if (restyp == GWP_DNS_RESTYP_PREFER_IPV4) {
623+
if (!fetch_i4(e, addr, port)) {
624+
if (!fetch_i6(e, addr, port))
625+
return -EHOSTUNREACH;
626+
}
627+
} else {
628+
return -EINVAL;
629+
}
728630

729-
if (exp)
730-
continue;
631+
return 0;
632+
}
731633

732-
if (strcmp(e->name, name))
733-
continue;
634+
int gwp_dns_cache_lookup(struct gwp_dns_ctx *ctx, const char *name,
635+
const char *service, struct gwp_sockaddr *addr)
636+
{
637+
struct gwp_dns_cache_entry *e;
638+
int r;
734639

735-
if (service && strcmp(e->service, service))
736-
continue;
737-
else if (!service && e->service[0] != '\0')
738-
continue;
640+
if (!ctx->cache)
641+
return -ENOSYS;
739642

740-
if (iterate_addr_list(e->res, addr, ctx->cfg.restyp)) {
741-
r = 0;
742-
break;
743-
}
744-
}
745-
pthread_rwlock_unlock(&cache->lock);
746-
if (found_expired)
747-
gwp_dns_cache_scan_and_delete_expired_entries(cache);
643+
r = gwp_dns_cache_getent(ctx->cache, name, &e);
644+
if (r)
645+
return r;
748646

647+
r = fetch_addr(e, addr, service ? atoi(service) : 0, ctx->cfg.restyp);
648+
gwp_dns_cache_putent(e);
749649
return r;
750650
}
751651

@@ -762,37 +662,21 @@ static int init_cache(struct gwp_dns_ctx *ctx)
762662
return 0;
763663
}
764664

765-
cache = calloc(1, sizeof(*cache));
766-
if (!cache)
767-
return -ENOMEM;
768-
769-
r = pthread_rwlock_init(&cache->lock, NULL);
770-
if (r) {
771-
r = -r;
772-
free(cache);
665+
r = gwp_dns_cache_init(&cache, 8192);
666+
if (r)
773667
return r;
774-
}
775668

776669
ctx->cache = cache;
777670
return 0;
778671
}
779672

780673
static void free_cache(struct gwp_dns_cache *cache)
781674
{
782-
uint32_t i;
783-
784675
if (!cache)
785676
return;
786677

787-
pthread_rwlock_destroy(&cache->lock);
788-
for (i = 0; i < cache->nr; i++) {
789-
struct cache_entry *e = &cache->entries[i];
790-
free(e->name);
791-
if (e->res)
792-
freeaddrinfo(e->res);
793-
}
794-
free(cache->entries);
795-
free(cache);
678+
gwp_dns_cache_free(cache);
679+
cache = NULL;
796680
}
797681

798682
int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg)
@@ -827,6 +711,7 @@ int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg)
827711
ctx->head = NULL;
828712
ctx->tail = NULL;
829713
ctx->should_stop = false;
714+
ctx->last_scan = time(NULL);
830715
r = init_workers(ctx);
831716
if (r)
832717
goto out_free_cache;

0 commit comments

Comments
 (0)