diff --git a/Makefile b/Makefile index cc1587e4..11822586 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -include make.conf -OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o +OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o rdns.o gen/version.o ifeq ($(DBG_BUILD),1) OBJS += debug.o endif diff --git a/hash.h b/hash.h new file mode 100644 index 00000000..13f66abd --- /dev/null +++ b/hash.h @@ -0,0 +1,85 @@ +#ifndef _LINUX_HASH_H +#define _LINUX_HASH_H +/* Fast hashing routine for ints, longs and pointers. + (C) 2002 Nadia Yvette Chambers, IBM */ + +/* + * Knuth recommends primes in approximately golden ratio to the maximum + * integer representable by a machine word for multiplicative hashing. + * Chuck Lever verified the effectiveness of this technique: + * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf + * + * These primes are chosen to be bit-sparse, that is operations on + * them can use shifts and additions instead of multiplications for + * machines where multiplications are slow. + */ + +// #include +// #include +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL +/* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */ +#define GOLDEN_RATIO_PRIME_64 0x9e37fffffffc0001UL + +#if __WORDSIZE == 32 +#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_32 +#define hash_long(val, bits) hash_32(val, bits) +#elif __WORDSIZE == 64 +#define hash_long(val, bits) hash_64(val, bits) +#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_64 +#else +#error Wordsize not 32 or 64 +#endif + +typedef unsigned int u32; +typedef unsigned long long u64; + +static __always_inline u64 hash_64(u64 val, unsigned int bits) +{ + u64 hash = val; + + /* Sigh, gcc can't optimise this alone like it does for 32 bits. */ + u64 n = hash; + n <<= 18; + hash -= n; + n <<= 33; + hash -= n; + n <<= 3; + hash += n; + n <<= 3; + hash -= n; + n <<= 4; + hash += n; + n <<= 2; + hash += n; + + /* High bits are more random, so use them. */ + return hash >> (64 - bits); +} + +static inline u32 hash_32(u32 val, unsigned int bits) +{ + /* On some cpus multiply is faster, on others gcc will do shifts */ + u32 hash = val * GOLDEN_RATIO_PRIME_32; + + /* High bits are more random, so use them. */ + return hash >> (32 - bits); +} + +static inline unsigned long hash_ptr(const void *ptr, unsigned int bits) +{ + return hash_long((unsigned long)ptr, bits); +} + +static inline u32 hash32_ptr(const void *ptr) +{ + unsigned long val = (unsigned long)ptr; + +#if BITS_PER_LONG == 64 + val ^= (val >> 32); +#endif + return (u32)val; +} +#endif /* _LINUX_HASH_H */ diff --git a/hashtable.h b/hashtable.h new file mode 100644 index 00000000..fd986be0 --- /dev/null +++ b/hashtable.h @@ -0,0 +1,157 @@ +/* + * Statically sized hash table implementation + * (C) 2012 Sasha Levin + */ + +#ifndef _LINUX_HASHTABLE_H +#define _LINUX_HASHTABLE_H + +#include "list.h" +// #include +// #include +#include "hash.h" + +static unsigned int ilog2( unsigned int x ) +{ + unsigned int ans = 0 ; + while( x>>=1 ) ans++; + return ans ; +} + +#define DEFINE_HASHTABLE(name, bits) \ + struct hlist_head_t name[1 << (bits)] = \ + { [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT } + +#define DECLARE_HASHTABLE(name, bits) \ + struct hlist_head_t name[1 << (bits)] + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define HASH_SIZE(name) (ARRAY_SIZE(name)) +#define HASH_BITS(name) ilog2(HASH_SIZE(name)) + +/* Use hash_32 when possible to allow for fast 32bit hashing in 64bit kernels. */ +#define hash_min(val, bits) \ + (sizeof(val) <= 4 ? hash_32(val, bits) : hash_long(val, bits)) + +static inline void __hash_init(struct hlist_head_t *ht, unsigned int sz) +{ + unsigned int i; + + for (i = 0; i < sz; i++) + INIT_HLIST_HEAD(&ht[i]); +} + +/** + * hash_init - initialize a hash table + * @hashtable: hashtable to be initialized + * + * Calculates the size of the hashtable from the given parameter, otherwise + * same as hash_init_size. + * + * This has to be a macro since HASH_BITS() will not work on pointers since + * it calculates the size during preprocessing. + */ +#define hash_init(hashtable) __hash_init(hashtable, HASH_SIZE(hashtable)) + +/** + * hash_add - add an object to a hashtable + * @hashtable: hashtable to add to + * @node: the &struct hlist_node of the object to be added + * @key: the key of the object to be added + */ +#define hash_add(hashtable, node, key) \ + hlist_add_head(node, &hashtable[hash_min(key, HASH_BITS(hashtable))]) + +/** + * hash_hashed - check whether an object is in any hashtable + * @node: the &struct hlist_node of the object to be checked + */ +static inline bool hash_hashed(struct hlist_node_t *node) +{ + return !hlist_unhashed(node); +} + +static inline bool __hash_empty(struct hlist_head_t *ht, unsigned int sz) +{ + unsigned int i; + + for (i = 0; i < sz; i++) + if (!hlist_empty(&ht[i])) + return false; + + return true; +} + +/** + * hash_empty - check whether a hashtable is empty + * @hashtable: hashtable to check + * + * This has to be a macro since HASH_BITS() will not work on pointers since + * it calculates the size during preprocessing. + */ +#define hash_empty(hashtable) __hash_empty(hashtable, HASH_SIZE(hashtable)) + +/** + * hash_del - remove an object from a hashtable + * @node: &struct hlist_node of the object to remove + */ +static inline void hash_del(struct hlist_node_t *node) +{ + hlist_del_init(node); +} + +/** + * hash_for_each - iterate over a hashtable + * @name: hashtable to iterate + * @bkt: integer to use as bucket loop cursor + * @node: the &struct list_head to use as a loop cursor for each entry + * @obj: the type * to use as a loop cursor for each entry + * @member: the name of the hlist_node within the struct + */ +#define hash_for_each(name, bkt, node, obj, member) \ + for ((bkt) = 0, node = NULL; node == NULL && (bkt) < HASH_SIZE(name); (bkt)++)\ + hlist_for_each_entry(obj, node, &name[bkt], member) + +/** + * hash_for_each_safe - iterate over a hashtable safe against removal of + * hash entry + * @name: hashtable to iterate + * @bkt: integer to use as bucket loop cursor + * @node: the &struct list_head to use as a loop cursor for each entry + * @tmp: a &struct used for temporary storage + * @obj: the type * to use as a loop cursor for each entry + * @member: the name of the hlist_node within the struct + */ +#define hash_for_each_safe(name, bkt, node, tmp, obj, member) \ + for ((bkt) = 0, node = NULL; node == NULL && (bkt) < HASH_SIZE(name); (bkt)++)\ + hlist_for_each_entry_safe(obj, node, tmp, &name[bkt], member) + +/** + * hash_for_each_possible - iterate over all possible objects hashing to the + * same bucket + * @name: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @node: the &struct list_head to use as a loop cursor for each entry + * @member: the name of the hlist_node within the struct + * @key: the key of the objects to iterate over + */ +#define hash_for_each_possible(name, obj, node, member, key) \ + hlist_for_each_entry(obj, node, &name[hash_min(key, HASH_BITS(name))], member) + +/** + * hash_for_each_possible_safe - iterate over all possible objects hashing to the + * same bucket safe against removals + * @name: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @node: the &struct list_head to use as a loop cursor for each entry + * @tmp: a &struct used for temporary storage + * @member: the name of the hlist_node within the struct + * @key: the key of the objects to iterate over + */ +#define hash_for_each_possible_safe(name, obj, node, tmp, member, key) \ + hlist_for_each_entry_safe(obj, node, tmp, \ + &name[hash_min(key, HASH_BITS(name))], member) + + +#endif + diff --git a/http-connect.c b/http-connect.c index 541821c5..5d69b2c0 100644 --- a/http-connect.c +++ b/http-connect.c @@ -30,6 +30,7 @@ #include "log.h" #include "redsocks.h" #include "http-auth.h" +#include "rdns.h" typedef enum httpc_state_t { httpc_new, @@ -246,7 +247,7 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) // TODO: do accurate evbuffer_expand() while cleaning up http-auth len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n", - inet_ntoa(client->destaddr.sin_addr), + get_hostname_for_addr(inet_ntoa(client->destaddr.sin_addr)), ntohs(client->destaddr.sin_port)); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); diff --git a/list.h b/list.h index 8200a160..b64ab46f 100644 --- a/list.h +++ b/list.h @@ -412,4 +412,171 @@ static inline void list_splice_init(struct list_head_t *list, &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, __typeof(*n), member)) +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head_t { + struct hlist_node_t *first; +} hlist_head; + +struct hlist_node_t { + struct hlist_node_t *next, **pprev; +} hlist_node; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head_t name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node_t *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +static inline int hlist_unhashed(const struct hlist_node_t *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head_t *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node_t *n) +{ + struct hlist_node_t *next = n->next; + struct hlist_node_t **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node_t *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + +static inline void hlist_del_init(struct hlist_node_t *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +static inline void hlist_add_head(struct hlist_node_t *n, struct hlist_head_t *h) +{ + struct hlist_node_t *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node_t *n, + struct hlist_node_t *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node_t *n, + struct hlist_node_t *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +/* after that we'll appear to be on some hlist and hlist_del will work */ +static inline void hlist_add_fake(struct hlist_node_t *n) +{ + n->pprev = &n->next; +} + +/* + * Move a list from one list head to another. Fixup the pprev + * reference of the first entry if it exists. + */ +static inline void hlist_move_list(struct hlist_head_t *old, + struct hlist_head_t *new) +{ + new->first = old->first; + if (new->first) + new->first->pprev = &new->first; + old->first = NULL; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos ; pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && \ + ({ tpos = hlist_entry(pos, __typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && \ + ({ tpos = hlist_entry(pos, __typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && \ + ({ tpos = hlist_entry(pos, __typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, __typeof(*tpos), member); 1;}); \ + pos = n) + #endif diff --git a/main.c b/main.c index 06585840..235933f2 100644 --- a/main.c +++ b/main.c @@ -34,6 +34,7 @@ extern app_subsys debug_subsys; extern app_subsys base_subsys; extern app_subsys redudp_subsys; extern app_subsys dnstc_subsys; +extern app_subsys rdns_subsys; app_subsys *subsystems[] = { &redsocks_subsys, @@ -43,6 +44,7 @@ app_subsys *subsystems[] = { &base_subsys, &redudp_subsys, &dnstc_subsys, + &rdns_subsys, }; static const char *confname = "redsocks.conf"; diff --git a/rdns.c b/rdns.c new file mode 100644 index 00000000..01ace67e --- /dev/null +++ b/rdns.c @@ -0,0 +1,324 @@ +/* redsocks - transparent TCP-to-proxy redirector + * Copyright (C) 2007-2011 Leonid Evdokimov + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "log.h" +#include "parser.h" +#include "main.h" +#include "redsocks.h" +#include "utils.h" +#include "rdns.h" + +#define rdns_log_error(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.host_fifo, prio, ## msg) +#define rdns_log_errno(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.host_fifo, prio, ## msg) + +struct host_entry_t { + struct hlist_node_t node; + char *addr; + char *name; +}; + +static void rdns_fini_instance(rdns_instance *instance); +static int rdns_init_instance(rdns_instance *instance); +static int rdns_fini(); + +/*********************************************************************** + * Init / shutdown + */ +static parser_entry rdns_entries[] = +{ + { .key = "fifo", .type = pt_pchar }, + { } +}; + +static DEFINE_HASHTABLE(hostnames, 8); + +static list_head instances = LIST_HEAD_INIT(instances); + +static int rdns_onenter(parser_section *section) +{ + rdns_instance *instance = calloc(1, sizeof(*instance)); + if (!instance) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&instance->list); + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "fifo") == 0) ? (void*)&instance->config.host_fifo_name : + NULL; + + section->data = instance; + return 0; +} + +static int rdns_onexit(parser_section *section) +{ + rdns_instance *instance = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + list_add(&instance->list, &instances); + + return 0; +} +unsigned long +hash(char *str) +{ + unsigned long hash = 5381; + int c; + + while (c = *str++) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + return hash; +} + +static void store_name_for_address(char *addr, char *name) { + struct host_entry_t *entry; + struct hlist_node_t *node; + + hash_for_each_possible(hostnames, entry, node, node, hash(addr)) { + if (!strcmp(addr, entry->addr)) { + if (strcmp(name, entry->name)) { + fprintf(stdout, "Replacing %s %s with %s %s\n", addr, entry->name, addr, name); + free(entry->name); + entry->name = strdup(name); + } + return; + } + } + + entry = malloc(sizeof(struct host_entry_t)); + if (!entry) + perror("malloc entry"); + entry->addr = strdup(addr); + if (!entry->addr) + perror("strdup entry->addr"); + entry->name = strdup(name); + if (!entry->name) + perror("strdup entry->name"); + hash_add(hostnames, &entry->node, hash(entry->addr)); +} + +static void clear_hostnames() { + struct host_entry_t *entry; + struct hlist_node_t *node; + int bkt; + + fprintf(stdout, "Purging hostnames\n"); + hash_for_each(hostnames, bkt, node, entry, node) { + fprintf(stdout, "%d: '%s'->'%s'\n", bkt, entry->addr, entry->name); + free(entry->addr); + free(entry->name); + hash_del(&entry->node); + free(entry); + } + +} + +char * get_hostname_for_addr(char *addr) { + struct host_entry_t *entry; + struct hlist_node_t *node; + + hash_for_each_possible(hostnames, entry, node, node, hash(addr)) { + if (!strcmp(addr, entry->addr)) { + return entry->name; + } + } + return addr; +} + +static void interpret_host_line(char *line) +{ + char *addr, *name, *sp = " \t"; + addr = strtok_r(line, sp, &line); + if (addr == NULL) + return; + name = strtok_r(line, sp, &line); + if (name == NULL) + return; + + store_name_for_address(addr, name); + +} + +static void rdns_host_entries_added(int fd, short what, void *_arg) +{ + char buf[1024]; + int len; + rdns_instance *instance = _arg; + + len = read(fd, buf, sizeof(buf) - 1); + + if (len <= 0) { + fprintf(stderr, "Disconnected\n"); + clear_hostnames(); + rdns_fini_instance(instance); + rdns_init_instance(instance); + return; + } + + buf[len] = '\0'; + char *ptr = buf, *line, *lf = "\r\n"; + + line = strtok_r(ptr, lf, &ptr); + while (line != NULL) { + if (line[0] != '#') + interpret_host_line(line); + line = strtok_r(ptr, lf, &ptr); + } +} + +static int rdns_init_instance(rdns_instance *instance) +{ + /* FIXME: rdns_fini_instance is called in case of failure, this + * function will remove instance from instances list - result + * looks ugly. + */ + int error; + int fd = -1; + + struct stat st; + if (lstat(instance->config.host_fifo_name, &st) == 0) { + if ((st.st_mode & S_IFMT) == S_IFREG) { + errno = EEXIST; + perror("lstat"); + goto fail; + } + } + unlink(instance->config.host_fifo_name); + if (mkfifo(instance->config.host_fifo_name, 0600) == -1) { + perror("mkfifo"); + goto fail; + } + + fd = open(instance->config.host_fifo_name, O_RDONLY | O_NONBLOCK, 0); + + if (fd == -1) { + perror("open"); + goto fail; + } + + instance->host_fifo = fd; + + event_set(&instance->listener, fd, EV_READ | EV_PERSIST, rdns_host_entries_added, instance); + error = event_add(&instance->listener, NULL); + if (error) { + log_errno(LOG_ERR, "event_add"); + goto fail; + } + + return 0; + +fail: + rdns_fini_instance(instance); + + if (fd != -1) { + if (close(fd) != 0) + log_errno(LOG_WARNING, "close"); + } + + return -1; +} + +/* Drops instance completely, freeing its memory and removing from + * instances list. + */ +static void rdns_fini_instance(rdns_instance *instance) +{ + if (event_initialized(&instance->listener)) { + if (event_del(&instance->listener) != 0) + log_errno(LOG_WARNING, "event_del"); + if (close(event_get_fd(&instance->listener)) != 0) + log_errno(LOG_WARNING, "close"); + memset(&instance->listener, 0, sizeof(instance->listener)); + } + + close(instance->host_fifo); + +// list_del(&instance->list); + +// memset(instance, 0, sizeof(*instance)); +// free(instance); +} + +static int rdns_init() +{ + rdns_instance *tmp, *instance = NULL; + + // TODO: init debug_dumper + + list_for_each_entry_safe(instance, tmp, &instances, list) { + if (rdns_init_instance(instance) != 0) + goto fail; + } + + hash_init(hostnames); + + return 0; + +fail: + rdns_fini(); + return -1; +} + +static int rdns_fini() +{ + rdns_instance *tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) + rdns_fini_instance(instance); + + return 0; +} + +static parser_section rdns_conf_section = +{ + .name = "rdns", + .entries = rdns_entries, + .onenter = rdns_onenter, + .onexit = rdns_onexit +}; + +app_subsys rdns_subsys = +{ + .init = rdns_init, + .fini = rdns_fini, + .conf_section = &rdns_conf_section, +}; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/rdns.h b/rdns.h new file mode 100644 index 00000000..349e57da --- /dev/null +++ b/rdns.h @@ -0,0 +1,22 @@ +#ifndef RDNS_H +#define RDNS_H + +#include "list.h" +#include "hashtable.h" + +typedef struct rdns_config_t { + char *host_fifo_name; +} rdns_config; + +typedef struct rdns_instance_t { + list_head list; + rdns_config config; + struct event listener; + int host_fifo; +} rdns_instance; + +char * get_hostname_for_addr(char *addr); + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ +#endif /* RDNS_H */