Index: src/Makefile
--- src/Makefile.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/Makefile	2017-10-31 00:49:08.314668000 +0100
@@ -2,8 +2,11 @@
 CC=gcc
 PROFILE?=
 OPTIMIZATION?=-O2
-CFLAGS=-std=c99 -Wall $(OPTIMIZATION) $(PROFILE) -g -ggdb -D_GNU_SOURCE -I../deps/jemalloc/include
-LDFLAGS=-pthread -rdynamic $(PROFILE)
+CFLAGS=-std=c99 -Wall $(OPTIMIZATION) $(PROFILE) -D_GNU_SOURCE -I../deps/jemalloc/include
+LDFLAGS=-pthread $(PROFILE)
+OS!=uname -s
+DEPS!=test "$(OS)" = FreeBSD && echo "epoll-shim/libepoll.a"
+LIBS!=test "$(OS)" = FreeBSD && echo "epoll-shim/libepoll.a -lrt"
 
 JEMALLOC=../deps/jemalloc/lib/libjemalloc.a
 BIN:=$(NAME)
@@ -13,7 +16,8 @@
 %.o: %.c $(JEMALLOC)
 	$(CC) $(CFLAGS) -c $<
 
-$(BIN): $(OBJ) $(JEMALLOC)
+$(BIN): $(OBJ) $(JEMALLOC) $(DEPS)
+	$(CC) $(LDFLAGS) -o $(BIN) $(OBJ) $(JEMALLOC) $(LIBS)
 
 $(JEMALLOC):
 	$(MAKE) jemalloc -C ../deps
@@ -29,3 +33,7 @@
 
 distclean:
 	$(MAKE) distclean -C ../deps
+
+epoll-shim/libepoll.a:
+	cd epoll-shim && $(MAKE) $(MFLAGS) libepoll.a
+
Index: src/config.c
--- src/config.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/config.c	2017-10-31 00:49:08.314890000 +0100
@@ -6,7 +6,11 @@
 #include <stdlib.h>
 #include <libgen.h>
 #include <inttypes.h>
+#if defined(__linux__)
 #include <linux/limits.h>
+#else
+#include <sys/limits.h>
+#endif
 #include "corvus.h"
 #include "alloc.h"
 #include "logging.h"
@@ -45,7 +49,8 @@
     memset(config.cluster, 0, CLUSTER_NAME_SIZE + 1);
     strncpy(config.cluster, "default", CLUSTER_NAME_SIZE);
 
-    config.bind = 12345;
+    strncpy(config.bind.ip, "0.0.0.0", sizeof(config.bind.ip));
+    config.bind.port = 12345;
     config.node = cv_calloc(1, sizeof(struct node_conf));
     config.node->refcount = 1;
     config.thread = DEFAULT_THREAD;
