mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-08-17 21:25:50 +02:00
Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b2807799e | ||
|
|
3749f1e1d9 | ||
|
|
98f3d78390 | ||
|
|
777190a7f8 | ||
|
|
15b5c64106 | ||
|
|
d337c801d4 | ||
|
|
e8be484ccc | ||
|
|
6c46de78d5 | ||
|
|
6903bd42af | ||
|
|
c214e7c4cb | ||
|
|
cd67cb6869 | ||
|
|
0b0dd88182 | ||
|
|
f262c2c051 | ||
|
|
290f94369f | ||
|
|
b777a38e0d | ||
|
|
5bba40f64a | ||
|
|
c6ab7d4f50 | ||
|
|
6849e91549 | ||
|
|
0bbc6205ee | ||
|
|
41463a62ae | ||
|
|
e082f2ac48 | ||
|
|
b907897790 | ||
|
|
69df3c29d9 | ||
|
|
806b4ddf67 | ||
|
|
ae93dcac76 | ||
|
|
4affdd940f | ||
|
|
1eb9ff1da3 | ||
|
|
ccf3d7190d | ||
|
|
4ad4cbdf74 | ||
|
|
46aeacc723 | ||
|
|
157defb03f | ||
|
|
0280e3446a | ||
|
|
2860ef040a | ||
|
|
f559c3c2ca | ||
|
|
2092d6535a | ||
|
|
ea25707cb9 | ||
|
|
fdfe51464b | ||
|
|
eba9696f4e | ||
|
|
22791a44ca | ||
|
|
87bc0a4f11 | ||
|
|
d39976b2e1 | ||
|
|
9f413c07e0 | ||
|
|
bd2bad94ce | ||
|
|
98235df888 | ||
|
|
4cbf2705f6 | ||
|
|
f9001ade25 | ||
|
|
e5d92f9358 | ||
|
|
e47137ad8d | ||
|
|
af41706579 | ||
|
|
06b25ffa5a | ||
|
|
1e129d341a | ||
|
|
3f61aca920 | ||
|
|
8fb3bb8bef | ||
|
|
09db7f94ed | ||
|
|
762af9f0a4 | ||
|
|
40b161b6f4 |
+10
-4
@@ -50,16 +50,18 @@ LINTFLAGS+="-DBN_ULONG=unsigned long" -Dkrb5_int32=int "-Dkrb5_ui_4=unsigned int
|
||||
|
||||
INSTALL=$(srcdir)/install-sh
|
||||
|
||||
COMMON_SRC=$(wildcard services/*.c util/*.c) util/configparser.c util/configlexer.c testcode/checklocks.c
|
||||
COMMON_SRC=$(wildcard services/*.c util/*.c util/data/*.c util/storage/*.c) util/configparser.c util/configlexer.c testcode/checklocks.c
|
||||
COMMON_OBJ=$(addprefix $(BUILD),$(COMMON_SRC:.c=.o))
|
||||
COMPAT_OBJ=$(addprefix $(BUILD)compat/,$(LIBOBJS))
|
||||
UNITTEST_SRC=testcode/unitmain.c $(COMMON_SRC)
|
||||
UNITTEST_SRC=$(wildcard testcode/unit*.c) $(COMMON_SRC)
|
||||
UNITTEST_OBJ=$(addprefix $(BUILD),$(UNITTEST_SRC:.c=.o)) $(COMPAT_OBJ)
|
||||
DAEMON_SRC=$(wildcard daemon/*.c) $(COMMON_SRC)
|
||||
DAEMON_OBJ=$(addprefix $(BUILD),$(DAEMON_SRC:.c=.o)) $(COMPAT_OBJ)
|
||||
TESTBOUND_SRC=testcode/testbound.c testcode/ldns-testpkts.c daemon/worker.c daemon/daemon.c testcode/replay.c testcode/fake_event.c $(filter-out util/netevent.c services/listen_dnsport.c services/outside_network.c, $(COMMON_SRC))
|
||||
TESTBOUND_OBJ=$(addprefix $(BUILD),$(TESTBOUND_SRC:.c=.o)) $(COMPAT_OBJ)
|
||||
ALL_SRC=$(COMMON_SRC) $(UNITTEST_SRC) $(DAEMON_SRC) $(TESTBOUND_SRC)
|
||||
LOCKVERIFY_SRC=testcode/lock_verify.c $(COMMON_SRC)
|
||||
LOCKVERIFY_OBJ=$(addprefix $(BUILD),$(LOCKVERIFY_SRC:.c=.o)) $(COMPAT_OBJ)
|
||||
ALL_SRC=$(COMMON_SRC) $(UNITTEST_SRC) $(DAEMON_SRC) $(TESTBOUND_SRC) $(LOCKVERIFY_SRC)
|
||||
ALL_OBJ=$(addprefix $(BUILD),$(ALL_SRC:.c=.o) $(addprefix compat/,$(LIBOBJS))) $(COMPAT_OBJ)
|
||||
|
||||
COMPILE=$(LIBTOOL) --tag=CC --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS)
|
||||
@@ -73,7 +75,7 @@ $(BUILD)%.o: $(srcdir)/%.c
|
||||
|
||||
.PHONY: clean realclean doc lint all
|
||||
|
||||
all: $(COMMON_OBJ) unbound unittest testbound
|
||||
all: $(COMMON_OBJ) unbound unittest testbound lock-verify
|
||||
|
||||
unbound: $(DAEMON_OBJ)
|
||||
$(INFO) Link $@
|
||||
@@ -87,6 +89,10 @@ testbound: $(TESTBOUND_OBJ)
|
||||
$(INFO) Link $@
|
||||
$Q$(LINK) -o $@ $^ $(LIBS)
|
||||
|
||||
lock-verify: $(LOCKVERIFY_OBJ)
|
||||
$(INFO) Link $@
|
||||
$Q$(LINK) -o $@ $^ $(LIBS)
|
||||
|
||||
testcode/ldns-testpkts.c: $(ldnsdir)/examples/ldns-testpkts.c \
|
||||
$(ldnsdir)/examples/ldns-testpkts.h
|
||||
cp $(ldnsdir)/examples/ldns-testpkts.c testcode/ldns-testpkts.c
|
||||
|
||||
+31
-4
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
AC_PREREQ(2.57)
|
||||
|
||||
AC_INIT(unbound, 0.1, wouter@nlnetlabs.nl, unbound)
|
||||
AC_INIT(unbound, 0.2, wouter@nlnetlabs.nl, unbound)
|
||||
|
||||
CFLAGS=
|
||||
AC_AIX
|
||||
@@ -90,7 +90,7 @@ CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"])
|
||||
|
||||
AC_CHECK_HEADERS([getopt.h time.h],,, [AC_INCLUDES_DEFAULT])
|
||||
|
||||
CHECK_COMPILER_FLAG_NEEDED(-std=c99 -D__EXTENSIONS__ -D_BSD_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600,
|
||||
CHECK_COMPILER_FLAG_NEEDED(-std=c99 -D__EXTENSIONS__ -D_BSD_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -D_ALL_SOURCE,
|
||||
[
|
||||
#include "confdefs.h"
|
||||
#include <stdlib.h>
|
||||
@@ -121,7 +121,7 @@ int test() {
|
||||
str = gai_strerror(0);
|
||||
return a;
|
||||
}
|
||||
], [CFLAGS="$CFLAGS -std=c99 -D__EXTENSIONS__ -D_BSD_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600"])
|
||||
], [CFLAGS="$CFLAGS -std=c99 -D__EXTENSIONS__ -D_BSD_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -D_ALL_SOURCE"])
|
||||
|
||||
CHECK_COMPILER_FLAG_NEEDED(-std=c99,
|
||||
[
|
||||
@@ -269,7 +269,7 @@ AC_PROG_LIBTOOL
|
||||
|
||||
# Checks for header files.
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h sys/param.h sys/socket.h arpa/inet.h],,, [AC_INCLUDES_DEFAULT])
|
||||
AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h sys/param.h sys/socket.h sys/uio.h arpa/inet.h],,, [AC_INCLUDES_DEFAULT])
|
||||
|
||||
# check for types
|
||||
AC_CHECK_TYPE(int8_t, char)
|
||||
@@ -313,6 +313,29 @@ AC_INCLUDES_DEFAULT
|
||||
#endif
|
||||
])
|
||||
|
||||
# Check the type of struct msghdr msg_iovlen.
|
||||
AC_MSG_CHECKING([sign of struct msghdr.msg_iovlen])
|
||||
AC_RUN_IFELSE([
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
int main()
|
||||
{
|
||||
struct msghdr h;
|
||||
h.msg_iovlen = -1;
|
||||
if(10/h.msg_iovlen == 0) return 0; /* unsigned */
|
||||
return 1; /* signed */
|
||||
}
|
||||
], [
|
||||
# if compiled and exit status 0
|
||||
cv_msgiovlen_type="unsigned" ], [
|
||||
# not compiled or bad exit status
|
||||
cv_msgiovlen_type="int" ],
|
||||
# when cross compiling
|
||||
[ cv_msgiovlen_type="int" ])
|
||||
AC_MSG_RESULT($cv_msgiovlen_type)
|
||||
AC_DEFINE_UNQUOTED([TYPE_MSGIOVLEN], $cv_msgiovlen_type, [Define to 'int' or type of struct msghdr.msg_iovlen.])
|
||||
|
||||
# Checks for libraries.
|
||||
AC_ARG_WITH(ssl, AC_HELP_STRING([--with-ssl=pathname],
|
||||
[enable SSL (will check /usr/local/ssl
|
||||
@@ -489,6 +512,10 @@ AH_BOTTOM([
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_UIO_H
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NETINET_IN_H
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
+14
-4
@@ -47,6 +47,8 @@
|
||||
#include "daemon/worker.h"
|
||||
#include "util/log.h"
|
||||
#include "util/config_file.h"
|
||||
#include "util/data/msgreply.h"
|
||||
#include "util/storage/slabhash.h"
|
||||
#include "services/listen_dnsport.h"
|
||||
#include <signal.h>
|
||||
|
||||
@@ -116,6 +118,14 @@ daemon_init()
|
||||
signal_handling_record();
|
||||
checklock_start();
|
||||
daemon->need_to_exit = 0;
|
||||
daemon->msg_cache = slabhash_create(HASH_DEFAULT_SLABS,
|
||||
HASH_DEFAULT_STARTARRAY, HASH_DEFAULT_MAXMEM,
|
||||
msgreply_sizefunc, query_info_compare,
|
||||
query_entry_delete, reply_info_delete, NULL);
|
||||
if(!daemon->msg_cache) {
|
||||
free(daemon);
|
||||
return NULL;
|
||||
}
|
||||
alloc_init(&daemon->superalloc, NULL);
|
||||
return daemon;
|
||||
}
|
||||
@@ -148,7 +158,7 @@ daemon_create_workers(struct daemon* daemon)
|
||||
sizeof(struct worker*));
|
||||
for(i=0; i<daemon->num; i++) {
|
||||
if(!(daemon->workers[i] = worker_create(daemon, i)))
|
||||
fatal_exit("malloc failure");
|
||||
fatal_exit("could not create worker");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +192,6 @@ static void*
|
||||
thread_start(void* arg)
|
||||
{
|
||||
struct worker* worker = (struct worker*)arg;
|
||||
int num = worker->thread_num;
|
||||
log_thread_set(&worker->thread_num);
|
||||
ub_thread_blocksigs();
|
||||
#if !defined(HAVE_PTHREAD) && !defined(HAVE_SOLARIS_THREADS)
|
||||
@@ -190,10 +199,10 @@ thread_start(void* arg)
|
||||
close(worker->cmd_send_fd);
|
||||
worker->cmd_send_fd = -1;
|
||||
close_other_pipes(worker->daemon, worker->thread_num);
|
||||
#endif /* no threads */
|
||||
#endif
|
||||
if(!worker_init(worker, worker->daemon->cfg, worker->daemon->ports,
|
||||
BUFSZ, 0))
|
||||
fatal_exit("Could not initialize thread #%d", num);
|
||||
fatal_exit("Could not initialize thread");
|
||||
|
||||
worker_work(worker);
|
||||
return NULL;
|
||||
@@ -303,6 +312,7 @@ daemon_delete(struct daemon* daemon)
|
||||
if(!daemon)
|
||||
return;
|
||||
listening_ports_free(daemon->ports);
|
||||
slabhash_delete(daemon->msg_cache);
|
||||
alloc_clear(&daemon->superalloc);
|
||||
free(daemon->cwd);
|
||||
free(daemon->pidfile);
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
struct config_file;
|
||||
struct worker;
|
||||
struct listen_port;
|
||||
struct slabhash;
|
||||
|
||||
/**
|
||||
* Structure holding worker list.
|
||||
@@ -71,6 +72,8 @@ struct daemon {
|
||||
int need_to_exit;
|
||||
/** master allocation cache */
|
||||
struct alloc_cache superalloc;
|
||||
/** the message cache, content is struct msgreply_entry* */
|
||||
struct slabhash* msg_cache;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+56
-34
@@ -44,6 +44,8 @@
|
||||
#include "util/log.h"
|
||||
#include "daemon/daemon.h"
|
||||
#include "util/config_file.h"
|
||||
#include "util/storage/slabhash.h"
|
||||
#include "util/data/msgreply.h"
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
@@ -66,7 +68,7 @@ static void usage()
|
||||
static void
|
||||
apply_dir(struct daemon* daemon, struct config_file* cfg, int cmdline_verbose)
|
||||
{
|
||||
/* apply changes if they have changed */
|
||||
/* apply if they have changed */
|
||||
daemon->cfg = cfg;
|
||||
verbosity = cmdline_verbose + cfg->verbosity;
|
||||
if(cfg->directory && cfg->directory[0]) {
|
||||
@@ -77,7 +79,18 @@ apply_dir(struct daemon* daemon, struct config_file* cfg, int cmdline_verbose)
|
||||
}
|
||||
free(daemon->cwd);
|
||||
if(!(daemon->cwd = strdup(cfg->directory)))
|
||||
fatal_exit("malloc failed");
|
||||
log_err("cwd: malloc failed");
|
||||
}
|
||||
}
|
||||
if(cfg->msg_cache_size != slabhash_get_size(daemon->msg_cache) ||
|
||||
cfg->msg_cache_slabs != daemon->msg_cache->size) {
|
||||
slabhash_delete(daemon->msg_cache);
|
||||
daemon->msg_cache = slabhash_create(cfg->msg_cache_slabs,
|
||||
HASH_DEFAULT_STARTARRAY, cfg->msg_cache_size,
|
||||
msgreply_sizefunc, query_info_compare,
|
||||
query_entry_delete, reply_info_delete, NULL);
|
||||
if(!daemon->msg_cache) {
|
||||
fatal_exit("malloc failure updating config settings");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +106,16 @@ readpid (const char* file)
|
||||
ssize_t l;
|
||||
|
||||
if ((fd = open(file, O_RDONLY)) == -1) {
|
||||
if(errno != ENOENT)
|
||||
log_err("Could not read pidfile %s: %s",
|
||||
file, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (((l = read(fd, pidbuf, sizeof(pidbuf)))) == -1) {
|
||||
if(errno != ENOENT)
|
||||
log_err("Could not read pidfile %s: %s",
|
||||
file, strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
@@ -105,7 +124,6 @@ readpid (const char* file)
|
||||
|
||||
/* Empty pidfile means no pidfile... */
|
||||
if (l == 0) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -143,13 +161,8 @@ static void
|
||||
checkoldpid(struct config_file* cfg)
|
||||
{
|
||||
pid_t old;
|
||||
if((old = readpid(cfg->pidfile)) == -1) {
|
||||
if(errno != ENOENT) {
|
||||
log_err("Could not read pidfile %s: %s",
|
||||
cfg->pidfile, strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/** see if it is still alive */
|
||||
if((old = readpid(cfg->pidfile)) != -1) {
|
||||
/* see if it is still alive */
|
||||
if(kill(old, 0) == 0 || errno == EPERM)
|
||||
log_warn("unbound is already running as pid %u.",
|
||||
(unsigned)old);
|
||||
@@ -158,6 +171,34 @@ checkoldpid(struct config_file* cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/** detach from command line */
|
||||
static void
|
||||
detach(struct config_file* cfg)
|
||||
{
|
||||
int fd;
|
||||
/* Take off... */
|
||||
switch (fork()) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
unlink(cfg->pidfile);
|
||||
fatal_exit("fork failed: %s", strerror(errno));
|
||||
default:
|
||||
/* exit interactive session */
|
||||
exit(0);
|
||||
}
|
||||
/* detach */
|
||||
if(setsid() == -1)
|
||||
fatal_exit("setsid() failed: %s", strerror(errno));
|
||||
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
|
||||
(void)dup2(fd, STDIN_FILENO);
|
||||
(void)dup2(fd, STDOUT_FILENO);
|
||||
(void)dup2(fd, STDERR_FILENO);
|
||||
if (fd > 2)
|
||||
(void)close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/** daemonize, drop user priviliges and chroot if needed */
|
||||
static void
|
||||
do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode)
|
||||
@@ -186,32 +227,12 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode)
|
||||
/* init logfile just before fork */
|
||||
log_init(cfg->logfile);
|
||||
if(!debug_mode && cfg->do_daemonize) {
|
||||
int fd;
|
||||
/* Take off... */
|
||||
switch (fork()) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
unlink(cfg->pidfile);
|
||||
fatal_exit("fork failed: %s", strerror(errno));
|
||||
default:
|
||||
/* exit interactive session */
|
||||
exit(0);
|
||||
}
|
||||
/* detach */
|
||||
if(setsid() == -1)
|
||||
fatal_exit("setsid() failed: %s", strerror(errno));
|
||||
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
|
||||
(void)dup2(fd, STDIN_FILENO);
|
||||
(void)dup2(fd, STDOUT_FILENO);
|
||||
(void)dup2(fd, STDERR_FILENO);
|
||||
if (fd > 2)
|
||||
(void)close(fd);
|
||||
}
|
||||
detach(cfg);
|
||||
}
|
||||
if(cfg->pidfile && cfg->pidfile[0]) {
|
||||
writepid(cfg->pidfile, getpid());
|
||||
daemon->pidfile = strdup(cfg->pidfile);
|
||||
if(!(daemon->pidfile = strdup(cfg->pidfile)))
|
||||
log_err("pidf: malloc failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +243,8 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode)
|
||||
* These increase verbosity as specified in the config file.
|
||||
* @param debug_mode: if set, do not daemonize.
|
||||
*/
|
||||
static void run_daemon(const char* cfgfile, int cmdline_verbose, int debug_mode)
|
||||
static void
|
||||
run_daemon(const char* cfgfile, int cmdline_verbose, int debug_mode)
|
||||
{
|
||||
struct config_file* cfg = NULL;
|
||||
struct daemon* daemon = NULL;
|
||||
|
||||
+178
-40
@@ -47,6 +47,7 @@
|
||||
#include "daemon/daemon.h"
|
||||
#include "util/netevent.h"
|
||||
#include "util/config_file.h"
|
||||
#include "util/storage/slabhash.h"
|
||||
#include "services/listen_dnsport.h"
|
||||
#include "services/outside_network.h"
|
||||
|
||||
@@ -56,10 +57,10 @@
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
|
||||
/** size of ID+FLAGS in a DNS message */
|
||||
#define DNS_ID_AND_FLAGS 4
|
||||
/** timeout in seconds for UDP queries to auth servers. TODO: proper rtt */
|
||||
#define UDP_QUERY_TIMEOUT 5
|
||||
/** the size of ID and flags, opcode, rcode in dns packet */
|
||||
#define ID_AND_FLAGS 4
|
||||
#define UDP_QUERY_TIMEOUT 4
|
||||
|
||||
void
|
||||
worker_send_cmd(struct worker* worker, ldns_buffer* buffer,
|
||||
@@ -75,18 +76,48 @@ worker_send_cmd(struct worker* worker, ldns_buffer* buffer,
|
||||
log_err("write socket: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/** release workrequest back to the freelist,
|
||||
* note that the w->qinfo still needs to be cleared after this.
|
||||
*/
|
||||
static void
|
||||
req_release(struct work_query* w)
|
||||
{
|
||||
if(w->worker->num_requests == w->worker->request_size) {
|
||||
/* no longer at max, start accepting again. */
|
||||
listen_resume(w->worker->front);
|
||||
}
|
||||
log_assert(w->worker->num_requests >= 1);
|
||||
w->worker->num_requests --;
|
||||
w->next = w->worker->free_queries;
|
||||
w->worker->free_queries = w;
|
||||
}
|
||||
|
||||
/** reply to query with given error code */
|
||||
static void
|
||||
replyerror(int r, struct worker* worker)
|
||||
replyerror(int r, struct work_query* w)
|
||||
{
|
||||
LDNS_QR_SET(ldns_buffer_begin(worker->query_reply.c->buffer));
|
||||
LDNS_RCODE_SET(ldns_buffer_begin(worker->query_reply.c->buffer), r);
|
||||
comm_point_send_reply(&worker->query_reply);
|
||||
if(worker->num_requests == 1) {
|
||||
/* no longer at max, start accepting again. */
|
||||
listen_resume(worker->front);
|
||||
}
|
||||
worker->num_requests --;
|
||||
ldns_buffer* buf = w->query_reply.c->buffer;
|
||||
uint16_t flags;
|
||||
verbose(VERB_DETAIL, "reply with error");
|
||||
|
||||
ldns_buffer_clear(buf);
|
||||
ldns_buffer_write(buf, &w->query_id, sizeof(uint16_t));
|
||||
flags = (uint16_t)(BIT_QR | r); /* QR and retcode*/
|
||||
flags |= (w->query_flags & (BIT_RD|BIT_CD)); /* copy RD and CD bit */
|
||||
ldns_buffer_write_u16(buf, flags);
|
||||
flags = 1;
|
||||
ldns_buffer_write_u16(buf, flags);
|
||||
flags = 0;
|
||||
ldns_buffer_write(buf, &flags, sizeof(uint16_t));
|
||||
ldns_buffer_write(buf, &flags, sizeof(uint16_t));
|
||||
ldns_buffer_write(buf, &flags, sizeof(uint16_t));
|
||||
ldns_buffer_write(buf, w->qinfo.qname, w->qinfo.qnamesize);
|
||||
ldns_buffer_write_u16(buf, w->qinfo.qtype);
|
||||
ldns_buffer_write_u16(buf, w->qinfo.qclass);
|
||||
ldns_buffer_flip(buf);
|
||||
comm_point_send_reply(&w->query_reply);
|
||||
req_release(w);
|
||||
query_info_clear(&w->qinfo);
|
||||
}
|
||||
|
||||
/** process incoming replies from the network */
|
||||
@@ -94,43 +125,65 @@ static int
|
||||
worker_handle_reply(struct comm_point* c, void* arg, int error,
|
||||
struct comm_reply* ATTR_UNUSED(reply_info))
|
||||
{
|
||||
struct worker* worker = (struct worker*)arg;
|
||||
struct work_query* w = (struct work_query*)arg;
|
||||
struct reply_info* rep;
|
||||
struct msgreply_entry* e;
|
||||
verbose(VERB_DETAIL, "reply to query with stored ID %d",
|
||||
worker->query_id);
|
||||
LDNS_ID_SET(ldns_buffer_begin(worker->query_reply.c->buffer),
|
||||
worker->query_id);
|
||||
ntohs(w->query_id)); /* byteswapped so same as dig prints */
|
||||
if(error != 0) {
|
||||
replyerror(LDNS_RCODE_SERVFAIL, worker);
|
||||
replyerror(LDNS_RCODE_SERVFAIL, w);
|
||||
return 0;
|
||||
}
|
||||
/* sanity check. */
|
||||
if(!LDNS_QR_WIRE(ldns_buffer_begin(c->buffer)))
|
||||
return 0; /* not a reply. */
|
||||
if(LDNS_OPCODE_WIRE(ldns_buffer_begin(c->buffer)) != LDNS_PACKET_QUERY)
|
||||
return 0; /* not a reply to a query. */
|
||||
if(LDNS_QDCOUNT(ldns_buffer_begin(c->buffer)) > 1)
|
||||
return 0; /* too much in the query section */
|
||||
/* woohoo a reply! */
|
||||
ldns_buffer_clear(worker->query_reply.c->buffer);
|
||||
ldns_buffer_skip(worker->query_reply.c->buffer, ID_AND_FLAGS);
|
||||
ldns_buffer_write(worker->query_reply.c->buffer,
|
||||
ldns_buffer_at(c->buffer, ID_AND_FLAGS),
|
||||
ldns_buffer_limit(c->buffer) - ID_AND_FLAGS);
|
||||
LDNS_QR_SET(ldns_buffer_begin(worker->query_reply.c->buffer));
|
||||
ldns_buffer_flip(worker->query_reply.c->buffer);
|
||||
comm_point_send_reply(&worker->query_reply);
|
||||
if(worker->num_requests == 1) {
|
||||
/* no longer at max, start accepting again. */
|
||||
listen_resume(worker->front);
|
||||
rep = (struct reply_info*)malloc(sizeof(struct reply_info));
|
||||
if(!rep) {
|
||||
log_err("out of memory");
|
||||
replyerror(LDNS_RCODE_SERVFAIL, w);
|
||||
return 0;
|
||||
}
|
||||
worker->num_requests --;
|
||||
rep->flags = ldns_buffer_read_u16_at(c->buffer, 2);
|
||||
rep->replysize = ldns_buffer_limit(c->buffer) - DNS_ID_AND_FLAGS;
|
||||
log_info("got reply of size %u", (unsigned)rep->replysize);
|
||||
rep->reply = (uint8_t*)malloc(rep->replysize);
|
||||
if(!rep->reply) {
|
||||
free(rep);
|
||||
log_err("out of memory");
|
||||
replyerror(LDNS_RCODE_SERVFAIL, w);
|
||||
return 0;
|
||||
}
|
||||
memcpy(rep->reply, ldns_buffer_at(c->buffer, DNS_ID_AND_FLAGS),
|
||||
rep->replysize);
|
||||
reply_info_answer_iov(rep, w->query_id, w->query_flags,
|
||||
&w->query_reply, 0);
|
||||
req_release(w);
|
||||
/* store or update reply in the cache */
|
||||
if(!(e = query_info_entrysetup(&w->qinfo, rep, w->query_hash))) {
|
||||
free(rep->reply);
|
||||
free(rep);
|
||||
log_err("out of memory");
|
||||
return 0;
|
||||
}
|
||||
slabhash_insert(w->worker->daemon->msg_cache, w->query_hash,
|
||||
&e->entry, rep);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** process incoming request */
|
||||
static void
|
||||
worker_process_query(struct worker* worker)
|
||||
worker_process_query(struct worker* worker, struct work_query* w)
|
||||
{
|
||||
/* query the forwarding address */
|
||||
worker->query_id = LDNS_ID_WIRE(ldns_buffer_begin(
|
||||
worker->query_reply.c->buffer));
|
||||
verbose(VERB_DETAIL, "process_query ID %d", worker->query_id);
|
||||
pending_udp_query(worker->back, worker->query_reply.c->buffer,
|
||||
verbose(VERB_DETAIL, "process_query ID %d", ntohs(w->query_id));
|
||||
pending_udp_query(worker->back, w->query_reply.c->buffer,
|
||||
&worker->fwd_addr, worker->fwd_addrlen, UDP_QUERY_TIMEOUT,
|
||||
worker_handle_reply, worker, worker->rndstate);
|
||||
worker_handle_reply, w, worker->rndstate);
|
||||
}
|
||||
|
||||
/** check request sanity. Returns error code, 0 OK, or -1 discard.
|
||||
@@ -212,6 +265,11 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
|
||||
{
|
||||
struct worker* worker = (struct worker*)arg;
|
||||
int ret;
|
||||
hashvalue_t h;
|
||||
struct lruhash_entry* e;
|
||||
struct query_info qinfo;
|
||||
struct work_query* w;
|
||||
|
||||
verbose(VERB_DETAIL, "worker handle request");
|
||||
if(error != NETEVENT_NOERROR) {
|
||||
log_err("called with err=%d", error);
|
||||
@@ -226,22 +284,59 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
|
||||
comm_point_drop_reply(repinfo);
|
||||
return 0;
|
||||
}
|
||||
if(worker->num_requests > 0) {
|
||||
/* see if query is in the cache */
|
||||
if(!query_info_parse(&qinfo, c->buffer)) {
|
||||
LDNS_QR_SET(ldns_buffer_begin(c->buffer));
|
||||
LDNS_RCODE_SET(ldns_buffer_begin(c->buffer),
|
||||
LDNS_RCODE_FORMERR);
|
||||
return 1;
|
||||
}
|
||||
h = query_info_hash(&qinfo);
|
||||
if((e=slabhash_lookup(worker->daemon->msg_cache, h, &qinfo, 0))) {
|
||||
/* answer from cache - we have acquired a readlock on it */
|
||||
uint16_t id;
|
||||
log_info("answer from the cache");
|
||||
memcpy(&id, ldns_buffer_begin(c->buffer), sizeof(uint16_t));
|
||||
reply_info_answer_iov((struct reply_info*)e->data, id,
|
||||
ldns_buffer_read_u16_at(c->buffer, 2), repinfo, 1);
|
||||
lock_rw_unlock(&e->lock);
|
||||
return 0;
|
||||
}
|
||||
ldns_buffer_rewind(c->buffer);
|
||||
/* perform memory allocation(s) */
|
||||
if(!query_info_allocqname(&qinfo)) {
|
||||
comm_point_drop_reply(repinfo);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* grab a work request structure for this new request */
|
||||
if(!(w = worker->free_queries)) {
|
||||
/* we could get this due to a slow tcp incoming query,
|
||||
that started before we performed listen_pushback */
|
||||
verbose(VERB_DETAIL, "worker: too many incoming requests "
|
||||
"active. dropping incoming query.");
|
||||
comm_point_drop_reply(repinfo);
|
||||
query_info_clear(&qinfo);
|
||||
return 0;
|
||||
}
|
||||
/* answer it */
|
||||
worker->free_queries = w->next;
|
||||
worker->num_requests ++;
|
||||
if(worker->num_requests >= 1) {
|
||||
log_assert(worker->num_requests <= worker->request_size);
|
||||
if(worker->num_requests == worker->request_size) {
|
||||
/* the max request number has been reached, stop accepting */
|
||||
listen_pushback(worker->front);
|
||||
}
|
||||
memcpy(&worker->query_reply, repinfo, sizeof(struct comm_reply));
|
||||
worker_process_query(worker);
|
||||
|
||||
/* init request */
|
||||
w->next = NULL;
|
||||
w->query_hash = h;
|
||||
memcpy(&w->query_reply, repinfo, sizeof(struct comm_reply));
|
||||
memcpy(&w->qinfo, &qinfo, sizeof(struct query_info));
|
||||
memcpy(&w->query_id, ldns_buffer_begin(c->buffer), sizeof(uint16_t));
|
||||
w->query_flags = ldns_buffer_read_u16_at(c->buffer, 2);
|
||||
|
||||
/* answer it */
|
||||
worker_process_query(worker, w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -259,14 +354,17 @@ worker_sighandler(int sig, void* arg)
|
||||
break;
|
||||
case SIGINT:
|
||||
log_info("caught signal SIGINT");
|
||||
worker->need_to_restart = 0;
|
||||
comm_base_exit(worker->base);
|
||||
break;
|
||||
case SIGQUIT:
|
||||
log_info("caught signal SIGQUIT");
|
||||
worker->need_to_restart = 0;
|
||||
comm_base_exit(worker->base);
|
||||
break;
|
||||
case SIGTERM:
|
||||
log_info("caught signal SIGTERM");
|
||||
worker->need_to_restart = 0;
|
||||
comm_base_exit(worker->base);
|
||||
break;
|
||||
default:
|
||||
@@ -306,6 +404,40 @@ worker_create(struct daemon* daemon, int id)
|
||||
return worker;
|
||||
}
|
||||
|
||||
/** create request handling structures */
|
||||
static int
|
||||
reqs_init(struct worker* worker)
|
||||
{
|
||||
size_t i;
|
||||
for(i=0; i<worker->request_size; i++) {
|
||||
struct work_query* q = (struct work_query*)calloc(1,
|
||||
sizeof(struct work_query));
|
||||
if(!q) return 0;
|
||||
q->worker = worker;
|
||||
q->next = worker->free_queries;
|
||||
worker->free_queries = q;
|
||||
q->all_next = worker->all_queries;
|
||||
worker->all_queries = q;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** delete request list */
|
||||
static void
|
||||
reqs_delete(struct worker* worker)
|
||||
{
|
||||
struct work_query* q = worker->all_queries;
|
||||
struct work_query* n;
|
||||
while(q) {
|
||||
n = q->all_next;
|
||||
log_assert(q->worker == worker);
|
||||
/* comm_reply closed in outside_network_delete */
|
||||
query_info_clear(&q->qinfo);
|
||||
free(q);
|
||||
q = n;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
worker_init(struct worker* worker, struct config_file *cfg,
|
||||
struct listen_port* ports, size_t buffer_size, int do_sigs)
|
||||
@@ -380,6 +512,11 @@ worker_init(struct worker* worker, struct config_file *cfg,
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
worker->request_size = cfg->num_queries_per_thread;
|
||||
if(!reqs_init(worker)) {
|
||||
worker_delete(worker);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set forwarder address */
|
||||
if(cfg->fwd_address && cfg->fwd_address[0]) {
|
||||
@@ -403,6 +540,7 @@ worker_delete(struct worker* worker)
|
||||
{
|
||||
if(!worker)
|
||||
return;
|
||||
reqs_delete(worker);
|
||||
listen_delete(worker->front);
|
||||
outside_network_delete(worker->back);
|
||||
comm_signal_delete(worker->comsig);
|
||||
|
||||
+27
-5
@@ -47,6 +47,7 @@
|
||||
#include "util/netevent.h"
|
||||
#include "util/locks.h"
|
||||
#include "util/alloc.h"
|
||||
#include "util/data/msgreply.h"
|
||||
struct listen_dnsport;
|
||||
struct outside_network;
|
||||
struct config_file;
|
||||
@@ -63,6 +64,25 @@ enum worker_commands {
|
||||
worker_cmd_quit
|
||||
};
|
||||
|
||||
/** information per query that is in processing */
|
||||
struct work_query {
|
||||
/** next query in freelist */
|
||||
struct work_query* next;
|
||||
/** the worker for this query */
|
||||
struct worker* worker;
|
||||
/** the query reply destination, packet buffer and where to send. */
|
||||
struct comm_reply query_reply;
|
||||
/** the query_info structure from the query */
|
||||
struct query_info qinfo;
|
||||
/** hash value of the query qinfo */
|
||||
hashvalue_t query_hash;
|
||||
/** next query in all-list */
|
||||
struct work_query* all_next;
|
||||
/** id of query, in network byteorder. */
|
||||
uint16_t query_id;
|
||||
/** flags uint16 from query */
|
||||
uint16_t query_flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure holding working information for unbound.
|
||||
@@ -91,11 +111,13 @@ struct worker {
|
||||
struct comm_point* cmd_com;
|
||||
|
||||
/** number of requests currently active */
|
||||
int num_requests;
|
||||
/** our one and only query, packet buffer and where to send. */
|
||||
struct comm_reply query_reply;
|
||||
/** id of query */
|
||||
uint16_t query_id;
|
||||
size_t num_requests;
|
||||
/** number of requests that can be handled by this worker */
|
||||
size_t request_size;
|
||||
/** the free working queries */
|
||||
struct work_query* free_queries;
|
||||
/** list of all working queries */
|
||||
struct work_query* all_queries;
|
||||
|
||||
/** address to forward to */
|
||||
struct sockaddr_storage fwd_addr;
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
3 April 2007: Wouter
|
||||
- detect sign of msghdr.msg_iovlen so that the cast to that type
|
||||
in netevent (which is there to please lint) can be correct.
|
||||
The type on several OSes ranges from int, int32, uint32, size_t.
|
||||
Detects unsigned or signed using math trick.
|
||||
- constants for DNS flags.
|
||||
- compilation without locks fixup.
|
||||
- removed include of unportable header from lookup3.c.
|
||||
- more portable use of struct msghdr.
|
||||
- casts for printf warning portability.
|
||||
- tweaks to tests to port them to the testbed.
|
||||
- 0.2 tag created.
|
||||
|
||||
2 April 2007: Wouter
|
||||
- check sizes of udp received messages, not too short.
|
||||
- review changes. Some memmoves can be memcpys: 4byte aligned.
|
||||
set id correctly on cached answers.
|
||||
- review changes msgreply.c, memleak on error condition. AA flag
|
||||
clear on cached reply. Lowercase queries on hashing.
|
||||
unit test on lowercasing. Test AA bit not set on cached reply.
|
||||
Note that no TTLs are managed.
|
||||
|
||||
29 March 2007: Wouter
|
||||
- writev or sendmsg used when answering from cache.
|
||||
This avoids a copy of the data.
|
||||
- do not do useless byteswap on query id. Store reply flags in uint16
|
||||
for easier access (and no repeated byteswapping).
|
||||
- reviewed code.
|
||||
- configure detects and config.h includes sys/uio.h for writev decl.
|
||||
|
||||
28 March 2007: Wouter
|
||||
- new config option: num-queries-per-thread.
|
||||
- added tpkg test for answering three queries at the same time
|
||||
using one thread (from the query service list).
|
||||
|
||||
27 March 2007: Wouter
|
||||
- added test for cache and not cached answers, in testbound replays.
|
||||
- testbound can give config file and commandline options from the
|
||||
replay file to unbound.
|
||||
- created test that checks if items drop out of the cache.
|
||||
- added word 'partitioned hash table' to documentation on slab hash.
|
||||
A slab hash is a partitioned hash table.
|
||||
- worker can handle multiple queries at a time.
|
||||
|
||||
26 March 2007: Wouter
|
||||
- config settings for slab hash message cache.
|
||||
- test for cached answer.
|
||||
- Fixup deleting fake answer from testbound list.
|
||||
|
||||
23 March 2007: Wouter
|
||||
- review of yesterday's commits.
|
||||
- covered up memory leak of the entry locks.
|
||||
- answers from the cache correctly. Copies flags correctly.
|
||||
- sanity check for incoming query replies.
|
||||
- slabbed hash table. Much nicer contention, need dual cpu to see.
|
||||
|
||||
22 March 2007: Wouter
|
||||
- AIX configure check.
|
||||
- lock-verify can handle references to locks that are created
|
||||
in files it has not yet read in.
|
||||
- threaded hash table test.
|
||||
- unit test runs lock-verify afterwards and checks result.
|
||||
- need writelock to update data on hash_insert.
|
||||
- message cache code, msgreply code.
|
||||
|
||||
21 March 2007: Wouter
|
||||
- unit test of hash table, fixup locking problem in table_grow().
|
||||
- fixup accounting of sizes for removing items from hashtable.
|
||||
- unit test for hash table, single threaded test of integrity.
|
||||
- lock-verify reports errors nicely. More quiet in operation.
|
||||
|
||||
16 March 2007: Wouter
|
||||
- lock-verifier, checks consistent order of locking.
|
||||
|
||||
14 March 2007: Wouter
|
||||
- hash table insert (and subroutines) and lookup implemented.
|
||||
- hash table remove.
|
||||
- unit tests for hash internal bin, lru functions.
|
||||
|
||||
13 March 2007: Wouter
|
||||
- lock_unprotect in checklocks.
|
||||
- util/storage/lruhash.h for LRU hash table structure.
|
||||
|
||||
12 March 2007: Wouter
|
||||
- configure.ac moved to 0.2.
|
||||
- query_info and replymsg util/data structure.
|
||||
|
||||
9 March 2007: Wouter
|
||||
- added rwlock writelock checking.
|
||||
So it will keep track of the writelock, and readlocks are enforced
|
||||
@@ -6,6 +93,7 @@
|
||||
- checklocks zeroes its destroyed lock after checking memory areas.
|
||||
- unit test for alloc.
|
||||
- identifier for union in checklocks to please older compilers.
|
||||
- created 0.1 tag.
|
||||
|
||||
8 March 2007: Wouter
|
||||
- Reviewed checklock code.
|
||||
|
||||
@@ -36,6 +36,17 @@ server:
|
||||
# But also takes more system resources (for open sockets).
|
||||
# outgoing-range: 16
|
||||
|
||||
# the amount of memory to use for the message cache.
|
||||
# in bytes. default is 4 Mb
|
||||
# msg-cache-size: 4194304
|
||||
|
||||
# the number of slabs to use for the message cache.
|
||||
# more slabs reduce lock contention, but fracture memory usage.
|
||||
# msg-cache-slabs: 4
|
||||
|
||||
# the number of queries that a thread gets to service.
|
||||
# num-queries-per-thread: 1024
|
||||
|
||||
# Enable IPv4, "yes" or "no".
|
||||
# do-ip4: yes
|
||||
|
||||
|
||||
@@ -63,6 +63,14 @@ Number of ports to open. This number is opened per thread for every outgoing
|
||||
query interface. Must be at least 1. Default is 16.
|
||||
Larger numbers give more protection against spoofing attempts, but need
|
||||
extra resources from the operating system.
|
||||
.It \fBmsg-cache-size:\fR <number>
|
||||
Number of bytes size of the message cache. Default is 4 megabytes.
|
||||
.It \fBmsg-cache-slabs:\fR <number>
|
||||
Number of slabs in the message cache. Slabs reduce lock contention by threads.
|
||||
Must be set to a power of 2. Setting (close) to the number of cpus is a
|
||||
reasonable guess.
|
||||
.It \fBnum-queries-per-thread:\fR <number>
|
||||
The number of queries that every thread will service simultaneously.
|
||||
.It \fBdo-ip4:\fR <yes or no>
|
||||
Enable or disable whether ip4 queries are answered. Default is yes.
|
||||
.It \fBdo-ip6:\fR <yes or no>
|
||||
|
||||
@@ -123,6 +123,10 @@ outnet_udp_cb(struct comm_point* c, void* arg, int error,
|
||||
log_info("outnetudp got udp error %d", error);
|
||||
return 0;
|
||||
}
|
||||
if(ldns_buffer_limit(c->buffer) < LDNS_HEADER_SIZE) {
|
||||
log_info("outnetudp udp too short");
|
||||
return 0;
|
||||
}
|
||||
log_assert(reply_info);
|
||||
|
||||
/* setup lookup key */
|
||||
|
||||
+124
-9
@@ -46,18 +46,26 @@
|
||||
* are passed to thread_create to make the thread numbers here the same as
|
||||
* those used for logging which is nice.
|
||||
*
|
||||
* Todo: - check global ordering of instances of locks.
|
||||
* - refcount statistics.
|
||||
* Todo:
|
||||
* - debug status print, of thread lock stacks, and current waiting.
|
||||
*/
|
||||
#ifdef USE_THREAD_DEBUG
|
||||
|
||||
/** How long to wait before lock attempt is a failure. */
|
||||
#define CHECK_LOCK_TIMEOUT 15 /* seconds */
|
||||
/** How long to wait before join attempt is a failure. */
|
||||
#define CHECK_JOIN_TIMEOUT 120 /* seconds */
|
||||
|
||||
/** if key has been created */
|
||||
static int key_created = 0;
|
||||
/** we hide the thread debug info with this key. */
|
||||
static ub_thread_key_t thr_debug_key;
|
||||
/** the list of threads, so all threads can be examined. NULL if unused. */
|
||||
static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS];
|
||||
/** do we check locking order */
|
||||
int check_locking_order = 1;
|
||||
/** the pid of this runset, reasonably unique. */
|
||||
static pid_t check_lock_pid;
|
||||
|
||||
/** print pretty lock error and exit */
|
||||
static void lock_error(struct checked_lock* lock,
|
||||
@@ -66,8 +74,9 @@ static void lock_error(struct checked_lock* lock,
|
||||
log_err("lock error (description follows)");
|
||||
log_err("Created at %s %s:%d", lock->create_func,
|
||||
lock->create_file, lock->create_line);
|
||||
log_err("Previously %s %s:%d", lock->holder_func,
|
||||
lock->holder_file, lock->holder_line);
|
||||
if(lock->holder_func && lock->holder_file)
|
||||
log_err("Previously %s %s:%d", lock->holder_func,
|
||||
lock->holder_file, lock->holder_line);
|
||||
log_err("At %s %s:%d", func, file, line);
|
||||
log_err("Error for %s lock: %s",
|
||||
(lock->type==check_lock_mutex)?"mutex": (
|
||||
@@ -132,6 +141,31 @@ lock_protect(void *p, void* area, size_t size)
|
||||
LOCKRET(pthread_mutex_unlock(&lock->lock));
|
||||
}
|
||||
|
||||
/** remove protected region */
|
||||
void
|
||||
lock_unprotect(void* mangled, void* area)
|
||||
{
|
||||
struct checked_lock* lock = *(struct checked_lock**)mangled;
|
||||
struct protected_area* p, **prevp;
|
||||
if(!lock)
|
||||
return;
|
||||
acquire_locklock(lock, __func__, __FILE__, __LINE__);
|
||||
p = lock->prot;
|
||||
prevp = &lock->prot;
|
||||
while(p) {
|
||||
if(p->region == area) {
|
||||
*prevp = p->next;
|
||||
free(p->hold);
|
||||
free(p);
|
||||
LOCKRET(pthread_mutex_unlock(&lock->lock));
|
||||
return;
|
||||
}
|
||||
prevp = &p->next;
|
||||
p = p->next;
|
||||
}
|
||||
LOCKRET(pthread_mutex_unlock(&lock->lock));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check protected memory region. Memory compare. Exit on error.
|
||||
* @param lock: which lock to check.
|
||||
@@ -166,6 +200,45 @@ prot_store(struct checked_lock* lock)
|
||||
}
|
||||
}
|
||||
|
||||
/** write lock trace info to file, while you hold those locks. */
|
||||
static void
|
||||
ordercheck_locklock(struct thr_check* thr, struct checked_lock* lock)
|
||||
{
|
||||
int info[4];
|
||||
if(!check_locking_order) return;
|
||||
if(!thr->holding_first) return; /* no older lock, no info */
|
||||
/* write: <lock id held> <lock id new> <file> <line> */
|
||||
info[0] = thr->holding_first->create_thread;
|
||||
info[1] = thr->holding_first->create_instance;
|
||||
info[2] = lock->create_thread;
|
||||
info[3] = lock->create_instance;
|
||||
if(fwrite(info, 4*sizeof(int), 1, thr->order_info) != 1 ||
|
||||
fwrite(lock->holder_file, strlen(lock->holder_file)+1, 1,
|
||||
thr->order_info) != 1 ||
|
||||
fwrite(&lock->holder_line, sizeof(int), 1,
|
||||
thr->order_info) != 1)
|
||||
log_err("fwrite: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/** write ordercheck lock creation details to file. */
|
||||
static void
|
||||
ordercheck_lockcreate(struct thr_check* thr, struct checked_lock* lock)
|
||||
{
|
||||
/* write: <ffff = create> <lock id> <file> <line> */
|
||||
int cmd = -1;
|
||||
if(!check_locking_order) return;
|
||||
|
||||
if( fwrite(&cmd, sizeof(int), 1, thr->order_info) != 1 ||
|
||||
fwrite(&lock->create_thread, sizeof(int), 1,
|
||||
thr->order_info) != 1 ||
|
||||
fwrite(&lock->create_instance, sizeof(int), 1,
|
||||
thr->order_info) != 1 ||
|
||||
fwrite(lock->create_file, strlen(lock->create_file)+1, 1,
|
||||
thr->order_info) != 1 ||
|
||||
fwrite(&lock->create_line, sizeof(int), 1,
|
||||
thr->order_info) != 1)
|
||||
log_err("fwrite: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/** alloc struct, init lock empty */
|
||||
void
|
||||
@@ -174,13 +247,21 @@ checklock_init(enum check_lock_type type, struct checked_lock** lock,
|
||||
{
|
||||
struct checked_lock* e = (struct checked_lock*)calloc(1,
|
||||
sizeof(struct checked_lock));
|
||||
struct thr_check *thr = (struct thr_check*)pthread_getspecific(
|
||||
thr_debug_key);
|
||||
if(!e)
|
||||
fatal_exit("%s %s %d: out of memory", func, file, line);
|
||||
if(!thr)
|
||||
fatal_exit("%s %s %d: lock_init no thread info", func, file,
|
||||
line);
|
||||
*lock = e;
|
||||
e->type = type;
|
||||
e->create_func = func;
|
||||
e->create_file = file;
|
||||
e->create_line = line;
|
||||
e->create_thread = thr->num;
|
||||
e->create_instance = thr->locks_created++;
|
||||
ordercheck_lockcreate(thr, e);
|
||||
LOCKRET(pthread_mutex_init(&e->lock, NULL));
|
||||
switch(e->type) {
|
||||
case check_lock_mutex:
|
||||
@@ -228,7 +309,7 @@ void
|
||||
checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
|
||||
const char* func, const char* file, int line)
|
||||
{
|
||||
const size_t contention_interest = 10;
|
||||
const size_t contention_interest = 1; /* promille contented locks */
|
||||
struct checked_lock* e;
|
||||
if(!lock)
|
||||
return;
|
||||
@@ -247,11 +328,14 @@ checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
|
||||
*lock = NULL; /* use after free will fail */
|
||||
LOCKRET(pthread_mutex_unlock(&e->lock));
|
||||
|
||||
/* contention */
|
||||
if(e->contention_count > contention_interest) {
|
||||
log_info("lock created %s %s %d has contention %u",
|
||||
/* contention, look at fraction in trouble. */
|
||||
if(e->history_count > 1 &&
|
||||
1000*e->contention_count/e->history_count > contention_interest) {
|
||||
log_info("lock created %s %s %d has contention %u of %u (%d%%)",
|
||||
e->create_func, e->create_file, e->create_line,
|
||||
(unsigned int)e->contention_count);
|
||||
(unsigned int)e->contention_count,
|
||||
(unsigned int)e->history_count,
|
||||
100*e->contention_count/e->history_count);
|
||||
}
|
||||
|
||||
/* delete it */
|
||||
@@ -288,6 +372,7 @@ finish_acquire_lock(struct thr_check* thr, struct checked_lock* lock,
|
||||
lock->holder_func = func;
|
||||
lock->holder_file = file;
|
||||
lock->holder_line = line;
|
||||
ordercheck_locklock(thr, lock);
|
||||
|
||||
/* insert in thread lock list, as first */
|
||||
lock->prev_held_lock[thr->num] = NULL;
|
||||
@@ -355,6 +440,7 @@ checklock_lockit(enum check_lock_type type, struct checked_lock* lock,
|
||||
|
||||
acquire_locklock(lock, func, file, line);
|
||||
lock->contention_count += contend;
|
||||
lock->history_count++;
|
||||
if(exclusive && lock->hold_count > 0)
|
||||
lock_error(lock, func, file, line, "got nonexclusive lock");
|
||||
if(type==check_lock_rwlock && getwr && lock->writeholder)
|
||||
@@ -520,6 +606,26 @@ checklock_unlock(enum check_lock_type type, struct checked_lock* lock,
|
||||
}
|
||||
}
|
||||
|
||||
/** open order info debug file, thr->num must be valid. */
|
||||
static void
|
||||
open_lockorder(struct thr_check* thr)
|
||||
{
|
||||
char buf[24];
|
||||
time_t t;
|
||||
snprintf(buf, sizeof(buf), "ublocktrace.%d", thr->num);
|
||||
thr->order_info = fopen(buf, "w");
|
||||
if(!thr->order_info)
|
||||
fatal_exit("could not open %s: %s", buf, strerror(errno));
|
||||
thr->locks_created = 0;
|
||||
t = time(NULL);
|
||||
/* write: <time_stamp> <runpid> <thread_num> */
|
||||
if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 ||
|
||||
fwrite(&thr->num, sizeof(thr->num), 1, thr->order_info) != 1 ||
|
||||
fwrite(&check_lock_pid, sizeof(check_lock_pid), 1,
|
||||
thr->order_info) != 1)
|
||||
log_err("fwrite: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/** checklock thread main, Inits thread structure. */
|
||||
static void* checklock_main(void* arg)
|
||||
{
|
||||
@@ -532,8 +638,12 @@ static void* checklock_main(void* arg)
|
||||
log_assert(thread_infos[thr->num] == NULL);
|
||||
thread_infos[thr->num] = thr;
|
||||
LOCKRET(pthread_setspecific(thr_debug_key, thr));
|
||||
if(check_locking_order)
|
||||
open_lockorder(thr);
|
||||
ret = thr->func(thr->arg);
|
||||
thread_infos[thr->num] = NULL;
|
||||
if(check_locking_order)
|
||||
fclose(thr->order_info);
|
||||
free(thr);
|
||||
return ret;
|
||||
}
|
||||
@@ -547,9 +657,12 @@ void checklock_start()
|
||||
if(!thisthr)
|
||||
fatal_exit("thrcreate: out of memory");
|
||||
key_created = 1;
|
||||
check_lock_pid = getpid();
|
||||
LOCKRET(pthread_key_create(&thr_debug_key, NULL));
|
||||
LOCKRET(pthread_setspecific(thr_debug_key, thisthr));
|
||||
thread_infos[0] = thisthr;
|
||||
if(check_locking_order)
|
||||
open_lockorder(thisthr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +671,8 @@ void checklock_stop()
|
||||
{
|
||||
if(key_created) {
|
||||
int i;
|
||||
if(check_locking_order)
|
||||
fclose(thread_infos[0]->order_info);
|
||||
free(thread_infos[0]);
|
||||
thread_infos[0] = NULL;
|
||||
for(i = 0; i < THRDEBUG_MAX_THREADS; i++)
|
||||
|
||||
+18
-4
@@ -71,12 +71,10 @@
|
||||
/******************* THREAD DEBUG ************************/
|
||||
#include <pthread.h>
|
||||
|
||||
/** How long to wait before lock attempt is a failure. */
|
||||
#define CHECK_LOCK_TIMEOUT 5 /* seconds */
|
||||
/** How long to wait before join attempt is a failure. */
|
||||
#define CHECK_JOIN_TIMEOUT 120 /* seconds */
|
||||
/** How many threads to allocate for */
|
||||
#define THRDEBUG_MAX_THREADS 32 /* threads */
|
||||
/** do we check locking order */
|
||||
extern int check_locking_order;
|
||||
|
||||
/**
|
||||
* Protection memory area.
|
||||
@@ -106,6 +104,10 @@ struct thr_check {
|
||||
void* arg;
|
||||
/** number of thread in list structure */
|
||||
int num;
|
||||
/** instance number - how many locks have been created by thread */
|
||||
int locks_created;
|
||||
/** file to write locking order information to */
|
||||
FILE* order_info;
|
||||
/**
|
||||
* List of locks that this thread is holding, double
|
||||
* linked list. The first element is the most recent lock acquired.
|
||||
@@ -128,8 +130,12 @@ struct checked_lock {
|
||||
const char* create_func, *create_file;
|
||||
/** where was this lock created */
|
||||
int create_line;
|
||||
/** unique instance identifier */
|
||||
int create_thread, create_instance;
|
||||
/** contention count */
|
||||
size_t contention_count;
|
||||
/** number of times locked, ever */
|
||||
size_t history_count;
|
||||
/** hold count (how many threads are holding this lock) */
|
||||
int hold_count;
|
||||
/** how many threads are waiting for this lock */
|
||||
@@ -182,6 +188,14 @@ struct checked_lock {
|
||||
*/
|
||||
void lock_protect(void* lock, void* area, size_t size);
|
||||
|
||||
/**
|
||||
* Remove protected area from lock.
|
||||
* No need to call this when deleting the lock.
|
||||
* @param lock: the lock, any type, (struct checked_lock**).
|
||||
* @param area: pointer to memory.
|
||||
*/
|
||||
void lock_unprotect(void* lock, void* area);
|
||||
|
||||
/**
|
||||
* Initialise checklock. Sets up internal debug structures.
|
||||
*/
|
||||
|
||||
+20
-3
@@ -280,7 +280,7 @@ static void
|
||||
answer_check_it(struct replay_runtime* runtime)
|
||||
{
|
||||
struct replay_answer* ans = runtime->answer_list,
|
||||
**prev = &runtime->answer_list;
|
||||
*prev = NULL;
|
||||
log_assert(runtime && runtime->now &&
|
||||
runtime->now->evt_type == repevt_front_reply);
|
||||
while(ans) {
|
||||
@@ -294,12 +294,16 @@ answer_check_it(struct replay_runtime* runtime)
|
||||
log_info("testbound: do STEP %d %s",
|
||||
runtime->now->time_step,
|
||||
repevt_string(runtime->now->evt_type));
|
||||
*prev = ans->next;
|
||||
if(prev)
|
||||
prev->next = ans->next;
|
||||
else runtime->answer_list = ans->next;
|
||||
if(!ans->next)
|
||||
runtime->answer_last = prev;
|
||||
delete_replay_answer(ans);
|
||||
ans = n;
|
||||
return;
|
||||
} else {
|
||||
prev = &ans->next;
|
||||
prev = ans;
|
||||
ans = ans->next;
|
||||
}
|
||||
}
|
||||
@@ -605,6 +609,19 @@ comm_point_send_reply(struct comm_reply* repinfo)
|
||||
log_pkt("reply pkt: ", ans->pkt);
|
||||
}
|
||||
|
||||
void
|
||||
comm_point_send_reply_iov(struct comm_reply* repinfo, struct iovec* iov,
|
||||
size_t iovlen)
|
||||
{
|
||||
size_t i;
|
||||
ldns_buffer_clear(repinfo->c->buffer);
|
||||
for(i=1; i<iovlen; i++)
|
||||
ldns_buffer_write(repinfo->c->buffer, iov[i].iov_base,
|
||||
iov[i].iov_len);
|
||||
ldns_buffer_flip(repinfo->c->buffer);
|
||||
comm_point_send_reply(repinfo);
|
||||
}
|
||||
|
||||
void
|
||||
comm_point_drop_reply(struct comm_reply* repinfo)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* testcode/lock_verify.c - verifier program for lock traces, checks order.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file checks the lock traces generated by checklock.c.
|
||||
* Checks if locks are consistently locked in the same order.
|
||||
* If not, this can lead to deadlock if threads execute the different
|
||||
* ordering at the same time.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "util/log.h"
|
||||
#include "util/rbtree.h"
|
||||
|
||||
/* --- data structures --- */
|
||||
struct lock_ref;
|
||||
|
||||
/** key for lock lookup */
|
||||
struct order_id {
|
||||
/** the thread id that created it */
|
||||
int thr;
|
||||
/** the instance number of creation */
|
||||
int instance;
|
||||
};
|
||||
|
||||
/** a lock */
|
||||
struct order_lock {
|
||||
/** rbnode in all tree */
|
||||
rbnode_t node;
|
||||
/** lock id */
|
||||
struct order_id id;
|
||||
/** the creation file */
|
||||
char* create_file;
|
||||
/** creation line */
|
||||
int create_line;
|
||||
/** set of all locks that are smaller than this one (locked earlier) */
|
||||
rbtree_t* smaller;
|
||||
/** during depthfirstsearch, this is a linked list of the stack
|
||||
* of locks. points to the next lock bigger than this one. */
|
||||
struct lock_ref* dfs_next;
|
||||
/** if lock has been visited (all smaller locks have been compared to
|
||||
* this lock), only need to compare this with all unvisited(bigger)
|
||||
* locks */
|
||||
int visited;
|
||||
};
|
||||
|
||||
/** reference to a lock in a rbtree set */
|
||||
struct lock_ref {
|
||||
/** rbnode, key is an order_id ptr */
|
||||
rbnode_t node;
|
||||
/** the lock referenced */
|
||||
struct order_lock* lock;
|
||||
/** why is this ref */
|
||||
char* file;
|
||||
/** line number */
|
||||
int line;
|
||||
};
|
||||
|
||||
/** count of errors detected */
|
||||
static int errors_detected = 0;
|
||||
/** verbose? */
|
||||
static int verb = 0;
|
||||
|
||||
/** print program usage help */
|
||||
static void
|
||||
usage()
|
||||
{
|
||||
printf("lock_verify <trace files>\n");
|
||||
}
|
||||
|
||||
/** compare two order_ids */
|
||||
int order_lock_cmp(const void* e1, const void* e2)
|
||||
{
|
||||
struct order_id* o1 = (struct order_id*)e1;
|
||||
struct order_id* o2 = (struct order_id*)e2;
|
||||
if(o1->thr < o2->thr) return -1;
|
||||
if(o1->thr > o2->thr) return 1;
|
||||
if(o1->instance < o2->instance) return -1;
|
||||
if(o1->instance > o2->instance) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** read header entry.
|
||||
* @param in: file to read header of.
|
||||
* @return: False if it does not belong to the rest. */
|
||||
static int
|
||||
read_header(FILE* in)
|
||||
{
|
||||
time_t t;
|
||||
pid_t p;
|
||||
int thrno;
|
||||
static int have_values = 0;
|
||||
static time_t the_time;
|
||||
static pid_t the_pid;
|
||||
static int threads[256];
|
||||
|
||||
if(fread(&t, sizeof(t), 1, in) != 1 ||
|
||||
fread(&thrno, sizeof(thrno), 1, in) != 1 ||
|
||||
fread(&p, sizeof(p), 1, in) != 1) {
|
||||
fatal_exit("fread failed");
|
||||
}
|
||||
/* check these values are sorta OK */
|
||||
if(!have_values) {
|
||||
the_time = t;
|
||||
the_pid = p;
|
||||
memset(threads, 0, 256*sizeof(int));
|
||||
threads[thrno] = 1;
|
||||
have_values = 1;
|
||||
printf(" trace %d from pid %u on %s", thrno,
|
||||
(unsigned)p, ctime(&t));
|
||||
} else {
|
||||
if(the_pid != p) {
|
||||
printf(" has pid %u, not %u. Skipped.\n",
|
||||
(unsigned)p, (unsigned)the_pid);
|
||||
return 0;
|
||||
}
|
||||
if(threads[thrno])
|
||||
fatal_exit("same threadno in two files");
|
||||
threads[thrno] = 1;
|
||||
if( abs((int)(the_time - t)) > 3600)
|
||||
fatal_exit("input files from different times: %u %u",
|
||||
(unsigned)the_time, (unsigned)t);
|
||||
printf(" trace of thread %u:%d\n", (unsigned)p, thrno);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** max length of strings: filenames and function names. */
|
||||
#define STRMAX 1024
|
||||
/** read a string from file, false on error */
|
||||
static int readup_str(char** str, FILE* in)
|
||||
{
|
||||
char buf[STRMAX];
|
||||
int len = 0;
|
||||
int c;
|
||||
/* ends in zero */
|
||||
while( (c = fgetc(in)) != 0) {
|
||||
if(c == EOF)
|
||||
fatal_exit("eof in readstr, file too short");
|
||||
buf[len++] = c;
|
||||
if(len == STRMAX) {
|
||||
fatal_exit("string too long, bad file format");
|
||||
}
|
||||
}
|
||||
buf[len] = 0;
|
||||
*str = strdup(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** read creation entry */
|
||||
static void read_create(rbtree_t* all, FILE* in)
|
||||
{
|
||||
struct order_lock* o = calloc(1, sizeof(struct order_lock));
|
||||
if(!o) fatal_exit("malloc failure");
|
||||
if(fread(&o->id.thr, sizeof(int), 1, in) != 1 ||
|
||||
fread(&o->id.instance, sizeof(int), 1, in) != 1 ||
|
||||
!readup_str(&o->create_file, in) ||
|
||||
fread(&o->create_line, sizeof(int), 1, in) != 1)
|
||||
fatal_exit("fread failed");
|
||||
o->smaller = rbtree_create(order_lock_cmp);
|
||||
o->node.key = &o->id;
|
||||
if(!rbtree_insert(all, &o->node)) {
|
||||
/* already inserted */
|
||||
struct order_lock* a = (struct order_lock*)rbtree_search(all,
|
||||
&o->id);
|
||||
log_assert(a);
|
||||
a->create_file = o->create_file;
|
||||
a->create_line = o->create_line;
|
||||
free(o->smaller);
|
||||
free(o);
|
||||
o = a;
|
||||
}
|
||||
if(verb) printf("read create %u %u %s %d\n",
|
||||
(unsigned)o->id.thr, (unsigned)o->id.instance,
|
||||
o->create_file, o->create_line);
|
||||
}
|
||||
|
||||
/** insert lock entry (empty) into list */
|
||||
static struct order_lock*
|
||||
insert_lock(rbtree_t* all, struct order_id* id)
|
||||
{
|
||||
struct order_lock* o = calloc(1, sizeof(struct order_lock));
|
||||
if(!o) fatal_exit("malloc failure");
|
||||
o->smaller = rbtree_create(order_lock_cmp);
|
||||
o->id = *id;
|
||||
o->node.key = &o->id;
|
||||
if(!rbtree_insert(all, &o->node))
|
||||
fatal_exit("insert fail should not happen");
|
||||
return o;
|
||||
}
|
||||
|
||||
/** read lock entry */
|
||||
static void read_lock(rbtree_t* all, FILE* in, int val)
|
||||
{
|
||||
struct order_id prev_id, now_id;
|
||||
struct lock_ref* ref;
|
||||
struct order_lock* prev, *now;
|
||||
ref = (struct lock_ref*)calloc(1, sizeof(struct lock_ref));
|
||||
if(!ref) fatal_exit("malloc failure");
|
||||
prev_id.thr = val;
|
||||
if(fread(&prev_id.instance, sizeof(int), 1, in) != 1 ||
|
||||
fread(&now_id.thr, sizeof(int), 1, in) != 1 ||
|
||||
fread(&now_id.instance, sizeof(int), 1, in) != 1 ||
|
||||
!readup_str(&ref->file, in) ||
|
||||
fread(&ref->line, sizeof(int), 1, in) != 1)
|
||||
fatal_exit("fread failed");
|
||||
if(verb) printf("read lock %u %u %u %u %s %d\n",
|
||||
(unsigned)prev_id.thr, (unsigned)prev_id.instance,
|
||||
(unsigned)now_id.thr, (unsigned)now_id.instance,
|
||||
ref->file, ref->line);
|
||||
/* find the two locks involved */
|
||||
prev = (struct order_lock*)rbtree_search(all, &prev_id);
|
||||
now = (struct order_lock*)rbtree_search(all, &now_id);
|
||||
/* if not there - insert 'em */
|
||||
if(!prev) prev = insert_lock(all, &prev_id);
|
||||
if(!now) now = insert_lock(all, &now_id);
|
||||
ref->lock = prev;
|
||||
ref->node.key = &prev->id;
|
||||
if(!rbtree_insert(now->smaller, &ref->node)) {
|
||||
free(ref->file);
|
||||
free(ref);
|
||||
}
|
||||
}
|
||||
|
||||
/** read input file */
|
||||
static void readinput(rbtree_t* all, char* file)
|
||||
{
|
||||
FILE *in = fopen(file, "r");
|
||||
int fst;
|
||||
if(!in) {
|
||||
perror(file);
|
||||
exit(1);
|
||||
}
|
||||
printf("file %s", file);
|
||||
if(!read_header(in)) {
|
||||
fclose(in);
|
||||
return;
|
||||
}
|
||||
while(fread(&fst, sizeof(fst), 1, in) == 1) {
|
||||
if(fst == -1)
|
||||
read_create(all, in);
|
||||
else read_lock(all, in, fst);
|
||||
}
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
/** print cycle message */
|
||||
static void found_cycle(struct lock_ref* visit, int level)
|
||||
{
|
||||
struct lock_ref* p;
|
||||
int i = 0;
|
||||
errors_detected++;
|
||||
printf("Found inconsistent locking order of length %d\n", level);
|
||||
printf("for lock %d %d created %s %d\n",
|
||||
visit->lock->id.thr, visit->lock->id.instance,
|
||||
visit->lock->create_file, visit->lock->create_line);
|
||||
printf("sequence is:\n");
|
||||
p = visit;
|
||||
while(p) {
|
||||
struct order_lock* next =
|
||||
p->lock->dfs_next?p->lock->dfs_next->lock:visit->lock;
|
||||
printf("[%d] is locked at line %s %d before lock %d %d\n",
|
||||
i, p->file, p->line, next->id.thr, next->id.instance);
|
||||
printf("[%d] lock %d %d is created at %s %d\n",
|
||||
i, next->id.thr, next->id.instance,
|
||||
next->create_file, next->create_line);
|
||||
i++;
|
||||
p = p->lock->dfs_next;
|
||||
if(p && p->lock == visit->lock)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Detect cycle by comparing visited now with all (unvisited) bigger nodes. */
|
||||
static int detect_cycle(struct lock_ref* visit, struct lock_ref* from)
|
||||
{
|
||||
struct lock_ref* p = from;
|
||||
while(p) {
|
||||
if(p->lock == visit->lock)
|
||||
return 1;
|
||||
p = p->lock->dfs_next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** recursive function to depth first search for cycles.
|
||||
* @param visit: the lock visited at this step.
|
||||
* its dfs_next pointer gives the visited lock up in recursion.
|
||||
* same as lookfor at level 0.
|
||||
* @param level: depth of recursion. 0 is start.
|
||||
* @param from: search for matches from unvisited node upwards.
|
||||
*/
|
||||
static void search_cycle(struct lock_ref* visit, int level,
|
||||
struct lock_ref* from)
|
||||
{
|
||||
struct lock_ref* ref;
|
||||
/* check for cycle */
|
||||
if(detect_cycle(visit, from) && level != 0) {
|
||||
found_cycle(visit, level);
|
||||
return;
|
||||
}
|
||||
/* recurse */
|
||||
if(!visit->lock->visited)
|
||||
from = visit;
|
||||
if(verb > 1) fprintf(stderr, "[%d] visit lock %u %u %s %d\n", level,
|
||||
(unsigned)visit->lock->id.thr,
|
||||
(unsigned)visit->lock->id.instance,
|
||||
visit->lock->create_file, visit->lock->create_line);
|
||||
RBTREE_FOR(ref, struct lock_ref*, visit->lock->smaller) {
|
||||
ref->lock->dfs_next = visit;
|
||||
search_cycle(ref, level+1, from);
|
||||
}
|
||||
visit->lock->visited = 1;
|
||||
}
|
||||
|
||||
/** Check ordering of one lock */
|
||||
static void check_order_lock(struct order_lock* lock)
|
||||
{
|
||||
struct lock_ref start;
|
||||
if(lock->visited) return;
|
||||
|
||||
start.node.key = &lock->id;
|
||||
start.lock = lock;
|
||||
start.file = lock->create_file;
|
||||
start.line = lock->create_line;
|
||||
|
||||
if(!lock->create_file)
|
||||
log_err("lock %u %u does not have create info",
|
||||
(unsigned)lock->id.thr, (unsigned)lock->id.instance);
|
||||
|
||||
/* depth first search to find cycle with this lock at head */
|
||||
lock->dfs_next = NULL;
|
||||
search_cycle(&start, 0, &start);
|
||||
}
|
||||
|
||||
/** Check ordering of locks */
|
||||
static void check_order(rbtree_t* all_locks)
|
||||
{
|
||||
/* check each lock */
|
||||
struct order_lock* lock;
|
||||
int i=0;
|
||||
RBTREE_FOR(lock, struct order_lock*, all_locks) {
|
||||
if(verb)
|
||||
printf("[%d/%d] Checking lock %d %d %s %d\n",
|
||||
i, (int)all_locks->count,
|
||||
lock->id.thr, lock->id.instance,
|
||||
lock->create_file, lock->create_line);
|
||||
else if (i % (all_locks->count/75) == 0)
|
||||
fprintf(stderr, ".");
|
||||
i++;
|
||||
check_order_lock(lock);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/** main program to verify all traces passed */
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
rbtree_t* all_locks;
|
||||
int i;
|
||||
time_t starttime = time(NULL);
|
||||
if(argc <= 1) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
log_init(NULL);
|
||||
log_ident_set("lock-verify");
|
||||
/* init */
|
||||
all_locks = rbtree_create(order_lock_cmp);
|
||||
errors_detected = 0;
|
||||
|
||||
/* read the input files */
|
||||
for(i=1; i<argc; i++) {
|
||||
readinput(all_locks, argv[i]);
|
||||
}
|
||||
|
||||
/* check ordering */
|
||||
check_order(all_locks);
|
||||
|
||||
/* do not free a thing, OS will do it */
|
||||
printf("checked %d locks in %d seconds with %d errors.\n",
|
||||
(int)all_locks->count, (int)(time(NULL)-starttime),
|
||||
errors_detected);
|
||||
if(errors_detected) return 1;
|
||||
return 0;
|
||||
}
|
||||
+9
-10
@@ -234,11 +234,10 @@ make_scenario(char* line)
|
||||
}
|
||||
|
||||
struct replay_scenario*
|
||||
replay_scenario_read(FILE* in, const char* name)
|
||||
replay_scenario_read(FILE* in, const char* name, int* lineno)
|
||||
{
|
||||
char line[MAX_LINE_LEN];
|
||||
char *parse;
|
||||
int lineno = 0;
|
||||
struct replay_scenario* scen = NULL;
|
||||
uint16_t ttl = 3600;
|
||||
ldns_rdf* or = NULL;
|
||||
@@ -247,7 +246,7 @@ replay_scenario_read(FILE* in, const char* name)
|
||||
|
||||
while(fgets(line, MAX_LINE_LEN-1, in)) {
|
||||
parse=line;
|
||||
lineno++;
|
||||
(*lineno)++;
|
||||
while(isspace(*parse))
|
||||
parse++;
|
||||
if(!*parse)
|
||||
@@ -257,26 +256,26 @@ replay_scenario_read(FILE* in, const char* name)
|
||||
if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
|
||||
scen = make_scenario(parse);
|
||||
if(!scen)
|
||||
fatal_exit("%d: could not make scen", lineno);
|
||||
fatal_exit("%d: could not make scen", *lineno);
|
||||
continue;
|
||||
}
|
||||
if(!scen)
|
||||
fatal_exit("%d: expected SCENARIO", lineno);
|
||||
fatal_exit("%d: expected SCENARIO", *lineno);
|
||||
if(parse_keyword(&parse, "RANGE_BEGIN")) {
|
||||
struct replay_range* newr = replay_range_read(parse,
|
||||
in, name, &lineno, line, &ttl, &or, &prev);
|
||||
in, name, lineno, line, &ttl, &or, &prev);
|
||||
if(!newr)
|
||||
fatal_exit("%d: bad range", lineno);
|
||||
fatal_exit("%d: bad range", *lineno);
|
||||
newr->next_range = scen->range_list;
|
||||
scen->range_list = newr;
|
||||
} else if(parse_keyword(&parse, "STEP")) {
|
||||
struct replay_moment* mom = replay_moment_read(parse,
|
||||
in, name, &lineno, &ttl, &or, &prev);
|
||||
in, name, lineno, &ttl, &or, &prev);
|
||||
if(!mom)
|
||||
fatal_exit("%d: bad moment", lineno);
|
||||
fatal_exit("%d: bad moment", *lineno);
|
||||
if(scen->mom_last &&
|
||||
scen->mom_last->time_step >= mom->time_step)
|
||||
fatal_exit("%d: time goes backwards", lineno);
|
||||
fatal_exit("%d: time goes backwards", *lineno);
|
||||
if(scen->mom_last)
|
||||
scen->mom_last->mom_next = mom;
|
||||
else scen->mom_first = mom;
|
||||
|
||||
+3
-1
@@ -263,9 +263,11 @@ struct replay_answer {
|
||||
* Read a replay scenario from the file.
|
||||
* @param in: file to read from.
|
||||
* @param name: name to print in errors.
|
||||
* @param lineno: incremented for every line read.
|
||||
* @return: Scenario. NULL if no scenario read.
|
||||
*/
|
||||
struct replay_scenario* replay_scenario_read(FILE* in, const char* name);
|
||||
struct replay_scenario* replay_scenario_read(FILE* in, const char* name,
|
||||
int* lineno);
|
||||
|
||||
/**
|
||||
* Delete scenario.
|
||||
|
||||
+53
-6
@@ -51,6 +51,9 @@
|
||||
#include "daemon/unbound.c"
|
||||
#undef main
|
||||
|
||||
/** maximum line length for lines in the replay file. */
|
||||
#define MAX_LINE_LEN 1024
|
||||
|
||||
/** give commandline usage for testbound. */
|
||||
static void
|
||||
testbound_usage()
|
||||
@@ -107,25 +110,67 @@ static void
|
||||
echo_cmdline(int argc, char* argv[])
|
||||
{
|
||||
int i;
|
||||
printf("testbound is starting:");
|
||||
fprintf(stderr, "testbound is starting:");
|
||||
for(i=0; i<argc; i++) {
|
||||
printf(" [%s]", argv[i]);
|
||||
fprintf(stderr, " [%s]", argv[i]);
|
||||
}
|
||||
printf("\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/** process config elements */
|
||||
static void
|
||||
setup_config(FILE* in, char* configfile, int* lineno,
|
||||
int* pass_argc, char* pass_argv[])
|
||||
{
|
||||
char line[MAX_LINE_LEN];
|
||||
char* parse;
|
||||
FILE* cfg;
|
||||
snprintf(configfile, MAX_LINE_LEN, "/tmp/testbound_cfg_%u.tmp",
|
||||
(unsigned)getpid());
|
||||
add_opts("-c", pass_argc, pass_argv);
|
||||
add_opts(configfile, pass_argc, pass_argv);
|
||||
cfg = fopen(configfile, "w");
|
||||
if(!cfg) fatal_exit("could not open %s: %s",
|
||||
configfile, strerror(errno));
|
||||
line[MAX_LINE_LEN-1] = 0;
|
||||
while(fgets(line, MAX_LINE_LEN-1, in)) {
|
||||
parse = line;
|
||||
(*lineno)++;
|
||||
while(isspace(*parse))
|
||||
parse++;
|
||||
if(!*parse || parse[0] == ';')
|
||||
continue;
|
||||
if(strncmp(parse, "COMMANDLINE", 11) == 0) {
|
||||
parse[strlen(parse)-1] = 0; /* strip off \n */
|
||||
add_opts(parse+11, pass_argc, pass_argv);
|
||||
continue;
|
||||
}
|
||||
if(strncmp(parse, "CONFIG_END", 10) == 0) {
|
||||
fclose(cfg);
|
||||
return;
|
||||
}
|
||||
fputs(line, cfg);
|
||||
}
|
||||
fatal_exit("No CONFIG_END in input file");
|
||||
|
||||
}
|
||||
|
||||
/** read playback file */
|
||||
static struct replay_scenario*
|
||||
setup_playback(const char* filename)
|
||||
setup_playback(const char* filename, char* configfile,
|
||||
int* pass_argc, char* pass_argv[])
|
||||
{
|
||||
struct replay_scenario* scen = NULL;
|
||||
int lineno = 0;
|
||||
|
||||
if(filename) {
|
||||
FILE *in = fopen(filename, "r");
|
||||
if(!in) {
|
||||
perror(filename);
|
||||
exit(1);
|
||||
}
|
||||
scen = replay_scenario_read(in, filename);
|
||||
setup_config(in, configfile, &lineno, pass_argc, pass_argv);
|
||||
scen = replay_scenario_read(in, filename, &lineno);
|
||||
fclose(in);
|
||||
if(!scen)
|
||||
fatal_exit("Could not read: %s", filename);
|
||||
@@ -150,6 +195,7 @@ main(int argc, char* argv[])
|
||||
int init_optind = optind;
|
||||
char* init_optarg = optarg;
|
||||
struct replay_scenario* scen = NULL;
|
||||
char cfgfile[MAX_LINE_LEN];
|
||||
|
||||
log_init(NULL);
|
||||
log_info("Start of %s testbound program.", PACKAGE_STRING);
|
||||
@@ -180,7 +226,7 @@ main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
/* setup test environment */
|
||||
scen = setup_playback(playback_file);
|
||||
scen = setup_playback(playback_file, cfgfile, &pass_argc, pass_argv);
|
||||
/* init fake event backend */
|
||||
fake_event_init(scen);
|
||||
|
||||
@@ -194,6 +240,7 @@ main(int argc, char* argv[])
|
||||
/* run the normal daemon */
|
||||
res = daemon_main(pass_argc, pass_argv);
|
||||
|
||||
unlink(cfgfile);
|
||||
fake_event_cleanup();
|
||||
for(c=1; c<pass_argc; c++)
|
||||
free(pass_argv[c]);
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* testcode/unitlruhash.c - unit test for lruhash table.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* \file
|
||||
* Tests the locking LRU keeping hash table implementation.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "testcode/unitmain.h"
|
||||
#include "util/log.h"
|
||||
#include "util/storage/lruhash.h"
|
||||
|
||||
/* --- test representation --- */
|
||||
/** structure contains test key */
|
||||
struct testkey {
|
||||
/** the key id */
|
||||
int id;
|
||||
/** the entry */
|
||||
struct lruhash_entry entry;
|
||||
};
|
||||
/** structure contains test data */
|
||||
struct testdata {
|
||||
/** data value */
|
||||
int data;
|
||||
};
|
||||
|
||||
/** sizefunc for lruhash */
|
||||
static size_t test_sizefunc(void*, void*);
|
||||
/** comparefunc for lruhash */
|
||||
static int test_compfunc(void*, void*);
|
||||
/** delkey for lruhash */
|
||||
static void test_delkey(void*, void*);
|
||||
/** deldata for lruhash */
|
||||
static void test_deldata(void*, void*);
|
||||
/* --- end test representation --- */
|
||||
|
||||
/** hash func, very bad to improve collisions. */
|
||||
static hashvalue_t myhash(int id) {return (hashvalue_t)id & 0x0f;}
|
||||
/** allocate new key, fill in hash. */
|
||||
static struct testkey* newkey(int id) {
|
||||
struct testkey* k = (struct testkey*)calloc(1, sizeof(struct testkey));
|
||||
if(!k) fatal_exit("out of memory");
|
||||
k->id = id;
|
||||
k->entry.hash = myhash(id);
|
||||
k->entry.key = k;
|
||||
lock_rw_init(&k->entry.lock);
|
||||
return k;
|
||||
}
|
||||
/** new data el */
|
||||
static struct testdata* newdata(int val) {
|
||||
struct testdata* d = (struct testdata*)calloc(1,
|
||||
sizeof(struct testdata));
|
||||
if(!d) fatal_exit("out of memory");
|
||||
d->data = val;
|
||||
return d;
|
||||
}
|
||||
/** delete key */
|
||||
static void delkey(struct testkey* k) {
|
||||
lock_rw_destroy(&k->entry.lock); free(k);}
|
||||
/** delete data */
|
||||
static void deldata(struct testdata* d) {free(d);}
|
||||
|
||||
/** test bin_find_entry function and bin_overflow_remove */
|
||||
static void
|
||||
test_bin_find_entry(struct lruhash* table)
|
||||
{
|
||||
struct testkey* k = newkey(12);
|
||||
struct testdata* d = newdata(128);
|
||||
struct testkey* k2 = newkey(12 + 1024);
|
||||
struct testkey* k3 = newkey(14);
|
||||
struct testkey* k4 = newkey(12 + 1024*2);
|
||||
hashvalue_t h = myhash(12);
|
||||
struct lruhash_bin bin;
|
||||
memset(&bin, 0, sizeof(bin));
|
||||
bin_init(&bin, 1);
|
||||
|
||||
/* remove from empty list */
|
||||
bin_overflow_remove(&bin, &k->entry);
|
||||
|
||||
/* find in empty list */
|
||||
unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
|
||||
|
||||
/* insert */
|
||||
lock_quick_lock(&bin.lock);
|
||||
bin.overflow_list = &k->entry;
|
||||
lock_quick_unlock(&bin.lock);
|
||||
|
||||
/* find, hash not OK. */
|
||||
unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL );
|
||||
|
||||
/* find, hash OK, but cmp not */
|
||||
unit_assert( k->entry.hash == k2->entry.hash );
|
||||
unit_assert( bin_find_entry(table, &bin, h, k2) == NULL );
|
||||
|
||||
/* find, hash OK, and cmp too */
|
||||
unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry );
|
||||
|
||||
/* remove the element */
|
||||
lock_quick_lock(&bin.lock);
|
||||
bin_overflow_remove(&bin, &k->entry);
|
||||
lock_quick_unlock(&bin.lock);
|
||||
unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
|
||||
|
||||
/* prepend two different elements; so the list is long */
|
||||
/* one has the same hash, but different cmp */
|
||||
lock_quick_lock(&bin.lock);
|
||||
unit_assert( k->entry.hash == k4->entry.hash );
|
||||
k4->entry.overflow_next = &k->entry;
|
||||
k3->entry.overflow_next = &k4->entry;
|
||||
bin.overflow_list = &k3->entry;
|
||||
lock_quick_unlock(&bin.lock);
|
||||
|
||||
/* find, hash not OK. */
|
||||
unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL );
|
||||
|
||||
/* find, hash OK, but cmp not */
|
||||
unit_assert( k->entry.hash == k2->entry.hash );
|
||||
unit_assert( bin_find_entry(table, &bin, h, k2) == NULL );
|
||||
|
||||
/* find, hash OK, and cmp too */
|
||||
unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry );
|
||||
|
||||
/* remove middle element */
|
||||
unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4)
|
||||
== &k4->entry );
|
||||
lock_quick_lock(&bin.lock);
|
||||
bin_overflow_remove(&bin, &k4->entry);
|
||||
lock_quick_unlock(&bin.lock);
|
||||
unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) == NULL);
|
||||
|
||||
/* remove last element */
|
||||
lock_quick_lock(&bin.lock);
|
||||
bin_overflow_remove(&bin, &k->entry);
|
||||
lock_quick_unlock(&bin.lock);
|
||||
unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
|
||||
|
||||
lock_quick_destroy(&bin.lock);
|
||||
delkey(k);
|
||||
delkey(k2);
|
||||
delkey(k3);
|
||||
delkey(k4);
|
||||
deldata(d);
|
||||
}
|
||||
|
||||
/** test lru_front lru_remove */
|
||||
static void test_lru(struct lruhash* table)
|
||||
{
|
||||
struct testkey* k = newkey(12);
|
||||
struct testkey* k2 = newkey(14);
|
||||
lock_quick_lock(&table->lock);
|
||||
|
||||
unit_assert( table->lru_start == NULL && table->lru_end == NULL);
|
||||
lru_remove(table, &k->entry);
|
||||
unit_assert( table->lru_start == NULL && table->lru_end == NULL);
|
||||
|
||||
/* add one */
|
||||
lru_front(table, &k->entry);
|
||||
unit_assert( table->lru_start == &k->entry &&
|
||||
table->lru_end == &k->entry);
|
||||
/* remove it */
|
||||
lru_remove(table, &k->entry);
|
||||
unit_assert( table->lru_start == NULL && table->lru_end == NULL);
|
||||
|
||||
/* add two */
|
||||
lru_front(table, &k->entry);
|
||||
unit_assert( table->lru_start == &k->entry &&
|
||||
table->lru_end == &k->entry);
|
||||
lru_front(table, &k2->entry);
|
||||
unit_assert( table->lru_start == &k2->entry &&
|
||||
table->lru_end == &k->entry);
|
||||
/* remove first in list */
|
||||
lru_remove(table, &k2->entry);
|
||||
unit_assert( table->lru_start == &k->entry &&
|
||||
table->lru_end == &k->entry);
|
||||
lru_front(table, &k2->entry);
|
||||
unit_assert( table->lru_start == &k2->entry &&
|
||||
table->lru_end == &k->entry);
|
||||
/* remove last in list */
|
||||
lru_remove(table, &k->entry);
|
||||
unit_assert( table->lru_start == &k2->entry &&
|
||||
table->lru_end == &k2->entry);
|
||||
|
||||
/* empty the list */
|
||||
lru_remove(table, &k2->entry);
|
||||
unit_assert( table->lru_start == NULL && table->lru_end == NULL);
|
||||
lock_quick_unlock(&table->lock);
|
||||
delkey(k);
|
||||
delkey(k2);
|
||||
}
|
||||
|
||||
/** test hashtable using short sequence */
|
||||
static void
|
||||
test_short_table(struct lruhash* table)
|
||||
{
|
||||
struct testkey* k = newkey(12);
|
||||
struct testkey* k2 = newkey(14);
|
||||
struct testdata* d = newdata(128);
|
||||
struct testdata* d2 = newdata(129);
|
||||
|
||||
k->entry.data = d;
|
||||
k2->entry.data = d2;
|
||||
|
||||
lruhash_insert(table, myhash(12), &k->entry, d);
|
||||
lruhash_insert(table, myhash(14), &k2->entry, d2);
|
||||
|
||||
unit_assert( lruhash_lookup(table, myhash(12), k, 0) == &k->entry);
|
||||
lock_rw_unlock( &k->entry.lock );
|
||||
unit_assert( lruhash_lookup(table, myhash(14), k2, 0) == &k2->entry);
|
||||
lock_rw_unlock( &k2->entry.lock );
|
||||
lruhash_remove(table, myhash(12), k);
|
||||
lruhash_remove(table, myhash(14), k2);
|
||||
}
|
||||
|
||||
/** number of hash test max */
|
||||
#define HASHTESTMAX 32
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testadd(struct lruhash* table, struct testdata* ref[])
|
||||
{
|
||||
int numtoadd = random() % HASHTESTMAX;
|
||||
struct testdata* data = newdata(numtoadd);
|
||||
struct testkey* key = newkey(numtoadd);
|
||||
key->entry.data = data;
|
||||
lruhash_insert(table, myhash(numtoadd), &key->entry, data);
|
||||
ref[numtoadd] = data;
|
||||
}
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testremove(struct lruhash* table, struct testdata* ref[])
|
||||
{
|
||||
int num = random() % HASHTESTMAX;
|
||||
struct testkey* key = newkey(num);
|
||||
lruhash_remove(table, myhash(num), key);
|
||||
ref[num] = NULL;
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testlookup(struct lruhash* table, struct testdata* ref[])
|
||||
{
|
||||
int num = random() % HASHTESTMAX;
|
||||
struct testkey* key = newkey(num);
|
||||
struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0);
|
||||
struct testdata* data = en? (struct testdata*)en->data : NULL;
|
||||
if(en) {
|
||||
unit_assert(en->key);
|
||||
unit_assert(en->data);
|
||||
}
|
||||
if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1,
|
||||
ref[num]? ref[num]->data : -1);
|
||||
unit_assert( data == ref[num] );
|
||||
if(en) { lock_rw_unlock(&en->lock); }
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** check integrity of hash table */
|
||||
static void
|
||||
check_table(struct lruhash* table)
|
||||
{
|
||||
struct lruhash_entry* p;
|
||||
size_t c = 0;
|
||||
lock_quick_lock(&table->lock);
|
||||
unit_assert( table->num <= table->size);
|
||||
unit_assert( table->size_mask == (int)table->size-1 );
|
||||
unit_assert( (table->lru_start && table->lru_end) ||
|
||||
(!table->lru_start && !table->lru_end) );
|
||||
unit_assert( table->space_used <= table->space_max );
|
||||
/* check lru list integrity */
|
||||
if(table->lru_start)
|
||||
unit_assert(table->lru_start->lru_prev == NULL);
|
||||
if(table->lru_end)
|
||||
unit_assert(table->lru_end->lru_next == NULL);
|
||||
p = table->lru_start;
|
||||
while(p) {
|
||||
if(p->lru_prev) {
|
||||
unit_assert(p->lru_prev->lru_next == p);
|
||||
}
|
||||
if(p->lru_next) {
|
||||
unit_assert(p->lru_next->lru_prev == p);
|
||||
}
|
||||
c++;
|
||||
p = p->lru_next;
|
||||
}
|
||||
unit_assert(c == table->num);
|
||||
|
||||
/* this assertion is specific to the unit test */
|
||||
unit_assert( table->space_used ==
|
||||
table->num * test_sizefunc(NULL, NULL) );
|
||||
lock_quick_unlock(&table->lock);
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testadd_unlim(struct lruhash* table, struct testdata** ref)
|
||||
{
|
||||
int numtoadd = random() % (HASHTESTMAX * 10);
|
||||
struct testdata* data = newdata(numtoadd);
|
||||
struct testkey* key = newkey(numtoadd);
|
||||
key->entry.data = data;
|
||||
lruhash_insert(table, myhash(numtoadd), &key->entry, data);
|
||||
if(ref)
|
||||
ref[numtoadd] = data;
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testremove_unlim(struct lruhash* table, struct testdata** ref)
|
||||
{
|
||||
int num = random() % (HASHTESTMAX*10);
|
||||
struct testkey* key = newkey(num);
|
||||
lruhash_remove(table, myhash(num), key);
|
||||
if(ref)
|
||||
ref[num] = NULL;
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testlookup_unlim(struct lruhash* table, struct testdata** ref)
|
||||
{
|
||||
int num = random() % (HASHTESTMAX*10);
|
||||
struct testkey* key = newkey(num);
|
||||
struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0);
|
||||
struct testdata* data = en? (struct testdata*)en->data : NULL;
|
||||
if(en) {
|
||||
unit_assert(en->key);
|
||||
unit_assert(en->data);
|
||||
}
|
||||
if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ?
|
||||
data->data :-1, ref[num] ? ref[num]->data : -1);
|
||||
if(data && ref) {
|
||||
/* its okay for !data, it fell off the lru */
|
||||
unit_assert( data == ref[num] );
|
||||
}
|
||||
if(en) { lock_rw_unlock(&en->lock); }
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test with long sequence of adds, removes and updates, and lookups */
|
||||
static void
|
||||
test_long_table(struct lruhash* table)
|
||||
{
|
||||
/* assuming it all fits in the hastable, this check will work */
|
||||
struct testdata* ref[HASHTESTMAX * 100];
|
||||
size_t i;
|
||||
memset(ref, 0, sizeof(ref));
|
||||
/* test assumption */
|
||||
if(0) log_info(" size %d x %d < %d", (int)test_sizefunc(NULL, NULL),
|
||||
(int)HASHTESTMAX, (int)table->space_max);
|
||||
unit_assert( test_sizefunc(NULL, NULL)*HASHTESTMAX < table->space_max);
|
||||
if(0) lruhash_status(table, "unit test", 1);
|
||||
srandom(48);
|
||||
for(i=0; i<1000; i++) {
|
||||
/* what to do? */
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd(table, ref);
|
||||
break;
|
||||
case 1:
|
||||
testremove(table, ref);
|
||||
break;
|
||||
case 2:
|
||||
testlookup(table, ref);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) lruhash_status(table, "unit test", 1);
|
||||
check_table(table);
|
||||
unit_assert( table->num <= HASHTESTMAX );
|
||||
}
|
||||
|
||||
/* test more, but 'ref' assumption does not hold anymore */
|
||||
for(i=0; i<1000; i++) {
|
||||
/* what to do? */
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd_unlim(table, ref);
|
||||
break;
|
||||
case 1:
|
||||
testremove_unlim(table, ref);
|
||||
break;
|
||||
case 2:
|
||||
testlookup_unlim(table, ref);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) lruhash_status(table, "unlim", 1);
|
||||
check_table(table);
|
||||
}
|
||||
}
|
||||
|
||||
/** structure to threaded test the lru hash table */
|
||||
struct test_thr {
|
||||
/** thread num, first entry. */
|
||||
int num;
|
||||
/** id */
|
||||
ub_thread_t id;
|
||||
/** hash table */
|
||||
struct lruhash* table;
|
||||
};
|
||||
|
||||
/** main routine for threaded hash table test */
|
||||
static void*
|
||||
test_thr_main(void* arg)
|
||||
{
|
||||
struct test_thr* t = (struct test_thr*)arg;
|
||||
int i;
|
||||
log_thread_set(&t->num);
|
||||
for(i=0; i<1000; i++) {
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd_unlim(t->table, NULL);
|
||||
break;
|
||||
case 1:
|
||||
testremove_unlim(t->table, NULL);
|
||||
break;
|
||||
case 2:
|
||||
testlookup_unlim(t->table, NULL);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) lruhash_status(t->table, "hashtest", 1);
|
||||
if(i % 100 == 0) /* because of locking, not all the time */
|
||||
check_table(t->table);
|
||||
}
|
||||
check_table(t->table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** test hash table access by multiple threads. */
|
||||
static void
|
||||
test_threaded_table(struct lruhash* table)
|
||||
{
|
||||
int numth = 10;
|
||||
struct test_thr t[100];
|
||||
int i;
|
||||
|
||||
for(i=1; i<numth; i++) {
|
||||
t[i].num = i;
|
||||
t[i].table = table;
|
||||
ub_thread_create(&t[i].id, test_thr_main, &t[i]);
|
||||
}
|
||||
|
||||
for(i=1; i<numth; i++) {
|
||||
ub_thread_join(t[i].id);
|
||||
}
|
||||
if(0) lruhash_status(table, "hashtest", 1);
|
||||
}
|
||||
|
||||
void lruhash_test()
|
||||
{
|
||||
/* start very very small array, so it can do lots of table_grow() */
|
||||
/* also small in size so that reclaim has to be done quickly. */
|
||||
struct lruhash* table ;
|
||||
printf("lruhash test\n");
|
||||
table = lruhash_create(2, 4096,
|
||||
test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
|
||||
test_bin_find_entry(table);
|
||||
test_lru(table);
|
||||
test_short_table(table);
|
||||
test_long_table(table);
|
||||
lruhash_delete(table);
|
||||
table = lruhash_create(2, 4096,
|
||||
test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
|
||||
test_threaded_table(table);
|
||||
lruhash_delete(table);
|
||||
}
|
||||
|
||||
static size_t test_sizefunc(void* ATTR_UNUSED(key), void* ATTR_UNUSED(data))
|
||||
{
|
||||
return sizeof(struct testkey) + sizeof(struct testdata);
|
||||
}
|
||||
|
||||
static int test_compfunc(void* key1, void* key2)
|
||||
{
|
||||
struct testkey* k1 = (struct testkey*)key1;
|
||||
struct testkey* k2 = (struct testkey*)key2;
|
||||
if(k1->id == k2->id)
|
||||
return 0;
|
||||
if(k1->id > k2->id)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_delkey(void* key, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
delkey((struct testkey*)key);
|
||||
}
|
||||
|
||||
static void test_deldata(void* data, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
deldata((struct testdata*)data);
|
||||
}
|
||||
+76
-4
@@ -41,11 +41,10 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "util/log.h"
|
||||
#include "testcode/unitmain.h"
|
||||
|
||||
/** number of tests done */
|
||||
int testcount = 0;
|
||||
/** test bool x, exits on failure, increases testcount. */
|
||||
#define unit_assert(x) testcount++; log_assert(x);
|
||||
|
||||
#include "util/alloc.h"
|
||||
/** test alloc code */
|
||||
@@ -55,7 +54,6 @@ alloc_test() {
|
||||
struct alloc_cache major, minor1, minor2;
|
||||
int i;
|
||||
|
||||
checklock_start();
|
||||
alloc_init(&major, NULL);
|
||||
alloc_init(&minor1, &major);
|
||||
alloc_init(&minor2, &major);
|
||||
@@ -84,7 +82,6 @@ alloc_test() {
|
||||
alloc_clear(&minor2);
|
||||
unit_assert(major.num_quar == 11);
|
||||
alloc_clear(&major);
|
||||
checklock_stop();
|
||||
}
|
||||
|
||||
#include "util/net_help.h"
|
||||
@@ -100,6 +97,76 @@ net_test()
|
||||
unit_assert( !str_is_ip6("213.154.224.12") );
|
||||
unit_assert( !str_is_ip6("213.154.224.255") );
|
||||
unit_assert( !str_is_ip6("255.255.255.0") );
|
||||
unit_assert( is_pow2(0) );
|
||||
unit_assert( is_pow2(1) );
|
||||
unit_assert( is_pow2(2) );
|
||||
unit_assert( is_pow2(4) );
|
||||
unit_assert( is_pow2(8) );
|
||||
unit_assert( is_pow2(16) );
|
||||
unit_assert( is_pow2(1024) );
|
||||
unit_assert( is_pow2(1024*1024) );
|
||||
unit_assert( is_pow2(1024*1024*1024) );
|
||||
unit_assert( !is_pow2(3) );
|
||||
unit_assert( !is_pow2(5) );
|
||||
unit_assert( !is_pow2(6) );
|
||||
unit_assert( !is_pow2(7) );
|
||||
unit_assert( !is_pow2(9) );
|
||||
unit_assert( !is_pow2(10) );
|
||||
unit_assert( !is_pow2(11) );
|
||||
unit_assert( !is_pow2(17) );
|
||||
unit_assert( !is_pow2(23) );
|
||||
unit_assert( !is_pow2(257) );
|
||||
unit_assert( !is_pow2(259) );
|
||||
}
|
||||
|
||||
/** put dname into buffer */
|
||||
static ldns_buffer*
|
||||
dname_to_buf(ldns_buffer* b, const char* str)
|
||||
{
|
||||
ldns_rdf* rdf;
|
||||
ldns_status status;
|
||||
ldns_buffer_clear(b);
|
||||
rdf = ldns_dname_new_frm_str(str);
|
||||
status = ldns_dname2buffer_wire(b, rdf);
|
||||
if(status != LDNS_STATUS_OK)
|
||||
fatal_exit("%s ldns: %s", __func__,
|
||||
ldns_get_errorstr_by_id(status));
|
||||
ldns_rdf_deep_free(rdf);
|
||||
ldns_buffer_flip(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
#include "util/data/msgreply.h"
|
||||
/** test query parse code */
|
||||
static void
|
||||
msgreply_test()
|
||||
{
|
||||
ldns_buffer* buff = ldns_buffer_new(65800);
|
||||
ldns_buffer_flip(buff);
|
||||
unit_assert( query_dname_len(buff) == 0);
|
||||
unit_assert( query_dname_len(dname_to_buf(buff, ".")) == 1 );
|
||||
unit_assert( query_dname_len(dname_to_buf(buff, "bla.foo.")) == 9 );
|
||||
unit_assert( query_dname_len(dname_to_buf(buff, "x.y.z.example.com.")) == 19 );
|
||||
|
||||
ldns_buffer_write_at(buff, 0, "\012abCDeaBCde\003cOm\000", 16);
|
||||
query_dname_tolower(ldns_buffer_begin(buff), 16);
|
||||
unit_assert( memcmp(ldns_buffer_begin(buff),
|
||||
"\012abcdeabcde\003com\000", 16) == 0);
|
||||
|
||||
ldns_buffer_write_at(buff, 0, "\001+\012abC{e-ZYXe\003NET\000", 18);
|
||||
query_dname_tolower(ldns_buffer_begin(buff), 18);
|
||||
unit_assert( memcmp(ldns_buffer_begin(buff),
|
||||
"\001+\012abc{e-zyxe\003net\000", 18) == 0);
|
||||
|
||||
ldns_buffer_write_at(buff, 0, "\000", 1);
|
||||
query_dname_tolower(ldns_buffer_begin(buff), 1);
|
||||
unit_assert( memcmp(ldns_buffer_begin(buff), "\000", 1) == 0);
|
||||
|
||||
ldns_buffer_write_at(buff, 0, "\002NL\000", 4);
|
||||
query_dname_tolower(ldns_buffer_begin(buff), 4);
|
||||
unit_assert( memcmp(ldns_buffer_begin(buff), "\002nl\000", 4) == 0);
|
||||
|
||||
ldns_buffer_free(buff);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,8 +184,13 @@ main(int argc, char* argv[])
|
||||
return 1;
|
||||
}
|
||||
printf("Start of %s unit test.\n", PACKAGE_STRING);
|
||||
checklock_start();
|
||||
net_test();
|
||||
alloc_test();
|
||||
msgreply_test();
|
||||
lruhash_test();
|
||||
slabhash_test();
|
||||
checklock_stop();
|
||||
printf("%d tests succeeded\n", testcount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* testcode/unitmain.h - unit test main program for unbound.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* \file
|
||||
* Declarations useful for the unit tests.
|
||||
*/
|
||||
|
||||
#ifndef TESTCODE_UNITMAIN_H
|
||||
#define TESTCODE_UNITMAIN_H
|
||||
#include "util/log.h"
|
||||
|
||||
/** number of tests done */
|
||||
extern int testcount;
|
||||
/** test bool x, exits on failure, increases testcount. */
|
||||
#define unit_assert(x) do {testcount++; log_assert(x);} while(0)
|
||||
|
||||
/** unit test lruhashtable implementation */
|
||||
void lruhash_test();
|
||||
/** unit test slabhashtable implementation */
|
||||
void slabhash_test();
|
||||
|
||||
#endif /* TESTCODE_UNITMAIN_H */
|
||||
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* testcode/unitslabhash.c - unit test for slabhash table.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* \file
|
||||
* Tests the locking LRU keeping hash table implementation.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "testcode/unitmain.h"
|
||||
#include "util/log.h"
|
||||
#include "util/storage/slabhash.h"
|
||||
|
||||
/* --- test representation --- */
|
||||
/** structure contains test key */
|
||||
struct slabtestkey {
|
||||
/** the key id */
|
||||
int id;
|
||||
/** the entry */
|
||||
struct lruhash_entry entry;
|
||||
};
|
||||
/** structure contains test data */
|
||||
struct slabtestdata {
|
||||
/** data value */
|
||||
int data;
|
||||
};
|
||||
|
||||
/** sizefunc for lruhash */
|
||||
static size_t test_sizefunc(void*, void*);
|
||||
/** comparefunc for lruhash */
|
||||
static int test_compfunc(void*, void*);
|
||||
/** delkey for lruhash */
|
||||
static void test_delkey(void*, void*);
|
||||
/** deldata for lruhash */
|
||||
static void test_deldata(void*, void*);
|
||||
/* --- end test representation --- */
|
||||
|
||||
/** hash func, very bad to improve collisions, both high and low bits. */
|
||||
static hashvalue_t myhash(int id) {
|
||||
hashvalue_t h = (hashvalue_t)id & 0x0f;
|
||||
h |= (h << 28);
|
||||
return h;
|
||||
}
|
||||
|
||||
/** allocate new key, fill in hash. */
|
||||
static struct slabtestkey* newkey(int id) {
|
||||
struct slabtestkey* k = (struct slabtestkey*)calloc(1, sizeof(struct slabtestkey));
|
||||
if(!k) fatal_exit("out of memory");
|
||||
k->id = id;
|
||||
k->entry.hash = myhash(id);
|
||||
k->entry.key = k;
|
||||
lock_rw_init(&k->entry.lock);
|
||||
return k;
|
||||
}
|
||||
/** new data el */
|
||||
static struct slabtestdata* newdata(int val) {
|
||||
struct slabtestdata* d = (struct slabtestdata*)calloc(1,
|
||||
sizeof(struct slabtestdata));
|
||||
if(!d) fatal_exit("out of memory");
|
||||
d->data = val;
|
||||
return d;
|
||||
}
|
||||
/** delete key */
|
||||
static void delkey(struct slabtestkey* k) {
|
||||
lock_rw_destroy(&k->entry.lock); free(k);}
|
||||
/** delete data */
|
||||
static void deldata(struct slabtestdata* d) {free(d);}
|
||||
|
||||
/** test hashtable using short sequence */
|
||||
static void
|
||||
test_short_table(struct slabhash* table)
|
||||
{
|
||||
struct slabtestkey* k = newkey(12);
|
||||
struct slabtestkey* k2 = newkey(14);
|
||||
struct slabtestdata* d = newdata(128);
|
||||
struct slabtestdata* d2 = newdata(129);
|
||||
|
||||
k->entry.data = d;
|
||||
k2->entry.data = d2;
|
||||
|
||||
slabhash_insert(table, myhash(12), &k->entry, d);
|
||||
slabhash_insert(table, myhash(14), &k2->entry, d2);
|
||||
|
||||
unit_assert( slabhash_lookup(table, myhash(12), k, 0) == &k->entry);
|
||||
lock_rw_unlock( &k->entry.lock );
|
||||
unit_assert( slabhash_lookup(table, myhash(14), k2, 0) == &k2->entry);
|
||||
lock_rw_unlock( &k2->entry.lock );
|
||||
slabhash_remove(table, myhash(12), k);
|
||||
slabhash_remove(table, myhash(14), k2);
|
||||
}
|
||||
|
||||
/** number of hash test max */
|
||||
#define HASHTESTMAX 32
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testadd(struct slabhash* table, struct slabtestdata* ref[])
|
||||
{
|
||||
int numtoadd = random() % HASHTESTMAX;
|
||||
struct slabtestdata* data = newdata(numtoadd);
|
||||
struct slabtestkey* key = newkey(numtoadd);
|
||||
key->entry.data = data;
|
||||
slabhash_insert(table, myhash(numtoadd), &key->entry, data);
|
||||
ref[numtoadd] = data;
|
||||
}
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testremove(struct slabhash* table, struct slabtestdata* ref[])
|
||||
{
|
||||
int num = random() % HASHTESTMAX;
|
||||
struct slabtestkey* key = newkey(num);
|
||||
slabhash_remove(table, myhash(num), key);
|
||||
ref[num] = NULL;
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test adding a random element */
|
||||
static void
|
||||
testlookup(struct slabhash* table, struct slabtestdata* ref[])
|
||||
{
|
||||
int num = random() % HASHTESTMAX;
|
||||
struct slabtestkey* key = newkey(num);
|
||||
struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
|
||||
struct slabtestdata* data = en? (struct slabtestdata*)en->data : NULL;
|
||||
if(en) {
|
||||
unit_assert(en->key);
|
||||
unit_assert(en->data);
|
||||
}
|
||||
if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1,
|
||||
ref[num]? ref[num]->data : -1);
|
||||
unit_assert( data == ref[num] );
|
||||
if(en) { lock_rw_unlock(&en->lock); }
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** check integrity of hash table */
|
||||
static void
|
||||
check_lru_table(struct lruhash* table)
|
||||
{
|
||||
struct lruhash_entry* p;
|
||||
size_t c = 0;
|
||||
lock_quick_lock(&table->lock);
|
||||
unit_assert( table->num <= table->size);
|
||||
unit_assert( table->size_mask == (int)table->size-1 );
|
||||
unit_assert( (table->lru_start && table->lru_end) ||
|
||||
(!table->lru_start && !table->lru_end) );
|
||||
unit_assert( table->space_used <= table->space_max );
|
||||
/* check lru list integrity */
|
||||
if(table->lru_start)
|
||||
unit_assert(table->lru_start->lru_prev == NULL);
|
||||
if(table->lru_end)
|
||||
unit_assert(table->lru_end->lru_next == NULL);
|
||||
p = table->lru_start;
|
||||
while(p) {
|
||||
if(p->lru_prev) {
|
||||
unit_assert(p->lru_prev->lru_next == p);
|
||||
}
|
||||
if(p->lru_next) {
|
||||
unit_assert(p->lru_next->lru_prev == p);
|
||||
}
|
||||
c++;
|
||||
p = p->lru_next;
|
||||
}
|
||||
unit_assert(c == table->num);
|
||||
|
||||
/* this assertion is specific to the unit test */
|
||||
unit_assert( table->space_used ==
|
||||
table->num * test_sizefunc(NULL, NULL) );
|
||||
lock_quick_unlock(&table->lock);
|
||||
}
|
||||
|
||||
/** check integrity of hash table */
|
||||
static void
|
||||
check_table(struct slabhash* table)
|
||||
{
|
||||
size_t i;
|
||||
for(i=0; i<table->size; i++)
|
||||
check_lru_table(table->array[i]);
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testadd_unlim(struct slabhash* table, struct slabtestdata** ref)
|
||||
{
|
||||
int numtoadd = random() % (HASHTESTMAX * 10);
|
||||
struct slabtestdata* data = newdata(numtoadd);
|
||||
struct slabtestkey* key = newkey(numtoadd);
|
||||
key->entry.data = data;
|
||||
slabhash_insert(table, myhash(numtoadd), &key->entry, data);
|
||||
if(ref)
|
||||
ref[numtoadd] = data;
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testremove_unlim(struct slabhash* table, struct slabtestdata** ref)
|
||||
{
|
||||
int num = random() % (HASHTESTMAX*10);
|
||||
struct slabtestkey* key = newkey(num);
|
||||
slabhash_remove(table, myhash(num), key);
|
||||
if(ref)
|
||||
ref[num] = NULL;
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test adding a random element (unlimited range) */
|
||||
static void
|
||||
testlookup_unlim(struct slabhash* table, struct slabtestdata** ref)
|
||||
{
|
||||
int num = random() % (HASHTESTMAX*10);
|
||||
struct slabtestkey* key = newkey(num);
|
||||
struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
|
||||
struct slabtestdata* data = en? (struct slabtestdata*)en->data : NULL;
|
||||
if(en) {
|
||||
unit_assert(en->key);
|
||||
unit_assert(en->data);
|
||||
}
|
||||
if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ?
|
||||
data->data :-1, ref[num] ? ref[num]->data : -1);
|
||||
if(data && ref) {
|
||||
/* its okay for !data, it fell off the lru */
|
||||
unit_assert( data == ref[num] );
|
||||
}
|
||||
if(en) { lock_rw_unlock(&en->lock); }
|
||||
delkey(key);
|
||||
}
|
||||
|
||||
/** test with long sequence of adds, removes and updates, and lookups */
|
||||
static void
|
||||
test_long_table(struct slabhash* table)
|
||||
{
|
||||
/* assuming it all fits in the hastable, this check will work */
|
||||
struct slabtestdata* ref[HASHTESTMAX * 100];
|
||||
size_t i;
|
||||
memset(ref, 0, sizeof(ref));
|
||||
/* test assumption */
|
||||
if(0) slabhash_status(table, "unit test", 1);
|
||||
srandom(48);
|
||||
for(i=0; i<1000; i++) {
|
||||
/* what to do? */
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd(table, ref);
|
||||
break;
|
||||
case 1:
|
||||
testremove(table, ref);
|
||||
break;
|
||||
case 2:
|
||||
testlookup(table, ref);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) slabhash_status(table, "unit test", 1);
|
||||
check_table(table);
|
||||
}
|
||||
|
||||
/* test more, but 'ref' assumption does not hold anymore */
|
||||
for(i=0; i<1000; i++) {
|
||||
/* what to do? */
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd_unlim(table, ref);
|
||||
break;
|
||||
case 1:
|
||||
testremove_unlim(table, ref);
|
||||
break;
|
||||
case 2:
|
||||
testlookup_unlim(table, ref);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) slabhash_status(table, "unlim", 1);
|
||||
check_table(table);
|
||||
}
|
||||
}
|
||||
|
||||
/** structure to threaded test the lru hash table */
|
||||
struct slab_test_thr {
|
||||
/** thread num, first entry. */
|
||||
int num;
|
||||
/** id */
|
||||
ub_thread_t id;
|
||||
/** hash table */
|
||||
struct slabhash* table;
|
||||
};
|
||||
|
||||
/** main routine for threaded hash table test */
|
||||
static void*
|
||||
test_thr_main(void* arg)
|
||||
{
|
||||
struct slab_test_thr* t = (struct slab_test_thr*)arg;
|
||||
int i;
|
||||
log_thread_set(&t->num);
|
||||
for(i=0; i<1000; i++) {
|
||||
switch(random() % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
testadd_unlim(t->table, NULL);
|
||||
break;
|
||||
case 1:
|
||||
testremove_unlim(t->table, NULL);
|
||||
break;
|
||||
case 2:
|
||||
testlookup_unlim(t->table, NULL);
|
||||
break;
|
||||
default:
|
||||
unit_assert(0);
|
||||
}
|
||||
if(0) slabhash_status(t->table, "hashtest", 1);
|
||||
if(i % 100 == 0) /* because of locking, not all the time */
|
||||
check_table(t->table);
|
||||
}
|
||||
check_table(t->table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** test hash table access by multiple threads. */
|
||||
static void
|
||||
test_threaded_table(struct slabhash* table)
|
||||
{
|
||||
int numth = 10;
|
||||
struct slab_test_thr t[100];
|
||||
int i;
|
||||
|
||||
for(i=1; i<numth; i++) {
|
||||
t[i].num = i;
|
||||
t[i].table = table;
|
||||
ub_thread_create(&t[i].id, test_thr_main, &t[i]);
|
||||
}
|
||||
|
||||
for(i=1; i<numth; i++) {
|
||||
ub_thread_join(t[i].id);
|
||||
}
|
||||
if(0) slabhash_status(table, "hashtest", 1);
|
||||
}
|
||||
|
||||
void slabhash_test()
|
||||
{
|
||||
/* start very very small array, so it can do lots of table_grow() */
|
||||
/* also small in size so that reclaim has to be done quickly. */
|
||||
struct slabhash* table;
|
||||
printf("slabhash test\n");
|
||||
table = slabhash_create(4, 2, 4096,
|
||||
test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
|
||||
test_short_table(table);
|
||||
test_long_table(table);
|
||||
slabhash_delete(table);
|
||||
table = slabhash_create(4, 2, 4096,
|
||||
test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
|
||||
test_threaded_table(table);
|
||||
slabhash_delete(table);
|
||||
}
|
||||
|
||||
static size_t test_sizefunc(void* ATTR_UNUSED(key), void* ATTR_UNUSED(data))
|
||||
{
|
||||
return sizeof(struct slabtestkey) + sizeof(struct slabtestdata);
|
||||
}
|
||||
|
||||
static int test_compfunc(void* key1, void* key2)
|
||||
{
|
||||
struct slabtestkey* k1 = (struct slabtestkey*)key1;
|
||||
struct slabtestkey* k2 = (struct slabtestkey*)key2;
|
||||
if(k1->id == k2->id)
|
||||
return 0;
|
||||
if(k1->id > k2->id)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_delkey(void* key, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
delkey((struct slabtestkey*)key);
|
||||
}
|
||||
|
||||
static void test_deldata(void* data, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
deldata((struct slabtestdata*)data);
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+3
@@ -1,10 +1,13 @@
|
||||
; This is a comment.
|
||||
; config options go here.
|
||||
CONFIG_END
|
||||
|
||||
SCENARIO_BEGIN Sample of a valid query
|
||||
RANGE_BEGIN 0 100
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
; This is a comment.
|
||||
; config options go here.
|
||||
CONFIG_END
|
||||
|
||||
SCENARIO_BEGIN Query receives answer from the cache
|
||||
|
||||
STEP 1 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
; the query is sent to the forwarder - no cache yet.
|
||||
STEP 2 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
STEP 3 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
; authoritative answer
|
||||
REPLY QR AA RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 4 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
; first reply, have AA set.
|
||||
REPLY QR AA RD RA
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
|
||||
; another query, same, so it must be answered from the cache
|
||||
STEP 5 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
; immediate answer without an OUT_QUERY happening (checked on exit)
|
||||
; also, the answer does not have AA set
|
||||
STEP 6 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
REPLY QR RD RA
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
|
||||
SCENARIO_END
|
||||
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
; config options go here.
|
||||
CONFIG_END
|
||||
SCENARIO_BEGIN Forwarder and an error happens on server query.
|
||||
STEP 1 QUERY
|
||||
ENTRY_BEGIN
|
||||
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
; This is a comment.
|
||||
; config options go here.
|
||||
; extremely small cache to force dropping old records.
|
||||
server:
|
||||
msg-cache-size: 1 # one whole byte!
|
||||
msg-cache-slabs: 1
|
||||
CONFIG_END
|
||||
|
||||
SCENARIO_BEGIN Old answer is dropped from the cache
|
||||
|
||||
STEP 1 QUERY
|
||||
ENTRY_BEGIN
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
; the query is sent to the forwarder - no cache yet.
|
||||
STEP 2 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
STEP 3 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 4 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qname qtype
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
ENTRY_END
|
||||
|
||||
; another query to force the cache to drop the example.com entry.
|
||||
STEP 11 QUERY
|
||||
ENTRY_BEGIN
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
ENTRY_END
|
||||
; the query is sent to the forwarder - no cache yet.
|
||||
STEP 12 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
ENTRY_END
|
||||
STEP 13 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
SECTION ANSWER
|
||||
www.example.net. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.net. IN NS ns.example.net.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.net. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 14 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qname qtype
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
SECTION ANSWER
|
||||
www.example.net. IN A 10.20.30.40
|
||||
ENTRY_END
|
||||
|
||||
|
||||
; query, same as first, but it fell out of the cache.
|
||||
STEP 21 QUERY
|
||||
ENTRY_BEGIN
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
STEP 22 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
STEP 23 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 24 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qname qtype
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
ENTRY_END
|
||||
|
||||
SCENARIO_END
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
; This is a comment.
|
||||
|
||||
; can set commandline options using something like this:
|
||||
; COMMANDLINE -v
|
||||
; here config file options:
|
||||
server:
|
||||
msg-cache-size: 1024
|
||||
CONFIG_END
|
||||
|
||||
SCENARIO_BEGIN Query receives answer not from the cache
|
||||
|
||||
STEP 1 QUERY
|
||||
ENTRY_BEGIN
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
; the query is sent to the forwarder - no cache yet.
|
||||
STEP 2 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
ENTRY_END
|
||||
STEP 3 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
SECTION AUTHORITY
|
||||
www.example.com. IN NS ns.example.com.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.com. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 4 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qname qtype
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
www.example.com. IN A 10.20.30.40
|
||||
ENTRY_END
|
||||
|
||||
; another query, different, so not from cache.
|
||||
STEP 5 QUERY
|
||||
ENTRY_BEGIN
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
ENTRY_END
|
||||
STEP 6 CHECK_OUT_QUERY
|
||||
ENTRY_BEGIN
|
||||
MATCH qname qtype opcode
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
ENTRY_END
|
||||
STEP 7 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
SECTION ANSWER
|
||||
www.example.net. IN A 10.20.30.60
|
||||
SECTION AUTHORITY
|
||||
www.example.net. IN NS ns.example.net.
|
||||
SECTION ADDITIONAL
|
||||
ns.example.net. IN A 10.20.30.50
|
||||
ENTRY_END
|
||||
STEP 8 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qname qtype
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
SECTION ANSWER
|
||||
www.example.net. IN A 10.20.30.60
|
||||
ENTRY_END
|
||||
|
||||
SCENARIO_END
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
; config options go here.
|
||||
CONFIG_END
|
||||
SCENARIO_BEGIN Forwarder and a timeout happens on server query.
|
||||
STEP 1 QUERY
|
||||
ENTRY_BEGIN
|
||||
|
||||
Vendored
+6
@@ -1,3 +1,7 @@
|
||||
; config options go here.
|
||||
server:
|
||||
num-queries-per-thread: 1
|
||||
CONFIG_END
|
||||
SCENARIO_BEGIN Sample of a valid query
|
||||
|
||||
; query responses from authority servers.
|
||||
@@ -5,6 +9,7 @@ RANGE_BEGIN 0 100
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.net. IN A
|
||||
SECTION ANSWER
|
||||
@@ -44,6 +49,7 @@ STEP 5 REPLY
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR RD RA NOERROR
|
||||
SECTION QUESTION
|
||||
www.example.com. IN A
|
||||
SECTION ANSWER
|
||||
|
||||
@@ -77,6 +77,9 @@ config_create()
|
||||
cfg->do_tcp = 1;
|
||||
cfg->outgoing_base_port = cfg->port + 1000;
|
||||
cfg->outgoing_num_ports = 16;
|
||||
cfg->msg_cache_size = 4 * 1024 * 1024;
|
||||
cfg->msg_cache_slabs = 4;
|
||||
cfg->num_queries_per_thread = 1024;
|
||||
if(!(cfg->fwd_address = strdup(""))) {config_delete(cfg); return NULL;}
|
||||
if(!(cfg->username = strdup(""))) {config_delete(cfg); return NULL;}
|
||||
if(!(cfg->chrootdir = strdup(""))) {config_delete(cfg); return NULL;}
|
||||
|
||||
@@ -69,6 +69,13 @@ struct config_file {
|
||||
/** outgoing port range number of ports (per thread, per if) */
|
||||
int outgoing_num_ports;
|
||||
|
||||
/** size of the message cache */
|
||||
size_t msg_cache_size;
|
||||
/** slabs in the message cache. */
|
||||
size_t msg_cache_slabs;
|
||||
/** number of queries every thread can service */
|
||||
size_t num_queries_per_thread;
|
||||
|
||||
/** forwarder address. string. If not NULL fwder mode is enabled. */
|
||||
char* fwd_address;
|
||||
/** forwarder port */
|
||||
|
||||
@@ -114,6 +114,9 @@ username{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_USERNAME;}
|
||||
directory{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DIRECTORY;}
|
||||
logfile{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOGFILE;}
|
||||
pidfile{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_PIDFILE;}
|
||||
msg-cache-size{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MSG_CACHE_SIZE;}
|
||||
msg-cache-slabs{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MSG_CACHE_SLABS;}
|
||||
num-queries-per-thread{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_NUM_QUERIES_PER_THREAD;}
|
||||
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}
|
||||
|
||||
/* Quoted strings. Strip leading and ending quotes */
|
||||
|
||||
+36
-1
@@ -46,6 +46,7 @@
|
||||
|
||||
#include "util/configyyrename.h"
|
||||
#include "util/config_file.h"
|
||||
#include "util/net_help.h"
|
||||
|
||||
int ub_c_lex(void);
|
||||
void ub_c_error(const char *message);
|
||||
@@ -71,6 +72,7 @@ extern struct config_parser_state* cfg_parser;
|
||||
%token VAR_DO_IP4 VAR_DO_IP6 VAR_DO_UDP VAR_DO_TCP
|
||||
%token VAR_FORWARD_TO VAR_FORWARD_TO_PORT VAR_CHROOT
|
||||
%token VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE
|
||||
%token VAR_MSG_CACHE_SIZE VAR_MSG_CACHE_SLABS VAR_NUM_QUERIES_PER_THREAD
|
||||
|
||||
%%
|
||||
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
|
||||
@@ -90,7 +92,9 @@ content_server: server_num_threads | server_verbosity | server_port |
|
||||
server_outgoing_port | server_outgoing_range | server_do_ip4 |
|
||||
server_do_ip6 | server_do_udp | server_do_tcp | server_forward_to |
|
||||
server_forward_to_port | server_interface | server_chroot |
|
||||
server_username | server_directory | server_logfile | server_pidfile;
|
||||
server_username | server_directory | server_logfile | server_pidfile |
|
||||
server_msg_cache_size | server_msg_cache_slabs |
|
||||
server_num_queries_per_thread;
|
||||
server_num_threads: VAR_NUM_THREADS STRING
|
||||
{
|
||||
OUTYY(("P(server_num_threads:%s)\n", $2));
|
||||
@@ -236,6 +240,37 @@ server_pidfile: VAR_PIDFILE STRING
|
||||
cfg_parser->cfg->pidfile = $2;
|
||||
}
|
||||
;
|
||||
server_msg_cache_size: VAR_MSG_CACHE_SIZE STRING
|
||||
{
|
||||
OUTYY(("P(server_msg_cache_size:%s)\n", $2));
|
||||
if(atoi($2) == 0)
|
||||
yyerror("number expected");
|
||||
else cfg_parser->cfg->msg_cache_size = atoi($2);
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
server_msg_cache_slabs: VAR_MSG_CACHE_SLABS STRING
|
||||
{
|
||||
OUTYY(("P(server_msg_cache_slabs:%s)\n", $2));
|
||||
if(atoi($2) == 0)
|
||||
yyerror("number expected");
|
||||
else {
|
||||
cfg_parser->cfg->msg_cache_slabs = atoi($2);
|
||||
if(!is_pow2(cfg_parser->cfg->msg_cache_slabs))
|
||||
yyerror("must be a power of 2");
|
||||
}
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
server_num_queries_per_thread: VAR_NUM_QUERIES_PER_THREAD STRING
|
||||
{
|
||||
OUTYY(("P(server_num_queries_per_thread:%s)\n", $2));
|
||||
if(atoi($2) == 0)
|
||||
yyerror("number expected");
|
||||
else cfg_parser->cfg->num_queries_per_thread = atoi($2);
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
%%
|
||||
|
||||
/* parse helper routines could be here */
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* util/data/msgreply.c - store message and reply data.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file contains a data structure to store a message and its reply.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "util/data/msgreply.h"
|
||||
#include "util/storage/lookup3.h"
|
||||
#include "util/log.h"
|
||||
#include "util/netevent.h"
|
||||
#include "util/net_help.h"
|
||||
|
||||
/** determine length of a dname in buffer, no compression pointers allowed. */
|
||||
size_t
|
||||
query_dname_len(ldns_buffer* query)
|
||||
{
|
||||
size_t len = 0;
|
||||
size_t labellen;
|
||||
while(1) {
|
||||
if(ldns_buffer_remaining(query) < 1)
|
||||
return 0; /* parse error, need label len */
|
||||
labellen = ldns_buffer_read_u8(query);
|
||||
if(labellen & 0xC0)
|
||||
return 0; /* no compression allowed in queries */
|
||||
len += labellen + 1;
|
||||
if(len > LDNS_MAX_DOMAINLEN)
|
||||
return 0; /* too long */
|
||||
if(labellen == 0)
|
||||
return len;
|
||||
if(ldns_buffer_remaining(query) < labellen)
|
||||
return 0; /* parse error, need content */
|
||||
ldns_buffer_skip(query, (ssize_t)labellen);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
query_info_parse(struct query_info* m, ldns_buffer* query)
|
||||
{
|
||||
uint8_t* q = ldns_buffer_begin(query);
|
||||
/* minimum size: header + \0 + qtype + qclass */
|
||||
if(ldns_buffer_limit(query) < LDNS_HEADER_SIZE + 5)
|
||||
return 0;
|
||||
log_assert(!LDNS_QR_WIRE(q));
|
||||
log_assert(LDNS_OPCODE_WIRE(q) == LDNS_PACKET_QUERY);
|
||||
log_assert(LDNS_QDCOUNT(q) == 1);
|
||||
log_assert(ldns_buffer_position(query) == 0);
|
||||
m->has_cd = (int)LDNS_CD_WIRE(q);
|
||||
ldns_buffer_skip(query, LDNS_HEADER_SIZE);
|
||||
m->qname = ldns_buffer_current(query);
|
||||
if((m->qnamesize = query_dname_len(query)) == 0)
|
||||
return 0; /* parse error */
|
||||
if(ldns_buffer_remaining(query) < 4)
|
||||
return 0; /* need qtype, qclass */
|
||||
m->qtype = ldns_buffer_read_u16(query);
|
||||
m->qclass = ldns_buffer_read_u16(query);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
query_info_allocqname(struct query_info* m)
|
||||
{
|
||||
uint8_t* q = m->qname;
|
||||
if(!(m->qname = (uint8_t*)malloc(m->qnamesize))) {
|
||||
log_err("query_info_allocqname: out of memory");
|
||||
return 0; /* out of memory */
|
||||
}
|
||||
memcpy(m->qname, q, m->qnamesize);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** tiny subroutine for msgreply_compare */
|
||||
#define COMPARE_IT(x, y) \
|
||||
if( (x) < (y) ) return -1; \
|
||||
else if( (x) > (y) ) return +1; \
|
||||
log_assert( (x) == (y) );
|
||||
|
||||
int
|
||||
query_info_compare(void* m1, void* m2)
|
||||
{
|
||||
struct query_info* msg1 = (struct query_info*)m1;
|
||||
struct query_info* msg2 = (struct query_info*)m2;
|
||||
int mc;
|
||||
/* from most different to least different for speed */
|
||||
COMPARE_IT(msg1->qtype, msg2->qtype);
|
||||
COMPARE_IT(msg1->qnamesize, msg2->qnamesize);
|
||||
if((mc = memcmp(msg1->qname, msg2->qname, msg1->qnamesize)) != 0)
|
||||
return mc;
|
||||
COMPARE_IT(msg1->has_cd, msg2->has_cd);
|
||||
COMPARE_IT(msg1->qclass, msg2->qclass);
|
||||
return 0;
|
||||
#undef COMPARE_IT
|
||||
}
|
||||
|
||||
void
|
||||
query_info_clear(struct query_info* m)
|
||||
{
|
||||
free(m->qname);
|
||||
m->qname = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
reply_info_clear(struct reply_info* m)
|
||||
{
|
||||
free(m->reply);
|
||||
m->reply = NULL;
|
||||
}
|
||||
|
||||
size_t
|
||||
msgreply_sizefunc(void* k, void* d)
|
||||
{
|
||||
struct query_info* q = (struct query_info*)k;
|
||||
struct reply_info* r = (struct reply_info*)d;
|
||||
return sizeof(struct msgreply_entry) + sizeof(struct reply_info)
|
||||
+ r->replysize + q->qnamesize;
|
||||
}
|
||||
|
||||
void
|
||||
query_entry_delete(void *k, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
struct msgreply_entry* q = (struct msgreply_entry*)k;
|
||||
lock_rw_destroy(&q->entry.lock);
|
||||
query_info_clear(&q->key);
|
||||
free(q);
|
||||
}
|
||||
|
||||
void
|
||||
reply_info_delete(void* d, void* ATTR_UNUSED(arg))
|
||||
{
|
||||
struct reply_info* r = (struct reply_info*)d;
|
||||
reply_info_clear(r);
|
||||
free(r);
|
||||
}
|
||||
|
||||
void
|
||||
query_dname_tolower(uint8_t* dname, size_t len)
|
||||
{
|
||||
/* the dname is stored uncompressed */
|
||||
uint8_t labellen;
|
||||
log_assert(len > 0);
|
||||
labellen = *dname;
|
||||
while(labellen) {
|
||||
dname++;
|
||||
while(labellen--) {
|
||||
*dname = (uint8_t)tolower((int)*dname);
|
||||
dname++;
|
||||
}
|
||||
labellen = *dname;
|
||||
}
|
||||
}
|
||||
|
||||
hashvalue_t
|
||||
query_info_hash(struct query_info *q)
|
||||
{
|
||||
hashvalue_t h = 0xab;
|
||||
h = hashlittle(&q->qtype, sizeof(q->qtype), h);
|
||||
h = hashlittle(&q->qclass, sizeof(q->qclass), h);
|
||||
h = hashlittle(&q->has_cd, sizeof(q->has_cd), h);
|
||||
query_dname_tolower(q->qname, q->qnamesize);
|
||||
h = hashlittle(q->qname, q->qnamesize, h);
|
||||
return h;
|
||||
}
|
||||
|
||||
void
|
||||
reply_info_answer(struct reply_info* rep, uint16_t qflags,
|
||||
ldns_buffer* buffer)
|
||||
{
|
||||
uint16_t flags;
|
||||
ldns_buffer_clear(buffer);
|
||||
ldns_buffer_skip(buffer, 2); /* ID */
|
||||
flags = rep->flags | (qflags & BIT_RD); /* copy RD bit */
|
||||
log_assert(flags & BIT_QR); /* QR bit must be on in our replies */
|
||||
ldns_buffer_write_u16(buffer, flags);
|
||||
ldns_buffer_write(buffer, rep->reply, rep->replysize);
|
||||
ldns_buffer_flip(buffer);
|
||||
}
|
||||
|
||||
void
|
||||
reply_info_answer_iov(struct reply_info* rep, uint16_t qid,
|
||||
uint16_t qflags, struct comm_reply* comrep, int cached)
|
||||
{
|
||||
/* [0]=reserved for tcplen, [1]=id, [2]=flags, [3]=message */
|
||||
struct iovec iov[4];
|
||||
|
||||
iov[1].iov_base = (void*)&qid;
|
||||
iov[1].iov_len = sizeof(uint16_t);
|
||||
if(!cached) {
|
||||
/* original flags, copy RD bit from query. */
|
||||
qflags = rep->flags | (qflags & BIT_RD);
|
||||
} else {
|
||||
/* remove AA bit, copy RD and CD bits from query. */
|
||||
qflags = (rep->flags & ~BIT_AA) | (qflags & (BIT_RD|BIT_CD));
|
||||
}
|
||||
log_assert(qflags & BIT_QR); /* QR bit must be on in our replies */
|
||||
qflags = htons(qflags);
|
||||
iov[2].iov_base = (void*)&qflags;
|
||||
iov[2].iov_len = sizeof(uint16_t);
|
||||
iov[3].iov_base = (void*)rep->reply;
|
||||
iov[3].iov_len = rep->replysize;
|
||||
comm_point_send_reply_iov(comrep, iov, 4);
|
||||
}
|
||||
|
||||
struct msgreply_entry*
|
||||
query_info_entrysetup(struct query_info* q, struct reply_info* r,
|
||||
hashvalue_t h)
|
||||
{
|
||||
struct msgreply_entry* e = (struct msgreply_entry*)malloc(
|
||||
sizeof(struct msgreply_entry));
|
||||
if(!e) return NULL;
|
||||
memcpy(&e->key, q, sizeof(*q));
|
||||
e->entry.hash = h;
|
||||
e->entry.key = e;
|
||||
e->entry.data = r;
|
||||
lock_rw_init(&e->entry.lock);
|
||||
lock_protect(&e->entry.lock, &e->key, sizeof(e->key));
|
||||
lock_protect(&e->entry.lock, &e->entry.hash, sizeof(e->entry.hash) +
|
||||
sizeof(e->entry.key) + sizeof(e->entry.data));
|
||||
lock_protect(&e->entry.lock, e->key.qname, e->key.qnamesize);
|
||||
q->qname = NULL;
|
||||
return e;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* util/data/msgreply.h - store message and reply data.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file contains a data structure to store a message and its reply.
|
||||
*/
|
||||
|
||||
#ifndef UTIL_DATA_MSGREPLY_H
|
||||
#define UTIL_DATA_MSGREPLY_H
|
||||
#include "util/storage/lruhash.h"
|
||||
struct comm_reply;
|
||||
|
||||
/**
|
||||
* Structure to store query information that makes answers to queries
|
||||
* different.
|
||||
*/
|
||||
struct query_info {
|
||||
/** salient data on the query: qname, in wireformat. */
|
||||
uint8_t* qname;
|
||||
/** length of qname (including last 0 octet) */
|
||||
size_t qnamesize;
|
||||
/** qtype */
|
||||
uint16_t qtype;
|
||||
/** qclass */
|
||||
uint16_t qclass;
|
||||
/** does query have CD bit set */
|
||||
int has_cd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure to store DNS query and the reply packet.
|
||||
* To use it, copy over the flags from reply and modify using flags from
|
||||
* the query (RD,CD if not AA). prepend ID.
|
||||
*/
|
||||
struct reply_info {
|
||||
/** the reply packet, skips ID and flags,
|
||||
* starts with opcode/rcode word */
|
||||
uint8_t* reply;
|
||||
/** the reply size */
|
||||
size_t replysize;
|
||||
/** the flags for the answer, host order. */
|
||||
uint16_t flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure to keep hash table entry for message replies.
|
||||
*/
|
||||
struct msgreply_entry {
|
||||
/** the hash table key */
|
||||
struct query_info key;
|
||||
/** the hash table entry, data is struct reply_info* */
|
||||
struct lruhash_entry entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse wire query into a queryinfo structure, return 0 on parse error.
|
||||
* initialises the (prealloced) queryinfo structure as well.
|
||||
* This query structure contains a pointer back info the buffer!
|
||||
* This pointer avoids memory allocation. allocqname does memory allocation.
|
||||
* @param m: the prealloced queryinfo structure to put query into.
|
||||
* must be unused, or _clear()ed.
|
||||
* @param query: the wireformat packet query. starts with ID.
|
||||
* @return: 0 on format error.
|
||||
*/
|
||||
int query_info_parse(struct query_info* m, ldns_buffer* query);
|
||||
|
||||
/**
|
||||
* Allocate and copy the qname (obtained from query_info_parse()).
|
||||
* @param m: the queryinfo structure.
|
||||
* @return: 0 on alloc failure.
|
||||
*/
|
||||
int query_info_allocqname(struct query_info* m);
|
||||
|
||||
/**
|
||||
* Compare two queryinfo structures, on query and type, class.
|
||||
* It is _not_ sorted in canonical ordering.
|
||||
* @param m1: struct query_info* , void* here to ease use as function pointer.
|
||||
* @param m2: struct query_info* , void* here to ease use as function pointer.
|
||||
* @return: 0 = same, -1 m1 is smaller, +1 m1 is larger.
|
||||
*/
|
||||
int query_info_compare(void* m1, void* m2);
|
||||
|
||||
/** clear out query info structure. */
|
||||
void query_info_clear(struct query_info* m);
|
||||
|
||||
/** helper routine, determine length of dname in buffer, no compression ptrs
|
||||
* allowed, returns 0 on parse failure. */
|
||||
size_t query_dname_len(ldns_buffer* query);
|
||||
|
||||
/** lowercase query dname */
|
||||
void query_dname_tolower(uint8_t* dname, size_t len);
|
||||
|
||||
/** clear out reply info structure */
|
||||
void reply_info_clear(struct reply_info* m);
|
||||
|
||||
/** calculate size of struct query_info + reply_info */
|
||||
size_t msgreply_sizefunc(void* k, void* d);
|
||||
|
||||
/** delete msgreply_entry key structure */
|
||||
void query_entry_delete(void *q, void* arg);
|
||||
|
||||
/** delete reply_info data structure */
|
||||
void reply_info_delete(void* d, void* arg);
|
||||
|
||||
/** calculate hash value of query_info, lowercases the qname. */
|
||||
hashvalue_t query_info_hash(struct query_info *q);
|
||||
|
||||
/**
|
||||
* Generate answer from reply_info.
|
||||
* @param rep: reply to fill in.
|
||||
* @param qflags: flags word from the query.
|
||||
* @param buf: buffer to put reply into. Note that the query ID must
|
||||
* be put in the buffer by caller.
|
||||
* The buffer must be large enough.
|
||||
*/
|
||||
void reply_info_answer(struct reply_info* rep, uint16_t qflags,
|
||||
ldns_buffer* buf);
|
||||
|
||||
/**
|
||||
* Generate and send out answer from reply_info.
|
||||
* @param rep: reply to fill in.
|
||||
* @param qid: query id, in network byte order.
|
||||
* @param qflags: flags word from the query.
|
||||
* @param comrep: communication reply point.
|
||||
* @param cached: set true if a cached reply (so no AA bit).
|
||||
* set false for the first reply.
|
||||
*/
|
||||
void reply_info_answer_iov(struct reply_info* rep, uint16_t qid,
|
||||
uint16_t qflags, struct comm_reply* comrep, int cached);
|
||||
|
||||
/**
|
||||
* Setup query info entry
|
||||
* @param q: query info to copy. Emptied as if clear is called.
|
||||
* @param r: reply to init data.
|
||||
* @param h: hash value.
|
||||
* @return: newly allocated message reply cache item.
|
||||
*/
|
||||
struct msgreply_entry* query_info_entrysetup(struct query_info* q,
|
||||
struct reply_info* r, hashvalue_t h);
|
||||
|
||||
#endif /* UTIL_DATA_MSGREPLY_H */
|
||||
@@ -81,6 +81,7 @@
|
||||
|
||||
#else /* USE_THREAD_DEBUG */
|
||||
#define lock_protect(lock, area, size) /* nop */
|
||||
#define lock_unprotect(lock, area) /* nop */
|
||||
#define checklock_start() /* nop */
|
||||
#define checklock_stop() /* nop */
|
||||
|
||||
|
||||
+9
-3
@@ -51,6 +51,8 @@ static FILE* logfile = 0;
|
||||
static int key_created = 0;
|
||||
/** pthread key for thread ids in logfile */
|
||||
static ub_thread_key_t logkey;
|
||||
/** the identity of this executable/process */
|
||||
static const char* ident="unbound";
|
||||
|
||||
void
|
||||
log_init(const char* filename)
|
||||
@@ -85,11 +87,15 @@ void log_thread_set(int* num)
|
||||
ub_thread_key_set(logkey, num);
|
||||
}
|
||||
|
||||
void log_ident_set(const char* id)
|
||||
{
|
||||
ident = id;
|
||||
}
|
||||
|
||||
void
|
||||
log_vmsg(const char* type, const char *format, va_list args)
|
||||
{
|
||||
char message[MAXSYSLOGMSGLEN];
|
||||
const char* ident="unbound";
|
||||
unsigned int* tid = (unsigned int*)ub_thread_key_get(logkey);
|
||||
vsnprintf(message, sizeof(message), format, args);
|
||||
fprintf(logfile, "[%d] %s[%d:%x] %s: %s\n",
|
||||
@@ -171,13 +177,13 @@ log_hex(const char* msg, void* data, size_t length)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t* data8 = (uint8_t*)data;
|
||||
const char const* hexchar = "0123456789ABCDEF";
|
||||
const char* hexchar = "0123456789ABCDEF";
|
||||
char* buf = malloc(length*2 + 1); /* alloc hex chars + \0 */
|
||||
for(i=0; i<length; i++) {
|
||||
buf[i*2] = hexchar[ data8[i] >> 4 ];
|
||||
buf[i*2 + 1] = hexchar[ data8[i] & 0xF ];
|
||||
}
|
||||
buf[length*2] = 0;
|
||||
log_info("%s[%d] %s", msg, length, buf);
|
||||
log_info("%s[%u] %s", msg, (unsigned)length, buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,12 @@ void log_init(const char* filename);
|
||||
*/
|
||||
void log_thread_set(int* num);
|
||||
|
||||
/**
|
||||
* Set identity to print, default is 'unbound'.
|
||||
* @param id: string to print. Name of executable.
|
||||
*/
|
||||
void log_ident_set(const char* id);
|
||||
|
||||
/**
|
||||
* Log informational message.
|
||||
* Pass printf formatted arguments. No trailing newline is needed.
|
||||
|
||||
@@ -87,3 +87,10 @@ fd_set_nonblock(int s)
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
is_pow2(size_t num)
|
||||
{
|
||||
if(num == 0) return 1;
|
||||
return (num & (num-1)) == 0;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,16 @@
|
||||
#ifndef NET_HELP_H
|
||||
#define NET_HELP_H
|
||||
|
||||
/** DNS constants for uint16_t style flag manipulation. host byteorder. */
|
||||
/** AA flag */
|
||||
#define BIT_AA 0x0400
|
||||
/** RD flag */
|
||||
#define BIT_RD 0x0100
|
||||
/** CD flag */
|
||||
#define BIT_CD 0x0010
|
||||
/** QR flag */
|
||||
#define BIT_QR 0x8000
|
||||
|
||||
/**
|
||||
* See if string is ip4 or ip6.
|
||||
* @param str: IP specification.
|
||||
@@ -66,4 +76,11 @@ write_socket(int s, const void *buf, size_t size);
|
||||
*/
|
||||
int fd_set_nonblock(int s);
|
||||
|
||||
/**
|
||||
* See if number is a power of 2.
|
||||
* @param num: the value.
|
||||
* @return: true if the number is a power of 2.
|
||||
*/
|
||||
int is_pow2(size_t num);
|
||||
|
||||
#endif /* NET_HELP_H */
|
||||
|
||||
@@ -780,6 +780,67 @@ comm_point_send_reply(struct comm_reply *repinfo)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
comm_point_send_reply_iov(struct comm_reply* repinfo, struct iovec* iov,
|
||||
size_t iovlen)
|
||||
{
|
||||
log_assert(repinfo && repinfo->c);
|
||||
if(repinfo->c->type == comm_udp) {
|
||||
struct msghdr hdr;
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.msg_name = &repinfo->addr;
|
||||
hdr.msg_namelen = repinfo->addrlen;
|
||||
hdr.msg_iov = iov + 1;
|
||||
hdr.msg_iovlen = (TYPE_MSGIOVLEN)(iovlen - 1);
|
||||
/* note that number of characters sent is not checked. */
|
||||
if(sendmsg(repinfo->c->fd, &hdr, 0) == -1)
|
||||
log_err("sendmsg: %s", strerror(errno));
|
||||
} else {
|
||||
/* try if it can be sent in writev right now */
|
||||
size_t i;
|
||||
uint16_t len = 0;
|
||||
ssize_t done;
|
||||
for(i=1; i<iovlen; i++)
|
||||
len += iov[i].iov_len;
|
||||
len = htons(len);
|
||||
iov[0].iov_base = (void*)&len;
|
||||
iov[0].iov_len = sizeof(uint16_t);
|
||||
if((done=writev(repinfo->c->fd, iov, (int)iovlen)) == -1) {
|
||||
#ifdef S_SPLINT_S
|
||||
/* don't complain about returning stack references */
|
||||
iov[0].iov_base = NULL;
|
||||
#endif
|
||||
if(errno != EINTR && errno != EAGAIN) {
|
||||
log_err("writev: %s", strerror(errno));
|
||||
comm_point_drop_reply(repinfo);
|
||||
return;
|
||||
}
|
||||
done = 0;
|
||||
}
|
||||
#ifdef S_SPLINT_S
|
||||
/* don't complain about returning stack references */
|
||||
iov[0].iov_base = NULL;
|
||||
#endif
|
||||
if((size_t)done == ntohs(len) + sizeof(uint16_t)) {
|
||||
/* done in one call */
|
||||
comm_point_drop_reply(repinfo);
|
||||
} else {
|
||||
/* sending remaining bytes */
|
||||
ldns_buffer_clear(repinfo->c->buffer);
|
||||
repinfo->c->tcp_byte_count = (size_t)done;
|
||||
for(i=1; i<iovlen; i++)
|
||||
ldns_buffer_write(repinfo->c->buffer,
|
||||
iov[i].iov_base, iov[i].iov_len);
|
||||
ldns_buffer_flip(repinfo->c->buffer);
|
||||
if((size_t)done >= sizeof(uint16_t))
|
||||
ldns_buffer_set_position(repinfo->c->buffer,
|
||||
(size_t)done - sizeof(uint16_t));
|
||||
comm_point_start_listening(repinfo->c, -1,
|
||||
TCP_QUERY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
comm_point_drop_reply(struct comm_reply* repinfo)
|
||||
{
|
||||
|
||||
@@ -327,6 +327,18 @@ void comm_point_set_cb_arg(struct comm_point* c, void* arg);
|
||||
*/
|
||||
void comm_point_send_reply(struct comm_reply* repinfo);
|
||||
|
||||
/**
|
||||
* Send reply. Message is not put into commpoint buffer, but in iovec.
|
||||
* If it cannot be sent immediately (TCP) the message is copied to the buffer.
|
||||
* @param repinfo: reply info copied from commpoint callback call.
|
||||
* @param iov: iovector, array of base, len parts to send out.
|
||||
* caller must keep entry 0 free for use by tcp handler. Start at entry 1.
|
||||
* @param iovlen: number of iov items to concatenate and send out.
|
||||
* this includes the entry 0, which is not filled in by caller.
|
||||
*/
|
||||
void comm_point_send_reply_iov(struct comm_reply* repinfo, struct iovec* iov,
|
||||
size_t iovlen);
|
||||
|
||||
/**
|
||||
* Drop reply. Cleans up.
|
||||
* @param repinfo: The reply info copied from a commpoint callback call.
|
||||
|
||||
@@ -0,0 +1,994 @@
|
||||
/*
|
||||
March 2007(Wouter) adapted from lookup3.c original, add config.h include.
|
||||
added #ifdef VALGRIND to remove 298,384,660 'unused variable k8' warnings.
|
||||
added include of lookup3.h to check definitions match declarations.
|
||||
removed include of stdint - config.h takes care of platform independence.
|
||||
url http://burtleburtle.net/bob/hash/index.html.
|
||||
*/
|
||||
/*
|
||||
-------------------------------------------------------------------------------
|
||||
lookup3.c, by Bob Jenkins, May 2006, Public Domain.
|
||||
|
||||
These are functions for producing 32-bit hashes for hash table lookup.
|
||||
hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
|
||||
are externally useful functions. Routines to test the hash are included
|
||||
if SELF_TEST is defined. You can use this free for any purpose. It's in
|
||||
the public domain. It has no warranty.
|
||||
|
||||
You probably want to use hashlittle(). hashlittle() and hashbig()
|
||||
hash byte arrays. hashlittle() is is faster than hashbig() on
|
||||
little-endian machines. Intel and AMD are little-endian machines.
|
||||
On second thought, you probably want hashlittle2(), which is identical to
|
||||
hashlittle() except it returns two 32-bit hashes for the price of one.
|
||||
You could implement hashbig2() if you wanted but I haven't bothered here.
|
||||
|
||||
If you want to find a hash of, say, exactly 7 integers, do
|
||||
a = i1; b = i2; c = i3;
|
||||
mix(a,b,c);
|
||||
a += i4; b += i5; c += i6;
|
||||
mix(a,b,c);
|
||||
a += i7;
|
||||
final(a,b,c);
|
||||
then use c as the hash value. If you have a variable length array of
|
||||
4-byte integers to hash, use hashword(). If you have a byte array (like
|
||||
a character string), use hashlittle(). If you have several byte arrays, or
|
||||
a mix of things, see the comments above hashlittle().
|
||||
|
||||
Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
|
||||
then mix those integers. This is fast (you can do a lot more thorough
|
||||
mixing with 12*3 instructions on 3 integers than you can with 3 instructions
|
||||
on 1 byte), but shoehorning those bytes into integers efficiently is messy.
|
||||
-------------------------------------------------------------------------------
|
||||
*/
|
||||
/*#define SELF_TEST 1*/
|
||||
|
||||
#include "config.h"
|
||||
#include "util/storage/lookup3.h"
|
||||
#include <stdio.h> /* defines printf for tests */
|
||||
#include <time.h> /* defines time_t for timings in the test */
|
||||
/*#include <stdint.h> defines uint32_t etc (from config.h) */
|
||||
#include <sys/param.h> /* attempt to define endianness */
|
||||
#ifdef linux
|
||||
# include <endian.h> /* attempt to define endianness */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* My best guess at if you are big-endian or little-endian. This may
|
||||
* need adjustment.
|
||||
*/
|
||||
#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
|
||||
__BYTE_ORDER == __LITTLE_ENDIAN) || \
|
||||
(defined(i386) || defined(__i386__) || defined(__i486__) || \
|
||||
defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL))
|
||||
# define HASH_LITTLE_ENDIAN 1
|
||||
# define HASH_BIG_ENDIAN 0
|
||||
#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
|
||||
__BYTE_ORDER == __BIG_ENDIAN) || \
|
||||
(defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel))
|
||||
# define HASH_LITTLE_ENDIAN 0
|
||||
# define HASH_BIG_ENDIAN 1
|
||||
#else
|
||||
# define HASH_LITTLE_ENDIAN 0
|
||||
# define HASH_BIG_ENDIAN 0
|
||||
#endif
|
||||
|
||||
#define hashsize(n) ((uint32_t)1<<(n))
|
||||
#define hashmask(n) (hashsize(n)-1)
|
||||
#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
|
||||
|
||||
/*
|
||||
-------------------------------------------------------------------------------
|
||||
mix -- mix 3 32-bit values reversibly.
|
||||
|
||||
This is reversible, so any information in (a,b,c) before mix() is
|
||||
still in (a,b,c) after mix().
|
||||
|
||||
If four pairs of (a,b,c) inputs are run through mix(), or through
|
||||
mix() in reverse, there are at least 32 bits of the output that
|
||||
are sometimes the same for one pair and different for another pair.
|
||||
This was tested for:
|
||||
* pairs that differed by one bit, by two bits, in any combination
|
||||
of top bits of (a,b,c), or in any combination of bottom bits of
|
||||
(a,b,c).
|
||||
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
||||
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
||||
is commonly produced by subtraction) look like a single 1-bit
|
||||
difference.
|
||||
* the base values were pseudorandom, all zero but one bit set, or
|
||||
all zero plus a counter that starts at zero.
|
||||
|
||||
Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
|
||||
satisfy this are
|
||||
4 6 8 16 19 4
|
||||
9 15 3 18 27 15
|
||||
14 9 3 7 17 3
|
||||
Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
|
||||
for "differ" defined as + with a one-bit base and a two-bit delta. I
|
||||
used http://burtleburtle.net/bob/hash/avalanche.html to choose
|
||||
the operations, constants, and arrangements of the variables.
|
||||
|
||||
This does not achieve avalanche. There are input bits of (a,b,c)
|
||||
that fail to affect some output bits of (a,b,c), especially of a. The
|
||||
most thoroughly mixed value is c, but it doesn't really even achieve
|
||||
avalanche in c.
|
||||
|
||||
This allows some parallelism. Read-after-writes are good at doubling
|
||||
the number of bits affected, so the goal of mixing pulls in the opposite
|
||||
direction as the goal of parallelism. I did what I could. Rotates
|
||||
seem to cost as much as shifts on every machine I could lay my hands
|
||||
on, and rotates are much kinder to the top and bottom bits, so I used
|
||||
rotates.
|
||||
-------------------------------------------------------------------------------
|
||||
*/
|
||||
#define mix(a,b,c) \
|
||||
{ \
|
||||
a -= c; a ^= rot(c, 4); c += b; \
|
||||
b -= a; b ^= rot(a, 6); a += c; \
|
||||
c -= b; c ^= rot(b, 8); b += a; \
|
||||
a -= c; a ^= rot(c,16); c += b; \
|
||||
b -= a; b ^= rot(a,19); a += c; \
|
||||
c -= b; c ^= rot(b, 4); b += a; \
|
||||
}
|
||||
|
||||
/*
|
||||
-------------------------------------------------------------------------------
|
||||
final -- final mixing of 3 32-bit values (a,b,c) into c
|
||||
|
||||
Pairs of (a,b,c) values differing in only a few bits will usually
|
||||
produce values of c that look totally different. This was tested for
|
||||
* pairs that differed by one bit, by two bits, in any combination
|
||||
of top bits of (a,b,c), or in any combination of bottom bits of
|
||||
(a,b,c).
|
||||
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
||||
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
||||
is commonly produced by subtraction) look like a single 1-bit
|
||||
difference.
|
||||
* the base values were pseudorandom, all zero but one bit set, or
|
||||
all zero plus a counter that starts at zero.
|
||||
|
||||
These constants passed:
|
||||
14 11 25 16 4 14 24
|
||||
12 14 25 16 4 14 24
|
||||
and these came close:
|
||||
4 8 15 26 3 22 24
|
||||
10 8 15 26 3 22 24
|
||||
11 8 15 26 3 22 24
|
||||
-------------------------------------------------------------------------------
|
||||
*/
|
||||
#define final(a,b,c) \
|
||||
{ \
|
||||
c ^= b; c -= rot(b,14); \
|
||||
a ^= c; a -= rot(c,11); \
|
||||
b ^= a; b -= rot(a,25); \
|
||||
c ^= b; c -= rot(b,16); \
|
||||
a ^= c; a -= rot(c,4); \
|
||||
b ^= a; b -= rot(a,14); \
|
||||
c ^= b; c -= rot(b,24); \
|
||||
}
|
||||
|
||||
/*
|
||||
--------------------------------------------------------------------
|
||||
This works on all machines. To be useful, it requires
|
||||
-- that the key be an array of uint32_t's, and
|
||||
-- that the length be the number of uint32_t's in the key
|
||||
|
||||
The function hashword() is identical to hashlittle() on little-endian
|
||||
machines, and identical to hashbig() on big-endian machines,
|
||||
except that the length has to be measured in uint32_ts rather than in
|
||||
bytes. hashlittle() is more complicated than hashword() only because
|
||||
hashlittle() has to dance around fitting the key bytes into registers.
|
||||
--------------------------------------------------------------------
|
||||
*/
|
||||
uint32_t hashword(
|
||||
const uint32_t *k, /* the key, an array of uint32_t values */
|
||||
size_t length, /* the length of the key, in uint32_ts */
|
||||
uint32_t initval) /* the previous hash, or an arbitrary value */
|
||||
{
|
||||
uint32_t a,b,c;
|
||||
|
||||
/* Set up the internal state */
|
||||
a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
|
||||
|
||||
/*------------------------------------------------- handle most of the key */
|
||||
while (length > 3)
|
||||
{
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
mix(a,b,c);
|
||||
length -= 3;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*------------------------------------------- handle the last 3 uint32_t's */
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 3 : c+=k[2];
|
||||
case 2 : b+=k[1];
|
||||
case 1 : a+=k[0];
|
||||
final(a,b,c);
|
||||
case 0: /* case 0: nothing left to add */
|
||||
break;
|
||||
}
|
||||
/*------------------------------------------------------ report the result */
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
--------------------------------------------------------------------
|
||||
hashword2() -- same as hashword(), but take two seeds and return two
|
||||
32-bit values. pc and pb must both be nonnull, and *pc and *pb must
|
||||
both be initialized with seeds. If you pass in (*pb)==0, the output
|
||||
(*pc) will be the same as the return value from hashword().
|
||||
--------------------------------------------------------------------
|
||||
*/
|
||||
void hashword2 (
|
||||
const uint32_t *k, /* the key, an array of uint32_t values */
|
||||
size_t length, /* the length of the key, in uint32_ts */
|
||||
uint32_t *pc, /* IN: seed OUT: primary hash value */
|
||||
uint32_t *pb) /* IN: more seed OUT: secondary hash value */
|
||||
{
|
||||
uint32_t a,b,c;
|
||||
|
||||
/* Set up the internal state */
|
||||
a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc;
|
||||
c += *pb;
|
||||
|
||||
/*------------------------------------------------- handle most of the key */
|
||||
while (length > 3)
|
||||
{
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
mix(a,b,c);
|
||||
length -= 3;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*------------------------------------------- handle the last 3 uint32_t's */
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 3 : c+=k[2];
|
||||
case 2 : b+=k[1];
|
||||
case 1 : a+=k[0];
|
||||
final(a,b,c);
|
||||
case 0: /* case 0: nothing left to add */
|
||||
break;
|
||||
}
|
||||
/*------------------------------------------------------ report the result */
|
||||
*pc=c; *pb=b;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
-------------------------------------------------------------------------------
|
||||
hashlittle() -- hash a variable-length key into a 32-bit value
|
||||
k : the key (the unaligned variable-length array of bytes)
|
||||
length : the length of the key, counting by bytes
|
||||
initval : can be any 4-byte value
|
||||
Returns a 32-bit value. Every bit of the key affects every bit of
|
||||
the return value. Two keys differing by one or two bits will have
|
||||
totally different hash values.
|
||||
|
||||
The best hash table sizes are powers of 2. There is no need to do
|
||||
mod a prime (mod is sooo slow!). If you need less than 32 bits,
|
||||
use a bitmask. For example, if you need only 10 bits, do
|
||||
h = (h & hashmask(10));
|
||||
In which case, the hash table should have hashsize(10) elements.
|
||||
|
||||
If you are hashing n strings (uint8_t **)k, do it like this:
|
||||
for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
|
||||
|
||||
By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
|
||||
code any way you wish, private, educational, or commercial. It's free.
|
||||
|
||||
Use for hash table lookup, or anything where one collision in 2^^32 is
|
||||
acceptable. Do NOT use for cryptographic purposes.
|
||||
-------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
uint32_t hashlittle( const void *key, size_t length, uint32_t initval)
|
||||
{
|
||||
uint32_t a,b,c; /* internal state */
|
||||
union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
|
||||
|
||||
/* Set up the internal state */
|
||||
a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*----------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]&0xffffff" actually reads beyond the end of the string, but
|
||||
* then masks off the part it's not allowed to read. Because the
|
||||
* string is aligned, the masked-off tail is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
|
||||
case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
|
||||
case 6 : b+=k[1]&0xffff; a+=k[0]; break;
|
||||
case 5 : b+=k[1]&0xff; a+=k[0]; break;
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=k[0]&0xffffff; break;
|
||||
case 2 : a+=k[0]&0xffff; break;
|
||||
case 1 : a+=k[0]&0xff; break;
|
||||
case 0 : return c; /* zero length strings require no mixing */
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
||||
case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
|
||||
case 9 : c+=k8[8]; /* fall through */
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
||||
case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
|
||||
case 5 : b+=k8[4]; /* fall through */
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
||||
case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
|
||||
case 1 : a+=k8[0]; break;
|
||||
case 0 : return c;
|
||||
}
|
||||
|
||||
#endif /* !valgrind */
|
||||
|
||||
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
|
||||
const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
|
||||
const uint8_t *k8;
|
||||
|
||||
/*--------------- all but last block: aligned reads and different mixing */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0] + (((uint32_t)k[1])<<16);
|
||||
b += k[2] + (((uint32_t)k[3])<<16);
|
||||
c += k[4] + (((uint32_t)k[5])<<16);
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 6;
|
||||
}
|
||||
|
||||
/*----------------------------- handle the last (probably partial) block */
|
||||
k8 = (const uint8_t *)k;
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[4]+(((uint32_t)k[5])<<16);
|
||||
b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
||||
case 10: c+=k[4];
|
||||
b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 9 : c+=k8[8]; /* fall through */
|
||||
case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
||||
case 6 : b+=k[2];
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 5 : b+=k8[4]; /* fall through */
|
||||
case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
||||
case 2 : a+=k[0];
|
||||
break;
|
||||
case 1 : a+=k8[0];
|
||||
break;
|
||||
case 0 : return c; /* zero length requires no mixing */
|
||||
}
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0];
|
||||
a += ((uint32_t)k[1])<<8;
|
||||
a += ((uint32_t)k[2])<<16;
|
||||
a += ((uint32_t)k[3])<<24;
|
||||
b += k[4];
|
||||
b += ((uint32_t)k[5])<<8;
|
||||
b += ((uint32_t)k[6])<<16;
|
||||
b += ((uint32_t)k[7])<<24;
|
||||
c += k[8];
|
||||
c += ((uint32_t)k[9])<<8;
|
||||
c += ((uint32_t)k[10])<<16;
|
||||
c += ((uint32_t)k[11])<<24;
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*-------------------------------- last block: affect all 32 bits of (c) */
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 12: c+=((uint32_t)k[11])<<24;
|
||||
case 11: c+=((uint32_t)k[10])<<16;
|
||||
case 10: c+=((uint32_t)k[9])<<8;
|
||||
case 9 : c+=k[8];
|
||||
case 8 : b+=((uint32_t)k[7])<<24;
|
||||
case 7 : b+=((uint32_t)k[6])<<16;
|
||||
case 6 : b+=((uint32_t)k[5])<<8;
|
||||
case 5 : b+=k[4];
|
||||
case 4 : a+=((uint32_t)k[3])<<24;
|
||||
case 3 : a+=((uint32_t)k[2])<<16;
|
||||
case 2 : a+=((uint32_t)k[1])<<8;
|
||||
case 1 : a+=k[0];
|
||||
break;
|
||||
case 0 : return c;
|
||||
}
|
||||
}
|
||||
|
||||
final(a,b,c);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hashlittle2: return 2 32-bit hash values
|
||||
*
|
||||
* This is identical to hashlittle(), except it returns two 32-bit hash
|
||||
* values instead of just one. This is good enough for hash table
|
||||
* lookup with 2^^64 buckets, or if you want a second hash if you're not
|
||||
* happy with the first, or if you want a probably-unique 64-bit ID for
|
||||
* the key. *pc is better mixed than *pb, so use *pc first. If you want
|
||||
* a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
|
||||
*/
|
||||
void hashlittle2(
|
||||
const void *key, /* the key to hash */
|
||||
size_t length, /* length of the key */
|
||||
uint32_t *pc, /* IN: primary initval, OUT: primary hash */
|
||||
uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */
|
||||
{
|
||||
uint32_t a,b,c; /* internal state */
|
||||
union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
|
||||
|
||||
/* Set up the internal state */
|
||||
a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc;
|
||||
c += *pb;
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*----------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]&0xffffff" actually reads beyond the end of the string, but
|
||||
* then masks off the part it's not allowed to read. Because the
|
||||
* string is aligned, the masked-off tail is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
|
||||
case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
|
||||
case 6 : b+=k[1]&0xffff; a+=k[0]; break;
|
||||
case 5 : b+=k[1]&0xff; a+=k[0]; break;
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=k[0]&0xffffff; break;
|
||||
case 2 : a+=k[0]&0xffff; break;
|
||||
case 1 : a+=k[0]&0xff; break;
|
||||
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
||||
case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
|
||||
case 9 : c+=k8[8]; /* fall through */
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
||||
case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
|
||||
case 5 : b+=k8[4]; /* fall through */
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
||||
case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
|
||||
case 1 : a+=k8[0]; break;
|
||||
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
||||
}
|
||||
|
||||
#endif /* !valgrind */
|
||||
|
||||
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
|
||||
const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
|
||||
const uint8_t *k8;
|
||||
|
||||
/*--------------- all but last block: aligned reads and different mixing */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0] + (((uint32_t)k[1])<<16);
|
||||
b += k[2] + (((uint32_t)k[3])<<16);
|
||||
c += k[4] + (((uint32_t)k[5])<<16);
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 6;
|
||||
}
|
||||
|
||||
/*----------------------------- handle the last (probably partial) block */
|
||||
k8 = (const uint8_t *)k;
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[4]+(((uint32_t)k[5])<<16);
|
||||
b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
||||
case 10: c+=k[4];
|
||||
b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 9 : c+=k8[8]; /* fall through */
|
||||
case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
||||
case 6 : b+=k[2];
|
||||
a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 5 : b+=k8[4]; /* fall through */
|
||||
case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
||||
case 2 : a+=k[0];
|
||||
break;
|
||||
case 1 : a+=k8[0];
|
||||
break;
|
||||
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
||||
}
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0];
|
||||
a += ((uint32_t)k[1])<<8;
|
||||
a += ((uint32_t)k[2])<<16;
|
||||
a += ((uint32_t)k[3])<<24;
|
||||
b += k[4];
|
||||
b += ((uint32_t)k[5])<<8;
|
||||
b += ((uint32_t)k[6])<<16;
|
||||
b += ((uint32_t)k[7])<<24;
|
||||
c += k[8];
|
||||
c += ((uint32_t)k[9])<<8;
|
||||
c += ((uint32_t)k[10])<<16;
|
||||
c += ((uint32_t)k[11])<<24;
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*-------------------------------- last block: affect all 32 bits of (c) */
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 12: c+=((uint32_t)k[11])<<24;
|
||||
case 11: c+=((uint32_t)k[10])<<16;
|
||||
case 10: c+=((uint32_t)k[9])<<8;
|
||||
case 9 : c+=k[8];
|
||||
case 8 : b+=((uint32_t)k[7])<<24;
|
||||
case 7 : b+=((uint32_t)k[6])<<16;
|
||||
case 6 : b+=((uint32_t)k[5])<<8;
|
||||
case 5 : b+=k[4];
|
||||
case 4 : a+=((uint32_t)k[3])<<24;
|
||||
case 3 : a+=((uint32_t)k[2])<<16;
|
||||
case 2 : a+=((uint32_t)k[1])<<8;
|
||||
case 1 : a+=k[0];
|
||||
break;
|
||||
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
||||
}
|
||||
}
|
||||
|
||||
final(a,b,c);
|
||||
*pc=c; *pb=b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* hashbig():
|
||||
* This is the same as hashword() on big-endian machines. It is different
|
||||
* from hashlittle() on all machines. hashbig() takes advantage of
|
||||
* big-endian byte ordering.
|
||||
*/
|
||||
uint32_t hashbig( const void *key, size_t length, uint32_t initval)
|
||||
{
|
||||
uint32_t a,b,c;
|
||||
union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
|
||||
|
||||
/* Set up the internal state */
|
||||
a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*----------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]<<8" actually reads beyond the end of the string, but
|
||||
* then shifts out the part it's not allowed to read. Because the
|
||||
* string is aligned, the illegal read is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch(length)
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
|
||||
case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
|
||||
case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
|
||||
case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=k[0]&0xffffff00; break;
|
||||
case 2 : a+=k[0]&0xffff0000; break;
|
||||
case 1 : a+=k[0]&0xff000000; break;
|
||||
case 0 : return c; /* zero length strings require no mixing */
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c+=((uint32_t)k8[10])<<8; /* fall through */
|
||||
case 10: c+=((uint32_t)k8[9])<<16; /* fall through */
|
||||
case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */
|
||||
case 8 : b+=k[1]; a+=k[0]; break;
|
||||
case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */
|
||||
case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */
|
||||
case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */
|
||||
case 4 : a+=k[0]; break;
|
||||
case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */
|
||||
case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */
|
||||
case 1 : a+=((uint32_t)k8[0])<<24; break;
|
||||
case 0 : return c;
|
||||
}
|
||||
|
||||
#endif /* !VALGRIND */
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (length > 12)
|
||||
{
|
||||
a += ((uint32_t)k[0])<<24;
|
||||
a += ((uint32_t)k[1])<<16;
|
||||
a += ((uint32_t)k[2])<<8;
|
||||
a += ((uint32_t)k[3]);
|
||||
b += ((uint32_t)k[4])<<24;
|
||||
b += ((uint32_t)k[5])<<16;
|
||||
b += ((uint32_t)k[6])<<8;
|
||||
b += ((uint32_t)k[7]);
|
||||
c += ((uint32_t)k[8])<<24;
|
||||
c += ((uint32_t)k[9])<<16;
|
||||
c += ((uint32_t)k[10])<<8;
|
||||
c += ((uint32_t)k[11]);
|
||||
mix(a,b,c);
|
||||
length -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*-------------------------------- last block: affect all 32 bits of (c) */
|
||||
switch(length) /* all the case statements fall through */
|
||||
{
|
||||
case 12: c+=k[11];
|
||||
case 11: c+=((uint32_t)k[10])<<8;
|
||||
case 10: c+=((uint32_t)k[9])<<16;
|
||||
case 9 : c+=((uint32_t)k[8])<<24;
|
||||
case 8 : b+=k[7];
|
||||
case 7 : b+=((uint32_t)k[6])<<8;
|
||||
case 6 : b+=((uint32_t)k[5])<<16;
|
||||
case 5 : b+=((uint32_t)k[4])<<24;
|
||||
case 4 : a+=k[3];
|
||||
case 3 : a+=((uint32_t)k[2])<<8;
|
||||
case 2 : a+=((uint32_t)k[1])<<16;
|
||||
case 1 : a+=((uint32_t)k[0])<<24;
|
||||
break;
|
||||
case 0 : return c;
|
||||
}
|
||||
}
|
||||
|
||||
final(a,b,c);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SELF_TEST
|
||||
|
||||
/* used for timings */
|
||||
void driver1()
|
||||
{
|
||||
uint8_t buf[256];
|
||||
uint32_t i;
|
||||
uint32_t h=0;
|
||||
time_t a,z;
|
||||
|
||||
time(&a);
|
||||
for (i=0; i<256; ++i) buf[i] = 'x';
|
||||
for (i=0; i<1; ++i)
|
||||
{
|
||||
h = hashlittle(&buf[0],1,h);
|
||||
}
|
||||
time(&z);
|
||||
if (z-a > 0) printf("time %d %.8x\n", z-a, h);
|
||||
}
|
||||
|
||||
/* check that every input bit changes every output bit half the time */
|
||||
#define HASHSTATE 1
|
||||
#define HASHLEN 1
|
||||
#define MAXPAIR 60
|
||||
#define MAXLEN 70
|
||||
void driver2()
|
||||
{
|
||||
uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
|
||||
uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
|
||||
uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
|
||||
uint32_t x[HASHSTATE],y[HASHSTATE];
|
||||
uint32_t hlen;
|
||||
|
||||
printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
|
||||
for (hlen=0; hlen < MAXLEN; ++hlen)
|
||||
{
|
||||
z=0;
|
||||
for (i=0; i<hlen; ++i) /*----------------------- for each input byte, */
|
||||
{
|
||||
for (j=0; j<8; ++j) /*------------------------ for each input bit, */
|
||||
{
|
||||
for (m=1; m<8; ++m) /*------------ for serveral possible initvals, */
|
||||
{
|
||||
for (l=0; l<HASHSTATE; ++l)
|
||||
e[l]=f[l]=g[l]=h[l]=x[l]=y[l]=~((uint32_t)0);
|
||||
|
||||
/*---- check that every output bit is affected by that input bit */
|
||||
for (k=0; k<MAXPAIR; k+=2)
|
||||
{
|
||||
uint32_t finished=1;
|
||||
/* keys have one bit different */
|
||||
for (l=0; l<hlen+1; ++l) {a[l] = b[l] = (uint8_t)0;}
|
||||
/* have a and b be two keys differing in only one bit */
|
||||
a[i] ^= (k<<j);
|
||||
a[i] ^= (k>>(8-j));
|
||||
c[0] = hashlittle(a, hlen, m);
|
||||
b[i] ^= ((k+1)<<j);
|
||||
b[i] ^= ((k+1)>>(8-j));
|
||||
d[0] = hashlittle(b, hlen, m);
|
||||
/* check every bit is 1, 0, set, and not set at least once */
|
||||
for (l=0; l<HASHSTATE; ++l)
|
||||
{
|
||||
e[l] &= (c[l]^d[l]);
|
||||
f[l] &= ~(c[l]^d[l]);
|
||||
g[l] &= c[l];
|
||||
h[l] &= ~c[l];
|
||||
x[l] &= d[l];
|
||||
y[l] &= ~d[l];
|
||||
if (e[l]|f[l]|g[l]|h[l]|x[l]|y[l]) finished=0;
|
||||
}
|
||||
if (finished) break;
|
||||
}
|
||||
if (k>z) z=k;
|
||||
if (k==MAXPAIR)
|
||||
{
|
||||
printf("Some bit didn't change: ");
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x ",
|
||||
e[0],f[0],g[0],h[0],x[0],y[0]);
|
||||
printf("i %d j %d m %d len %d\n", i, j, m, hlen);
|
||||
}
|
||||
if (z==MAXPAIR) goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (z < MAXPAIR)
|
||||
{
|
||||
printf("Mix success %2d bytes %2d initvals ",i,m);
|
||||
printf("required %d trials\n", z/2);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Check for reading beyond the end of the buffer and alignment problems */
|
||||
void driver3()
|
||||
{
|
||||
uint8_t buf[MAXLEN+20], *b;
|
||||
uint32_t len;
|
||||
uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
|
||||
uint32_t h;
|
||||
uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t i;
|
||||
uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t j;
|
||||
uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t ref,x,y;
|
||||
uint8_t *p;
|
||||
|
||||
printf("Endianness. These lines should all be the same (for values filled in):\n");
|
||||
printf("%.8x %.8x %.8x\n",
|
||||
hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13),
|
||||
hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13),
|
||||
hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13));
|
||||
p = q;
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
|
||||
hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
|
||||
hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
|
||||
hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
|
||||
hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
|
||||
hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
|
||||
p = &qq[1];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
|
||||
hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
|
||||
hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
|
||||
hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
|
||||
hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
|
||||
hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
|
||||
p = &qqq[2];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
|
||||
hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
|
||||
hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
|
||||
hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
|
||||
hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
|
||||
hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
|
||||
p = &qqqq[3];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
|
||||
hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
|
||||
hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
|
||||
hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
|
||||
hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
|
||||
hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
|
||||
printf("\n");
|
||||
|
||||
/* check that hashlittle2 and hashlittle produce the same results */
|
||||
i=47; j=0;
|
||||
hashlittle2(q, sizeof(q), &i, &j);
|
||||
if (hashlittle(q, sizeof(q), 47) != i)
|
||||
printf("hashlittle2 and hashlittle mismatch\n");
|
||||
|
||||
/* check that hashword2 and hashword produce the same results */
|
||||
len = 0xdeadbeef;
|
||||
i=47, j=0;
|
||||
hashword2(&len, 1, &i, &j);
|
||||
if (hashword(&len, 1, 47) != i)
|
||||
printf("hashword2 and hashword mismatch %x %x\n",
|
||||
i, hashword(&len, 1, 47));
|
||||
|
||||
/* check hashlittle doesn't read before or after the ends of the string */
|
||||
for (h=0, b=buf+1; h<8; ++h, ++b)
|
||||
{
|
||||
for (i=0; i<MAXLEN; ++i)
|
||||
{
|
||||
len = i;
|
||||
for (j=0; j<i; ++j) *(b+j)=0;
|
||||
|
||||
/* these should all be equal */
|
||||
ref = hashlittle(b, len, (uint32_t)1);
|
||||
*(b+i)=(uint8_t)~0;
|
||||
*(b-1)=(uint8_t)~0;
|
||||
x = hashlittle(b, len, (uint32_t)1);
|
||||
y = hashlittle(b, len, (uint32_t)1);
|
||||
if ((ref != x) || (ref != y))
|
||||
{
|
||||
printf("alignment error: %.8x %.8x %.8x %d %d\n",ref,x,y,
|
||||
h, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check for problems with nulls */
|
||||
void driver4()
|
||||
{
|
||||
uint8_t buf[1];
|
||||
uint32_t h,i,state[HASHSTATE];
|
||||
|
||||
|
||||
buf[0] = ~0;
|
||||
for (i=0; i<HASHSTATE; ++i) state[i] = 1;
|
||||
printf("These should all be different\n");
|
||||
for (i=0, h=0; i<8; ++i)
|
||||
{
|
||||
h = hashlittle(buf, 0, h);
|
||||
printf("%2ld 0-byte strings, hash is %.8x\n", i, h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
driver1(); /* test that the key is hashed: used for timings */
|
||||
driver2(); /* test that whole key is hashed thoroughly */
|
||||
driver3(); /* test that nothing but the key is hashed */
|
||||
driver4(); /* test hashing multiple buffers (all buffers are null) */
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* SELF_TEST */
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* util/storage/lookup3.h - header file for hashing functions.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file contains header definitions for the hash functions we use.
|
||||
* The hash functions are public domain (see lookup3.c).
|
||||
*/
|
||||
|
||||
#ifndef UTIL_STORAGE_LOOKUP3_H
|
||||
#define UTIL_STORAGE_LOOKUP3_H
|
||||
|
||||
/**
|
||||
* Hash key made of 4byte chunks.
|
||||
* @param k: the key, an array of uint32_t values
|
||||
* @param length: the length of the key, in uint32_ts
|
||||
* @param initval: the previous hash, or an arbitrary value
|
||||
* @return: hash value.
|
||||
*/
|
||||
uint32_t hashword(const uint32_t *k, size_t length, uint32_t initval);
|
||||
|
||||
/**
|
||||
* Hash key data.
|
||||
* @param k: the key, array of uint8_t
|
||||
* @param length: the length of the key, in uint8_ts
|
||||
* @param initval: the previous hash, or an arbitrary value
|
||||
* @return: hash value.
|
||||
*/
|
||||
uint32_t hashlittle(const void *k, size_t length, uint32_t initval);
|
||||
|
||||
#endif /* UTIL_STORAGE_LOOKUP3_H */
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* util/storage/lruhash.c - hashtable, hash function, LRU keeping.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file contains a hashtable with LRU keeping of entries.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "util/storage/lruhash.h"
|
||||
|
||||
void
|
||||
bin_init(struct lruhash_bin* array, size_t size)
|
||||
{
|
||||
size_t i;
|
||||
for(i=0; i<size; i++) {
|
||||
lock_quick_init(&array[i].lock);
|
||||
lock_protect(&array[i].lock, &array[i],
|
||||
sizeof(struct lruhash_bin));
|
||||
}
|
||||
}
|
||||
|
||||
struct lruhash*
|
||||
lruhash_create(size_t start_size, size_t maxmem, lruhash_sizefunc_t sizefunc,
|
||||
lruhash_compfunc_t compfunc, lruhash_delkeyfunc_t delkeyfunc,
|
||||
lruhash_deldatafunc_t deldatafunc, void* arg)
|
||||
{
|
||||
struct lruhash* table = (struct lruhash*)calloc(1,
|
||||
sizeof(struct lruhash));
|
||||
if(!table)
|
||||
return NULL;
|
||||
lock_quick_init(&table->lock);
|
||||
table->sizefunc = sizefunc;
|
||||
table->compfunc = compfunc;
|
||||
table->delkeyfunc = delkeyfunc;
|
||||
table->deldatafunc = deldatafunc;
|
||||
table->cb_arg = arg;
|
||||
table->size = start_size;
|
||||
table->size_mask = (int)(start_size-1);
|
||||
table->lru_start = NULL;
|
||||
table->lru_end = NULL;
|
||||
table->num = 0;
|
||||
table->space_used = 0;
|
||||
table->space_max = maxmem;
|
||||
table->array = calloc(table->size, sizeof(struct lruhash_bin));
|
||||
if(!table->array) {
|
||||
lock_quick_destroy(&table->lock);
|
||||
free(table);
|
||||
return NULL;
|
||||
}
|
||||
bin_init(table->array, table->size);
|
||||
lock_protect(&table->lock, table, sizeof(*table));
|
||||
lock_protect(&table->lock, table->array,
|
||||
table->size*sizeof(struct lruhash_bin));
|
||||
return table;
|
||||
}
|
||||
|
||||
void
|
||||
bin_delete(struct lruhash* table, struct lruhash_bin* bin)
|
||||
{
|
||||
struct lruhash_entry* p, *np;
|
||||
void *d;
|
||||
if(!bin)
|
||||
return;
|
||||
lock_quick_destroy(&bin->lock);
|
||||
p = bin->overflow_list;
|
||||
bin->overflow_list = NULL;
|
||||
while(p) {
|
||||
np = p->overflow_next;
|
||||
d = p->data;
|
||||
(*table->delkeyfunc)(p->key, table->cb_arg);
|
||||
(*table->deldatafunc)(d, table->cb_arg);
|
||||
p = np;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bin_split(struct lruhash* table, struct lruhash_bin* newa,
|
||||
int newmask)
|
||||
{
|
||||
size_t i;
|
||||
struct lruhash_entry *p, *np;
|
||||
struct lruhash_bin* newbin;
|
||||
/* move entries to new table. Notice that since hash x is mapped to
|
||||
* bin x & mask, and new mask uses one more bit, so all entries in
|
||||
* one bin will go into the old bin or bin | newbit */
|
||||
int newbit = newmask - table->size_mask;
|
||||
/* so, really, this task could also be threaded, per bin. */
|
||||
/* LRU list is not changed */
|
||||
for(i=0; i<table->size; i++)
|
||||
{
|
||||
lock_quick_lock(&table->array[i].lock);
|
||||
p = table->array[i].overflow_list;
|
||||
/* lock both destination bins */
|
||||
lock_quick_lock(&newa[i].lock);
|
||||
lock_quick_lock(&newa[newbit|i].lock);
|
||||
while(p) {
|
||||
np = p->overflow_next;
|
||||
/* link into correct new bin */
|
||||
newbin = &newa[p->hash & newmask];
|
||||
p->overflow_next = newbin->overflow_list;
|
||||
newbin->overflow_list = p;
|
||||
p=np;
|
||||
}
|
||||
lock_quick_unlock(&newa[i].lock);
|
||||
lock_quick_unlock(&newa[newbit|i].lock);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
lruhash_delete(struct lruhash* table)
|
||||
{
|
||||
size_t i;
|
||||
if(!table)
|
||||
return;
|
||||
/* delete lock on hashtable to force check its OK */
|
||||
lock_quick_destroy(&table->lock);
|
||||
for(i=0; i<table->size; i++)
|
||||
bin_delete(table, &table->array[i]);
|
||||
free(table->array);
|
||||
free(table);
|
||||
}
|
||||
|
||||
void
|
||||
bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry)
|
||||
{
|
||||
struct lruhash_entry* p = bin->overflow_list;
|
||||
struct lruhash_entry** prevp = &bin->overflow_list;
|
||||
while(p) {
|
||||
if(p == entry) {
|
||||
*prevp = p->overflow_next;
|
||||
return;
|
||||
}
|
||||
prevp = &p->overflow_next;
|
||||
p = p->overflow_next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reclaim_space(struct lruhash* table, struct lruhash_entry** list)
|
||||
{
|
||||
struct lruhash_entry* d;
|
||||
struct lruhash_bin* bin;
|
||||
log_assert(table);
|
||||
/* does not delete MRU entry, so table will not be empty. */
|
||||
while(table->num > 1 && table->space_used > table->space_max) {
|
||||
/* notice that since we hold the hashtable lock, nobody
|
||||
can change the lru chain. So it cannot be deleted underneath
|
||||
us. We still need the hashbin and entry write lock to make
|
||||
sure we flush all users away from the entry.
|
||||
which is unlikely, since it is LRU, if someone got a rdlock
|
||||
it would be moved to front, but to be sure. */
|
||||
d = table->lru_end;
|
||||
/* specialised, delete from end of double linked list,
|
||||
and we know num>1, so there is a previous lru entry. */
|
||||
log_assert(d && d->lru_prev);
|
||||
table->lru_end = d->lru_prev;
|
||||
d->lru_prev->lru_next = NULL;
|
||||
/* schedule entry for deletion */
|
||||
bin = &table->array[d->hash & table->size_mask];
|
||||
table->num --;
|
||||
lock_quick_lock(&bin->lock);
|
||||
bin_overflow_remove(bin, d);
|
||||
d->overflow_next = *list;
|
||||
*list = d;
|
||||
lock_rw_wrlock(&d->lock);
|
||||
table->space_used -= table->sizefunc(d->key, d->data);
|
||||
lock_quick_unlock(&bin->lock);
|
||||
}
|
||||
}
|
||||
|
||||
struct lruhash_entry*
|
||||
bin_find_entry(struct lruhash* table,
|
||||
struct lruhash_bin* bin, hashvalue_t hash, void* key)
|
||||
{
|
||||
struct lruhash_entry* p = bin->overflow_list;
|
||||
while(p) {
|
||||
if(p->hash == hash && table->compfunc(p->key, key) == 0)
|
||||
return p;
|
||||
p = p->overflow_next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
table_grow(struct lruhash* table)
|
||||
{
|
||||
struct lruhash_bin* newa;
|
||||
int newmask;
|
||||
size_t i;
|
||||
if(table->size_mask == (int)(((size_t)-1)>>1)) {
|
||||
log_err("hash array malloc: size_t too small");
|
||||
return;
|
||||
}
|
||||
/* try to allocate new array, if not fail */
|
||||
newa = calloc(table->size*2, sizeof(struct lruhash_bin));
|
||||
if(!newa) {
|
||||
log_err("hash grow: malloc failed");
|
||||
/* continue with smaller array. Though its slower. */
|
||||
return;
|
||||
}
|
||||
bin_init(newa, table->size*2);
|
||||
newmask = (table->size_mask << 1) | 1;
|
||||
bin_split(table, newa, newmask);
|
||||
/* delete the old bins */
|
||||
lock_unprotect(&table->lock, table->array);
|
||||
for(i=0; i<table->size; i++) {
|
||||
lock_quick_unlock(&table->array[i].lock);
|
||||
lock_quick_destroy(&table->array[i].lock);
|
||||
}
|
||||
free(table->array);
|
||||
|
||||
table->size *= 2;
|
||||
table->size_mask = newmask;
|
||||
table->array = newa;
|
||||
lock_protect(&table->lock, table->array,
|
||||
table->size*sizeof(struct lruhash_bin));
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
lru_front(struct lruhash* table, struct lruhash_entry* entry)
|
||||
{
|
||||
entry->lru_prev = NULL;
|
||||
entry->lru_next = table->lru_start;
|
||||
if(!table->lru_start)
|
||||
table->lru_end = entry;
|
||||
else table->lru_start->lru_prev = entry;
|
||||
table->lru_start = entry;
|
||||
}
|
||||
|
||||
void
|
||||
lru_remove(struct lruhash* table, struct lruhash_entry* entry)
|
||||
{
|
||||
if(entry->lru_prev)
|
||||
entry->lru_prev->lru_next = entry->lru_next;
|
||||
else table->lru_start = entry->lru_next;
|
||||
if(entry->lru_next)
|
||||
entry->lru_next->lru_prev = entry->lru_prev;
|
||||
else table->lru_end = entry->lru_prev;
|
||||
}
|
||||
|
||||
void
|
||||
lru_touch(struct lruhash* table, struct lruhash_entry* entry)
|
||||
{
|
||||
log_assert(table && entry);
|
||||
if(entry == table->lru_start)
|
||||
return; /* nothing to do */
|
||||
/* remove from current lru position */
|
||||
lru_remove(table, entry);
|
||||
/* add at front */
|
||||
lru_front(table, entry);
|
||||
}
|
||||
|
||||
void
|
||||
lruhash_insert(struct lruhash* table, hashvalue_t hash,
|
||||
struct lruhash_entry* entry, void* data)
|
||||
{
|
||||
struct lruhash_bin* bin;
|
||||
struct lruhash_entry* found, *reclaimlist=NULL;
|
||||
size_t need_size;
|
||||
need_size = table->sizefunc(entry->key, data);
|
||||
|
||||
/* find bin */
|
||||
lock_quick_lock(&table->lock);
|
||||
bin = &table->array[hash & table->size_mask];
|
||||
lock_quick_lock(&bin->lock);
|
||||
|
||||
/* see if entry exists already */
|
||||
if(!(found=bin_find_entry(table, bin, hash, entry->key))) {
|
||||
/* if not: add to bin */
|
||||
entry->overflow_next = bin->overflow_list;
|
||||
bin->overflow_list = entry;
|
||||
lru_front(table, entry);
|
||||
table->num++;
|
||||
table->space_used += need_size;
|
||||
} else {
|
||||
/* if so: update data - needs a writelock */
|
||||
table->space_used += need_size -
|
||||
(*table->sizefunc)(found->key, found->data);
|
||||
(*table->delkeyfunc)(entry->key, table->cb_arg);
|
||||
lru_touch(table, found);
|
||||
lock_rw_wrlock(&found->lock);
|
||||
(*table->deldatafunc)(found->data, table->cb_arg);
|
||||
found->data = data;
|
||||
lock_rw_unlock(&found->lock);
|
||||
}
|
||||
lock_quick_unlock(&bin->lock);
|
||||
if(table->space_used > table->space_max)
|
||||
reclaim_space(table, &reclaimlist);
|
||||
if(table->num >= table->size)
|
||||
table_grow(table);
|
||||
lock_quick_unlock(&table->lock);
|
||||
|
||||
/* finish reclaim if any (outside of critical region) */
|
||||
while(reclaimlist) {
|
||||
struct lruhash_entry* n = reclaimlist->overflow_next;
|
||||
void* d = reclaimlist->data;
|
||||
lock_rw_unlock(&reclaimlist->lock);
|
||||
(*table->delkeyfunc)(reclaimlist->key, table->cb_arg);
|
||||
(*table->deldatafunc)(d, table->cb_arg);
|
||||
reclaimlist = n;
|
||||
}
|
||||
}
|
||||
|
||||
struct lruhash_entry*
|
||||
lruhash_lookup(struct lruhash* table, hashvalue_t hash, void* key, int wr)
|
||||
{
|
||||
struct lruhash_entry* entry;
|
||||
struct lruhash_bin* bin;
|
||||
|
||||
lock_quick_lock(&table->lock);
|
||||
bin = &table->array[hash & table->size_mask];
|
||||
lock_quick_lock(&bin->lock);
|
||||
if((entry=bin_find_entry(table, bin, hash, key)))
|
||||
lru_touch(table, entry);
|
||||
lock_quick_unlock(&table->lock);
|
||||
|
||||
if(entry) {
|
||||
if(wr) { lock_rw_wrlock(&entry->lock); }
|
||||
else { lock_rw_rdlock(&entry->lock); }
|
||||
}
|
||||
lock_quick_unlock(&bin->lock);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void
|
||||
lruhash_remove(struct lruhash* table, hashvalue_t hash, void* key)
|
||||
{
|
||||
struct lruhash_entry* entry;
|
||||
struct lruhash_bin* bin;
|
||||
void *d;
|
||||
|
||||
lock_quick_lock(&table->lock);
|
||||
bin = &table->array[hash & table->size_mask];
|
||||
lock_quick_lock(&bin->lock);
|
||||
if((entry=bin_find_entry(table, bin, hash, key))) {
|
||||
bin_overflow_remove(bin, entry);
|
||||
lru_remove(table, entry);
|
||||
} else {
|
||||
lock_quick_unlock(&table->lock);
|
||||
lock_quick_unlock(&bin->lock);
|
||||
return;
|
||||
}
|
||||
table->num--;
|
||||
table->space_used -= (*table->sizefunc)(entry->key, entry->data);
|
||||
lock_quick_unlock(&table->lock);
|
||||
lock_rw_wrlock(&entry->lock);
|
||||
lock_quick_unlock(&bin->lock);
|
||||
/* finish removal */
|
||||
lock_rw_unlock(&entry->lock);
|
||||
d = entry->data;
|
||||
(*table->delkeyfunc)(entry->key, table->cb_arg);
|
||||
(*table->deldatafunc)(d, table->cb_arg);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
lruhash_status(struct lruhash* table, const char* id, int extended)
|
||||
{
|
||||
lock_quick_lock(&table->lock);
|
||||
log_info("%s: %u entries, memory %u / %u",
|
||||
id, (unsigned)table->num, (unsigned)table->space_used,
|
||||
(unsigned)table->space_max);
|
||||
log_info(" itemsize %u, array %u, mask %d",
|
||||
(unsigned)(table->num? table->space_used/table->num : 0),
|
||||
(unsigned)table->size, table->size_mask);
|
||||
if(extended) {
|
||||
size_t i;
|
||||
int min=(int)table->size*2, max=-2;
|
||||
for(i=0; i<table->size; i++) {
|
||||
int here = 0;
|
||||
struct lruhash_entry *en;
|
||||
lock_quick_lock(&table->array[i].lock);
|
||||
en = table->array[i].overflow_list;
|
||||
while(en) {
|
||||
here ++;
|
||||
en = en->overflow_next;
|
||||
}
|
||||
lock_quick_unlock(&table->array[i].lock);
|
||||
if(extended >= 2)
|
||||
log_info("bin[%d] %d", (int)i, here);
|
||||
if(here > max) max = here;
|
||||
if(here < min) min = here;
|
||||
}
|
||||
log_info(" bin min %d, avg %.2lf, max %d", min,
|
||||
(double)table->num/(double)table->size, max);
|
||||
}
|
||||
lock_quick_unlock(&table->lock);
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* util/storage/lruhash.h - hashtable, hash function, LRU keeping.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* This file contains a hashtable with LRU keeping of entries.
|
||||
*
|
||||
* The hash table keeps a maximum memory size. Old entries are removed
|
||||
* to make space for new entries.
|
||||
*
|
||||
* The locking strategy is as follows:
|
||||
* o since (almost) every read also implies a LRU update, the
|
||||
* hashtable lock is a spinlock, not rwlock.
|
||||
* o the idea is to move every thread through the hash lock quickly,
|
||||
* so that the next thread can access the lookup table.
|
||||
* o User performs hash function.
|
||||
*
|
||||
* For read:
|
||||
* o lock hashtable.
|
||||
* o lookup hash bin.
|
||||
* o lock hash bin.
|
||||
* o find entry (if failed, unlock hash, unl bin, exit).
|
||||
* o swizzle pointers for LRU update.
|
||||
* o unlock hashtable.
|
||||
* o lock entry (rwlock).
|
||||
* o unlock hash bin.
|
||||
* o work on entry.
|
||||
* o unlock entry.
|
||||
*
|
||||
* To update an entry, gain writelock and change the entry.
|
||||
* (the entry must keep the same hashvalue, so a data update.)
|
||||
* (you cannot upgrade a readlock to a writelock, because the item may
|
||||
* be deleted, it would cause race conditions. So instead, unlock and
|
||||
* relookup it in the hashtable.)
|
||||
*
|
||||
* To delete an entry:
|
||||
* o unlock the entry if you hold the lock already.
|
||||
* o lock hashtable.
|
||||
* o lookup hash bin.
|
||||
* o lock hash bin.
|
||||
* o find entry (if failed, unlock hash, unl bin, exit).
|
||||
* o remove entry from hashtable bin overflow chain.
|
||||
* o unlock hashtable.
|
||||
* o lock entry (writelock).
|
||||
* o unlock hash bin.
|
||||
* o unlock entry (nobody else should be waiting for this lock,
|
||||
* since you removed it from hashtable, and you got writelock while
|
||||
* holding the hashbinlock so you are the only one.)
|
||||
* Note you are only allowed to obtain a lock while holding hashbinlock.
|
||||
* o delete entry.
|
||||
*
|
||||
* The above sequence is:
|
||||
* o race free, works with read, write and delete.
|
||||
* o but has a queue, imagine someone needing a writelock on an item.
|
||||
* but there are still readlocks. The writelocker waits, but holds
|
||||
* the hashbinlock. The next thread that comes in and needs the same
|
||||
* hashbin will wait for the lock while holding the hashtable lock.
|
||||
* thus halting the entire system on hashtable.
|
||||
* This is because of the delete protection.
|
||||
* Readlocks will be easier on the rwlock on entries.
|
||||
* While the writer is holding writelock, similar problems happen with
|
||||
* a reader or writer needing the same item.
|
||||
* the scenario requires more than three threads.
|
||||
* o so the queue length is 3 threads in a bad situation. The fourth is
|
||||
* unable to use the hashtable.
|
||||
*/
|
||||
|
||||
#ifndef UTIL_STORAGE_LRUHASH_H
|
||||
#define UTIL_STORAGE_LRUHASH_H
|
||||
#include "util/locks.h"
|
||||
struct lruhash_bin;
|
||||
struct lruhash_entry;
|
||||
|
||||
/** default start size for hash arrays */
|
||||
#define HASH_DEFAULT_STARTARRAY 1024 /* entries in array */
|
||||
/** default max memory for hash arrays */
|
||||
#define HASH_DEFAULT_MAXMEM 4*1024*1024 /* bytes */
|
||||
|
||||
/** the type of a hash value */
|
||||
typedef uint32_t hashvalue_t;
|
||||
|
||||
/**
|
||||
* Type of function that calculates the size of an entry.
|
||||
* Result must include the size of struct lruhash_entry.
|
||||
* Keys that are identical must also calculate to the same size.
|
||||
* size = func(key, data).
|
||||
*/
|
||||
typedef size_t (*lruhash_sizefunc_t)(void*, void*);
|
||||
|
||||
/** type of function that compares two keys. return 0 if equal. */
|
||||
typedef int (*lruhash_compfunc_t)(void*, void*);
|
||||
|
||||
/** old keys is deleted. This function is called: func(key, userarg) */
|
||||
typedef void (*lruhash_delkeyfunc_t)(void*, void*);
|
||||
|
||||
/** old data is deleted. This function is called: func(data, userarg). */
|
||||
typedef void (*lruhash_deldatafunc_t)(void*, void*);
|
||||
|
||||
/**
|
||||
* Hash table that keeps LRU list of entries.
|
||||
*/
|
||||
struct lruhash {
|
||||
/** lock for exclusive access, to the lookup array */
|
||||
lock_quick_t lock;
|
||||
/** the size function for entries in this table */
|
||||
lruhash_sizefunc_t sizefunc;
|
||||
/** the compare function for entries in this table. */
|
||||
lruhash_compfunc_t compfunc;
|
||||
/** how to delete keys. */
|
||||
lruhash_delkeyfunc_t delkeyfunc;
|
||||
/** how to delete data. */
|
||||
lruhash_deldatafunc_t deldatafunc;
|
||||
/** user argument for user functions */
|
||||
void* cb_arg;
|
||||
|
||||
/** the size of the lookup array */
|
||||
size_t size;
|
||||
/** size bitmask - since size is a power of 2 */
|
||||
int size_mask;
|
||||
/** lookup array of bins */
|
||||
struct lruhash_bin* array;
|
||||
|
||||
/** the lru list, start and end, noncyclical double linked list. */
|
||||
struct lruhash_entry* lru_start;
|
||||
/** lru list end item (least recently used) */
|
||||
struct lruhash_entry* lru_end;
|
||||
|
||||
/** the number of entries in the hash table. */
|
||||
size_t num;
|
||||
/** the amount of space used, roughly the number of bytes in use. */
|
||||
size_t space_used;
|
||||
/** the amount of space the hash table is maximally allowed to use. */
|
||||
size_t space_max;
|
||||
};
|
||||
|
||||
/**
|
||||
* A single bin with a linked list of entries in it.
|
||||
*/
|
||||
struct lruhash_bin {
|
||||
/**
|
||||
* Lock for exclusive access to the linked list
|
||||
* This lock makes deletion of items safe in this overflow list.
|
||||
*/
|
||||
lock_quick_t lock;
|
||||
/** linked list of overflow entries */
|
||||
struct lruhash_entry* overflow_list;
|
||||
};
|
||||
|
||||
/**
|
||||
* An entry into the hash table.
|
||||
* To change overflow_next you need to hold the bin lock.
|
||||
* To change the lru items you need to hold the hashtable lock.
|
||||
* This structure is designed as part of key struct. And key pointer helps
|
||||
* to get the surrounding structure. Data should be allocated on its own.
|
||||
*/
|
||||
struct lruhash_entry {
|
||||
/**
|
||||
* rwlock for access to the contents of the entry
|
||||
* Note that it does _not_ cover the lru_ and overflow_ ptrs.
|
||||
* Even with a writelock, you cannot change hash and key.
|
||||
* You need to delete it to change hash or key.
|
||||
*/
|
||||
lock_rw_t lock;
|
||||
/** next entry in overflow chain. Covered by hashlock and binlock. */
|
||||
struct lruhash_entry* overflow_next;
|
||||
/** next entry in lru chain. covered by hashlock. */
|
||||
struct lruhash_entry* lru_next;
|
||||
/** prev entry in lru chain. covered by hashlock. */
|
||||
struct lruhash_entry* lru_prev;
|
||||
/** hash value of the key. It may not change, until entry deleted. */
|
||||
hashvalue_t hash;
|
||||
/** key */
|
||||
void* key;
|
||||
/** data */
|
||||
void* data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new hash table.
|
||||
* @param start_size: size of hashtable array at start, must be power of 2.
|
||||
* @param maxmem: maximum amount of memory this table is allowed to use.
|
||||
* @param sizefunc: calculates memory usage of entries.
|
||||
* @param compfunc: compares entries, 0 on equality.
|
||||
* @param delkeyfunc: deletes key.
|
||||
* Calling both delkey and deldata will also free the struct lruhash_entry.
|
||||
* Make it part of the key structure and delete it in delkeyfunc.
|
||||
* @param deldatafunc: deletes data.
|
||||
* @param arg: user argument that is passed to user function calls.
|
||||
* @return: new hash table or NULL on malloc failure.
|
||||
*/
|
||||
struct lruhash* lruhash_create(size_t start_size, size_t maxmem,
|
||||
lruhash_sizefunc_t sizefunc, lruhash_compfunc_t compfunc,
|
||||
lruhash_delkeyfunc_t delkeyfunc, lruhash_deldatafunc_t deldatafunc,
|
||||
void* arg);
|
||||
|
||||
/**
|
||||
* Delete hash table. Entries are all deleted.
|
||||
* @param table: to delete.
|
||||
*/
|
||||
void lruhash_delete(struct lruhash* table);
|
||||
|
||||
/**
|
||||
* Insert a new element into the hashtable.
|
||||
* If key is already present data pointer in that entry is updated.
|
||||
* The space calculation function is called with the key, data.
|
||||
* If necessary the least recently used entries are deleted to make space.
|
||||
* If necessary the hash array is grown up.
|
||||
*
|
||||
* @param table: hash table.
|
||||
* @param hash: hash value. User calculates the hash.
|
||||
* @param entry: identifies the entry.
|
||||
* If key already present, this entry->key is deleted immediately.
|
||||
* But entry->data is set to NULL before deletion, and put into
|
||||
* the existing entry. The data is then freed.
|
||||
* @param data: the data.
|
||||
*/
|
||||
void lruhash_insert(struct lruhash* table, hashvalue_t hash,
|
||||
struct lruhash_entry* entry, void* data);
|
||||
|
||||
/**
|
||||
* Lookup an entry in the hashtable.
|
||||
* At the end of the function you hold a (read/write)lock on the entry.
|
||||
* The LRU is updated for the entry (if found).
|
||||
* @param table: hash table.
|
||||
* @param hash: hash of key.
|
||||
* @param key: what to look for, compared against entries in overflow chain.
|
||||
* the hash value must be set, and must work with compare function.
|
||||
* @param wr: set to true if you desire a writelock on the entry.
|
||||
* with a writelock you can update the data part.
|
||||
* @return: pointer to the entry or NULL. The entry is locked.
|
||||
* The user must unlock the entry when done.
|
||||
*/
|
||||
struct lruhash_entry* lruhash_lookup(struct lruhash* table, hashvalue_t hash,
|
||||
void* key, int wr);
|
||||
|
||||
|
||||
/************************* Internal functions ************************/
|
||||
/*** these are only exposed for unit tests. ***/
|
||||
|
||||
/**
|
||||
* Remove entry from hashtable. Does nothing if not found in hashtable.
|
||||
* Delfunc is called for the entry.
|
||||
* @param table: hash table.
|
||||
* @param hash: hash of key.
|
||||
* @param key: what to look for.
|
||||
*/
|
||||
void lruhash_remove(struct lruhash* table, hashvalue_t hash, void* key);
|
||||
|
||||
/** init the hash bins for the table. */
|
||||
void bin_init(struct lruhash_bin* array, size_t size);
|
||||
|
||||
/** delete the hash bin and entries inside it */
|
||||
void bin_delete(struct lruhash* table, struct lruhash_bin* bin);
|
||||
|
||||
/**
|
||||
* Find entry in hash bin. You must have locked the bin.
|
||||
* @param table: hash table with function pointers.
|
||||
* @param bin: hash bin to look into.
|
||||
* @param hash: hash value to look for.
|
||||
* @param key: key to look for.
|
||||
* @return: the entry or NULL if not found.
|
||||
*/
|
||||
struct lruhash_entry* bin_find_entry(struct lruhash* table,
|
||||
struct lruhash_bin* bin, hashvalue_t hash, void* key);
|
||||
|
||||
/**
|
||||
* Remove entry from bin overflow chain.
|
||||
* You must have locked the bin.
|
||||
* @param bin: hash bin to look into.
|
||||
* @param entry: entry ptr that needs removal.
|
||||
*/
|
||||
void bin_overflow_remove(struct lruhash_bin* bin,
|
||||
struct lruhash_entry* entry);
|
||||
|
||||
/**
|
||||
* Split hash bin into two new ones. Based on increased size_mask.
|
||||
* Caller must hold hash table lock.
|
||||
* At the end the routine acquires all hashbin locks (in the old array).
|
||||
* This makes it wait for other threads to finish with the bins.
|
||||
* So the bins are ready to be deleted after this function.
|
||||
* @param table: hash table with function pointers.
|
||||
* @param newa: new increased array.
|
||||
* @param newmask: new lookup mask.
|
||||
*/
|
||||
void bin_split(struct lruhash* table, struct lruhash_bin* newa,
|
||||
int newmask);
|
||||
|
||||
/**
|
||||
* Try to make space available by deleting old entries.
|
||||
* Assumes that the lock on the hashtable is being held by caller.
|
||||
* Caller must not hold bin locks.
|
||||
* @param table: hash table.
|
||||
* @param list: list of entries that are to be deleted later.
|
||||
* Entries have been removed from the hash table and writelock is held.
|
||||
*/
|
||||
void reclaim_space(struct lruhash* table, struct lruhash_entry** list);
|
||||
|
||||
/**
|
||||
* Grow the table lookup array. Becomes twice as large.
|
||||
* Caller must hold the hash table lock. Must not hold any bin locks.
|
||||
* Tries to grow, on malloc failure, nothing happened.
|
||||
* @param table: hash table.
|
||||
*/
|
||||
void table_grow(struct lruhash* table);
|
||||
|
||||
/**
|
||||
* Touch entry, so it becomes the most recently used in the LRU list.
|
||||
* Caller must hold hash table lock. The entry must be inserted already.
|
||||
* @param table: hash table.
|
||||
* @param entry: entry to make first in LRU.
|
||||
*/
|
||||
void lru_touch(struct lruhash* table, struct lruhash_entry* entry);
|
||||
|
||||
/**
|
||||
* Put entry at front of lru. entry must be unlinked from lru.
|
||||
* Caller must hold hash table lock.
|
||||
* @param table: hash table with lru head and tail.
|
||||
* @param entry: entry to make most recently used.
|
||||
*/
|
||||
void lru_front(struct lruhash* table, struct lruhash_entry* entry);
|
||||
|
||||
/**
|
||||
* Remove entry from lru list.
|
||||
* Caller must hold hash table lock.
|
||||
* @param table: hash table with lru head and tail.
|
||||
* @param entry: entry to remove from lru.
|
||||
*/
|
||||
void lru_remove(struct lruhash* table, struct lruhash_entry* entry);
|
||||
|
||||
/**
|
||||
* Output debug info to the log as to state of the hash table.
|
||||
* @param table: hash table.
|
||||
* @param id: string printed with table to identify the hash table.
|
||||
* @param extended: set to true to print statistics on overflow bin lengths.
|
||||
*/
|
||||
void lruhash_status(struct lruhash* table, const char* id, int extended);
|
||||
|
||||
#endif /* UTIL_STORAGE_LRUHASH_H */
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* util/storage/slabhash.c - hashtable consisting of several smaller tables.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* Implementation of hash table that consists of smaller hash tables.
|
||||
* This results in a partitioned lruhash table.
|
||||
* It cannot grow, but that gives it the ability to have multiple
|
||||
* locks. Also this means there are multiple LRU lists.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "util/storage/slabhash.h"
|
||||
|
||||
struct slabhash* slabhash_create(size_t numtables, size_t start_size,
|
||||
size_t maxmem, lruhash_sizefunc_t sizefunc,
|
||||
lruhash_compfunc_t compfunc, lruhash_delkeyfunc_t delkeyfunc,
|
||||
lruhash_deldatafunc_t deldatafunc, void* arg)
|
||||
{
|
||||
size_t i;
|
||||
struct slabhash* sl = (struct slabhash*)calloc(1,
|
||||
sizeof(struct slabhash));
|
||||
if(!sl) return NULL;
|
||||
sl->size = numtables;
|
||||
log_assert(sl->size > 0);
|
||||
sl->array = (struct lruhash**)calloc(sl->size, sizeof(struct lruhash*));
|
||||
if(!sl->array) {
|
||||
free(sl);
|
||||
return NULL;
|
||||
}
|
||||
sl->mask = (uint32_t)(sl->size - 1);
|
||||
if(sl->mask == 0) {
|
||||
sl->shift = 0;
|
||||
} else {
|
||||
log_assert( (sl->size & sl->mask) == 0
|
||||
/* size must be power of 2 */ );
|
||||
sl->shift = 0;
|
||||
while(!(sl->mask & 0x80000000)) {
|
||||
sl->mask <<= 1;
|
||||
sl->shift ++;
|
||||
}
|
||||
}
|
||||
for(i=0; i<sl->size; i++) {
|
||||
sl->array[i] = lruhash_create(start_size, maxmem / sl->size,
|
||||
sizefunc, compfunc, delkeyfunc, deldatafunc, arg);
|
||||
if(!sl->array[i]) {
|
||||
slabhash_delete(sl);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return sl;
|
||||
}
|
||||
|
||||
void slabhash_delete(struct slabhash* sl)
|
||||
{
|
||||
if(!sl)
|
||||
return;
|
||||
if(sl->array) {
|
||||
size_t i;
|
||||
for(i=0; i<sl->size; i++)
|
||||
lruhash_delete(sl->array[i]);
|
||||
free(sl->array);
|
||||
}
|
||||
free(sl);
|
||||
}
|
||||
|
||||
/** helper routine to calculate the slabhash index */
|
||||
static unsigned int
|
||||
slab_idx(struct slabhash* sl, hashvalue_t hash)
|
||||
{
|
||||
return ((hash & sl->mask) >> sl->shift);
|
||||
}
|
||||
|
||||
void slabhash_insert(struct slabhash* sl, hashvalue_t hash,
|
||||
struct lruhash_entry* entry, void* data)
|
||||
{
|
||||
lruhash_insert(sl->array[slab_idx(sl, hash)], hash, entry, data);
|
||||
}
|
||||
|
||||
struct lruhash_entry* slabhash_lookup(struct slabhash* sl,
|
||||
hashvalue_t hash, void* key, int wr)
|
||||
{
|
||||
return lruhash_lookup(sl->array[slab_idx(sl, hash)], hash, key, wr);
|
||||
}
|
||||
|
||||
void slabhash_remove(struct slabhash* sl, hashvalue_t hash, void* key)
|
||||
{
|
||||
lruhash_remove(sl->array[slab_idx(sl, hash)], hash, key);
|
||||
}
|
||||
|
||||
void slabhash_status(struct slabhash* sl, const char* id, int extended)
|
||||
{
|
||||
size_t i;
|
||||
char num[17];
|
||||
log_info("Slabhash %s: %u tables mask=%x shift=%d",
|
||||
id, (unsigned)sl->size, sl->mask, sl->shift);
|
||||
for(i=0; i<sl->size; i++) {
|
||||
snprintf(num, sizeof(num), "table %u", (unsigned)i);
|
||||
lruhash_status(sl->array[i], num, extended);
|
||||
}
|
||||
}
|
||||
|
||||
size_t slabhash_get_size(struct slabhash* sl)
|
||||
{
|
||||
size_t i, total = 0;
|
||||
for(i=0; i<sl->size; i++) {
|
||||
lock_quick_lock(&sl->array[i]->lock);
|
||||
total += sl->array[i]->space_max;
|
||||
lock_quick_unlock(&sl->array[i]->lock);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* util/storage/slabhash.h - hashtable consisting of several smaller tables.
|
||||
*
|
||||
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||||
*
|
||||
* This software is open source.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* Hash table that consists of smaller hash tables.
|
||||
* It cannot grow, but that gives it the ability to have multiple
|
||||
* locks. Also this means there are multiple LRU lists.
|
||||
*/
|
||||
|
||||
#ifndef UTIL_STORAGE_SLABHASH_H
|
||||
#define UTIL_STORAGE_SLABHASH_H
|
||||
#include "util/storage/lruhash.h"
|
||||
|
||||
/** default number of slabs */
|
||||
#define HASH_DEFAULT_SLABS 4
|
||||
|
||||
/**
|
||||
* Hash table formed from several smaller ones.
|
||||
* This results in a partitioned lruhash table.
|
||||
* None of the data inside the slabhash may be altered.
|
||||
* Therefore, no locks are needed to access the structure.
|
||||
*/
|
||||
struct slabhash {
|
||||
/** the size of the array - must be power of 2 */
|
||||
size_t size;
|
||||
/** size bitmask - uses high bits. */
|
||||
uint32_t mask;
|
||||
/** shift right this many bits to get index into array. */
|
||||
unsigned int shift;
|
||||
/** lookup array of hash tables */
|
||||
struct lruhash** array;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new slabbed hash table.
|
||||
* @param numtables: number of hash tables to use, other parameters used to
|
||||
* initialize these smaller hashtables.
|
||||
* @param start_size: size of hashtable array at start, must be power of 2.
|
||||
* @param maxmem: maximum amount of memory this table is allowed to use.
|
||||
* so every table gets maxmem/numtables to use for itself.
|
||||
* @param sizefunc: calculates memory usage of entries.
|
||||
* @param compfunc: compares entries, 0 on equality.
|
||||
* @param delkeyfunc: deletes key.
|
||||
* @param deldatafunc: deletes data.
|
||||
* @param arg: user argument that is passed to user function calls.
|
||||
* @return: new hash table or NULL on malloc failure.
|
||||
*/
|
||||
struct slabhash* slabhash_create(size_t numtables, size_t start_size,
|
||||
size_t maxmem, lruhash_sizefunc_t sizefunc,
|
||||
lruhash_compfunc_t compfunc, lruhash_delkeyfunc_t delkeyfunc,
|
||||
lruhash_deldatafunc_t deldatafunc, void* arg);
|
||||
|
||||
/**
|
||||
* Delete hash table. Entries are all deleted.
|
||||
* @param table: to delete.
|
||||
*/
|
||||
void slabhash_delete(struct slabhash* table);
|
||||
|
||||
/**
|
||||
* Insert a new element into the hashtable, uses lruhash_insert.
|
||||
* If key is already present data pointer in that entry is updated.
|
||||
*
|
||||
* @param table: hash table.
|
||||
* @param hash: hash value. User calculates the hash.
|
||||
* @param entry: identifies the entry.
|
||||
* If key already present, this entry->key is deleted immediately.
|
||||
* But entry->data is set to NULL before deletion, and put into
|
||||
* the existing entry. The data is then freed.
|
||||
* @param data: the data.
|
||||
*/
|
||||
void slabhash_insert(struct slabhash* table, hashvalue_t hash,
|
||||
struct lruhash_entry* entry, void* data);
|
||||
|
||||
/**
|
||||
* Lookup an entry in the hashtable. Uses lruhash_lookup.
|
||||
* At the end of the function you hold a (read/write)lock on the entry.
|
||||
* The LRU is updated for the entry (if found).
|
||||
* @param table: hash table.
|
||||
* @param hash: hash of key.
|
||||
* @param key: what to look for, compared against entries in overflow chain.
|
||||
* the hash value must be set, and must work with compare function.
|
||||
* @param wr: set to true if you desire a writelock on the entry.
|
||||
* with a writelock you can update the data part.
|
||||
* @return: pointer to the entry or NULL. The entry is locked.
|
||||
* The user must unlock the entry when done.
|
||||
*/
|
||||
struct lruhash_entry* slabhash_lookup(struct slabhash* table,
|
||||
hashvalue_t hash, void* key, int wr);
|
||||
|
||||
/**
|
||||
* Remove entry from hashtable. Does nothing if not found in hashtable.
|
||||
* Delfunc is called for the entry. Uses lruhash_remove.
|
||||
* @param table: hash table.
|
||||
* @param hash: hash of key.
|
||||
* @param key: what to look for.
|
||||
*/
|
||||
void slabhash_remove(struct slabhash* table, hashvalue_t hash, void* key);
|
||||
|
||||
/**
|
||||
* Output debug info to the log as to state of the hash table.
|
||||
* @param table: hash table.
|
||||
* @param id: string printed with table to identify the hash table.
|
||||
* @param extended: set to true to print statistics on overflow bin lengths.
|
||||
*/
|
||||
void slabhash_status(struct slabhash* table, const char* id, int extended);
|
||||
|
||||
/**
|
||||
* Retrieve slab hash total size.
|
||||
* @param table: hash table.
|
||||
*/
|
||||
size_t slabhash_get_size(struct slabhash* table);
|
||||
|
||||
#endif /* UTIL_STORAGE_SLABHASH_H */
|
||||
Reference in New Issue
Block a user