From af5c6f0c6a7cddd46b801b7b59af7a5e9383c645 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Thu, 8 Nov 2012 01:18:19 +0100 Subject: [PATCH] replace hostent lookup with better performing in-memory copy. the central dns resolver function proxy_gethostbyname() used to iterate over the gethostent() db (/etc/hosts) on each dns request. since this is not threadsafe, we synchronized access to it previously using mutexes. the parsing of this file is slow, and blocking all threads to do it even moreso. since gethostent_r() is only available on a few platforms, i decided to read the hostent db once and then use a quick in-memory lookup on further usage. + some further refactoring. --- Makefile | 4 ++- src/allocator_thread.c | 24 +++------------ src/core.c | 28 +++++++----------- src/hash.c | 12 ++++++++ src/hash.h | 10 +++++++ src/hostentdb.c | 56 +++++++++++++++++++++++++++++++++++ src/hostentdb.h | 23 +++++++++++++++ src/libproxychains.c | 4 ++- src/stringdump.c | 13 +++++++++ src/stringdump.h | 12 ++++++++ tests/test_gethostent_r.c | 61 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 208 insertions(+), 39 deletions(-) create mode 100644 src/hash.c create mode 100644 src/hash.h create mode 100644 src/hostentdb.c create mode 100644 src/hostentdb.h create mode 100644 src/stringdump.c create mode 100644 src/stringdump.h create mode 100644 tests/test_gethostent_r.c diff --git a/Makefile b/Makefile index bdaab44..481d904 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,9 @@ sysconfdir=$(prefix)/etc SRCS = $(sort $(wildcard src/*.c)) OBJS = $(SRCS:.c=.o) -LOBJS = src/core.o src/common.o src/libproxychains.o src/shm.o src/allocator_thread.o src/ip_type.o +LOBJS = src/core.o src/common.o src/libproxychains.o src/shm.o \ + src/allocator_thread.o src/ip_type.o src/stringdump.o \ + src/hostentdb.o src/hash.o CFLAGS += -Wall -O0 -g -std=c99 -D_GNU_SOURCE -pipe LDFLAGS = -shared -fPIC -Wl,--no-as-needed -ldl -lpthread diff --git a/src/allocator_thread.c b/src/allocator_thread.c index 3ff0122..01aee47 100644 --- a/src/allocator_thread.c +++ b/src/allocator_thread.c @@ -13,13 +13,8 @@ #include "debug.h" #include "ip_type.h" #include "mutex.h" - -struct stringpool mem; -static char *at_dumpstring(char* s, size_t len) { - PFUNC(); - return stringpool_add(&mem, s, len); -} - +#include "hash.h" +#include "stringdump.h" /* stuff for our internal translation table */ @@ -38,16 +33,6 @@ pthread_mutex_t internal_ips_lock; internal_ip_lookup_table *internal_ips = NULL; internal_ip_lookup_table internal_ips_buf; -uint32_t dalias_hash(char *s0) { - unsigned char *s = (void *) s0; - uint_fast32_t h = 0; - while(*s) { - h = 16 * h + *s++; - h ^= h >> 24 & 0xf0; - } - return h & 0xfffffff; -} - uint32_t index_from_internal_ip(ip_type internalip) { PFUNC(); ip_type tmp = internalip; @@ -113,7 +98,7 @@ static ip_type ip_from_internal_list(char* name, size_t len) { goto err_plus_unlock; string_hash_tuple tmp = { 0 }; - new_mem = at_dumpstring((char*) &tmp, sizeof(string_hash_tuple)); + new_mem = dumpstring((char*) &tmp, sizeof(string_hash_tuple)); if(!new_mem) goto oom; @@ -122,7 +107,7 @@ static ip_type ip_from_internal_list(char* name, size_t len) { internal_ips->list[internal_ips->counter] = new_mem; internal_ips->list[internal_ips->counter]->hash = hash; - new_mem = at_dumpstring((char*) name, len + 1); + new_mem = dumpstring((char*) name, len + 1); if(!new_mem) { internal_ips->list[internal_ips->counter] = 0; @@ -285,7 +270,6 @@ void at_init(void) { memset(internal_ips, 0, sizeof *internal_ips); initpipe(req_pipefd); initpipe(resp_pipefd); - stringpool_init(&mem); pthread_attr_init(&allocator_thread_attr); pthread_attr_setstacksize(&allocator_thread_attr, 16 * 1024); pthread_create(&allocator_thread, &allocator_thread_attr, threadfunc, 0); diff --git a/src/core.c b/src/core.c index 5701948..e6bd5cb 100644 --- a/src/core.c +++ b/src/core.c @@ -34,8 +34,6 @@ #include #include #include -#include "mutex.h" -pthread_mutex_t hostdb_lock; #include "core.h" #include "common.h" @@ -666,12 +664,14 @@ int connect_proxy_chain(int sock, ip_type target_ip, return -1; } +#include "hostentdb.h" +struct hostent_list hl; + void core_initialize(void) { - MUTEX_INIT(&hostdb_lock); + hdb_init(&hl); } void core_unload(void) { - MUTEX_DESTROY(&hostdb_lock); } static void gethostbyname_data_setstring(struct gethostbyname_data* data, char* name) { @@ -682,9 +682,6 @@ static void gethostbyname_data_setstring(struct gethostbyname_data* data, char* struct hostent *proxy_gethostbyname(const char *name, struct gethostbyname_data* data) { PFUNC(); char buff[256]; - size_t l = strlen(name); - - struct hostent *hp; data->resolved_addr_p[0] = (char *) &data->resolved_addr; data->resolved_addr_p[1] = NULL; @@ -709,16 +706,13 @@ struct hostent *proxy_gethostbyname(const char *name, struct gethostbyname_data* memset(buff, 0, sizeof(buff)); // this iterates over the "known hosts" db, usually /etc/hosts - MUTEX_LOCK(&hostdb_lock); - while((hp = gethostent())) - if(!strcmp(hp->h_name, name) && hp->h_addrtype == AF_INET && hp->h_length == sizeof(in_addr_t)) { - data->resolved_addr = *((in_addr_t*)(hp->h_addr_list[0])); - MUTEX_UNLOCK(&hostdb_lock); - goto retname; - } - MUTEX_UNLOCK(&hostdb_lock); - - data->resolved_addr = at_get_ip_for_host((char*) name, l).as_int; + ip_type hdb_res = hdb_get(&hl, (char*) name); + if(hdb_res.as_int != ip_type_invalid.as_int) { + data->resolved_addr = hdb_res.as_int; + goto retname; + } + + data->resolved_addr = at_get_ip_for_host((char*) name, strlen(name)).as_int; if(data->resolved_addr == (in_addr_t) ip_type_invalid.as_int) return NULL; retname: diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 000000000..792dd87 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,12 @@ +#include "hash.h" + +/* dalias' version of the elf hash */ +uint32_t dalias_hash(char *s0) { + unsigned char *s = (void *) s0; + uint_fast32_t h = 0; + while(*s) { + h = 16 * h + *s++; + h ^= h >> 24 & 0xf0; + } + return h & 0xfffffff; +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 000000000..811e6f0 --- /dev/null +++ b/src/hash.h @@ -0,0 +1,10 @@ +#ifndef HASH_H +#define HASH_H + +#include + +uint32_t dalias_hash(char *s0); + +//RcB: DEP "hash.c" + +#endif diff --git a/src/hostentdb.c b/src/hostentdb.c new file mode 100644 index 000000000..1562162 --- /dev/null +++ b/src/hostentdb.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "ip_type.h" +#include "hash.h" +#include "stringdump.h" +#include "hostentdb.h" +#include "common.h" +#include "debug.h" + +#define STEP 16 +static void hdb_add(struct hostent_list* hl, char* host, ip_type ip) { + if(hl->count +1 > hl->capa) { + void * nu = realloc(hl->entries, (hl->capa + STEP) * sizeof(struct hostent_entry)); + if(!nu) return; + hl->entries = nu; + hl->capa += STEP; + } + struct hostent_entry *h = &hl->entries[hl->count]; + h->hash = dalias_hash(host); + h->ip.as_int = ip.as_int; + h->str = dumpstring(host, strlen(host) + 1); + if(h->str) hl->count++; +} + +static void hdb_fill(struct hostent_list *hl) { + struct hostent* hp; + while((hp = gethostent())) + if(hp->h_addrtype == AF_INET && hp->h_length == sizeof(in_addr_t)) { + hdb_add(hl, hp->h_name, (ip_type) { .as_int = *((in_addr_t*)(hp->h_addr_list[0])) }); + } +} + +void hdb_init(struct hostent_list *hl) { + memset(hl, 0, sizeof *hl); + hdb_fill(hl); +} + +ip_type hdb_get(struct hostent_list *hl, char* host) { + size_t i; + PFUNC(); + uint32_t hash = dalias_hash(host); + for(i = 0; i < hl->count; i++) { + if(hl->entries[i].hash == hash && !strcmp(hl->entries[i].str, host)) { + #ifdef DEBUG + char ipbuf[16]; + pc_stringfromipv4(hl->entries[i].ip.octet, ipbuf); + PDEBUG("got ip %s for hostent entry %s\n", ipbuf, host); + #endif + return hl->entries[i].ip; + } + } + return ip_type_invalid; +} \ No newline at end of file diff --git a/src/hostentdb.h b/src/hostentdb.h new file mode 100644 index 000000000..4f807d1 --- /dev/null +++ b/src/hostentdb.h @@ -0,0 +1,23 @@ +#ifndef HOSTENTDB_H +#define HOSTENTDB_H + +#include "ip_type.h" +#include + +struct hostent_entry { + uint32_t hash; + ip_type ip; + char* str; +}; + +struct hostent_list { + size_t count; + size_t capa; + struct hostent_entry *entries; +}; + +void hdb_init(struct hostent_list *hl); +ip_type hdb_get(struct hostent_list *hl, char* host); + +//RcB: DEP "hostendb.c" +#endif diff --git a/src/libproxychains.c b/src/libproxychains.c index d293006..41b193b 100644 --- a/src/libproxychains.c +++ b/src/libproxychains.c @@ -94,9 +94,11 @@ static void* load_sym(char* symname, void* proxyfunc) { #include "shm.h" #include "allocator_thread.h" +#include "stringdump.h" static void do_init(void) { srand(time(NULL)); + dumpstring_init(); // global string garbage can core_initialize(); at_init(); @@ -357,7 +359,7 @@ int getaddrinfo(const char *node, const char *service, const struct addrinfo *hi INIT(); - PDEBUG("getaddrinfo: %s %s\n", node, service); + PDEBUG("getaddrinfo: %s %s\n", node ? node : "null", service ? service : "null"); if(proxychains_resolver) ret = proxy_getaddrinfo(node, service, hints, res); diff --git a/src/stringdump.c b/src/stringdump.c new file mode 100644 index 000000000..ff946f0 --- /dev/null +++ b/src/stringdump.c @@ -0,0 +1,13 @@ +#include "stringdump.h" +#include "debug.h" + +struct stringpool mem; + +char *dumpstring(char* s, size_t len) { + PFUNC(); + return stringpool_add(&mem, s, len); +} + +void dumpstring_init(void) { + stringpool_init(&mem); +} diff --git a/src/stringdump.h b/src/stringdump.h new file mode 100644 index 000000000..4c16d6f --- /dev/null +++ b/src/stringdump.h @@ -0,0 +1,12 @@ +#ifndef STRINGDUMP_H +#define STRINGDUMP_H + +#include "shm.h" +#include + +char *dumpstring(char* s, size_t len); +void dumpstring_init(void); + +//RcB: DEP "stringdump.h" + +#endif diff --git a/tests/test_gethostent_r.c b/tests/test_gethostent_r.c new file mode 100644 index 000000000..c846775 --- /dev/null +++ b/tests/test_gethostent_r.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include "../src/common.h" + +/* +int gethostent_r( + struct hostent *ret, char *buf, size_t buflen, + struct hostent **result, int *h_errnop); + +Glibc2 also has reentrant versions gethostent_r(), gethostbyaddr_r(), +gethostbyname_r() and gethostbyname2_r(). + +The caller supplies a hostent structure ret which will be filled in on success, +and a temporary work buffer buf of size buflen. +After the call, result will point to the result on success. +In case of an error or if no entry is found result will be NULL. +The functions return 0 on success and a nonzero error number on failure. +In addition to the errors returned by the nonreentrant versions of these functions, +if buf is too small, the functions will return ERANGE, and the call should be retried +with a larger buffer. +The global variable h_errno is not modified, but the address of a variable in which +to store error numbers is passed in h_errnop. +*/ + +void printhostent(struct hostent *hp) { + char ipbuf[16]; + pc_stringfromipv4(hp->h_addr_list[0], ipbuf); + printf("alias: %p, len: %d, name: %s, addrlist: %p, addrtype: %d, ip: %s\n", + hp->h_aliases, + hp->h_length, + hp->h_name, + hp->h_addr_list, + hp->h_addrtype, + ipbuf + ); +} + +int main(int argc, char** argv) { + struct hostent he_buf; + struct hostent *he_res; + char h_buf[1024]; + int ch_errno; + int ret; + do { + ret = gethostent_r(&he_buf, h_buf, sizeof(h_buf), &he_res, &ch_errno); + printf("ret: %d, h_errno: %d\n", ret, ch_errno); + if(ret != 0) { + errno = ret; + ret = -1; + } + if(ret == -1) { + perror("gethostent_r"); + break; + } + if(he_res) { + printhostent(he_res); + } + } while (he_res); + return 0; +} \ No newline at end of file