about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/cache/cache.c6
-rw-r--r--src/cache/conf.c36
-rw-r--r--src/cache/deps-exe/shibari-cache8
-rw-r--r--src/cache/shibari-cache-internal.h97
-rw-r--r--src/cache/shibari-cache.c276
-rw-r--r--src/cache/tcpconnection.c49
-rw-r--r--src/cache/udpqueue.c80
-rw-r--r--src/config/defaults.c1
-rw-r--r--src/config/lexparse.c24
-rw-r--r--src/libdcache/dcache-internal.h (renamed from src/cache/dcache-internal.h)4
-rw-r--r--src/libdcache/dcache_add.c (renamed from src/cache/dcache_add.c)0
-rw-r--r--src/libdcache/dcache_clean_expired.c (renamed from src/cache/dcache_clean_expired.c)0
-rw-r--r--src/libdcache/dcache_delete.c (renamed from src/cache/dcache_delete.c)0
-rw-r--r--src/libdcache/dcache_free.c (renamed from src/cache/dcache_free.c)0
-rw-r--r--src/libdcache/dcache_init.c (renamed from src/cache/dcache_init.c)0
-rw-r--r--src/libdcache/dcache_load.c (renamed from src/cache/dcache_load.c)3
-rw-r--r--src/libdcache/dcache_save.c (renamed from src/cache/dcache_save.c)0
-rw-r--r--src/libdcache/dcache_search.c (renamed from src/cache/dcache_search.c)0
-rw-r--r--src/libdcache/deps-lib/dcache (renamed from src/cache/deps-lib/dcache)0
-rw-r--r--src/server/deps-exe/shibari-server-tcp1
-rw-r--r--src/server/deps-exe/shibari-server-udp1
21 files changed, 417 insertions, 169 deletions
diff --git a/src/cache/cache.c b/src/cache/cache.c
new file mode 100644
index 0000000..8c820fb
--- /dev/null
+++ b/src/cache/cache.c
@@ -0,0 +1,6 @@
+/* ISC license. */
+
+#include <shibari/dcache.h>
+#include "shibari-cache-internal.h"
+
+dcache_t cache = DCACHE_ZERO ;
diff --git a/src/cache/conf.c b/src/cache/conf.c
new file mode 100644
index 0000000..ea35646
--- /dev/null
+++ b/src/cache/conf.c
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/cdb.h>
+
+#include "shibari-cache-internal.h"
+
+#include <skalibs/posixishard.h>
+
+int conf_getb (cdb const *c, char const *key, size_t keylen, cdb_data *data)
+{
+  if (keylen > 4096) return (errno = EINVAL, 0) ;
+  switch (cdb_find(c, data, key, keylen))
+  {
+    case -1 : return (errno = EILSEQ, 0) ;
+    case 0 : return (errno = ENOENT, 0) ;
+    default : return 1 ;
+  }
+}
+
+int conf_get (cdb const *c, char const *key, cdb_data *data)
+{
+  return conf_get(c, key, strlen(key), data) ;
+}
+
+int conf_get_uint32 (cdb const *c, char const *key, uint32_t *value)
+{
+  cdb_data data ;
+  if (!conf_get(conf, key, &data)) return 0 ;
+  if (data.len != 4) return (errno = EPROTO, 0) ;
+  uint32_unpack_big(data.s, value) ;
+  return 1 ;
+}
diff --git a/src/cache/deps-exe/shibari-cache b/src/cache/deps-exe/shibari-cache
new file mode 100644
index 0000000..841cf80
--- /dev/null
+++ b/src/cache/deps-exe/shibari-cache
@@ -0,0 +1,8 @@
+cache.o
+conf.o
+${LIBDCACHE}
+${LIBSHIBARI_COMMON}
+-ls6dns
+-lskarnet
+${SOCKET_LIB}
+${SYSCLOCK_LIB}
diff --git a/src/cache/shibari-cache-internal.h b/src/cache/shibari-cache-internal.h
new file mode 100644
index 0000000..7e03282
--- /dev/null
+++ b/src/cache/shibari-cache-internal.h
@@ -0,0 +1,97 @@
+/* ISC license. */
+
+#ifndef SHIBARI_CACHE_INTERNAL_H
+#define SHIBARI_CACHE_INTERNAL_H
+
+#include <stdint.h>
+
+#include <skalibs/cdb.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/bufalloc.h>
+#include <skalibs/genset.h>
+#include <skalibs/ip46.h>
+
+#include <s6-dns/s6dns-engine.h>
+
+#define MAXXED 1000
+
+ /* cache */
+
+extern dcache_t cache ;
+
+
+ /* conf */
+
+extern int conf_getb (cdb const *, char const *, size_t, cdb_data *) ;
+extern int conf_get (cdb const *, char const *, cdb_data *) ;
+extern int conf_get_uint32 (cdb const *, char const *, uint32_t *) ;
+
+
+ /* tcpconnection */
+
+typedef struct tcpconnection_s tcpconnection, *tcpconnection_ref ;
+struct tcpconnection_s
+{
+  bufalloc out ;
+  stralloc in ;
+  uint32_t instate ;
+} ;
+#define TCPCONNECTION_ZERO { .out = BUFALLOC_ZERO, .in = STRALLOC_ZERO, .instate = 0 }
+
+extern genset *tcpconn ;  /* tcpconnection */
+#define ntcp (genset_n(tcpconn))
+
+
+ /* udpqueue */
+
+typedef struct udp4msg_s udp4msg, *udp4msg_ref ;
+struct udp4msg_s
+{
+  char ip[4] ;
+  uint16_t port ;
+  uint16_t len ;
+} ;
+#define UDP4MSG_ZERO { .ip = { 0 }, .port = 0, .len = 0 }
+
+typedef struct udp6msg_s udp6msg, *udp6msg_ref ;
+struct udp4msg_s
+{
+  char ip[16] ;
+  uint16_t port ;
+  uint16_t len ;
+} ;
+#define UDP6MSG_ZERO { .ip = { 0 }, .port = 0, .len = 0 }
+
+typedef struct udpqueue_s udpqueue, *udpqueue_ref ;
+struct udpqueue_s
+{
+  int fd ;
+  stralloc storage ;
+  genalloc messages ; /* udp[46]msg */
+} ;
+#define UDPQUEUE_ZERO { .fd = -1, .storage = STRALLOC_ZERO, .messages = GENALLOC_ZERO }
+
+extern int udpqueue_add4 (udpqueue *, char const *, uint16_t) ;
+extern int udpqueue_flush4 (udpqueue *) ;
+
+#ifdef SKALIBS_IPV6_ENABLED
+extern int udpqueue_add6 (udpqueue *, char const *, uint16_t) ;
+extern int udpqueue_flush6 (udpqueue *) ;
+#endif
+
+
+ /* main */
+
+typedef struct query_s query, *query_ref ;
+struct query_s
+{
+  s6dns_engine_t dt ;
+  size_t origin ;
+} ;
+
+extern uint32_t verbosity ;
+extern cdb confdb ;
+extern size_t n4, n6 ;
+
+#endif
diff --git a/src/cache/shibari-cache.c b/src/cache/shibari-cache.c
index 1d88556..09743d9 100644
--- a/src/cache/shibari-cache.c
+++ b/src/cache/shibari-cache.c
@@ -8,136 +8,42 @@
 #include <fcntl.h>
 #include <signal.h>
 