@@ -173,7 +178,7 @@
         if (strlen(value) <= 0) return CORVUS_OK;
         strncpy(config.cluster, value, CLUSTER_NAME_SIZE);
     } else if (strcmp(name, "bind") == 0) {
-        if (socket_parse_port(value, &config.bind) == CORVUS_ERR) {
+        if (socket_parse_ip(value, &config.bind) == CORVUS_ERR) {
             return CORVUS_ERR;
         }
     } else if (strcmp(name, "syslog") == 0) {
@@ -294,7 +299,7 @@
     if (strcmp(name, "cluster") == 0) {
         strncpy(value, config.cluster, max_len);
     } else if (strcmp(name, "bind") == 0) {
-        snprintf(value, max_len, "%u", config.bind);
+        snprintf(value, max_len, "%s:%u", config.bind.ip, config.bind.port);
     } else if (strcmp(name, "node") == 0) {
         config_node_to_str(value, max_len);
     } else if (strcmp(name, "thread") == 0) {
Index: src/config.h
--- src/config.h.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/config.h	2017-10-31 00:49:08.315004000 +0100
@@ -15,7 +15,7 @@
 struct corvus_config {
     char config_file_path[CONFIG_FILE_PATH_SIZE + 1];
     char cluster[CLUSTER_NAME_SIZE + 1];
-    uint16_t bind;
+    struct address bind;
     struct node_conf *node;
     int thread;
     int loglevel;
@@ -47,4 +47,4 @@
 int config_add(char *name, char *value);
 bool config_option_changable(const char *option);
 
-#endif /* end of include guard: CONFIG_H */
\ No newline at end of file
+#endif /* end of include guard: CONFIG_H */
Index: src/corvus.c
--- src/corvus.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/corvus.c	2017-10-31 00:49:08.315175000 +0100
@@ -30,9 +30,11 @@
     }
 
     struct sigaction act;
+#if defined(__linux__)
     void *trace[100];
     int size = backtrace(trace, 100);
     backtrace_symbols_fd(trace, size, STDOUT_FILENO);
+#endif
 
     // restore default signal handlers
     sigemptyset(&act.sa_mask);
@@ -276,7 +278,7 @@
         exit(EXIT_FAILURE);
     }
 
-    if (proxy_init(&ctx->proxy, ctx, "0.0.0.0", config.bind) == -1) {
+    if (proxy_init(&ctx->proxy, ctx, config.bind.ip, config.bind.port) == -1) {
         LOG(ERROR, "Fatal: fail to create proxy.");
         exit(EXIT_FAILURE);
     }
@@ -438,9 +440,11 @@
      * https://www.scribd.com/doc/3726406/Crash-N-Burn-Writing-Linux-application-fault-handlers
      * https://github.com/gby/libcrash/blob/master/crash.c#L279
      */
+#if defined(__linux__)
     void *dummy_trace[1];
     backtrace(dummy_trace, 1);
     backtrace_symbols_fd(dummy_trace, 0, -1);
+#endif
     if ((err = pthread_spin_init(&signal_lock, 0)) != 0) {
         LOG(ERROR, "Fail to init spin lock: %s", strerror(err));
         return EXIT_FAILURE;
@@ -503,7 +507,7 @@
         stats_init();
     }
 
-    LOG(INFO, "serve at 0.0.0.0:%d", config.bind);
+    LOG(INFO, "serve at %s:%d", config.bind.ip, config.bind.port);
 
     for (i = 0; i < config.thread; i++) {
         if ((err = pthread_join(contexts[i].thread, NULL)) != 0) {
Index: src/epoll-shim/Makefile
--- src/epoll-shim/Makefile.orig	2017-10-31 00:49:08.315315000 +0100
+++ src/epoll-shim/Makefile	2017-10-31 00:49:08.315300000 +0100
@@ -0,0 +1,18 @@
+
+CC = cc
+AR = ar
+RANLIB = ranlib
+
+all: libepoll.a
+
+.c.o:
+	$(CC) -Iinclude -o $@ -c $<
+
+libepoll.a: src/epoll.o src/timerfd.o src/signalfd.o src/common.o
+	$(AR) rc libepoll.a src/epoll.o src/timerfd.o src/signalfd.o src/common.o
+	$(RANLIB) libepoll.a
+
+clean:
+	-rm -f libepoll.a
+	-rm -f src/epoll.o src/timerfd.o src/signalfd.o src/common.o
+
Index: src/epoll-shim/README
--- src/epoll-shim/README.orig	2017-10-31 00:49:08.315402000 +0100
+++ src/epoll-shim/README	2017-10-31 00:49:08.315389000 +0100
@@ -0,0 +1 @@
+https://github.com/jiixyj/epoll-shim
Index: src/epoll-shim/include/sys/epoll.h
--- src/epoll-shim/include/sys/epoll.h.orig	2017-10-31 00:49:08.315542000 +0100
+++ src/epoll-shim/include/sys/epoll.h	2017-10-31 00:49:08.315528000 +0100
@@ -0,0 +1,70 @@
+#ifndef	SHIM_SYS_EPOLL_H
+#define	SHIM_SYS_EPOLL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#if 0
+#define __NEED_sigset_t
+
+#include <bits/alltypes.h>
+#endif
+
+#define EPOLL_CLOEXEC O_CLOEXEC
+#define EPOLL_NONBLOCK O_NONBLOCK
+
+enum EPOLL_EVENTS { __EPOLL_DUMMY };
+#define EPOLLIN 0x001
+#define EPOLLPRI 0x002
+#define EPOLLOUT 0x004
+#define EPOLLRDNORM 0x040
+#define EPOLLRDBAND 0x080
+#define EPOLLWRNORM 0x100
+#define EPOLLWRBAND 0x200
+#define EPOLLMSG 0x400
+#define EPOLLERR 0x008
+#define EPOLLHUP 0x010
+#define EPOLLRDHUP 0x2000
+#define EPOLLWAKEUP (1U<<29)
+#define EPOLLONESHOT (1U<<30)
+#define EPOLLET (1U<<31)
+
+#define EPOLL_CTL_ADD 1
+#define EPOLL_CTL_DEL 2
+#define EPOLL_CTL_MOD 3
+
+typedef union epoll_data {
+	void *ptr;
+	int fd;
+	uint32_t u32;
+	uint64_t u64;
+} epoll_data_t;
+
+struct epoll_event {
+	uint32_t events;
+	epoll_data_t data;
+}
+#if defined(__amd64__)
+__attribute__((packed))
+#endif
+;
+
+int epoll_create(int);
+int epoll_create1(int);
+int epoll_ctl(int, int, int, struct epoll_event *);
+int epoll_wait(int, struct epoll_event *, int, int);
+#if 0
+int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* sys/epoll.h */
Index: src/epoll-shim/include/sys/signalfd.h
--- src/epoll-shim/include/sys/signalfd.h.orig	2017-10-31 00:49:08.315644000 +0100
+++ src/epoll-shim/include/sys/signalfd.h	2017-10-31 00:49:08.315631000 +0100
@@ -0,0 +1,55 @@
+#ifndef SHIM_SYS_SIGNALFD_H
+#define SHIM_SYS_SIGNALFD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+#include <stdint.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#if 0
+#define __NEED_sigset_t
+
+#include <bits/alltypes.h>
+#endif
+
+#define SFD_CLOEXEC O_CLOEXEC
+#define SFD_NONBLOCK O_NONBLOCK
+
+int signalfd(int, const sigset_t *, int);
+
+struct signalfd_siginfo {
+	uint32_t  ssi_signo;
+	int32_t   ssi_errno;
+	int32_t   ssi_code;
+	uint32_t  ssi_pid;
+	uint32_t  ssi_uid;
+	int32_t   ssi_fd;
+	uint32_t  ssi_tid;
+	uint32_t  ssi_band;
+	uint32_t  ssi_overrun;
+	uint32_t  ssi_trapno;
+	int32_t   ssi_status;
+	int32_t   ssi_int;
+	uint64_t  ssi_ptr;
+	uint64_t  ssi_utime;
+	uint64_t  ssi_stime;
+	uint64_t  ssi_addr;
+	uint16_t  ssi_addr_lsb;
+	uint8_t   pad[128-12*4-4*8-2];
+};
+
+extern int epoll_shim_close(int fd);
+extern ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes);
+#define read epoll_shim_read
+#define close epoll_shim_close
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: src/epoll-shim/include/sys/timerfd.h
--- src/epoll-shim/include/sys/timerfd.h.orig	2017-10-31 00:49:08.315738000 +0100
+++ src/epoll-shim/include/sys/timerfd.h	2017-10-31 00:49:08.315725000 +0100
@@ -0,0 +1,34 @@
+#ifndef SHIM_SYS_TIMERFD_H
+#define SHIM_SYS_TIMERFD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define TFD_NONBLOCK O_NONBLOCK
+#define TFD_CLOEXEC O_CLOEXEC
+
+#define TFD_TIMER_ABSTIME 1
+
+struct itimerspec;
+
+int timerfd_create(int, int);
+int timerfd_settime(int, int, const struct itimerspec *, struct itimerspec *);
+#if 0
+int timerfd_gettime(int, struct itimerspec *);
+#endif
+
+extern int epoll_shim_close(int fd);
+extern ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes);
+#define read epoll_shim_read
+#define close epoll_shim_close
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: src/epoll-shim/src/common.c
--- src/epoll-shim/src/common.c.orig	2017-10-31 00:49:08.315856000 +0100
+++ src/epoll-shim/src/common.c	2017-10-31 00:49:08.315843000 +0100
@@ -0,0 +1,50 @@
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+extern pthread_mutex_t timerfd_context_mtx;
+extern struct timerfd_context *get_timerfd_context(int fd, bool create_new);
+extern ssize_t timerfd_read(
+    struct timerfd_context *, void *buf, size_t nbytes);
+extern int timerfd_close(struct timerfd_context *);
+
+extern pthread_mutex_t signalfd_context_mtx;
+extern struct signalfd_context *get_signalfd_context(int fd, bool create_new);
+extern ssize_t signalfd_read(
+    struct signalfd_context *, void *buf, size_t nbytes);
+extern int signalfd_close(struct signalfd_context *);
+
+#define WRAP(context, return_type, call, unlock_after_call)                   \
+	if (fd >= 0) {                                                        \
+		pthread_mutex_lock(&context##_mtx);                           \
+		struct context *ctx = get_##context(fd, false);               \
+		if (ctx) {                                                    \
+			return_type ret = (call);                             \
+			if (unlock_after_call) {                              \
+				pthread_mutex_unlock(&context##_mtx);         \
+			}                                                     \
+			return ret;                                           \
+		}                                                             \
+		pthread_mutex_unlock(&context##_mtx);                         \
+	}
+
+int
+epoll_shim_close(int fd)
+{
+	WRAP(timerfd_context, int, timerfd_close(ctx), true)
+	WRAP(signalfd_context, int, signalfd_close(ctx), true)
+
+	return close(fd);
+}
+
+ssize_t
+epoll_shim_read(int fd, void *buf, size_t nbytes)
+{
+	WRAP(timerfd_context, ssize_t, timerfd_read(ctx, buf, nbytes), false)
+	WRAP(signalfd_context, ssize_t, signalfd_read(ctx, buf, nbytes), false)
+
+	return read(fd, buf, nbytes);
+}
Index: src/epoll-shim/src/epoll.c
--- src/epoll-shim/src/epoll.c.orig	2017-10-31 00:49:08.315986000 +0100
+++ src/epoll-shim/src/epoll.c	2017-10-31 00:49:08.315974000 +0100
@@ -0,0 +1,284 @@
+#include <sys/epoll.h>
+
+#include <sys/types.h>
+
+#include <sys/event.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+epoll_create(int size __unused)
+{
+	fprintf(stderr,
+	    "ERROR: epoll_create() is deprecated, use "
+	    "epoll_create1(EPOLL_CLOEXEC).\n");
+	errno = EINVAL;
+	return -1;
+}
+
+int
+epoll_create1(int flags)
+{
+	if (flags != EPOLL_CLOEXEC) {
+		fprintf(stderr, "ERROR: Use epoll_create1(EPOLL_CLOEXEC).\n");
+		errno = EINVAL;
+		return -1;
+	}
+	return kqueue();
+}
+
+static int poll_fd = -1;
+static int poll_epoll_fd = -1;
+static void *poll_ptr;
+
+int
+epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev)
+{
+	if ((!ev && op != EPOLL_CTL_DEL) ||
+	    (ev &&
+		((ev->events &
+		    ~(EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP | EPOLLERR))
+		    /* the user should really set one of EPOLLIN or EPOLLOUT
+		     * so that EPOLLHUP and EPOLLERR work. Don't make this a
+		     * hard error for now, though. */
+		    /* || !(ev->events & (EPOLLIN | EPOLLOUT)) */))) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	struct kevent kev[4];
+
+	if (op == EPOLL_CTL_ADD) {
+		/* Check if the fd already has been registered in this kqueue.
+		 * See below for an explanation of this 'cookie' mechanism. */
+		EV_SET(&kev[0], fd2 * 2, EVFILT_USER, 0, 0, 0, 0);
+		if (!(kevent(fd, kev, 1, NULL, 0, NULL) < 0 &&
+			errno == ENOENT)) {
+			errno = EEXIST;
+			return -1;
+		}
+
+		EV_SET(&kev[0], fd2, EVFILT_READ,
+		    EV_ADD | (ev->events & EPOLLIN ? 0 : EV_DISABLE), 0, 0,
+		    ev->data.ptr);
+		EV_SET(&kev[1], fd2, EVFILT_WRITE,
+		    EV_ADD | (ev->events & EPOLLOUT ? 0 : EV_DISABLE), 0, 0,
+		    ev->data.ptr);
+		/* We save a 'cookie' knote inside the kq to signal if the fd
+		 * has been 'registered'. We need this because there is no way
+		 * to ask a kqueue if a knote has been registered without
+		 * modifying the udata. */
+		EV_SET(&kev[2], fd2 * 2, EVFILT_USER, EV_ADD, 0, 0, 0);
+		EV_SET(&kev[3], fd2 * 2 + 1, EVFILT_USER,
+		    (ev->events & EPOLLRDHUP) ? EV_ADD : 0, 0, 0, 0);
+	} else if (op == EPOLL_CTL_DEL) {
+		if (poll_fd == fd2 && fd == poll_epoll_fd) {
+			poll_fd = -1;
+			poll_epoll_fd = -1;
+			poll_ptr = NULL;
+			return 0;
+		}
+
+		EV_SET(&kev[0], fd2, EVFILT_READ, EV_DELETE, 0, 0, 0);
+		EV_SET(&kev[1], fd2, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
+		EV_SET(&kev[2], fd2 * 2, EVFILT_USER, EV_DELETE, 0, 0, 0);
+		EV_SET(&kev[3], fd2 * 2 + 1, EVFILT_USER, EV_DELETE, 0, 0, 0);
+	} else if (op == EPOLL_CTL_MOD) {
+		EV_SET(&kev[0], fd2, EVFILT_READ,
+		    ev->events & EPOLLIN ? EV_ENABLE : EV_DISABLE, 0, 0,
+		    ev->data.ptr);
+		EV_SET(&kev[1], fd2, EVFILT_WRITE,
+		    ev->events & EPOLLOUT ? EV_ENABLE : EV_DISABLE, 0, 0,
+		    ev->data.ptr);
+		EV_SET(&kev[2], fd2 * 2, EVFILT_USER, 0, 0, 0, 0);
+		EV_SET(&kev[3], fd2 * 2 + 1, EVFILT_USER,
+		    (ev->events & EPOLLRDHUP) ? EV_ADD : EV_DELETE, 0, 0, 0);
+	} else {
+		errno = EINVAL;
+		return -1;
+	}
+
+	for (int i = 0; i < 4; ++i) {
+		kev[i].flags |= EV_RECEIPT;
+	}
+
+	int ret = kevent(fd, kev, 4, kev, 4, NULL);
+	if (ret < 0) {
+		return -1;
+	}
+
+	if (ret != 4) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	for (int i = 0; i < 4; ++i) {
+		if (kev[i].flags != EV_ERROR) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		if (kev[i].data == ENODEV && poll_fd < 0) {
+			poll_fd = fd2;
+			poll_epoll_fd = fd;
+			poll_ptr = ev->data.ptr;
+			return 0;
+		}
+
+		/* ignore EVFILT_WRITE registration EINVAL errors (some fd
+		 * types such as kqueues themselves don't support it) */
+		if (i == 1 && kev[i].data == EINVAL) {
+			continue;
+		}
+
+		/* ignore EVFILT_USER registration ENOENT errors (used for
+		 * EPOLLRDHUP cookie fd and may therefore not exist) */
+		if (i == 3 && kev[i].data == ENOENT) {
+			continue;
+		}
+
+		if (kev[i].data != 0) {
+			errno = kev[i].data;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+#if 0
+int
+epoll_pwait(
+    int fd, struct epoll_event *ev, int cnt, int to, const sigset_t *sigs)
+{
+	int r = __syscall(SYS_epoll_pwait, fd, ev, cnt, to, sigs, _NSIG / 8);
+#ifdef SYS_epoll_wait
+	if (r == -ENOSYS && !sigs)
+		r = __syscall(SYS_epoll_wait, fd, ev, cnt, to);
+#endif
+	return __syscall_ret(r);
+}
+#endif
+
+int
+epoll_wait(int fd, struct epoll_event *ev, int cnt, int to)
+{
+	if (cnt < 1 || cnt > 32) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (poll_fd != -1 && fd == poll_epoll_fd) {
+		struct pollfd pfds[2];
+		pfds[0].fd = poll_fd;
+		pfds[0].events = POLLIN;
+		pfds[1].fd = fd;
+		pfds[1].events = POLLIN;
+		int ret = poll(pfds, 2, to);
+		if (ret <= 0) {
+			return ret;
+		}
+		if (pfds[0].revents & POLLIN) {
+			ev[0].events = EPOLLIN;
+			ev[0].data.ptr = poll_ptr;
+			return 1;
+		}
+		to = 0;
+	}
+
+	struct kevent evlist[32];
+
+	struct timespec timeout = {0, 0};
+	if (to > 0) {
+		timeout.tv_sec = to / 1000;
+		timeout.tv_nsec = (to % 1000) * 1000 * 1000;
+	}
+
+	struct timespec *ptimeout = NULL;
+	if (to >= 0) {
+		ptimeout = &timeout;
+	}
+
+	int ret = kevent(fd, NULL, 0, evlist, cnt, ptimeout);
+	if (ret < 0) {
+		return -1;
+	}
+
+	for (int i = 0; i < ret; ++i) {
+		int events = 0;
+		if (evlist[i].filter == EVFILT_READ) {
+			events |= EPOLLIN;
+		} else if (evlist[i].filter == EVFILT_WRITE) {
+			events |= EPOLLOUT;
+		}
+
+		if (evlist[i].flags & EV_ERROR) {
+			events |= EPOLLERR;
+		}
+
+		if (evlist[i].flags & EV_EOF) {
+			// fprintf(stderr, "got fflags: %d\n",
+			// evlist[i].fflags);
+			if (evlist[i].fflags) {
+				events |= EPOLLERR;
+			}
+
+			int epoll_event = EPOLLHUP;
+
+			struct stat statbuf;
+			if (fstat(evlist[i].ident, &statbuf) < 0) {
+				return -1;
+			}
+
+			if (S_ISFIFO(statbuf.st_mode)) {
+				if (evlist[i].filter == EVFILT_READ &&
+				    evlist[i].data == 0) {
+					events &= ~EPOLLIN;
+				} else if (evlist[i].filter == EVFILT_WRITE) {
+					epoll_event = EPOLLERR;
+				}
+			} else if (S_ISSOCK(statbuf.st_mode) &&
+			    evlist[i].filter == EVFILT_READ) {
+				/* do some special EPOLLRDHUP handling for
+				 * sockets */
+				struct kevent kev[1];
+				/* if we are reading, we just know for
+				 * sure that we can't receive any more,
+				 * so use EPOLLIN/EPOLLRDHUP per
+				 * default */
+				epoll_event = EPOLLIN;
+				EV_SET(&kev[0], evlist[i].ident * 2 + 1,
+				    EVFILT_USER, 0, 0, 0, 0);
+				int old_errno = errno;
+				if (!(kevent(fd, kev, 1, NULL, 0, NULL) ==
+					    -1 &&
+					errno == ENOENT)) {
+					epoll_event |= EPOLLRDHUP;
+				}
+				errno = old_errno;
+
+				/* only set EPOLLHUP if the stat says
+				 * that writing is also impossible */
+				if (!(statbuf.st_mode &
+					(S_IWUSR | S_IWGRP | S_IWOTH))) {
+					epoll_event |= EPOLLHUP;
+				}
+			}
+
+			events |= epoll_event;
+		}
+		ev[i].events = events;
+		ev[i].data.ptr = evlist[i].udata;
+	}
+	return ret;
+}
Index: src/epoll-shim/src/signalfd.c
--- src/epoll-shim/src/signalfd.c.orig	2017-10-31 00:49:08.316086000 +0100
+++ src/epoll-shim/src/signalfd.c	2017-10-31 00:49:08.316074000 +0100
@@ -0,0 +1,143 @@
+#include <sys/signalfd.h>
+#undef read
+#undef close
+
+#include <sys/types.h>
+
+#include <sys/event.h>
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct signalfd_context {
+	int fd;
+	int flags;
+	struct signalfd_context *next;
+};
+
+static struct signalfd_context *signalfd_contexts;
+pthread_mutex_t signalfd_context_mtx = PTHREAD_MUTEX_INITIALIZER;
+
+struct signalfd_context *
+get_signalfd_context(int fd, bool create_new)
+{
+	for (struct signalfd_context *ctx = signalfd_contexts; ctx;
+	     ctx = ctx->next) {
+		if (fd == ctx->fd) {
+			return ctx;
+		}
+	}
+
+	if (create_new) {
+		struct signalfd_context *new_ctx =
+		    calloc(1, sizeof(struct signalfd_context));
+		if (!new_ctx) {
+			return NULL;
+		}
+		new_ctx->fd = -1;
+		new_ctx->next = signalfd_contexts;
+		signalfd_contexts = new_ctx;
+		return new_ctx;
+	}
+
+	return NULL;
+}
+
+static int
+signalfd_impl(int fd, const sigset_t *sigs, int flags)
+{
+	if (fd != -1) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (flags & ~(SFD_NONBLOCK | SFD_CLOEXEC)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	struct signalfd_context *ctx = get_signalfd_context(-1, true);
+	if (!ctx) {
+		errno = EMFILE;
+		return -1;
+	}
+
+	ctx->fd = kqueue();
+	if (ctx->fd == -1) {
+		return -1;
+	}
+
+	ctx->flags = flags;
+
+	struct kevent kevs[_SIG_MAXSIG];
+	int n = 0;
+
+	for (int i = 1; i <= _SIG_MAXSIG; ++i) {
+		if (sigismember(sigs, i)) {
+			EV_SET(&kevs[n++], i, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
+		}
+	}
+
+	int ret = kevent(ctx->fd, kevs, n, NULL, 0, NULL);
+	if (ret == -1) {
+		close(ctx->fd);
+		ctx->fd = -1;
+		return -1;
+	}
+
+	return ctx->fd;
+}
+
+int
+signalfd(int fd, const sigset_t *sigs, int flags)
+{
+	pthread_mutex_lock(&signalfd_context_mtx);
+	int ret = signalfd_impl(fd, sigs, flags);
+	pthread_mutex_unlock(&signalfd_context_mtx);
+	return ret;
+}
+
+ssize_t
+signalfd_read(struct signalfd_context *ctx, void *buf, size_t nbytes)
+{
+	int fd = ctx->fd;
+	int flags = ctx->flags;
+	pthread_mutex_unlock(&signalfd_context_mtx);
+
+	// TODO: fix this to read multiple signals
+	if (nbytes != sizeof(struct signalfd_siginfo)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	struct timespec timeout = {0, 0};
+	struct kevent kev;
+	int ret = kevent(
+	    fd, NULL, 0, &kev, 1, (flags & SFD_NONBLOCK) ? &timeout : NULL);
+	if (ret == -1) {
+		return -1;
+	} else if (ret == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	memset(buf, '\0', nbytes);
+	struct signalfd_siginfo *sig_buf = buf;
+	sig_buf->ssi_signo = (uint32_t)kev.ident;
+	return (ssize_t)nbytes;
+}
+
+int
+signalfd_close(struct signalfd_context *ctx)
+{
+	int ret = close(ctx->fd);
+	ctx->fd = -1;
+	return ret;
+}
Index: src/epoll-shim/src/timerfd.c
--- src/epoll-shim/src/timerfd.c.orig	2017-10-31 00:49:08.316198000 +0100
+++ src/epoll-shim/src/timerfd.c	2017-10-31 00:49:08.316185000 +0100
@@ -0,0 +1,232 @@
+#include <sys/timerfd.h>
+#undef read
+#undef close
+
+#include <sys/types.h>
+
+#include <sys/event.h>
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <pthread.h>
+#include <pthread_np.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct timerfd_context {
+	int fd;
+	pthread_t worker;
+	timer_t timer;
+	int flags;
+	struct timerfd_context *next;
+};
+
+static struct timerfd_context *timerfd_contexts;
+pthread_mutex_t timerfd_context_mtx = PTHREAD_MUTEX_INITIALIZER;
+
+struct timerfd_context *
+get_timerfd_context(int fd, bool create_new)
+{
+	for (struct timerfd_context *ctx = timerfd_contexts; ctx;
+	     ctx = ctx->next) {
+		if (fd == ctx->fd) {
+			return ctx;
+		}
+	}
+
+	if (create_new) {
+		struct timerfd_context *new_ctx =
+		    calloc(1, sizeof(struct timerfd_context));
+		if (!new_ctx) {
+			return NULL;
+		}
+		new_ctx->fd = -1;
+		new_ctx->next = timerfd_contexts;
+		timerfd_contexts = new_ctx;
+		return new_ctx;
+	}
+
+	return NULL;
+}
+
+static void *
+worker_function(void *arg)
+{
+	struct timerfd_context *ctx = arg;
+
+	siginfo_t info;
+	sigset_t set;
+	sigemptyset(&set);
+	sigaddset(&set, SIGRTMIN);
+	sigaddset(&set, SIGRTMIN + 1);
+	(void)pthread_sigmask(SIG_BLOCK, &set, NULL);
+
+	struct kevent kev;
+	EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0,
+	    (void *)(intptr_t)pthread_getthreadid_np());
+	(void)kevent(ctx->fd, &kev, 1, NULL, 0, NULL);
+
+	for (;;) {
+		if (sigwaitinfo(&set, &info) != SIGRTMIN) {
+			break;
+		}
+		EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0,
+		    (void *)(intptr_t)timer_getoverrun(ctx->timer));
+		(void)kevent(ctx->fd, &kev, 1, NULL, 0, NULL);
+	}
+
+	return NULL;
+}
+
+static int
+timerfd_create_impl(int clockid, int flags)
+{
+	if (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME) {
+		return EINVAL;
+	}
+
+	if (flags & ~(TFD_CLOEXEC | TFD_NONBLOCK)) {
+		return EINVAL;
+	}
+
+	struct timerfd_context *ctx = get_timerfd_context(-1, true);
+	if (!ctx) {
+		errno = ENOMEM;
+		return -1;
+	}
+
+	ctx->fd = kqueue();
+	if (ctx->fd == -1) {
+		return -1;
+	}
+
+	ctx->flags = flags;
+
+	struct kevent kev;
+	EV_SET(&kev, 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, 0);
+	if (kevent(ctx->fd, &kev, 1, NULL, 0, NULL) == -1) {
+		close(ctx->fd);
+		ctx->fd = -1;
+		return -1;
+	}
+
+	if (pthread_create(&ctx->worker, NULL, worker_function, ctx) == -1) {
+		close(ctx->fd);
+		ctx->fd = -1;
+		return -1;
+	}
+
+	int ret = kevent(ctx->fd, NULL, 0, &kev, 1, NULL);
+	if (ret == -1) {
+		pthread_kill(ctx->worker, SIGRTMIN + 1);
+		pthread_join(ctx->worker, NULL);
+		close(ctx->fd);
+		ctx->fd = -1;
+		return -1;
+	}
+
+	int tid = (int)(intptr_t)kev.udata;
+
+	struct sigevent sigev = {.sigev_notify = SIGEV_THREAD_ID,
+	    .sigev_signo = SIGRTMIN,
+	    .sigev_notify_thread_id = tid};
+
+	if (timer_create(clockid, &sigev, &ctx->timer) == -1) {
+		pthread_kill(ctx->worker, SIGRTMIN + 1);
+		pthread_join(ctx->worker, NULL);
+		close(ctx->fd);
+		ctx->fd = -1;
+		return -1;
+	}
+
+	return ctx->fd;
+}
+
+int
+timerfd_create(int clockid, int flags)
+{
+	pthread_mutex_lock(&timerfd_context_mtx);
+	int ret = timerfd_create_impl(clockid, flags);
+	pthread_mutex_unlock(&timerfd_context_mtx);
+	return ret;
+}
+
+static int
+timerfd_settime_impl(
+    int fd, int flags, const struct itimerspec *new, struct itimerspec *old)
+{
+	struct timerfd_context *ctx = get_timerfd_context(fd, false);
+	if (!ctx) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (flags & ~(TFD_TIMER_ABSTIME)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	return timer_settime(ctx->timer,
+	    (flags & TFD_TIMER_ABSTIME) ? TIMER_ABSTIME : 0, new, old);
+}
+
+int
+timerfd_settime(
+    int fd, int flags, const struct itimerspec *new, struct itimerspec *old)
+{
+	pthread_mutex_lock(&timerfd_context_mtx);
+	int ret = timerfd_settime_impl(fd, flags, new, old);
+	pthread_mutex_unlock(&timerfd_context_mtx);
+	return ret;
+}
+
+#if 0
+int timerfd_gettime(int fd, struct itimerspec *cur)
+{
+	return syscall(SYS_timerfd_gettime, fd, cur);
+}
+#endif
+
+ssize_t
+timerfd_read(struct timerfd_context *ctx, void *buf, size_t nbytes)
+{
+	int fd = ctx->fd;
+	int flags = ctx->flags;
+	pthread_mutex_unlock(&timerfd_context_mtx);
+
+	if (nbytes < sizeof(uint64_t)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	struct timespec timeout = {0, 0};
+	struct kevent kev;
+	int ret = kevent(
+	    fd, NULL, 0, &kev, 1, (flags & TFD_NONBLOCK) ? &timeout : NULL);
+	if (ret == -1) {
+		return -1;
+	} else if (ret == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	uint64_t nr_expired = 1 + (uint64_t)kev.udata;
+	memcpy(buf, &nr_expired, sizeof(uint64_t));
+
+	return sizeof(uint64_t);
+}
+
+int
+timerfd_close(struct timerfd_context *ctx)
+{
+	timer_delete(ctx->timer);
+	pthread_kill(ctx->worker, SIGRTMIN + 1);
+	pthread_join(ctx->worker, NULL);
+	int ret = close(ctx->fd);
+	ctx->fd = -1;
+	return ret;
+}
Index: src/event.c
--- src/event.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/event.c	2017-10-31 00:53:52.719983000 +0100
@@ -16,7 +16,11 @@
 
     assert(nevent > 0);
 
+#if defined(__linux__)
     epfd = epoll_create(nevent);
+#elif defined(__FreeBSD__)
+    epfd = epoll_create1(EPOLL_CLOEXEC);
+#endif
     if (epfd < 0) {
         return -1;
     }
@@ -48,7 +52,11 @@
     struct epoll_event event;
 
     event.data.ptr = c;
+#if defined(__linux__)
     event.events = EPOLLET;
+#elif defined(__FreeBSD__)
+    event.events = 0;
+#endif
     if (mask & E_WRITABLE) event.events |= EPOLLOUT;
     if (mask & E_READABLE) event.events |= EPOLLIN;
 
@@ -67,7 +75,11 @@
     int op = mask == E_NONE ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;
 
     event.data.ptr = c;
+#if defined(__linux__)
     event.events = EPOLLET;
+#elif defined(__FreeBSD__)
+    event.events = 0;
+#endif
     if (mask & E_WRITABLE) event.events |= EPOLLOUT;
     if (mask & E_READABLE) event.events |= EPOLLIN;
 
Index: src/event.h
--- src/event.h.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/event.h	2017-10-31 00:49:08.316395000 +0100
@@ -1,7 +1,11 @@
 #ifndef EVENT_H
 #define EVENT_H
 
+#if defined(__linux__)
 #include <sys/epoll.h>
+#elif defined(__FreeBSD__)
+#include "epoll-shim/include/sys/epoll.h"
+#endif
 #include "connection.h"
 
 #define E_NONE 0
Index: src/logging.c
--- src/logging.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/logging.c	2017-10-31 00:49:08.316511000 +0100
@@ -7,6 +7,9 @@
 #include <time.h>
 #include "corvus.h"
 #include "logging.h"
+#if defined(__FreeBSD__)
+#include <sys/thr.h>
+#endif
 
 static const char *LEVEL_MAP[] = {"DEBUG", "INFO", "WARN", "ERROR", "CRIT"};
 static const int SYSLOG_LEVEL_MAP[] = {LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT};
@@ -20,7 +23,15 @@
 
     if (level < ATOMIC_GET(config.loglevel)) return;
 
+#if defined(__FreeBSD__)
+    long lwpid;
+    thr_self(&lwpid);
+    pid_t thread_id = (pid_t)lwpid;
+#elif defined(__linux__)
     pid_t thread_id = (pid_t)syscall(SYS_gettid);
+#else
+    pid_t thread_id = (pid_t)0
+#endif
     pid_t process_id = getpid();
 
     va_start(ap, fmt);
@@ -28,14 +39,14 @@
     va_end(ap);
 
     if (config.syslog) {
-        syslog(SYSLOG_LEVEL_MAP[level], "[%s %d %d %d] %s", config.cluster,
-            (int)config.bind, (int)process_id, (int)thread_id, msg);
+        syslog(SYSLOG_LEVEL_MAP[level], "[%s %s:%d %d %d] %s", config.cluster,
+            config.bind.ip, (int)config.bind.port, (int)process_id, (int)thread_id, msg);
     } else {
         gettimeofday(&now, NULL);
         int n = strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S,",
                         localtime(&now.tv_sec));
         snprintf(timestamp + n, sizeof(timestamp) - n, "%03d", (int)now.tv_usec/1000);
-        fprintf(stderr, "%s %s [%s %d %d %d]: %s (%s:%d)\n", timestamp, LEVEL_MAP[level],
-            config.cluster, (int)config.bind, (int)process_id, (int)thread_id, msg, file, line);
+        fprintf(stderr, "%s %s [%s %s:%d %d %d]: %s (%s:%d)\n", timestamp, LEVEL_MAP[level],
+            config.cluster, config.bind.ip, (int)config.bind.port, (int)process_id, (int)thread_id, msg, file, line);
     }
 }
Index: src/socket.c
--- src/socket.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/socket.c	2017-10-31 00:49:08.316651000 +0100
@@ -2,7 +2,13 @@
 #include <sys/socket.h>
 #include <netinet/tcp.h>
 #include <sys/uio.h>
+#if defined(__linux__)
 #include <sys/eventfd.h>
+#elif defined(__FreeBSD__)
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#endif
 #include <netdb.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -374,10 +380,25 @@
 
 int socket_create_eventfd()
 {
+#if defined(__linux__)
     int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
     if (fd == -1) {
         LOG(WARN, "%s: %s", __func__, strerror(errno));
     }
+#elif defined(__FreeBSD__)
+    int fd = kqueue();
+    if (fd == -1)
+        LOG(WARN, "%s: kqueue: %s", __func__, strerror(errno));
+    else {
+        struct kevent change;
+        EV_SET(&change, 123456, EVFILT_USER, EV_ADD, 0, 0, NULL);
+        if (kevent(fd, &change, 1, NULL, 0, NULL) < 0) {
+            LOG(WARN, "%s: kevent: %s", __func__, strerror(errno));
+            close(fd);
+            fd = -1;
+        }
+    }
+#endif
     return fd;
 }
 
Index: src/socket.h
--- src/socket.h.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/socket.h	2017-10-31 00:49:08.316757000 +0100
@@ -1,6 +1,8 @@
 #ifndef SOCKET_H
 #define SOCKET_H
 
+#include <sys/types.h>
+#include <netinet/in.h>
 #include <arpa/inet.h>
 #include <limits.h>
 #include "mbuf.h"
Index: src/timer.c
--- src/timer.c.orig	2017-09-11 08:34:23.000000000 +0200
+++ src/timer.c	2017-10-31 00:49:08.316864000 +0100
@@ -1,4 +1,10 @@
+
+#if defined(__linux__)
 #include <sys/timerfd.h>
+#endif
+#if defined(__FreeBSD__)
+#include "epoll-shim/include/sys/timerfd.h"
+#endif
 #include <string.h>
 #include <errno.h>
 #include <time.h>
