[rbldnsd] dynamically loadable hook [was: Problems configuring BIND 9 with rbldnsd]

Michael Tokarev mjt at tls.msk.ru
Wed Sep 6 02:39:55 MSD 2006


Michael Tokarev wrote:
> Chris Gabe wrote:
>> We would like to use rbldnsd to help scrape urls for our zone file
>> providers.   Our rbldnsd implementation services several DNSBLs and url
>> block lists at once.  It is problematic to use a log of *all* the
>> queries to obtain the list in question.  It would be better if rbldnsd
>> provided the following capabilities:
>>
>> - log each query that goes to one of a specified set of block lists,
>> which is not present in any of them.  We don't want to include those
>> already in any of the lists; but queries will go to specific lists.  We
>> also want to exclude queries to those lists that are not appropriate,
>> like ip4sets.
>>
>> - support very frequent updates, required to fight quick DNS games by
>> spammers.  E.g. log to a separate file each minute.  wrap after, say, 60
>> minutes to the first file.
>> Or some equivalent that lets us efficiently process the output every
>> minute, i.e. sort | uniq, and submit it to the url block list server for
>> examination and potential inclusion.
>> We could get around this by keeping track of the position in the syslog,
>> I suppose.  Or newsyslog every minute (blech)
> 
> Oh well.
> 
> The issue with all this is - everyone want different things wrt logging,
> statistics and the like.  For example, NJABL wants to log queries coming
> from certain IP addresses, logging only the query IP which is not yet
> listed.  Spamhaus wanted to see ALL realtime statistics per client IP.
> And so on.
> 
> So it's impossible to implement it all in generic way, the only way I
> see is to use some programming locally.
> 
> Rbldnsd has compile-time support for user-defined hooks in certain
> places.  See rbldnsd_hooks.h and rbldnsd_hooks.c for examples.
> There's no documentation about how to use it all, and I never tried
> to actually implement a real example, don't even know if it will
> work.  But I see no other way elegant way.
> 
> Maybe some dynamically-loaded object implementing similar hooks,
> but this places unnecessary overhead for all the rest of users
> (and believe me, high load is quite common for public DNSBL
> mirrors, so every extra instruction counts).

Ok, I prototyped something.  See the attached diff (against 0.996a).
Note for now it only works with gcc (-rdynamic), and only on systems
that use dlopen() in libdl to load dynamic objects.

Also attached is an example (dummy) program - just a quick hack,
printf()s should not be there.  To use, in rbldnsd source dir:
  gcc -o hooks.so hooks.c
  ./rbldnsd .. -x hooks.so -X hook-arg ...

/mjt
-------------- next part --------------
Index: Makefile.in
===================================================================
RCS file: /ws/CVS/rbldnsd/Makefile.in,v
retrieving revision 1.18
diff -u -p -r1.18 Makefile.in
--- Makefile.in	21 Dec 2005 21:42:06 -0000	1.18
+++ Makefile.in	5 Sep 2006 22:19:34 -0000
@@ -60,8 +60,8 @@ LIB_GSRC = $(LIBDNS_GSRC) $(LIBIP4_GSRC)
 RBLDNSD_SRCS = rbldnsd.c rbldnsd_zones.c rbldnsd_packet.c \
   rbldnsd_ip4set.c rbldnsd_ip4tset.c rbldnsd_ip4trie.c rbldnsd_dnset.c \
   rbldnsd_generic.c rbldnsd_combined.c rbldnsd_acl.c \
-  rbldnsd_hooks.c rbldnsd_util.c
-RBLDNSD_HDRS = rbldnsd.h rbldnsd_hooks.h
+  rbldnsd_util.c
+RBLDNSD_HDRS = rbldnsd.h
 RBLDNSD_OBJS = $(RBLDNSD_SRCS:.c=.o) lib$(NAME).a
 
 MISC = configure configure.lib \