-#include <skalibs/ip46.h>
 #include <skalibs/posixplz.h>
 #include <skalibs/uint16.h>
 #include <skalibs/uint32.h>
 #include <skalibs/types.h>
-#include <skalibs/error.h>
 #include <skalibs/strerr.h>
 #include <skalibs/sgetopt.h>
-#include <skalibs/allreadwrite.h>
 #include <skalibs/tai.h>
 #include <skalibs/socket.h>
-#include <skalibs/ip46.h>
 #include <skalibs/cdb.h>
 #include <skalibs/sig.h>
 #include <skalibs/iopause.h>
 #include <skalibs/selfpipe.h>
+#include <skalibs/ip46.h>
+#inclide <skalibs/genalloc.h>
 
-#include <s6/accessrules.h>
-
+#include <shibari/config.h>
 #include <shibari/common.h>
 #include <shibari/cache.h>
 
-#define USAGE "shibari-cache [ -U ] [ -v verbosity ] [ -d notif ] [ -D cachedumpfile ] [ -w wtimeout ] [ -i rulesdir | -x rulesfile ] ip[_port]..."
-#define dieusage() strerr_dieusage(100, USAGE)
-
-
-static char const *dumpfile = 0 ;
-
+#include "shibari-cache-internal.h"
 
-typedef struct shibari_ip4_s shibari_ip4 ;
-struct shibari_ip4_s
-{
-  int fd ;
-  char ip[4] ;
-} ;
+#define USAGE "shibari-cache [ -U ] [ -d notif ] [ -f conf.cdb ] [ -D cachedumpfile ] [ -w wtimeout ] [ -i rulesdir | -x rulesfile ]"
+#define dieusage() strerr_dieusage(100, USAGE)
 
-typedef struct shibari_ip6_s shibari_ip6 ;
-struct shibari_ip6_s
-{
-  int fd ;
-  char ip[16] ;
-} ;
 
-static inline void argv_pass1 (char const *const *argv, unsigned int *n4, unsigned int *n6)
-{
-  char ip[16] ;
-  for (; *argv ; argv++)
-    if (ip6_scan(argv, ip))
-#ifdef SKALIBS_IPV6_ENABLED
-      n6++ ;
-#else
-      strerr_dief1x(100, "IPv6 listening addresses unsupported on this system") ;
-#endif
-    else n4++ ;
-}
+uint32_t verbosity ;
+cdb confdb = CDB_ZERO ;
+size_t n4 = 0, n6 = 0, ntcp = 0 ;
 
-static inline void argv_pass2 (char const *const *argv, shibari_ip4 *ip4, shibari_ip6 *ip6, uint16_t *ports)
-{
-  unsigned int i4 = 0, i6 = 0 ;
-  char ip[16] ;
-  size_t len ;
-  for (; *argv ; argv++)
-  {
-    len = ip6_scan(argv, ip) ;
-    if (len)
-    {
-      if (argv[0][len] == '_')
-      {
-        uint16_t port ;
-        if (!uint160_scan(*argv + len + 1))
-          strerr_dief
-      }
-    }
-  }
-}
+static int cont = 1 ;
+static int sfd = -1 ;
+static char const *dumpfile = 0 ;
 
-static inline void reload_cdbs (void)
-{
-  cdb newtdb = CDB_ZERO ;
-  if (!cdb_init(&newtdb, tdbfile))
-  {
-    if (verbosity) strerr_warnwu2sys("reopen DNS data file ", tdbfile) ;
-  }
-  else
-  {
-    cdb_free(&tdb) ;
-    tdb = newtdb ;
-  }
-  if (rulestype == 2)
-  {
-    cdb newrules = CDB_ZERO ;
-    if (!cdb_init(&newrules, rulesfile))
-    {
-      if (verbosity) strerr_warnwu2sys("reopen access rules file ", rulesfile) ;
-    }
-    else
-    {
-      cdb_free(&rules) ;
-      rules = newrules ;
-    }
-  }
-}
 
-static int check_rules (ip46 const *remoteip, s6_accessrules_params_t *params, char const **loc)
+static inline void reload (void)
 {
-  s6_accessrules_result_t r ;
-  params->env.len = 0 ;
-  params->exec.len = 0 ;
-  r = rulestype == 2 ?
-    s6_accessrules_ip46_cdb(remoteip, &rules, params) :
-    s6_accessrules_ip46_fs(remoteip, rulesfile, params) ;
-  if (r != S6_ACCESSRULES_ALLOW) return 0 ;
-
-  if (params->env.len)
-  {
-    char const *p ;
-    if (params->env.s[params->env.len - 1])
-    {
-      if (verbosity)
-      {
-        char fmt[IP46_FMT] ;
-        fmt[ip46_fmt(fmt, remoteip)] = 0 ;
-        strerr_warnw6x("invalid environment parameters in rules ", rulestype == 2 ? "cdb " : "directory ", rulesfile, " for ip ", fmt, " - denying connection") ;
-      }
-      return 0 ;
-    }
-    p = memmem(params->env.s, params->env.len - 1, VAR "=", sizeof(VAR)) ;
-    if (p && (p == params->env.s || !p[-1])) *loc = p + sizeof(VAR) ;
-  }
-  return 1 ;
 }
 
 static inline void handle_signals (void)
@@ -147,16 +53,18 @@ static inline void handle_signals (void)
     case -1 : strerr_diefu1sys(111, "read selfpipe") ;
     case 0 : return ;
     case SIGTERM : cont = 0 ; break ;
-    case SIGHUP : reload_cdbs() ; break ;
-    case SIGALRM : dump_cache() ; break ;
+    case SIGHUP : reload() ; break ;
+    case SIGALRM : dump_cache(dumpfile) ; break ;
     default : break ;
   }
 }
 
 int main (int argc, char const *const *argv)
 {
+  char const *conffile = SHIBARI_SYSCONFDIR "/shibari-cache.conf.cdb" ;
   unsigned int notif = 0 ;
-  unsigned int n4 = 0, n6 = 0 ;
+  char const *ip4 ;
+  char const *ip6 ;
   uid_t uid = 0 ;
   gid_t gid = 0 ;
   PROG = "shibari-cache" ;
@@ -165,13 +73,13 @@ int main (int argc, char const *const *argv)
     subgetopt l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "Uv:d:D:w:i:x:", &l) ;