Index: configure
===================================================================
RCS file: /ws/CVS/rbldnsd/configure,v
retrieving revision 1.14
diff -u -p -r1.14 configure
--- configure	12 Jun 2006 21:14:48 -0000	1.14
+++ configure	5 Sep 2006 22:19:34 -0000
@@ -13,7 +13,7 @@ else
   exit 1
 fi
 
-options="ipv6 stats master_dump zlib"
+options="ipv6 stats master_dump zlib dso"
 
 for opt in $options; do
   eval enable_$opt=
@@ -26,7 +26,7 @@ fi
 enable() {
   opt=`echo "$1" | sed 's/^--[^-]*-//'`
   case "$opt" in
-    ipv6|stats|master_dump|zlib) ;;
+    ipv6|stats|master_dump|zlib|dso) ;;
     master-dump) opt=master_dump ;;
     *) echo "configure: unrecognized option \`$1'" >&2; exit 1;;
   esac
@@ -246,8 +246,7 @@ fi
 
 if [ n = "$enable_zlib" ]; then
   echo "#define NO_ZLIB	1	/* option disabled */" >>confdef.h
-else
-  if ac_link_v "for zlib support" -lz <<EOF
+elif ac_link_v "for zlib support" -lz <<EOF
 #include <sys/types.h>
 #include <stdio.h>
 #include <zlib.h>
@@ -260,14 +259,31 @@ int main() {
   return 0;
 }
 EOF
-  then
-    LIBS="$LIBS -lz"
-  else
-    if [ "$enable_zlib" ]; then
-      ac_fatal "zlib support is requested but not found/available"
-    fi
-    echo "#define NO_ZLIB" >>confdef.h
-  fi
+then
+  LIBS="$LIBS -lz"
+elif [ "$enable_zlib" ]; then
+  ac_fatal "zlib support is requested but not found/available"
+else
+  echo "#define NO_ZLIB" >>confdef.h
+fi
+
+if [ n = "$enable_dso" ]; then
+  echo "#define NO_DSO		1	/* option disabled */" >>confdef.h
+elif ac_link_v "for dlopen() in -dl with -rdynamic" -ldl -rdynamic <<EOF
+#include <dlfcn.h>
+int main() {
+  void *handle, *func;
+  handle = dlopen("testfile", RTLD_NOW);
+  func = dlsym(handle, "function");
+  return 0;
+}
+EOF
+then
+  LIBS="$LIBS -ldl -rdynamic"
+elif [ "$enable_dso" ]; then
+  ac_fatal "dso support requires dlopen() in -ldl"
+else
+  echo "#define NO_DSO	1	/* not available */" >> confdef.h
 fi
 
 if [ n = "$enable_stats" ]; then
Index: rbldnsd.c
===================================================================
RCS file: /ws/CVS/rbldnsd/rbldnsd.c,v
retrieving revision 1.113
diff -u -p -r1.113 rbldnsd.c
--- rbldnsd.c	27 Jul 2006 09:37:49 -0000	1.113
+++ rbldnsd.c	5 Sep 2006 22:19:35 -0000
@@ -1,4 +1,4 @@
-/* $Id: rbldnsd.c,v 1.113 2006/07/27 09:37:49 mjt Exp $
+/* $Id: rbldnsd.c,v 1.114 2006/09/05 17:30:00 mjt Exp $
  * rbldnsd: main program
  */
 
@@ -23,7 +23,6 @@
 #include <fcntl.h>
 #include <sys/wait.h>
 #include "rbldnsd.h"
-#include "rbldnsd_hooks.h"
 
 #ifndef NO_SELECT_H
 # include <sys/select.h>
@@ -47,6 +46,9 @@
 #  define STATS_IPC_IOVEC 1
 # endif
 #endif
+#ifndef NO_DSO
+# include <dlfcn.h>
+#endif
 
 #ifndef NI_MAXHOST
 # define NI_MAXHOST 1025
@@ -109,6 +111,10 @@ static int fork_on_reload;
 #if STATS_IPC_IOVEC
 static struct iovec *stats_iov;
 #endif