+      int opt = subgetopt_r(argc, argv, "Ud:f:D:w:i:x:", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
         case 'U' : flagdrop = 1 ; break ;
-        case 'v' : if (!uint320_scan(l.arg, &verbosity)) dieusage() ; break ;
         case 'd' : if (!uint0_scan(l.arg, &notif)) dieusage() ; break ;
+        case 'f' : conffile = l.arg ; break ;
         case 'D' : dumpfile = l.arg ; break ;
         case 'w' : if (!uint0_scan(l.arg, &wtimeout)) dieusage() ; break ;
         case 'i' : rulesfile = l.arg ; rulestype = 1 ; break ;
@@ -180,8 +88,6 @@ int main (int argc, char const *const *argv)
       }
     }
     argc -= l.ind ; argv += l.ind ;
-    if (!argc) default_iplist ;
-
     if (!ip46_scan(argv[0], &localip)) dieusage() ;
     if (flagdrop)
     {
@@ -203,80 +109,118 @@ int main (int argc, char const *const *argv)
 
   close(0) ;
   close(1) ;
-  x[0].fd = selfpipe_init() ;
-  if (x[0].fd == -1) strerr_diefu1sys(111, "create selfpipe") ;
+  sfd = selfpipe_init() ;
+  if (sfd == -1) strerr_diefu1sys(111, "create selfpipe") ;
   if (!sig_altignore(SIGPIPE)) strerr_diefu1sys(111, "ignore SIGPIPE") ;
   {
     sigset_t set ;
     sigemptyset(&set) ;
     sigaddset(&set, SIGHUP) ;
     sigaddset(&set, SIGTERM) ;
+    sigaddset(&set, SIGALRM) ;
     if (!selfpipe_trapset(&set)) strerr_diefu1sys(111, "trap signals") ;
   }
 
-  if (!cdb_init(&tdb, tdbfile)) strerr_diefu2sys(111, "open cdb file ", tdbfile) ;
-  if (rulestype == 2 && !cdb_init(&rules, rulesfile)) strerr_diefu2sys(111, "open rules file ", rulesfile) ;
-
-  x[1].fd = socket_udp46_nb(ip46_is6(&localip)) ;
-  if (x[1].fd == -1) strerr_diefu1sys(111, "create socket") ;
-  if (socket_bind46_reuse(x[1].fd, &localip, localport) == -1) strerr_diefu1sys(111, "bind socket") ;
-
-  if (gid && setgid(gid) == -1) strerr_diefu1sys(111, "setgid") ;
-  if (uid && setuid(uid) == -1) strerr_diefu1sys(111, "setuid") ;
-  if (!tain_now_set_stopwatch_g()) strerr_diefu1sys(111, "initialize clock") ;
+  if (!cdb_init(&confdb, conffile)) strerr_diefu2sys(111, "open cdb file ", conffile) ;
 
-  shibari_log_start(verbosity, &localip, localport) ;
-  if (notif)
   {
-    write(notif, "\n", 1) ;
-    close(notif) ;
+    cdb_data data ;
+    if (!conf_get_uint32(&confdb, "G:logv", &verbosity))
+      strerr_diefu1sys(111, "read verbosity from config") ;
+    if (!conf_get_uint32(&confdb, "G:maxtcp", &maxtcp))
+      strerr_diefu1sys(111, "read maxtcp from config") ;
+    if (maxtcp > 4000 || maxtcp < 1)
+      strerr_dief1x(102, "invalid maxtcp in config") ;
+    if (!conf_get(&confdb, "G:listen4", &data))
+      strerr_diefu3sys(111, "read ", "G:listen4", " entry from config") ;
+    if (data.len & 3)
+      strerr_diefu2sys(102, "invalid length for ", "G:listen4") ;
+    n4 = data.len >> 2 ;
+    ip4 = data.s ;
+#ifdef SKALIBS_IPV6_ENABLED
+    if (!conf_get(&confdb, "G:listen6", &data))
+      strerr_diefu3sys(111, "read ", "G:listen6", " entry from config") ;
+    if (data.len & 15)
+      strerr_diefu2sys(102, "invalid length for ", "G:listen6") ;
+    n6 = data.len >> 4 ;
+    ip6 = data.s ;
+#endif
   }
+  if (!n4 && !n6) strerr_diefu1x(102, "no listen addresses configured" ;
 
-  while (cont)
   {
-    tain wstamp = TAIN_INFINITE ;
-    char const *loc = 0 ;
-    s6dns_message_header_t hdr ;
-    s6dns_message_counts_t counts ;
-    s6dns_domain_t name ;
-    unsigned int rcode ;
-    ssize_t r ;
-    uint16_t qtype ;
-    uint16_t remoteport ;
-    ip46 remoteip ;
+    genalloc queries = GENALLOC_ZERO ; /* query */
+    int fd4[n4 ? n4 : 1][2] ;
+    int fd6[n6 ? n6 : 1][2] ;
+    tcpconnection tcpconn_storage[maxtcp] ;
+    uint32_t tcpconn_freelist[maxtcp] ;
+    genset tcpconn_genset ;
+    tcpconn = &tcpconn_genset ;
+    GENSET_init(tcpconn, tcpconnection, tcpconn_storage, tcpconn_freelist, maxtcp) ;
+
+    for (size_t i = 0 ; i < n4 ; i++)
+    {
+      fd4[i][0] = socket_udp4_nbcoe() ;
+      if (fd4[i][0] == -1) strerr_diefu1sys(111, "create udp4 socket") ;
+      if (socket_bind4_reuse(fd4[i][0], ip4 + (i << 2), 53) == -1)
+      {
+        char fmt[IP4_FMT] ;
+        fmt[ip4_fmt(fmt, ip4 + (i << 2))] = 0 ;
+        strerr_diefu3sys(111, "bind to ip ", fmt, " UDP port 53") ;
+      }
+      fd4[i][1] = socket_tcp4_nbcoe() ;
+      if (fd4[i][1] == -1) strerr_diefu1sys(111, "create tcp4 socket") ;
+      if (socket_bind4_reuse(fd4[i][1], ip4 + (i << 2), 53) == -1)
+      {
+        char fmt[IP4_FMT] ;
+        fmt[ip4_fmt(fmt, ip4 + (i << 2))] = 0 ;
+        strerr_diefu3sys(111, "bind to ip ", fmt, " TCP port 53") ;
+      }
+    }
+#ifdef SKALIBS_IPV6_ENABLED
+    for (size_t i = 0 ; i < n6 ; i++)
+    {
+      fd6[i][0] = socket_udp6_nbcoe() ;
+      if (fd6[i][0] == -1) strerr_diefu1sys(111, "create udp6 socket") ;
+      if (socket_bind6_reuse(fd6[i][0], ip6 + (i << 4), 53) == -1)
+      {
+        char fmt[IP6_FMT] ;
+        fmt[ip6_fmt(fmt, ip6 + (i << 4))] = 0 ;
+        strerr_diefu3sys(111, "bind to ip ", fmt, " UDP port 53") ;
+      }
+      fd6[i][1] = socket_tcp6_nbcoe() ;
+      if (fd6[i][1] == -1) strerr_diefu1sys(111, "create tcp6 socket") ;
+      if (socket_bind4_reuse(fd6[i][1], ip6 + (i << 4), 53) == -1)
+      {
+        char fmt[IP6_FMT] ;
+        fmt[ip6_fmt(fmt, ip6 + (i << 4))] = 0 ;
+        strerr_diefu3sys(111, "bind to ip ", fmt, " TCP port 53") ;
+      }
+    }
+#endif
 
-    if (iopause_g(x, 2, &wstamp) == -1) strerr_diefu1sys(111, "iopause") ;
-    if (x[0].revents & IOPAUSE_EXCEPT) strerr_dief1x(111, "trouble with selfpipe") ;
-    if (x[0].revents & IOPAUSE_READ) { handle_signals() ; continue ; }
+    if (gid && setgid(gid) == -1) strerr_diefu1sys(111, "setgid") ;
+    if (uid && setuid(uid) == -1) strerr_diefu1sys(111, "setuid") ;
+    if (!tain_now_set_stopwatch_g()) strerr_diefu1sys(111, "initialize clock") ;
 
-    r = sanitize_read(socket_recv46(x[1].fd, buf, 512, &remoteip, &remoteport, ip46_is6(&localip))) ;
-    if (!r) continue ;
-    if (r == -1) strerr_diefu1sys(111, "recv from socket") ;
-    if (rulestype && !check_rules(&remoteip, &params, &loc)) continue ;
-    if (!s6dns_message_parse_init(&hdr, &counts, buf, r, &rcode)) continue ;
-    if (hdr.opcode) { rcode = 4 ; goto answer ; }
-    if (!s6dns_message_parse_question(&counts, &name, &qtype, buf, r, &rcode) || !s6dns_domain_encode(&name))
+    if (notif)
     {
-      rcode = errno == ENOTSUP ? 4 : 1 ;
-      goto answer ;
+      write(notif, "\n", 1) ;
+      close(notif) ;
     }
-    shibari_log_queryplus(verbosity, &name, qtype, &remoteip, remoteport) ;
-    tain_wallclock_read(&wstamp) ;
-    rcode = shibari_packet_tdb_answer_query(&pkt, &tdb, &hdr, &name, qtype, loc, &wstamp) ;
 
- answer:
-    if (rcode && rcode != 3)
+    while (cont)
     {
-      shibari_packet_begin(&pkt, hdr.id, &name, qtype) ;
-      pkt.hdr.rcode = rcode ;
-      shibari_packet_end(&pkt) ;
+      size_t n = genalloc_len(query, &queries) ;
+      iopause_fd x[1 + n4 + n6 + n] ;
+      x[0].fd = sfd ;
+      x[0].events = IOPAUSE_READ ;
+      for (size_t i = 0 ; i < n4 ; i++)
+      {
+      }
     }
-    shibari_log_answer(verbosity, &pkt.hdr, pkt.pos) ;
-    tain_add_g(&wstamp, &wtto) ;
-    if (socket_sendnb46_g(x[1].fd, buf, pkt.pos, &remoteip, remoteport, &wstamp) < pkt.pos && verbosity)
-      strerr_warnwu1sys("send answer") ;
-  }
 
+  }
   shibari_log_exit(verbosity, 0) ;
   return 0 ;
 }