+#ifndef NO_DSO
+int (*hook_reload_check)(), (*hook_reload)();
+int (*hook_query_access)(), (*hook_query_result)();
+#endif
 
 /* a list of zonetypes. */
 const struct dstype *ds_types[] = {
@@ -138,7 +144,7 @@ static int satoi(const char *s) {
 static void NORETURN usage(int exitcode) {
    const struct dstype **dstp;
    printf(
-"%s: rbl dns daemon version %s%s\n"
+"%s: rbl dns daemon version %s\n"
 "Usage is: %s options zonespec...\n"
 "where options are:\n"
 " -u user[:group] - run as this user:group (rbldns)\n"
@@ -174,14 +180,15 @@ static void NORETURN usage(int exitcode)
 #ifndef NO_ZLIB
 " -C - disable on-the-fly decompression of dataset files\n"
 #endif
-#ifdef do_hook_getopt
-" -H local_hook_options - process custom options (for custom builds)\n"
+#ifndef NO_DZO
+" -x extension - load given extension module (.so file)\n"
+" -X extarg - pass extarg to extension init routine\n"
 #endif
 " -d - dump all zones in BIND format to standard output and exit\n"
 "each zone specified using `name:type:file,file...'\n"
 "syntax, repeated names constitute the same zone.\n"
 "Available dataset types:\n"
-, progname, version, hook_info, progname);
+, progname, version, progname);
   for(dstp = ds_types; *dstp; ++dstp)
     printf(" %s - %s\n", (*dstp)->dst_name, (*dstp)->dst_descr);
   exit(exitcode);
@@ -349,6 +356,11 @@ static void init(int argc, char **argv) 
   int nodaemon = 0, quickstart = 0, dump = 0, nover = 0, forkon = 0;
   int family = AF_UNSPEC;
   int cfd = -1;
+  const struct zone *z;
+#ifndef NO_DSO
+  char *ext = NULL, *extarg = NULL;
+  int (*extinit)(const char *arg, struct zone *zonelist) = NULL;
+#endif
 
   if ((progname = strrchr(argv[0], '/')) != NULL)
     argv[0] = ++progname;
@@ -357,7 +369,7 @@ static void init(int argc, char **argv) 
 
   if (argc <= 1) usage(1);
 
-  while((c = getopt(argc, argv, "u:r:b:w:t:c:p:nel:qs:h46dvaAfCH:")) != EOF)
+  while((c = getopt(argc, argv, "u:r:b:w:t:c:p:nel:qs:h46dvaAfCx:X:")) != EOF)
     switch(c) {
     case 'u': user = optarg; break;
     case 'r': rootdir = optarg; break;
@@ -440,13 +452,13 @@ break;
     case 'A': lazy = 0; break;
     case 'f': forkon = 1; break;
     case 'C': nouncompress = 1; break;
-    case 'H':
-#ifdef do_hook_getopt
-      if (hook_getopt(optarg) != 0)
-        error(0, "error processing custom option `%s'", optarg);
-      break;
+#ifndef NO_DSO
+    case 'x': ext = optarg; break;
+    case 'X': extarg = optarg; break;
 #else
-      error(0, "no custom option processing is compiled in");
+    case 'x':
+    case 'X':
+      error(0, "extension support is not compiled in");
 #endif
     case 'h': usage(0);
     default: error(0, "type `%.50s -h' for help", progname);
@@ -458,7 +470,6 @@ break;
 
 #ifndef NO_MASTER_DUMP
   if (dump) {
-    struct zone *z;
     time_t now;
     logto = LOGTO_STDERR;
     for(c = 0; c < argc; ++c)
@@ -506,6 +517,17 @@ break;
 
   initsockets(bindaddr, nba, family);
 
+#ifndef NO_DSO
+  if (ext) {
+    void *handle = dlopen(ext, RTLD_NOW);
+    if (!handle)
+      error(0, "unable to load extension `%s': %s", ext, dlerror());
+    extinit = dlsym(handle, "rbldnsd_extension_init");
+    if (!extinit)
+      error(0, "unable to find extension init routine in `%s'", ext);
+  }
+#endif
+
   if (!user && !(uid = getuid()))
     user = "rbldns";
 
@@ -560,25 +582,25 @@ break;
   for(c = 0; c < argc; ++c)
     zonelist = addzone(zonelist, argv[c]);
   init_zones_caches(zonelist);
-#ifdef do_hook_init
-  if (hook_init(zonelist) != 0) error(0, "error processing init hook");
+
+#ifndef NO_DSO
+  if (extinit && extinit(extarg, zonelist) != 0)
+    error(0, "unable to iniitialize extension `%s'", ext);
 #endif
 
   if (!quickstart && !do_reload(0))
     error(0, "zone loading errors, aborting");
 
-  { const struct zone *z;
-    for(c = 0, z = zonelist; z; z = z->z_next)
-     ++c;
-    numzones = c;
-  }
+  /* count number of zones */
+  for(c = 0, z = zonelist; z; z = z->z_next)
+    ++c;
+  numzones = c;
+
 #if STATS_IPC_IOVEC
   stats_iov = (struct iovec *)emalloc(numzones * sizeof(struct iovec));
-  { struct zone *z;
-    for(c = 0, z = zonelist; z; z = z->z_next, ++c) {
-      stats_iov[c].iov_base = (char*)&z->z_stats;
-      stats_iov[c].iov_len = sizeof(z->z_stats);
-    }
+  for(c = 0, z = zonelist; z; z = z->z_next, ++c) {
+    stats_iov[c].iov_base = (char*)&z->z_stats;
+    stats_iov[c].iov_len = sizeof(z->z_stats);
   }
 #endif
   dslog(LOG_INFO, 0, "rbldnsd version %s started (%d socket(s), %d zone(s))",
@@ -820,7 +842,7 @@ static int do_reload(int do_fork) {
 #endif /* NO_TIMES */
 
   ds = nextdataset2reload(NULL);
-  if (!ds) {
+  if (!ds && call_hook(reload_check, (zonelist)) == 0) {
     check_expires();
     return 1;	/* nothing to reload */
   }
@@ -869,10 +891,11 @@ static int do_reload(int do_fork) {
 #endif /* NO_TIMES */
 
   r = 1;
-  do {
+  while(ds) {
     if (!loaddataset(ds))
       r = 0;
-  } while ((ds = nextdataset2reload(ds)) != NULL);
+    ds = nextdataset2reload(ds);
+  }
 
   for (zone = zonelist; zone; zone = zone->z_next) {
     time_t stamp = 0;
@@ -911,9 +934,8 @@ static int do_reload(int do_fork) {
            "NS or SOA RRs are too long, will be ignored");
   }
 
-#ifdef do_hook_reload
-  hook_reload(zonelist);
-#endif
+  if (call_hook(reload, (zonelist)) != 0)
+    r = 0;
 
   ip = ssprintf(ibuf, sizeof(ibuf), "zones reloaded");
 #ifndef NO_TIMES
Index: rbldnsd.h
===================================================================
RCS file: /ws/CVS/rbldnsd/rbldnsd.h,v
retrieving revision 1.98
diff -u -p -r1.98 rbldnsd.h
--- rbldnsd.h	22 Jul 2006 07:03:51 -0000	1.98
+++ rbldnsd.h	5 Sep 2006 22:19:35 -0000
@@ -271,6 +271,9 @@ struct zone {	/* zone, list of zones */
   struct dnsstats z_stats;		/* statistic counters */
   struct dnsstats z_pstats;		/* for stats monitoring: prev values */
 #endif
+#ifndef NO_DSO
+  void *z_hookdata;			/* data ptr for hooks */
+#endif
   struct zone *z_next;			/* next in list */
 };
 
@@ -431,3 +434,38 @@ const char *ip4trie_lookup(const struct 
 struct ip4trie_node *
 ip4trie_addnode(struct ip4trie *trie, ip4addr_t prefix, unsigned bits,
                 struct mempool *mp);
+
+/* hooks from a DSO extensions */
+#ifndef NO_DSO
+
+/* prototype for init routine */
+int rbldnsd_extension_init(char *arg, struct zone *zonelist);
+
+/* return true/false depending whenever hook needs to be reloaded */
+extern int (*hook_reload_check)(const struct zone *zonelist);
+
+/* perform actual reload, after all zones has been reloaded. */
+extern int (*hook_reload)(struct zone *zonelist);
+
+/* check whenever this query is allowed for this client:
+ *  * 0 = ok, <0 = drop the packet, >0 = refuse */
+extern int (*hook_query_access)
+  (const struct sockaddr *requestor,
+   const struct zone *zone,
+   const struct dnsqinfo *qinfo);
+
+/* notice result of the OK query */
+extern int (*hook_query_result)
+  (const struct sockaddr *requestor,
+   const struct zone *zone,
+   const struct dnsqinfo *qinfo,
+   int positive);
+
+#define call_hook(name, args)	\
+	(hook_##name ? hook_##name args : 0)
+
+#else	/* dummy "functions" */
+
+#define call_hook(name, args) 0
+
+#endif
Index: rbldnsd_packet.c
===================================================================
RCS file: /ws/CVS/rbldnsd/rbldnsd_packet.c,v
retrieving revision 1.97
diff -u -p -r1.97 rbldnsd_packet.c
--- rbldnsd_packet.c	19 Dec 2005 14:36:21 -0000	1.97
+++ rbldnsd_packet.c	5 Sep 2006 22:19:36 -0000
@@ -11,7 +11,6 @@
 #include <netdb.h>
 #include <syslog.h>
 #include "rbldnsd.h"
-#include "rbldnsd_hooks.h"
 
 #ifndef NO_IPv6
 # ifndef NI_MAXHOST
@@ -362,12 +361,10 @@ int replypacket(struct dnspacket *pkt, u
   if (qi.qi_tflag & NSQUERY_REFUSE)
     refuse(DNS_R_REFUSED);
 
-#ifdef do_hook_query_access
-  if ((found = hook_query_access(zone, NULL, &qi))) {
+  if ((found = call_hook(query_access, (pkt->p_peer, zone, &qi)))) {
     if (found < 0) return 0;
     refuse(DNS_R_REFUSED);
   }
-#endif
 
   if (qi.qi_dnlab == 0) {	/* query to base zone: SOA and NS */
 
@@ -414,9 +411,7 @@ int replypacket(struct dnspacket *pkt, u
     addrr_soa(pkt, zone, 1);	/* add SOA if any to AUTHORITY */
     h[p_f2] = DNS_R_NXDOMAIN;
     do_stats(zone->z_stats.q_nxd += 1);
-#ifdef do_hook_query_result
-    hook_query_result(zone, NULL, &qi, 0);
-#endif
+    (void)call_hook(query_result, (pkt->p_peer, zone, &qi, 0));
   }
   else {
     if (!h[p_ancnt2]) {	/* positive reply, no answers */
@@ -427,9 +422,7 @@ int replypacket(struct dnspacket *pkt, u
              !lazy)
       addrr_ns(pkt, zone, 1); /* add nameserver records to positive reply */
     do_stats(zone->z_stats.q_ok += 1);
-#ifdef do_hook_query_result
-    hook_query_result(zone, NULL, &qi, 1);
-#endif
+    (void)call_hook(query_result, (pkt->p_peer, zone, &qi, 1));
   }
   if (rlen() > DNS_MAXPACKET) {	/* add OPT record for long replies */
     /* as per parsequery(), we always have 11 bytes for minimal OPT record at
-------------- next part --------------
A non-text attachment was scrubbed...
Name: hooks.c
Type: text/x-csrc
Size: 1895 bytes
Desc: not available
Url : http://www.corpit.ru/pipermail/rbldnsd/attachments/20060906/fc0817d5/hooks.c


More information about the rbldnsd mailing list