diff --git a/src/cache/tcpconnection.c b/src/cache/tcpconnection.c
new file mode 100644
index 0000000..7c7f714
--- /dev/null
+++ b/src/cache/tcpconnection.c
@@ -0,0 +1,49 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/cdb.h>
+
+#include "shibari-cache-internal.h"
+
+genset *tcpconn = 0 ;
+
+static inline int check (char const *key, size_t keylen)
+{
+  cdb_data data ;
+  return cdb_find(&confdb, &data, key, keylen) ;
+}
+
+int tcp4_access (char const *ip)
+{
+  int r ;
+  char key[9] = "A4:" ;
+  uint8_t i = 33 ;
+  memcpy(key+4, ip, 4) ;
+  key[8] = 0 ;
+  while (i--)
+  {
+    key[3] = i ;
+    key[4 + (i>>3)] &= ~(1U << (7 - (i & 7))) ;
+    r = check(key, 8) ;
+    if (r) return r ;
+  }
+  return 0 ;
+}
+
+int tcp6_access (char const *ip)
+{
+  int r ;
+  char key[21] = "A6:" ;
+  uint8_t i = 129 ;
+  memcpy(key+4, ip, 16) ;
+  key[20] = 0 ;
+  while (i--)
+  {
+    key[3] = i ;
+    key[4 + (i>>3)] &= ~(1U << (7 - (i & 7))) ;
+    r = check(key, 20) ;
+    if (r) return r ;
+  }
+  return 0 ;
+}
diff --git a/src/cache/udpqueue.c b/src/cache/udpqueue.c
new file mode 100644
index 0000000..022df3d
--- /dev/null
+++ b/src/cache/udpqueue.c
@@ -0,0 +1,80 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/socket.h>
+#include <skalibs/ip46.h>
+
+#include "shibari-cache-internal.h"
+
+int udpqueue_add4 (udpqueue *q, char const *ip, uint16_t port, char const *s, uint16_t len)
+{
+  udp4msg msg = { .port = port, .len = len } ;
+  if (!stralloc_readyplus(&q->storage, len)) return 0 ;
+  memcpy(msg.ip, ip, 4) ;
+  if (!genalloc_append(udp4msg, &q->messages, &msg)) return 0 ;
+  stralloc_catb(&q->storage, s, len) ;
+  return 1 ;
+}
+
+int udpqueue_flush4 (udpqueue *q)
+{
+  size_t n = genalloc_n(udp4msg, &q->messages) ;
+  size_t shead = 0, head = 0 ;
+  while (head < n)
+  {
+    udp4msg const *msg = genalloc_s(udp4msg, &q->messages) + head ;
+    ssize_t r = socket_send4(q->fd, q->storage.s + shead, msg->len, msg->ip, msg->port) ;
+    if (r <= 0) goto adjust ;
+    shead += msg->len ;
+  }
+  q->storage.len = 0 ;
+  genalloc_setlen(udp4msg, &q->messages, 0) ;
+  return 1 ;
+
+ adjust:
+  memmove(q->storage.s, q->storage.s + shead, q->storage.len - shead) ;
+  q->storage.len -= shead ;
+  memmove(genalloc_s(&udp4msg, &q->messages), genalloc_s(&udp4msg, &q->messages) + head, (n - head) * sizeof(udp4msg)) ;
+  genalloc_setlen(&udp4msg, &q->messages, n - head) ;
+  return 0 ;
+}
+
+#ifdef SKALIBS_IPv6_ENABLED
+
+int udpqueue_add6 (udpqueue *q, char const *ip, uint16_t port, char const *s, uint16_t len)
+{
+  udp6msg msg = { .port = port, .len = len } ;
+  if (!stralloc_readyplus(&q->storage, len)) return 0 ;
+  memcpy(msg.ip, ip, 16) ;
+  if (!genalloc_append(udp6msg, &q->messages, &msg)) return 0 ;
+  stralloc_catb(&q->storage, s, len) ;
+  return 1 ;
+}
+
+int udpqueue_flush6 (udpqueue *q)
+{
+  size_t n = genalloc_n(udp6msg, &q->messages) ;
+  size_t shead = 0, head = 0 ;
+  while (head < n)
+  {
+    udp6msg const *msg = genalloc_s(udp4msg, &q->messages) + head ;
+    ssize_t r = socket_send6(q->fd, q->storage.s + shead, msg->len, msg->ip, msg->port) ;
+    if (r <= 0) goto adjust ;
+    shead += msg->len ;
+  }
+  q->storage.len = 0 ;
+  genalloc_setlen(udp6msg, &q->messages, 0) ;
+  return 1 ;
+
+ adjust:
+  memmove(q->storage.s, q->storage.s + shead, q->storage.len - shead) ;
+  q->storage.len -= shead ;
+  memmove(genalloc_s(&udp6msg, &q->messages), genalloc_s(&udp6msg, &q->messages) + head, (n - head) * sizeof(udp6msg)) ;
+  genalloc_setlen(&udp6msg, &q->messages, n - head) ;
+  return 0 ;
+}
+
+#endif
diff --git a/src/config/defaults.c b/src/config/defaults.c
index 14fce76..d4ff2a8 100644
--- a/src/config/defaults.c
+++ b/src/config/defaults.c
@@ -18,6 +18,7 @@ struct defaults_s
 static struct defaults_s const defaults[] =
 {
   RECU32("G:logv", 1),
+  RECU32("G:maxtcp", 256),
   REC("G:listen4", "\0\0\0\0\0\35", 6),
   REC("G:listen6", "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\35", 18),
 
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
index 9987fbd..f1b3600 100644
--- a/src/config/lexparse.c
+++ b/src/config/lexparse.c
@@ -38,6 +38,7 @@ struct namevalue_s
 enum directivevalue_e
 {
   T_VERBOSITY,
+  T_MAXTCP,
   T_LISTEN,
   T_SERVER,
   T_FORWARD,
@@ -87,6 +88,20 @@ static inline void parse_verbosity (char const *s, size_t const *word, size_t n,
   add_unique("G:logv", pack, 4, md) ;
 }
 
+static inline void parse_maxtcp (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+  uint32_t max ;
+  char pack[4] ;
+  if (n != 1)
+    strerr_dief8x(1, "too ", n ? "many" : "few", " arguments to directive ", "maxtcp", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  if (!uint320_scan(s + word[0], &max))
+    strerr_dief7x(1, " argument to directive ", "maxtcp", " must be an integer ", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  if (max > 4000)
+    strerr_dief7x(1, " argument to directive ", "maxtcp", " must be 4000 or less ", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  uint32_pack_big(pack, max) ;
+  add_unique("G:maxtcp", pack, 4, md) ;
+}
+
 static inline void parse_listen (char const *s, size_t const *word, size_t n, mdt const *md)
 {
   if (!n)
@@ -140,8 +155,10 @@ static inline void process_line (char const *s, size_t const *word, size_t n, md
 {
   static struct namevalue_s const directives[] =
   {
+    { .name = "accept", .value = T_ACCEPT },
     { .name = "forward", .value = T_FORWARD },
     { .name = "listen", .value = T_LISTEN },
+    { .name = "maxtcp", .value = T_MAXTCP },
     { .name = "server", .value = T_SERVER },
     { .name = "verbosity", .value = T_VERBOSITY },
   } ;
@@ -157,9 +174,15 @@ static inline void process_line (char const *s, size_t const *word, size_t n, md
     case T_VERBOSITY :
       parse_verbosity(s, word, n, md) ;
       break ;
+    case T_MAXTCP :
+      parse_maxtcp(s, word, n, md) ;
+      break ;
     case T_LISTEN :
       parse_listen(s, word, n, md) ;
       break ;
+    case T_ACCEPT :
+      parse_accept(s, word, n, md) ;
+      break ;
     case T_SERVER :
       parse_server(s, word, n, md, 0) ;
       break ;
@@ -227,4 +250,5 @@ void conf_lexparse (buffer *b, char const *ifile)
   }
   genalloc_free(size_t, &words) ;
   stralloc_free(&sa) ;
+  default_accept() ;
 }
diff --git a/src/cache/dcache-internal.h b/src/libdcache/dcache-internal.h
index 2a2e36f..7a21044 100644
--- a/src/cache/dcache-internal.h
+++ b/src/libdcache/dcache-internal.h
@@ -1,7 +1,7 @@
 /* ISC license. */
 
-#ifndef SHIBARI_DCACHE_INTERNAL_H
-#define SHIBARI_DCACHE_INTERNAL_H
+#ifndef DCACHE_INTERNAL_H
+#define DCACHE_INTERNAL_H
 
 #include <stdint.h>
 
diff --git a/src/cache/dcache_add.c b/src/libdcache/dcache_add.c
index 7260726..7260726 100644
--- a/src/cache/dcache_add.c
+++ b/src/libdcache/dcache_add.c
diff --git a/src/cache/dcache_clean_expired.c b/src/libdcache/dcache_clean_expired.c
index 0e23443..0e23443 100644
--- a/src/cache/dcache_clean_expired.c
+++ b/src/libdcache/dcache_clean_expired.c
diff --git a/src/cache/dcache_delete.c b/src/libdcache/dcache_delete.c
index 92a5fcc..92a5fcc 100644
--- a/src/cache/dcache_delete.c
+++ b/src/libdcache/dcache_delete.c
diff --git a/src/cache/dcache_free.c b/src/libdcache/dcache_free.c
index 16c074e..16c074e 100644
--- a/src/cache/dcache_free.c
+++ b/src/libdcache/dcache_free.c
diff --git a/src/cache/dcache_init.c b/src/libdcache/dcache_init.c
index d42ec62..d42ec62 100644
--- a/src/cache/dcache_init.c
+++ b/src/libdcache/dcache_init.c
diff --git a/src/cache/dcache_load.c b/src/libdcache/dcache_load.c
index a0ff233..d1b9274 100644
--- a/src/cache/dcache_load.c
+++ b/src/libdcache/dcache_load.c
@@ -4,7 +4,6 @@
 #include <string.h>
 #include <errno.h>
 
-#include <skalibs/posixishard.h>
 #include <skalibs/uint16.h>
 #include <skalibs/uint64.h>
 #include <skalibs/buffer.h>
@@ -13,6 +12,8 @@
 
 #include <shibari/dcache.h>
 
+#include <skalibs/posixishard.h>
+
 static inline int dcache_load_node (dcache_t *z, uint64_t max, buffer *b)
 {
   tain entry = { .nano = 0 } ;
diff --git a/src/cache/dcache_save.c b/src/libdcache/dcache_save.c
index 7277771..7277771 100644
--- a/src/cache/dcache_save.c
+++ b/src/libdcache/dcache_save.c
diff --git a/src/cache/dcache_search.c b/src/libdcache/dcache_search.c
index 0239b88..0239b88 100644
--- a/src/cache/dcache_search.c
+++ b/src/libdcache/dcache_search.c
diff --git a/src/cache/deps-lib/dcache b/src/libdcache/deps-lib/dcache
index fcc09a0..fcc09a0 100644
--- a/src/cache/deps-lib/dcache
+++ b/src/libdcache/deps-lib/dcache
diff --git a/src/server/deps-exe/shibari-server-tcp b/src/server/deps-exe/shibari-server-tcp
index c25f645..5a43e40 100644
--- a/src/server/deps-exe/shibari-server-tcp
+++ b/src/server/deps-exe/shibari-server-tcp
@@ -2,3 +2,4 @@ ${LIBSHIBARI_SERVER}
 ${LIBSHIBARI_COMMON}
 -ls6dns
 -lskarnet
+${SYSCLOCK_LIB}
diff --git a/src/server/deps-exe/shibari-server-udp b/src/server/deps-exe/shibari-server-udp
index 0c1f81d..bfe41cd 100644
--- a/src/server/deps-exe/shibari-server-udp
+++ b/src/server/deps-exe/shibari-server-udp
@@ -4,3 +4,4 @@ ${LIBSHIBARI_COMMON}
 -ls6
 -lskarnet
 ${SOCKET_LIB}
+${SYSCLOCK_LIB}