- dns cache support (ser will cache both positive and negative responses if
authorAndrei Pelinescu-Onciul <andrei@iptel.org>
Fri, 15 Sep 2006 10:37:44 +0000 (10:37 +0000)
committerAndrei Pelinescu-Onciul <andrei@iptel.org>
Fri, 15 Sep 2006 10:37:44 +0000 (10:37 +0000)
 turned on, see doc/dns.txt for more details & config options)
- dns failover support: when a name resolves to more then 1 ip (either
  multiple A or AAAA records or multiple SRVs) and sending to the first
   ip fails, ser will retry with the others. By default is off. See
  doc/dns.txt for more details/config options a.s.o.
- destination blacklist: when sending to some destination (defined by
  ip:port and protocol) fails, ser will temporarily add this destination
   in a blacklist giving future sends the opportunity of immediately
    failing. Destination are also added to the blacklist on tm invite
   timeouts (when no response is received in the fr_timer interval).
  By default is off, see doc/dst_blacklist.txt form more details/config
   options.
- small makefile fixes (in mode=debug)
- resolver get_record api changes (updated enum)

WARNING: there are a lot of changes in tm

37 files changed:
Makefile.defs
NEWS
action.c
cfg.lex
cfg.y
clist.h
core_cmd.c
dns_cache.c [new file with mode: 0644]
dns_cache.h [new file with mode: 0644]
dns_wrappers.h [new file with mode: 0644]
doc/dns.txt [new file with mode: 0644]
doc/dst_blacklist.txt [new file with mode: 0644]
dst_blacklist.c [new file with mode: 0644]
dst_blacklist.h [new file with mode: 0644]
error.h
forward.c
forward.h
globals.h
main.c
modules/tm/h_table.c
modules/tm/h_table.h
modules/tm/t_funcs.c
modules/tm/t_fwd.c
modules/tm/t_fwd.h
modules/tm/t_msgbuilder.c
modules/tm/t_reply.c
modules/tm/timer.c
modules/tm/uac.c
modules/tm/ut.h
msg_translator.h
resolve.c
resolve.h
socket_info.c
socket_info.h
ut.h
utils/sercmd/sercmd.c
version.h

index e961c32..a09a709 100644 (file)
@@ -66,7 +66,7 @@ MAIN_NAME=ser
 VERSION = 0
 PATCHLEVEL = 10
 SUBLEVEL =   99
-EXTRAVERSION = -dev42
+EXTRAVERSION = -dev43-dns_cache
 
 SER_VER = $(shell expr $(VERSION) \* 1000000 + $(PATCHLEVEL) \* 1000 + \
                        $(SUBLEVEL) )
@@ -339,6 +339,14 @@ endif
 #              compiles in checks and use for maddr parameter in uri.
 #              Required to support Windows Messenger 5.x over TCP connection
 #              which (mis)uses this parameter.
+# -DUSE_DNS_CACHE
+#              use an internal dns cache instead of making dns requests each time
+# -DUSE_DNS_FAILOVER
+#              if the destination resolves to multiple ips, on send error fall back
+#              to the others
+# -DUSE_DST_BLACKLIST
+#              blacklist bad destination (timeout, failed to connect, error sending
+#        a.s.o)
 
 # Sometimes is needes correct non-quoted $OS. HACK: gcc translates known OS to number ('linux'), so there is added underscore
 
@@ -356,6 +364,9 @@ DEFS+= $(extra_defs) \
         -DDISABLE_NAGLE \
         -DHAVE_RESOLV_RES \
         -DDBG_QM_MALLOC \
+        -DUSE_DNS_CACHE \
+        -DUSE_DNS_FAILOVER \
+        -DUSE_DST_BLACKLIST \
         #-DF_MALLOC \
         #-DDBG_F_MALLOC \
         #-DNO_DEBUG \
@@ -1092,7 +1103,6 @@ $(error                   Unsupported compiler ($(CC):$(CC_NAME)), try gcc)
 endif          #CC_NAME, gcc
 endif  #ARCH, ppc 
 
-CFLAGS+= $(CC_EXTRA_OPTS)
 
 
 # setting LDFLAGS
@@ -1122,6 +1132,7 @@ endif
 else   #mode,release
 ifeq   ($(CC_NAME), gcc)
                CFLAGS=-g -Wcast-align $(PROFILE)
+               DEFS+=-DCC_GCC_LIKE_ASM
 ifeq           ($(ARCH), sparc64)
                        DEFS+=SPARC64_MODE
                        CFLAGS+= -mcpu=ultrasparc -m64
@@ -1138,6 +1149,7 @@ else
 endif
 endif
 ifeq   ($(CC_NAME), icc)
+               DEFS+=-DCC_GCC_LIKE_ASM
                CFLAGS=-g  $(PROFILE)
                LDFLAGS+=-g -Wl,-E $(PROFILE)
                MOD_LDFLAGS=-shared $(LDFLAGS)
@@ -1150,6 +1162,7 @@ endif
 
 endif #mode=release
 
+CFLAGS+= $(CC_EXTRA_OPTS)
 
 #*FLAGS used for compiling the modules
 ifeq   ($(CC_NAME), gcc)
diff --git a/NEWS b/NEWS
index f908f9c..5515bdd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -24,7 +24,8 @@ modules:
                 hashing after an uri (to, from or request uri)
               - improved uri hashing (password is ignored, port is used only
                 if != 5060 or 5061)
- - tm        - migrated to the new timers (tm timers completely rewritten)
+ - tm        - dns failover and dst blacklist support
+             - migrated to the new timers (tm timers completely rewritten)
              - improved speed and less memory usage
              - much more precise reptransmissions timing
              - params: - retr_timer1p1, retr_timer1p2, retr_timer1p3 removed
@@ -49,6 +50,11 @@ modules:
               Vias a.s.o) and not on the original message
  
 core:
+ - dns cache and dns failover support added (see doc/dns.txt)
+ - destination blacklist added -- destinations to which forwarding fails
+   (send error, tm timeout a.s.o) are temporarily added to a blacklist which 
+    is consulted before each send => faster send error detection
+    in the near future (see doc/dst_blacklist.txt)
  - default log level switched to 0 (only messages < L_WARN will be printed
    by default)
  - separate memdbg log level which controls the memory/malloc related
@@ -113,7 +119,7 @@ core:
  - tcp: - improved  performance (io event handling), using OS specific
            optimizations
         - 1024 connections limit removed (see tcp_max_connections)
--  resolver: - timeouts, retries a.s.o can no be set from ser.cfg
+-  resolver: - timeouts, retries a.s.o can now be set from ser.cfg
              (see below dns_* and man resolv.conf(6)).
              The maximum time a dns request can take (before failing) is:
               (dns_retr_time*dns_retr_no)*(search_list_domains)
@@ -130,6 +136,21 @@ core:
    are available (see tm docs)
 - avps directly accessible from script with %avp_name (variable style)
 new config variables:
+   use_dns_cache = on | off  (default on)  
+   use_dns_failover = on | off (default off)
+   dns_cache_flags = number (default 0)
+   dns_cache_negative_ttl = number in seconds (default 60)
+   dns_cache_min_ttl = time in seconds (default 0)
+   dns_cache_max_ttl = time in seconds (default MAXINT)
+   dns_cache_mem = maximum memory used for the dns cache in Kb (default 500 K)
+   dns_cache_gc_interval = interval in seconds after which the dns cache is
+      garbage collected (default: 120 s)
+   use_dst_blacklist = on | off (default off)
+   dst_blacklist_expire = time in s (default 60)
+   dst_blacklist_mem = maximum memory used for the blacklist in Kb (default 250
+      K)
+   dst_blacklist_gc_interval = interval in seconds after which the destination 
+      blacklist is garbage collected (default 60)
    tos = number  - ip type of service (TOS) value
    dns_try_ipv6 = yes/no - if yes and a dns lookup fails, it will retry it
       for ipv6 (AAAA record). Default: yes
@@ -138,7 +159,7 @@ new config variables:
       (usually 5s).
    dns_retr_no = no. - number of dns retransmissions before giving up.
       Default: see above (usually 4)
-   dns_server_no = no. - how many dns servers from the ones defined in 
+   dns_servers_no = no. - how many dns servers from the ones defined in 
       /etc/resolv.conf will be used. Default: all of them.
    dns_use_search_list= yes/no - if no, the search list in /etc/resolv.conf
       will be ignored (=> fewer lookups => gives up faster). Default: yes.
index 181390d..0943e38 100644 (file)
--- a/action.c
+++ b/action.c
@@ -41,6 +41,7 @@
  *  2005-12-12  return & drop/exit differentiation (andrei)
  *  2005-12-19  select framework (mma)
  *  2006-04-12  updated *_send() calls to use a struct dest_info (andrei)
+ *  2006-07-27  dns cache and dns based send address failover support (andrei)
  */
 
 
@@ -107,6 +108,7 @@ int do_action(struct action* a, struct sip_msg* msg)
        unsigned short port;
        unsigned short flags;
        int_str name, value;
+       str* dst_host;
 
        /* reset the value of error to E_UNSPEC so avoid unknowledgable
           functions to return with error (status<0) and not setting it
@@ -208,32 +210,24 @@ int do_action(struct action* a, struct sip_msg* msg)
 #endif
                                }
 
-#ifdef HONOR_MADDR
-                               if (u->maddr_val.s && u->maddr_val.len) {
-                                       if (sip_hostport2su(&dst.to, &u->maddr_val, port, dst.proto)<0){
-                                               LOG(L_ERR, "ERROR:  bad maddr param in uri,"
-                                                               " dropping packet\n");
-                                               ret=E_BAD_ADDRESS;
-                                               goto error_fwd_uri;
-                                       }
-                               } else
+#ifdef HONOR_MADDR                             
+                               if (u->maddr_val.s && u->maddr_val.len)
+                                       dst_host=&u->maddr_val;
+                               else
 #endif
-                               if (sip_hostport2su(&dst.to, &u->host, port, dst.proto)<0){
-                                       LOG(L_ERR, "ERROR:  bad host name in uri,"
-                                                       " dropping packet\n");
-                                       ret=E_BAD_ADDRESS;
-                                       goto error_fwd_uri;
-                               }
+                                       dst_host=&u->host;
 #ifdef USE_COMP
                                dst.comp=u->comp;
 #endif
-                               ret=forward_request(msg, &dst);
-                               if (ret>=0) ret=1;
+                               ret=forward_request(msg, dst_host, port, &dst);
+                               if (ret>=0){
+                                       ret=1;
+                               }
                        }else if ((a->val[0].type==PROXY_ST) && (a->val[1].type==NUMBER_ST)){
                                if (dst.proto==PROTO_NONE)
                                        dst.proto=msg->rcv.proto;
                                proxy2su(&dst.to,  (struct proxy_l*)a->val[0].u.data);
-                               ret=forward_request(msg, &dst);
+                               ret=forward_request(msg, 0, 0, &dst);
                                if (ret>=0){
                                        ret=1;
                                        proxy_mark((struct proxy_l*)a->val[0].u.data, ret);
diff --git a/cfg.lex b/cfg.lex
index bd2540c..4e43dd6 100644 (file)
--- a/cfg.lex
+++ b/cfg.lex
@@ -60,6 +60,8 @@
  *              to_{ip,port} (andrei)
  *  2005-12-12  separated drop, exit, break, return, added RETCODE (andrei)
  *  2005-12-19  select framework (mma)
+ * 2006-09-11  added dns cache (use, flags, ttls, mem ,gc) & dst blacklist
+ *              options (andrei)
  */
 
 
@@ -228,6 +230,22 @@ DNS_RETR_TIME      dns_retr_time
 DNS_RETR_NO            dns_retr_no
 DNS_SERVERS_NO dns_servers_no
 DNS_USE_SEARCH dns_use_search_list
+/* dns cache */
+DNS_USE_CACHE  use_dns_cache
+DNS_USE_FAILOVER       use_dns_failover
+DNS_CACHE_FLAGS                dns_cache_flags
+DNS_CACHE_NEG_TTL      dns_cache_negative_ttl
+DNS_CACHE_MIN_TTL      dns_cache_min_ttl
+DNS_CACHE_MAX_TTL      dns_cache_max_ttl
+DNS_CACHE_MEM          dns_cache_mem
+DNS_CACHE_GC_INT       dns_cache_gc_interval
+/* blacklist */
+USE_DST_BLST           use_dst_blacklist
+DST_BLST_MEM           dst_blacklist_mem
+DST_BLST_TTL           dst_blacklist_expire|dst_blacklist_ttl
+DST_BLST_GC_INT                dst_blacklist_gc_interval
+
+
 PORT   port
 STAT   statistics
 MAXBUFFER maxbuffer
@@ -422,6 +440,30 @@ EAT_ABLE   [\ \t\b\r]
                                                                return DNS_SERVERS_NO; }
 <INITIAL>{DNS_USE_SEARCH}      { count(); yylval.strval=yytext;
                                                                return DNS_USE_SEARCH; }
+<INITIAL>{DNS_USE_CACHE}       { count(); yylval.strval=yytext;
+                                                               return DNS_USE_CACHE; }
+<INITIAL>{DNS_USE_FAILOVER}    { count(); yylval.strval=yytext;
+                                                               return DNS_USE_FAILOVER; }
+<INITIAL>{DNS_CACHE_FLAGS}     { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_FLAGS; }
+<INITIAL>{DNS_CACHE_NEG_TTL}   { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_NEG_TTL; }
+<INITIAL>{DNS_CACHE_MIN_TTL}   { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_MIN_TTL; }
+<INITIAL>{DNS_CACHE_MAX_TTL}   { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_MAX_TTL; }
+<INITIAL>{DNS_CACHE_MEM}       { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_MEM; }
+<INITIAL>{DNS_CACHE_GC_INT}    { count(); yylval.strval=yytext;
+                                                               return DNS_CACHE_GC_INT; }
+<INITIAL>{USE_DST_BLST}        { count(); yylval.strval=yytext;
+                                                               return USE_DST_BLST; }
+<INITIAL>{DST_BLST_MEM}        { count(); yylval.strval=yytext;
+                                                               return DST_BLST_MEM; }
+<INITIAL>{DST_BLST_TTL}        { count(); yylval.strval=yytext;
+                                                               return DST_BLST_TTL; }
+<INITIAL>{DST_BLST_GC_INT}     { count(); yylval.strval=yytext;
+                                                               return DST_BLST_GC_INT; }
 <INITIAL>{PORT}        { count(); yylval.strval=yytext; return PORT; }
 <INITIAL>{STAT}        { count(); yylval.strval=yytext; return STAT; }
 <INITIAL>{MAXBUFFER}   { count(); yylval.strval=yytext; return MAXBUFFER; }
diff --git a/cfg.y b/cfg.y
index f6fc9cc..de145d0 100644 (file)
--- a/cfg.y
+++ b/cfg.y
@@ -72,6 +72,8 @@
  * 2006-02-02  named flags support (andrei)
  * 2006-02-06  named routes support (andrei)
  * 2006-05-30  avp flags (tma)
+ * 2006-09-11  added dns cache (use, flags, ttls, mem ,gc) & dst blacklist
+ *              options (andrei)
  */
 
 %{
                if (rt!=ONSEND_ROUTE) yyerror( s " allowed only in onsend_routes");\
        }while(0)
 
+
+#ifdef USE_DNS_CACHE
+       #define IF_DNS_CACHE(x) x
+#else
+       #define IF_DNS_CACHE(x) warn("dns cache support not compiled in")
+#endif
+
+#ifdef USE_DNS_FAILOVER
+       #define IF_DNS_FAILOVER(x) x
+#else
+       #define IF_DNS_FAILOVER(x) warn("dns failover support not compiled in")
+#endif
+
+#ifdef USE_DST_BLACKLIST
+       #define IF_DST_BLACKLIST(x) x
+#else
+       #define IF_DST_BLACKLIST(x) warn("dst blacklist support not compiled in")
+#endif
+
+
 extern int yylex();
 static void yyerror(char* s);
 static char* tmp;
@@ -234,6 +256,20 @@ static struct socket_id* mk_listen_id(char*, int, int);
 %token DNS_RETR_NO
 %token DNS_SERVERS_NO
 %token DNS_USE_SEARCH
+%token DNS_USE_CACHE
+%token DNS_USE_FAILOVER
+%token DNS_CACHE_FLAGS
+%token DNS_CACHE_NEG_TTL
+%token DNS_CACHE_MIN_TTL
+%token DNS_CACHE_MAX_TTL
+%token DNS_CACHE_MEM
+%token DNS_CACHE_GC_INT
+/*blacklist*/
+%token USE_DST_BLST
+%token DST_BLST_MEM
+%token DST_BLST_TTL
+%token DST_BLST_GC_INT
+
 %token PORT
 %token STAT
 %token CHILDREN
@@ -512,6 +548,30 @@ assign_stm:
        | DNS_SERVERS_NO error { yyerror("number expected"); }
        | DNS_USE_SEARCH EQUAL NUMBER   { dns_search_list=$3; }
        | DNS_USE_SEARCH error { yyerror("boolean value expected"); }
+       | DNS_USE_CACHE EQUAL NUMBER   { IF_DNS_CACHE(use_dns_cache=$3); }
+       | DNS_USE_CACHE error { yyerror("boolean value expected"); }
+       | DNS_USE_FAILOVER EQUAL NUMBER   { IF_DNS_FAILOVER(use_dns_failover=$3);}
+       | DNS_USE_FAILOVER error { yyerror("boolean value expected"); }
+       | DNS_CACHE_FLAGS EQUAL NUMBER   { IF_DNS_CACHE(dns_flags=$3); }
+       | DNS_CACHE_FLAGS error { yyerror("boolean value expected"); }
+       | DNS_CACHE_NEG_TTL EQUAL NUMBER   { IF_DNS_CACHE(dns_neg_cache_ttl=$3); }
+       | DNS_CACHE_NEG_TTL error { yyerror("boolean value expected"); }
+       | DNS_CACHE_MAX_TTL EQUAL NUMBER   { IF_DNS_CACHE(dns_cache_max_ttl=$3); }
+       | DNS_CACHE_MAX_TTL error { yyerror("boolean value expected"); }
+       | DNS_CACHE_MIN_TTL EQUAL NUMBER   { IF_DNS_CACHE(dns_cache_min_ttl=$3); }
+       | DNS_CACHE_MIN_TTL error { yyerror("boolean value expected"); }
+       | DNS_CACHE_MEM EQUAL NUMBER   { IF_DNS_CACHE(dns_cache_max_mem=$3); }
+       | DNS_CACHE_MEM error { yyerror("boolean value expected"); }
+       | DNS_CACHE_GC_INT EQUAL NUMBER   { IF_DNS_CACHE(dns_timer_interval=$3); }
+       | DNS_CACHE_GC_INT error { yyerror("boolean value expected"); }
+       | USE_DST_BLST EQUAL NUMBER   { IF_DST_BLACKLIST(use_dst_blacklist=$3); }
+       | USE_DST_BLST error { yyerror("boolean value expected"); }
+       | DST_BLST_MEM EQUAL NUMBER   { IF_DST_BLACKLIST(blst_max_mem=$3); }
+       | DST_BLST_MEM error { yyerror("boolean value expected"); }
+       | DST_BLST_TTL EQUAL NUMBER   { IF_DST_BLACKLIST(blst_timeout=$3); }
+       | DST_BLST_TTL error { yyerror("boolean value expected"); }
+       | DST_BLST_GC_INT EQUAL NUMBER { IF_DST_BLACKLIST(blst_timer_interval=$3);}
+       | DST_BLST_GC_INT error { yyerror("boolean value expected"); }
        | PORT EQUAL NUMBER   { port_no=$3; }
        | STAT EQUAL STRING {
                #ifdef STATS
@@ -1828,7 +1888,7 @@ static void warn(char* s)
 {
        LOG(L_WARN, "cfg. warning: (%d,%d-%d): %s\n", line, startcolumn,
                        column, s);
-       cfg_errors++;
+       cfg_warnings++;
 }
 
 static void yyerror(char* s)
diff --git a/clist.h b/clist.h
index 22c27ca..f10c104 100644 (file)
--- a/clist.h
+++ b/clist.h
 
 /* adds an entire sublist { s,e } (including s & e )
  * after head
+ * WARNING: clist_insert_sublist(head, n, n->prev) won't work,
+ *          same for clist_insert_sublist(head, n->next, n)
+ *  (macro!), use  e=n->prev; clist_insert_sublist(head, n, e, ...)
+ *  instead!
  */
 #define clist_insert_sublist(head, s, e, next, prev) \
        do{ \
@@ -59,6 +63,9 @@
 
 /* appends an entire sublist { s,e } (including s & e )
  * at the end of the list
+ * WARNING: clist_append_sublist(head, n, n->prev, ...) won't work,
+ *  (macro!), use  e=n->prev; clist_append_sublist(head, n, e, ...)
+ *  instead!
  */
 #define clist_append_sublist(head, s, e, next, prev) \
        do{ \
 
 
 
+
 /* remove sublist { s,e } (including s & e )
  * always, if start is the beginning of the list use
- * clist_rm_sublist(head->next, e, next, prev ) */
+ * clist_rm_sublist(head->next, e, next, prev )
+ * WARNING: clist_rm_sublist(n, n->prev, ...) won't work,
+ *  (macro!), use  e=n->prev; clist_rm_sublist(n, e, ...)
+ *  instead! */
 #define clist_rm_sublist(s, e, next, prev) \
        do{\
                (s)->prev->next=(e)->next;  \
index ce934f7..6118683 100644 (file)
 #include "tcp_info.h"
 #include "core_cmd.h"
 
+#ifdef USE_DNS_CACHE
+void dns_cache_debug(rpc_t* rpc, void* ctx);
+void dns_cache_debug_all(rpc_t* rpc, void* ctx);
+void dns_cache_mem_info(rpc_t* rpc, void* ctx);
+
+static const char* dns_cache_mem_info_doc[] = {
+       "dns cache memory info.",    /* Documentation string */
+       0                      /* Method signature(s) */
+};
+static const char* dns_cache_debug_doc[] = {
+       "dns debug  info.",    /* Documentation string */
+       0                      /* Method signature(s) */
+};
+
+static const char* dns_cache_debug_all_doc[] = {
+       "complete dns debug  dump",    /* Documentation string */
+       0                              /* Method signature(s) */
+};
+#endif
+#ifdef USE_DST_BLACKLIST
+void dst_blst_debug(rpc_t* rpc, void* ctx);
+void dst_blst_mem_info(rpc_t* rpc, void* ctx);
+
+static const char* dst_blst_mem_info_doc[] = {
+       "dst blacklist memory usage info.",  /* Documentation string */
+       0                                    /* Method signature(s) */
+};
+static const char* dst_blst_debug_doc[] = {
+       "dst blacklist  debug  info.",  /* Documentation string */
+       0                               /* Method signature(s) */
+};
+#endif
+
+
 
 #define MAX_CTIME_LEN 128
 
@@ -312,6 +346,15 @@ rpc_export_t core_rpc_methods[] = {
        {"core.kill",              core_kill,              core_kill_doc,              0        },
        {"core.shmmem",            core_shmmem,            core_shmmem_doc,            0        },
        {"core.tcp_info",          core_tcpinfo,           core_tcpinfo_doc,          0 },
+#ifdef USE_DNS_CACHE
+       {"dns.mem_info",          dns_cache_mem_info,     dns_cache_mem_info_doc,     0 },
+       {"dns.debug",          dns_cache_debug,           dns_cache_debug_doc,        0 },
+       {"dns.debug_all",      dns_cache_debug_all,       dns_cache_debug_all_doc,        0     },
+#endif
+#ifdef USE_DST_BLACKLIST
+       {"dst_blacklist.mem_info",  dst_blst_mem_info,     dst_blst_mem_info_doc,     0 },
+       {"dst_blacklist.debug",    dst_blst_debug,         dst_blst_debug_doc,        0 },
+#endif
        {0, 0, 0, 0}
 };
 
diff --git a/dns_cache.c b/dns_cache.c
new file mode 100644 (file)
index 0000000..6231c1f
--- /dev/null
@@ -0,0 +1,2436 @@
+/*
+ * $Id$
+ *
+ * resolver related functions
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-07-13  created by andrei
+ */
+
+#ifdef USE_DNS_CACHE
+
+#include "globals.h"
+#include "dns_cache.h"
+#include "dns_wrappers.h"
+#include "mem/shm_mem.h"
+#include "hashes.h"
+#include "clist.h"
+#include "locking.h"
+#include "atomic_ops.h"
+#include "ut.h"
+#include "timer.h"
+#include "timer_ticks.h"
+#include "error.h"
+#include "rpc.h"
+
+
+
+#define DNS_CACHE_DEBUG /* extra sanity checks and debugging */
+
+
+#ifndef MAX
+       #define MAX(a,b) ( ((a)>(b))?(a):(b))
+#endif
+
+#define MAX_DNS_RECORDS 255  /* maximum dns records number  received in a 
+                                                          dns answer*/
+
+#define DNS_HASH_SIZE  1024 /* must be <= 65535 */
+#define DEFAULT_DNS_NEG_CACHE_TTL 60 /* 1 min. */
+#define DEFAULT_DNS_CACHE_MIN_TTL 0 /* (disabled) */
+#define DEFAULT_DNS_CACHE_MAX_TTL ((unsigned int)(-1)) /* (maxint) */
+#define DEFAULT_DNS_MAX_MEM 500 /* 500 Kb */
+#define DEFAULT_DNS_TIMER_INTERVAL 120  /* 2 min. */
+#define DNS_HE_MAX_ADDR 10  /* maxium addresses returne in a hostent struct */
+#define MAX_CNAME_CHAIN  10 
+
+
+static gen_lock_t* dns_hash_lock=0;
+static volatile unsigned int *dns_cache_mem_used=0; /* current mem. use */
+unsigned int dns_cache_max_mem=DEFAULT_DNS_MAX_MEM; /* maximum memory used for
+                                                                                                        the cached entries */
+unsigned int dns_neg_cache_ttl=DEFAULT_DNS_NEG_CACHE_TTL; /* neg. cache ttl */
+unsigned int dns_cache_max_ttl=DEFAULT_DNS_CACHE_MAX_TTL; /* maximum ttl */
+unsigned int dns_cache_min_ttl=DEFAULT_DNS_CACHE_MIN_TTL; /* minimum ttl */
+unsigned int dns_timer_interval=DEFAULT_DNS_TIMER_INTERVAL; /* in s */
+int dns_flags=0; /* default flags used for the  dns_*resolvehost 
+                    (compatibility wrappers) */
+
+#define LOCK_DNS_HASH()                lock_get(dns_hash_lock)
+#define UNLOCK_DNS_HASH()      lock_release(dns_hash_lock)
+
+#define FIX_TTL(t)  (((t)<dns_cache_min_ttl)?dns_cache_min_ttl: \
+                                               (((t)>dns_cache_max_ttl)?dns_cache_max_ttl:(t)))
+
+
+struct dns_hash_head{
+       struct dns_hash_entry* next;
+       struct dns_hash_entry* prev;
+};
+
+#ifdef DNS_LU_LST
+struct dns_lu_lst* dns_last_used_lst=0;
+#endif
+
+static struct dns_hash_head* dns_hash=0;
+
+
+static struct timer_ln* dns_timer_h=0;
+
+
+
+static const char* dns_str_errors[]={
+       "no error",
+       "no more records", /* not an error, but and end condition */
+       "unknown error",
+       "internal error",
+       "bad SRV entry",
+       "unresolvable SRV request",
+       "bad A or AAAA entry",
+       "unresovlable A or AAAA request",
+       "invalid ip in A or AAAA record",
+       "blacklisted ip",
+       "name too long ", /* try again with a shorter name */
+       "ip AF mismatch", /* address family mismatch */
+       "bug - critical error"
+};
+
+
+
+/* param: err (negative error number) */
+const char* dns_strerror(int err)
+{
+       err=-err;
+       if ((err>=0) && (err<sizeof(dns_str_errors)/sizeof(char*)))
+               return dns_str_errors[err];
+       return "bug -- bad error number";
+}
+
+
+
+/* "internal" only, don't use unless you really know waht you're doing */
+inline static void dns_destroy_entry(struct dns_hash_entry* e)
+{
+#ifdef DNS_CACHE_DEBUG
+       memset(e, 0, e->total_size);
+#endif
+       shm_free(e); /* nice having it in one block isn't it? :-) */
+}
+
+
+/* "internal" only, same as above, asumes shm_lock() held (tm optimization) */
+inline static void dns_destroy_entry_shm_unsafe(struct dns_hash_entry* e)
+{
+#ifdef DNS_CACHE_DEBUG
+       memset(e, 0, e->total_size);
+#endif
+       shm_free_unsafe(e); /* nice having it in one block isn't it? :-) */
+}
+
+
+
+/* dec. the internal refcnt and if 0 deletes the entry */
+void dns_hash_put(struct dns_hash_entry* e)
+{
+       if(e && atomic_dec_and_test(&e->refcnt)){
+               /* atomic_sub_long(dns_cache_total_used, e->total_size); */
+               dns_destroy_entry(e);
+       }
+}
+
+
+
+/* same as above but uses dns_destroy_unsafe (assumes shm_lock held -- tm
+ *  optimization) */
+void dns_hash_put_shm_unsafe(struct dns_hash_entry* e)
+{
+       if(e && atomic_dec_and_test(&e->refcnt)){
+               /* atomic_sub_long(dns_cache_total_used, e->total_size); */
+               dns_destroy_entry_shm_unsafe(e);
+       }
+}
+
+
+inline static int dns_cache_clean(unsigned int no, int expired_only);
+inline static int dns_cache_free_mem(unsigned int target, int expired_only);
+
+static ticks_t dns_timer(ticks_t ticks, struct timer_ln* tl, void* data)
+{
+       if (*dns_cache_mem_used>12*(dns_cache_max_mem/16)){ /* ~ 75% used */
+               dns_cache_free_mem(dns_cache_max_mem/2, 1); 
+       }else{
+               dns_cache_clean(-1, 1); /* all the table, only expired entries */
+               /* TODO: better strategy? */
+       }
+       return (ticks_t)(-1);
+}
+
+
+
+void destroy_dns_cache()
+{
+       if (dns_timer_h){
+               timer_del(dns_timer_h);
+               timer_free(dns_timer_h);
+               dns_timer_h=0;
+       }
+       if (dns_hash_lock){
+               lock_destroy(dns_hash_lock);
+               lock_dealloc(dns_hash_lock);
+               dns_hash_lock=0;
+       }
+       if (dns_hash){
+               shm_free(dns_hash);
+               dns_hash=0;
+       }
+#ifdef DNS_LU_LST
+       if (dns_last_used_lst){
+               shm_free(dns_last_used_lst);
+               dns_last_used_lst=0;
+       }
+#endif
+       if (dns_cache_mem_used){
+               shm_free((void*)dns_cache_mem_used);
+               dns_cache_mem_used=0;
+       }
+}
+
+
+
+int init_dns_cache()
+{
+       int r;
+       int ret;
+       
+       ret=0;
+       /* sanity check */
+       if (E_DNS_CRITICAL>=sizeof(dns_str_errors)/sizeof(char*)){
+               LOG(L_CRIT, "BUG: dns_cache_init: bad dns error table\n");
+               ret=E_BUG;
+               goto error;
+       }
+       dns_cache_mem_used=shm_malloc(sizeof(*dns_cache_mem_used));
+       if (dns_cache_mem_used==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+#ifdef DNS_LU_LST
+       dns_last_used_lst=shm_malloc(sizeof(*dns_last_used_lst));
+       if (dns_last_used_lst==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       clist_init(dns_last_used_lst, next, prev);
+#endif
+       dns_hash=shm_malloc(sizeof(struct dns_hash_head)*DNS_HASH_SIZE);
+       if (dns_hash==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       for (r=0; r<DNS_HASH_SIZE; r++)
+               clist_init(&dns_hash[r], next, prev);
+       
+       dns_hash_lock=lock_alloc();
+       if (dns_hash_lock==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       if (lock_init(dns_hash_lock)==0){
+               lock_dealloc(dns_hash_lock);
+               dns_hash_lock=0;
+               ret=-1;
+               goto error;
+       }
+       
+       /* fix options */
+       dns_cache_max_mem<<=10; /* Kb */ /* TODO: test with 0 */
+       /* fix flags */
+       if (dns_try_ipv6==0){
+               dns_flags|=DNS_IPV4_ONLY;
+       }
+       if (dns_flags & DNS_IPV4_ONLY){
+               dns_flags&=~(DNS_IPV6_ONLY|DNS_IPV6_FIRST);
+       }
+                       ;
+       dns_timer_h=timer_alloc();
+       if (dns_timer_h==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       if (dns_timer_interval){
+               timer_init(dns_timer_h, dns_timer, 0, 0); /* "slow" timer */
+               if (timer_add(dns_timer_h, S_TO_TICKS(dns_timer_interval))<0){
+                       LOG(L_CRIT, "BUG: dns_cache_init: failed to add the timer\n");
+                       timer_free(dns_timer_h);
+                       dns_timer_h=0;
+                       goto error;
+               }
+       }
+       
+       return 0;
+error:
+       destroy_dns_cache();
+       return ret;
+}
+
+
+/* hash function, based on get_hash1_raw, but case insensitive
+ * type is not used (obsolete)
+ * returns the hash value
+ */
+inline static unsigned int dns_hash_no(char* s, int len, int type)
+{
+       char* p;
+       char* end;
+       
+       register unsigned v;
+       register unsigned h;
+       
+       h=0;
+       
+       hash_update_str(s, s+len, p, v, h);
+       end=s+len;
+       for (p=s; p<=(end-4); p+=4){
+               v=((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3])|0x20202020;
+               h+=v^(v>>3);
+       }
+       v=0;
+       for (;p<end; p++){ v<<=8; v+=*p|0x20;}
+       h+=v^(v>>3);
+       return hash_finish(h) % DNS_HASH_SIZE;
+}
+
+
+
+#ifdef DNS_CACHE_DEBUG
+#define DEBUG_LU_LST
+#ifdef DEBUG_LU_LST
+
+#include <stdlib.h> /* abort() */
+#define check_lu_lst(l) ((((l)->next==(l)) || ((l)->prev==(l))) && \
+                                                       ((l)!=dns_last_used_lst))
+
+#define dbg_lu_lst(txt, l) \
+               LOG(L_CRIT, "BUG: %s: crt(%p, %p, %p)," \
+                                       " prev(%p, %p, %p), next(%p, %p, %p)\n", txt, \
+                                       (l), (l)->next, (l)->prev, \
+                                       (l)->prev, (l)->prev->next, (l)->prev->prev, \
+                                       (l)->next, (l)->next->next, (l)->next->prev \
+                               ) 
+
+#define debug_lu_lst( txt, l) \
+       do{ \
+               if (check_lu_lst((l))){  \
+                       dbg_lu_lst(txt  " crt:", (l)); \
+                       abort(); \
+               } \
+               if (check_lu_lst((l)->next)){ \
+                       dbg_lu_lst(txt  " next:",  (l)); \
+                       abort(); \
+               } \
+               if (check_lu_lst((l)->prev)){ \
+                       dbg_lu_lst(txt  " prev:", (l)); \
+                       abort(); \
+               } \
+       }while(0)
+
+#endif
+#endif /* DNS_CACHE_DEBUG */
+
+
+/* must be called with the DNS_LOCK hold
+ * remove and entry from the hash, dec. its refcnt and if not referenced
+ * anymore deletes it */
+void _dns_hash_remove(struct dns_hash_entry* e)
+{
+       clist_rm(e, next, prev);
+#ifdef DNS_CACHE_DEBUG
+       e->next=e->prev=0;
+#endif
+#ifdef DNS_LU_LST
+#ifdef DEBUG_LU_LST
+       debug_lu_lst("_dns_hash_remove: pre rm:", &e->last_used_lst);
+#endif
+       clist_rm(&e->last_used_lst, next, prev);
+#ifdef DEBUG_LU_LST
+       debug_lu_lst("_dns_hash_remove: post rm:", &e->last_used_lst);
+#endif
+#ifdef DNS_CACHE_DEBUG
+       e->last_used_lst.next=e->last_used_lst.prev=0;
+#endif
+#endif
+       *dns_cache_mem_used-=e->total_size;
+       dns_hash_put(e);
+}
+
+
+
+/* non locking  version (the dns hash must _be_ locked externally)
+ * returns 0 when not found, or the entry on success (an entry with a
+ * similar name but with a CNAME type will always match).
+ * it doesn't increase the internal refcnt
+ * returns the entry when found, 0 when not found and sets *err to !=0
+ *  on error (e.g. recursive cnames)
+ * WARNING: - internal use only
+ *          - always check if the returned entry type is CNAME */
+inline static struct dns_hash_entry* _dns_hash_find(str* name, int type,
+                                                                                                               int* h, int* err)
+{
+       struct dns_hash_entry* e;
+       struct dns_hash_entry* tmp;
+       struct dns_hash_entry* ret;
+       ticks_t now;
+       int cname_chain;
+       str cname;
+       
+       cname_chain=0;
+       ret=0;
+       now=get_ticks_raw();
+       *err=0;
+again:
+       *h=dns_hash_no(name->s, name->len, type);
+       DBG("dns_hash_find(%.*s(%d), %d), h=%d\n", name->len, name->s,
+                                                                                               name->len, type, *h);
+       clist_foreach_safe(&dns_hash[*h], e, tmp, next){
+               /* automatically remove expired elements */
+               if ((s_ticks_t)(now-e->expire)>=0){
+                               _dns_hash_remove(e);
+               }else if ((e->type==type) && (e->name_len==name->len) &&
+                       (strncasecmp(e->name, name->s, e->name_len)==0)){
+                       e->last_used=now;
+#ifdef DNS_LU_LST
+                       /* add it at the end */
+#ifdef DEBUG_LU_LST
+                       debug_lu_lst("_dns_hash_find: pre rm:", &e->last_used_lst);
+#endif
+                       clist_rm(&e->last_used_lst, next, prev);
+                       clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
+#ifdef DEBUG_LU_LST
+                       debug_lu_lst("_dns_hash_find: post append:", &e->last_used_lst);
+#endif
+#endif
+                       return e;
+               }else if ((e->type==T_CNAME) && (e->name_len==name->len) &&
+                       (strncasecmp(e->name, name->s, e->name_len)==0)){
+                       e->last_used=now;
+#ifdef DNS_LU_LST
+                       /* add it at the end */
+#ifdef DEBUG_LU_LST
+                       debug_lu_lst("_dns_hash_find: cname: pre rm:", &e->last_used_lst);
+#endif
+                       clist_rm(&e->last_used_lst, next, prev);
+                       clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
+#ifdef DEBUG_LU_LST
+                       debug_lu_lst("_dns_hash_find: cname: post append:",
+                                                       &e->last_used_lst);
+#endif
+#endif         
+                       ret=e; /* if this is an unfinished cname chain, we try to
+                                         return the last cname */
+                       /* this is a cname => retry using its value */
+                       if (cname_chain> MAX_CNAME_CHAIN){
+                               LOG(L_ERR, "ERROR: _dns_hash_find: cname chain too long "
+                                               "or recursive (\"%.*s\")\n", name->len, name->s);
+                               ret=0; /* error*/
+                               *err=-1;
+                               break;
+                       }
+                       cname_chain++;
+                       cname.s=((struct cname_rdata*)e->rr_lst->rdata)->name;
+                       cname.len= ((struct cname_rdata*)e->rr_lst->rdata)->name_len;
+                       name=&cname;
+                       goto again;
+               }
+       }
+       return ret;
+}
+
+
+
+/* frees cache entries, if expired_only=0 only expired entries will be 
+ * removed, else all of them
+ * it will process maximum no entries (to process all of them use -1)
+ * returns the number of deleted entries
+ * This should be called from a timer process*/
+inline static int dns_cache_clean(unsigned int no, int expired_only)
+{
+       struct dns_hash_entry* e;
+       ticks_t now;
+       unsigned int n;
+       unsigned int deleted;
+#ifdef DNS_LU_LST
+       struct dns_lu_lst* l;
+       struct dns_lu_lst* tmp;
+#else
+       struct dns_hash_entry* t;
+       unsigned int h;
+       static unsigned int start=0;
+#endif
+       
+       n=0;
+       deleted=0;
+       now=get_ticks_raw();
+       LOCK_DNS_HASH();
+#ifdef DNS_LU_LST
+       clist_foreach_safe(dns_last_used_lst, l, tmp, next){
+               e=(struct dns_hash_entry*)(((char*)l)-
+                               (char*)&((struct dns_hash_entry*)(0))->last_used_lst);
+               if (!expired_only || ((s_ticks_t)(now-e->expire)>=0)){
+                               _dns_hash_remove(e);
+                               deleted++;
+               }
+               n++;
+               if (n>=no) break;
+       }
+#else
+       for(h=start; h!=(start+DNS_HASH_SIZE); h++){
+               clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
+                       if  ((s_ticks_t)(now-e->expire)>=0){
+                               _dns_hash_remove(e);
+                               deleted++;
+                       }
+                       n++;
+                       if (n>=no) break;
+               }
+       }
+       /* not fair, but faster then random() */
+       if (!expired_only){
+               for(h=start; h!=(start+DNS_HASH_SIZE); h++){
+                       clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
+                               if  ((s_ticks_t)(now-e->expire)>=0){
+                                       _dns_hash_remove(e);
+                                       deleted++;
+                               }
+                               n++;
+                               if (n>=no) goto skip;
+                       }
+               }
+       }
+skip:
+       start=h;
+#endif
+       UNLOCK_DNS_HASH();
+       return deleted;
+}
+
+
+
+/* frees cache entries, if expired_only=0 only expired entries will be 
+ * removed, else all of them
+ * it will stop when the dns cache used memory reaches target (to process all 
+ * of them use 0)
+ * returns the number of deleted entries */
+inline static int dns_cache_free_mem(unsigned int target, int expired_only)
+{
+       struct dns_hash_entry* e;
+       ticks_t now;
+       unsigned int deleted;
+#ifdef DNS_LU_LST
+       struct dns_lu_lst* l;
+       struct dns_lu_lst* tmp;
+#else
+       struct dns_hash_entry* t;
+       unsigned int h;
+       static unsigned int start=0;
+#endif
+       
+       deleted=0;
+       now=get_ticks_raw();
+       LOCK_DNS_HASH();
+#ifdef DNS_LU_LST
+       clist_foreach_safe(dns_last_used_lst, l, tmp, next){
+               if (*dns_cache_mem_used<=target) break;
+               e=(struct dns_hash_entry*)(((char*)l)-
+                               (char*)&((struct dns_hash_entry*)(0))->last_used_lst);
+               if (!expired_only || ((s_ticks_t)(now-e->expire)>=0)){
+                               _dns_hash_remove(e);
+                               deleted++;
+               }
+       }
+#else
+       for(h=start; h!=(start+DNS_HASH_SIZE); h++){
+               clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
+                       if (*dns_cache_mem_used<=target) 
+                               goto skip;
+                       if  ((s_ticks_t)(now-e->expire)>=0){
+                               _dns_hash_remove(e);
+                               deleted++;
+                       }
+               }
+       }
+       /* not fair, but faster then random() */
+       if (!expired_only){
+               for(h=start; h!=(start+DNS_HASH_SIZE); h++){
+                       clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
+                               if (*dns_cache_mem_used<=target) 
+                                       goto skip;
+                               if  ((s_ticks_t)(now-e->expire)>=0){
+                                       _dns_hash_remove(e);
+                                       deleted++;
+                               }
+                       }
+               }
+       }
+skip:
+       start=h;
+#endif
+       UNLOCK_DNS_HASH();
+       return deleted;
+}
+
+
+
+/* locking  version (the dns hash must _not_be locked externally)
+ * returns 0 when not found, the searched entry on success (with CNAMEs
+ *  followed) or the last CNAME entry from an unfinished CNAME chain, 
+ *  if the search matches a CNAME. On error sets *err (e.g. recursive CNAMEs).
+ * it increases the internal refcnt => when finished dns_hash_put() must
+ *  be called on the returned entry
+ *  WARNING: - the return might be a CNAME even if type!=CNAME, see above */
+inline static struct dns_hash_entry* dns_hash_get(str* name, int type, int* h,
+                                                                                                       int* err)
+{
+       struct dns_hash_entry* e;
+       
+       LOCK_DNS_HASH();
+       e=_dns_hash_find(name, type, h, err);
+       if (e){
+               atomic_inc(&e->refcnt);
+       }
+       UNLOCK_DNS_HASH();
+       return e;
+}
+
+
+
+/* adds a fully created and init. entry (see dns_cache_mk_entry()) to the hash
+ * table
+ * returns 0 on success, -1 on error */
+inline static int dns_cache_add(struct dns_hash_entry* e)
+{
+       int h;
+       
+       /* check space */
+       /* atomic_add_long(dns_cache_total_used, e->size); */
+       if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
+               LOG(L_WARN, "WARNING: dns_cache_add: cache full, trying to free...\n");
+               /* free ~ 12% of the cache */
+               dns_cache_free_mem(*dns_cache_mem_used/16*14, 1);
+               if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
+                       LOG(L_ERR, "ERROR: dns_cache_add: max. cache mem size exceeded\n");
+                       return -1;
+               }
+       }
+       atomic_inc(&e->refcnt);
+       h=dns_hash_no(e->name, e->name_len, e->type);
+       DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
+                       e->name_len, e->name, e->name_len, e->type, e->err_flags, h);
+       LOCK_DNS_HASH();
+               *dns_cache_mem_used+=e->total_size; /* no need for atomic ops, written
+                                                                                only from within a lock */
+               clist_append(&dns_hash[h], e, next, prev);
+#ifdef DNS_LU_LST
+               clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
+#endif
+       UNLOCK_DNS_HASH();
+       return 0;
+}
+
+
+
+/* same as above, but it must be called with the dns hash lock held
+ * returns 0 on success, -1 on error */
+inline static int dns_cache_add_unsafe(struct dns_hash_entry* e)
+{
+       int h;
+       
+       /* check space */
+       /* atomic_add_long(dns_cache_total_used, e->size); */
+       if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
+               LOG(L_WARN, "WARNING: dns_cache_add: cache full, trying to free...\n");
+               /* free ~ 12% of the cache */
+               UNLOCK_DNS_HASH();
+               dns_cache_free_mem(*dns_cache_mem_used/16*14, 1);
+               LOCK_DNS_HASH();
+               if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
+                       LOG(L_ERR, "ERROR: dns_cache_add: max. cache mem size exceeded\n");
+                       return -1;
+               }
+       }
+       atomic_inc(&e->refcnt);
+       h=dns_hash_no(e->name, e->name_len, e->type);
+       DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
+                       e->name_len, e->name, e->name_len, e->type, e->err_flags, h);
+       *dns_cache_mem_used+=e->total_size; /* no need for atomic ops, written
+                                                                                only from within a lock */
+       clist_append(&dns_hash[h], e, next, prev);
+#ifdef DNS_LU_LST
+       clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
+#endif
+       return 0;
+}
+
+
+
+/* creates a "negative" entry which will be valid for ttl seconds */
+inline static struct dns_hash_entry* dns_cache_mk_bad_entry(str* name,
+                                                                                                                       int type,
+                                                                                                                       int ttl,
+                                                                                                                       int flags)
+{
+       struct dns_hash_entry* e;
+       int size;
+       ticks_t now;
+       
+       DBG("dns_cache_mk_bad_entry(%.*s, %d, %d, %d)\n", name->len, name->s,
+                                                                       type, ttl, flags);
+       size=sizeof(struct dns_hash_entry)+name->len-1+1;
+       e=shm_malloc(size);
+       if (e==0){
+               LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
+               return 0;
+       }
+       memset(e, 0, size); /* init with 0*/
+       e->total_size=size;
+       e->name_len=name->len;
+       e->type=type;
+       now=get_ticks_raw();
+       e->last_used=now;
+       e->expire=now+S_TO_TICKS(ttl);
+       memcpy(e->name, name->s, name->len);
+       e->err_flags=flags;
+       return e;
+}
+
+
+
+/* create a a/aaaa hash entry from a name and ip address
+ * returns 0 on error */
+inline static struct dns_hash_entry* dns_cache_mk_ip_entry(str* name,
+                                                                                                                       struct ip_addr* ip)
+{
+       struct dns_hash_entry* e;
+       int size;
+       ticks_t now;
+       
+       /* everything is allocated in one block: dns_hash_entry + name +
+        * + dns_rr + rdata;  dns_rr must start at an aligned adress,
+        * hence we need to round dns_hash_entry+name size to a sizeof(long)
+        * multiple.
+        * Memory image:
+        * struct dns_hash_entry
+        * name (name_len+1 bytes)
+        * padding to multiple of sizeof(long)
+        * dns_rr
+        * rdata  (no padding needed, since for ip is just an array of chars)
+         */
+       size=ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1)+ 
+                       sizeof(struct dns_rr)+ ip->len;
+       e=shm_malloc(size);
+       if (e==0){
+               LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
+               return 0;
+       }
+       memset(e, 0, size); /* init with 0*/
+       e->total_size=size;
+       e->name_len=name->len;
+       e->type=(ip->af==AF_INET)?T_A:T_AAAA;
+       now=get_ticks_raw();
+       e->last_used=now;
+       e->expire=now-1; /* maximum expire */
+       memcpy(e->name, name->s, name->len); /* memset makes sure is 0-term. */
+       e->rr_lst=(void*)((char*)e+
+                               ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1));
+       e->rr_lst->rdata=(void*)((char*)e->rr_lst+sizeof(struct dns_rr));
+       e->rr_lst->expire=now-1; /* maximum expire */
+       /* no need to align rr_lst->rdata for a or aaaa records */
+       memcpy(e->rr_lst->rdata, ip->u.addr, ip->len);
+       return e;
+}
+
+
+
+/* create a dns hash entry from a name and a rdata list (pkg_malloc'ed)
+ * (it will use only the type records with the name "name" from the
+ *  rdata list with one exception: if a matching CNAME with the same
+ *  name is found, the search will stop and this will be the record used)
+ * returns 0 on error and removes the used elements from the rdata list*/
+inline static struct dns_hash_entry* dns_cache_mk_rd_entry(str* name, int type,
+                                                                                                               struct rdata** rd_lst)
+{
+       struct dns_hash_entry* e;
+       struct dns_rr* rr;
+       struct dns_rr** tail_rr;
+       struct rdata** p;
+       struct rdata* tmp_lst;
+       struct rdata** tail;
+       struct rdata* l;
+       int size;
+       ticks_t now;
+       unsigned int max_ttl;
+       unsigned int ttl;
+       
+#define rec_matches(rec, t, n) /*(struct rdata* record, int type, str* name)*/\
+       (       ((rec)->name_len==(n)->len) && ((rec)->type==(t)) && \
+               (strncasecmp((rec)->name, (n)->s, (n)->len)==0))
+       /* init */
+       tmp_lst=0;
+       tail=&tmp_lst;
+       
+       
+       /* everything is allocated in one block: dns_hash_entry + name +
+        * + dns_rr + rdata_raw+ ....;  dns_rr must start at an aligned adress,
+        * hence we need to round dns_hash_entry+name size to a sizeof(long)
+        * multiple. If rdata type requires it, rdata_raw might need to be also
+        * aligned.
+        * Memory image:
+        * struct dns_hash_entry  (e)
+        * name (name_len+1 bytes)  (&e->name[0])
+        * padding to multiple of sizeof(char*)
+        * dns_rr1 (e->rr_lst)
+        * possible padding: no padding for a_rdata or aaaa_rdata, 
+        *                   multipe of sizeof(short) for srv_rdata,
+        *                   multiple of sizeof(long) for naptr_rdata and others
+        * dns_rr1->rdata  (e->rr_lst->rdata)
+        * padding to multipe of sizeof long
+        * dns_rr2 (e->rr_lst->next)
+        * ....
+        *
+        */
+       size=0;
+       if (*rd_lst==0)
+               return 0;
+       /* find the first matching rr, if it's a CNAME use CNAME as type,
+        * if not continue with the original type */
+       for(p=rd_lst; *p; p=&(*p)->next){
+               if (((*p)->name_len==name->len) &&
+                               (((*p)->type==type) || ((*p)->type==T_CNAME)) &&
+                               (strncasecmp((*p)->name, name->s, name->len)==0)){
+                       type=(*p)->type;
+                       break;
+               }
+       }
+       /* continue, we found the type we are looking for */
+       switch(type){
+               case T_A:
+                       for(; *p;){
+                               if (!rec_matches((*p), type, name)){ 
+                                       /* skip this record */
+                                       p=&(*p)->next; /* advance */
+                                       continue;
+                               }
+                               size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                               sizeof(struct a_rdata));
+                               /* add it to our tmp. lst */
+                               *tail=*p;
+                               tail=&(*p)->next;
+                               /* detach it from the rd list */
+                               *p=(*p)->next;
+                               /* don't advance p, because the crt. elem. has
+                                * just been elimintated */
+                       }
+                       break;
+               case T_AAAA:
+                       for(; *p;){
+                               if (!rec_matches((*p), type, name)){ 
+                                       /* skip this record */
+                                       p=&(*p)->next; /* advance */
+                                       continue;
+                               }
+                               /* no padding */
+                               size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                                       sizeof(struct aaaa_rdata));
+                               /* add it to our tmp. lst */
+                               *tail=*p;
+                               tail=&(*p)->next;
+                               /* detach it from the rd list */
+                               *p=(*p)->next;
+                               /* don't advance p, because the crt. elem. has
+                                * just been elimintated */
+                       }
+                       break;
+               case T_SRV:
+                       for(; *p;){
+                               if (!rec_matches((*p), type, name)){ 
+                                       /* skip this record */
+                                       p=&(*p)->next; /* advance */
+                                       continue;
+                               }
+                               /* padding to short */
+                               size+=ROUND_POINTER(ROUND_SHORT(sizeof(struct dns_rr))+
+                                               SRV_RDATA_SIZE(*(struct srv_rdata*)(*p)->rdata));
+                               /* add it to our tmp. lst */
+                               *tail=*p;
+                               tail=&(*p)->next;
+                               /* detach it from the rd list */
+                               *p=(*p)->next;
+                               /* don't advance p, because the crt. elem. has
+                                * just been elimintated */
+                       }
+                       break;
+               case T_NAPTR:
+                       for(; *p;){
+                               if (!rec_matches((*p), type, name)){ 
+                                       /* skip this record */
+                                       p=&(*p)->next; /* advance */
+                                       continue;
+                               }
+                               /* padding to char* */
+                               size+=ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
+                                               NAPTR_RDATA_SIZE(*(struct naptr_rdata*)(*p)->rdata));
+                               /* add it to our tmp. lst */
+                               *tail=*p;
+                               tail=&(*p)->next;
+                               /* detach it from the rd list */
+                               *p=(*p)->next;
+                               /* don't advance p, because the crt. elem. has
+                                * just been elimintated */
+                       }
+                       break;
+               case T_CNAME:
+                       for(; *p;){
+                               if (!rec_matches((*p), type, name)){ 
+                                       /* skip this record */
+                                       p=&(*p)->next; /* advance */
+                                       continue;
+                               }
+                               /* no padding */
+                               size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                               CNAME_RDATA_SIZE(*(struct cname_rdata*)(*p)->rdata));
+                               /* add it to our tmp. lst */
+                               *tail=*p;
+                               tail=&(*p)->next;
+                               /* detach it from the rd list */
+                               *p=(*p)->next;
+                               /* don't advance p, because the crt. elem. has
+                                * just been elimintated */
+                       }
+                       break;
+               default:
+                       LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: type %d not "
+                                                       "supported\n", type);
+                       /* we don't know what to do with it, so don't
+                        * add it to the tmp_lst */
+                       return 0; /* error */
+       }
+       *tail=0; /* mark the end of our tmp_lst */
+       if (size==0){
+               DBG("dns_cache_mk_rd_entry: entry %.*s (%d) not found\n",
+                               name->len, name->s, type);
+               return 0;
+       }
+       /* compute size */
+       size+=ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1);
+       e=shm_malloc(size);
+       if (e==0){
+               LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
+               return 0;
+       }
+       memset(e, 0, size); /* init with 0 */
+       e->total_size=size;
+       e->name_len=name->len;
+       e->type=type;
+       now=get_ticks_raw();
+       e->last_used=now;
+       memcpy(e->name, name->s, name->len); /* memset makes sure is 0-term. */
+       e->rr_lst=(struct dns_rr*)((char*)e+
+                               ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1));
+       tail_rr=&(e->rr_lst);
+       rr=e->rr_lst;
+       max_ttl=0;
+       /* copy the actual data */
+       switch(type){
+               case T_A:
+                       for(l=tmp_lst; l; l=l->next){
+                               ttl=FIX_TTL(l->ttl);
+                               rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               max_ttl=MAX(max_ttl, ttl);
+                               rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
+                               memcpy(rr->rdata, l->rdata, sizeof(struct a_rdata));
+                               rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
+                                                       sizeof(struct a_rdata)));
+                               tail_rr=&(rr->next);
+                               rr=rr->next;
+                       }
+                       break;
+               case T_AAAA:
+                       for(l=tmp_lst; l; l=l->next){
+                               ttl=FIX_TTL(l->ttl);
+                               rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               max_ttl=MAX(max_ttl, ttl);
+                               rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
+                               memcpy(rr->rdata, l->rdata, sizeof(struct aaaa_rdata));
+                               rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
+                                                       sizeof(struct aaaa_rdata)));
+                               tail_rr=&(rr->next);
+                               rr=rr->next;
+                       }
+                       break;
+               case T_SRV:
+                       for(l=tmp_lst; l; l=l->next){
+                               ttl=FIX_TTL(l->ttl);
+                               rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               max_ttl=MAX(max_ttl, ttl);
+                               rr->rdata=(void*)((char*)rr+
+                                                               ROUND_SHORT(sizeof(struct dns_rr)));
+                               /* copy the whole srv_rdata block*/
+                               memcpy(rr->rdata, l->rdata, 
+                                               SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata) );
+                               rr->next=(void*)((char*)rr+
+                                                       ROUND_POINTER( ROUND_SHORT(sizeof(struct dns_rr))+
+                                                                               SRV_RDATA_SIZE(
+                                                                                       *(struct srv_rdata*)l->rdata)));
+                               tail_rr=&(rr->next);
+                               rr=rr->next;
+                       }
+                       break;
+               case T_NAPTR:
+                       for(l=tmp_lst; l; l=l->next){
+                               ttl=FIX_TTL(l->ttl);
+                               rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               max_ttl=MAX(max_ttl, ttl);
+                               rr->rdata=(void*)((char*)rr+
+                                                               ROUND_POINTER(sizeof(struct dns_rr)));
+                               /* copy the whole srv_rdata block*/
+                               memcpy(rr->rdata, l->rdata, 
+                                               NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata) );
+                               /* adjust the string pointer */
+                               ((struct naptr_rdata*)rr->rdata)->flags=
+                                       translate_pointer((char*)rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->flags));
+                               ((struct naptr_rdata*)rr->rdata)->services=
+                                       translate_pointer((char*)rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->services));
+                               ((struct naptr_rdata*)rr->rdata)->regexp=
+                                       translate_pointer((char*)rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->regexp));
+                               ((struct naptr_rdata*)rr->rdata)->repl=
+                                       translate_pointer((char*)rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->repl));
+                               rr->next=(void*)((char*)rr+
+                                                       ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
+                                                                               NAPTR_RDATA_SIZE(
+                                                                                       *(struct naptr_rdata*)l->rdata)));
+                               tail_rr=&(rr->next);
+                       }
+                       break;
+               case T_CNAME:
+                       for(l=tmp_lst; l; l=l->next){
+                               ttl=FIX_TTL(l->ttl);
+                               rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               max_ttl=MAX(max_ttl, ttl);
+                               rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
+                               memcpy(rr->rdata, l->rdata, 
+                                                       CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
+                               rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
+                                                       CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata)));
+                               tail_rr=&(rr->next);
+                               rr=rr->next;
+                       }
+                       break;
+               default:
+                       /* do nothing */
+                       LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: create: type %d not "
+                                                       "supported\n", type);
+                               ;
+       }
+       *tail_rr=0; /* terminate the list */
+       e->expire=now+S_TO_TICKS(max_ttl);
+       free_rdata_list(tmp_lst);
+       return e;
+}
+
+
+
+/* structure used only inside dns_cache_mk_rd_entry2 to break
+ *  the list of records into records of the same type */
+struct tmp_rec{
+       struct rdata* rd;
+       struct dns_hash_entry* e;
+       struct dns_rr* rr;
+       struct dns_rr** tail_rr;
+       int max_ttl;
+       int size;
+};
+
+
+
+/* create several dns hash entries from a list of rdata structs
+ * returns 0 on error */
+inline static struct dns_hash_entry* dns_cache_mk_rd_entry2(struct rdata* rd)
+{
+       struct rdata* l;
+       ticks_t now;
+       struct tmp_rec rec[MAX_DNS_RECORDS];
+       int rec_idx[MAX_DNS_RECORDS];
+       int r, i;
+       int no_records; /* number of different records */
+       unsigned int ttl;
+       
+       
+       no_records=0; 
+       rec[0].e=0;
+       /* everything is allocated in one block: dns_hash_entry + name +
+        * + dns_rr + rdata_raw+ ....;  dns_rr must start at an aligned adress,
+        * hence we need to round dns_hash_entry+name size to a sizeof(long)
+        * multiple. If rdata type requires it, rdata_raw might need to be also
+        * aligned.
+        * Memory image:
+        * struct dns_hash_entry  (e)
+        * name (name_len+1 bytes)  (&e->name[0])
+        * padding to multiple of sizeof(char*)
+        * dns_rr1 (e->rr_lst)
+        * possible padding: no padding for a_rdata or aaaa_rdata, 
+        *                   multipe of sizeof(short) for srv_rdata,
+        *                   multiple of sizeof(long) for naptr_rdata and others
+        * dns_rr1->rdata  (e->rr_lst->rdata)
+        * padding to multipe of sizeof long
+        * dns_rr2 (e->rr_lst->next)
+        * ....
+        *
+        */
+       /* compute size */
+       for(l=rd, i=0; l && (i<MAX_DNS_RECORDS); l=l->next, i++){
+               for (r=0; r<no_records; r++){
+                       if ((l->type==rec[r].rd->type) && 
+                                       (l->name_len==rec[r].rd->name_len)
+                               && (strncasecmp(l->name, rec[r].rd->name, l->name_len)==0)){
+                               /* found */
+                               goto found;
+                       }
+               }
+               /* not found, create new */
+               if (no_records<MAX_DNS_RECORDS){
+                       rec[r].rd=l;
+                       rec[r].e=0;
+                       rec[r].size=ROUND_POINTER(sizeof(struct dns_hash_entry)+
+                                                       rec[r].rd->name_len-1+1);
+                       no_records++;
+               }else{
+                       LOG(L_ERR, "ERROR: dns_cache_mk_rd_entry2: too many records: %d\n",
+                                               no_records);
+                       /* skip */
+                       continue;
+               }
+found:
+               rec_idx[i]=r;
+               switch(l->type){
+                       case T_A:
+                               /* no padding */
+                               rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                               sizeof(struct a_rdata));
+                               break;
+                       case T_AAAA:
+                               /* no padding */
+                               rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                                               sizeof(struct aaaa_rdata));
+                               break;
+                       case T_SRV:
+                               /* padding to short */
+                               rec[r].size+=ROUND_POINTER(ROUND_SHORT(sizeof(struct dns_rr))+
+                                                               SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata));
+                               break;
+                       case T_NAPTR:
+                                       /* padding to char* */
+                               rec[r].size+=ROUND_POINTER(ROUND_POINTER(
+                                                                                               sizeof(struct dns_rr))+
+                                                       NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata));
+                               break;
+                       case T_CNAME:
+                                       /* no padding */
+                               rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
+                                                       CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
+                               break;
+                       default:
+                               LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: type %d not "
+                                                       "supported\n", l->type);
+               }
+       }
+       
+       now=get_ticks_raw();
+       /* alloc & init the entries */
+       for (r=0; r<no_records; r++){
+               rec[r].e=shm_malloc(rec[r].size);
+               if (rec[r].e==0){
+                       LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
+                       goto error;
+               }
+               memset(rec[r].e, 0, rec[r].size); /* init with 0*/
+               rec[r].e->total_size=rec[r].size;
+               rec[r].e->name_len=rec[r].rd->name_len;
+               rec[r].e->type=rec[r].rd->type;
+               rec[r].e->last_used=now;
+               /* memset makes sure is 0-term. */
+               memcpy(rec[r].e->name, rec[r].rd->name, rec[r].rd->name_len); 
+               rec[r].e->rr_lst=(struct dns_rr*)((char*)rec[r].e+
+                               ROUND_POINTER(sizeof(struct dns_hash_entry)+rec[r].e->name_len
+                                                                -1+1));
+               rec[r].tail_rr=&(rec[r].e->rr_lst);
+               rec[r].rr=rec[r].e->rr_lst;
+               rec[r].max_ttl=0;
+               /* link them in a list */
+               if (r==0){
+                       clist_init(rec[r].e, next, prev);
+               }else{
+                       clist_append(rec[0].e, rec[r].e, next, prev);
+               }
+       }
+       /* copy the actual data */
+       for(l=rd, i=0; l && (i<MAX_DNS_RECORDS); l=l->next, i++){
+               r=rec_idx[i];
+               ttl=FIX_TTL(l->ttl);
+               switch(l->type){
+                       case T_A:
+                               rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
+                               rec[r].rr->rdata=(void*)((char*)rec[r].rr+
+                                                                       sizeof(struct dns_rr));
+                               memcpy(rec[r].rr->rdata, l->rdata, sizeof(struct a_rdata));
+                               rec[r].rr->next=(void*)((char*)rec[r].rr+
+                                                                       ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                       sizeof(struct a_rdata)));
+                               rec[r].tail_rr=&(rec[r].rr->next);
+                               rec[r].rr=rec[r].rr->next;
+                               break;
+                       case T_AAAA:
+                               rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
+                               rec[r].rr->rdata=(void*)((char*)rec[r].rr+
+                                                                       sizeof(struct dns_rr));
+                               memcpy(rec[r].rr->rdata, l->rdata, sizeof(struct aaaa_rdata));
+                               rec[r].rr->next=(void*)((char*)rec[r].rr+
+                                                                       ROUND_POINTER(sizeof(struct dns_rr)+
+                                                                       sizeof(struct aaaa_rdata)));
+                               rec[r].tail_rr=&(rec[r].rr->next);
+                               rec[r].rr=rec[r].rr->next;
+                               break;
+                       case T_SRV:
+                               rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
+                               rec[r].rr->rdata=(void*)((char*)rec[r].rr+
+                                                               ROUND_SHORT(sizeof(struct dns_rr)));
+                               /* copy the whole srv_rdata block*/
+                               memcpy(rec[r].rr->rdata, l->rdata, 
+                                               SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata) );
+                               rec[r].rr->next=(void*)((char*)rec[r].rr+
+                                                       ROUND_POINTER( ROUND_SHORT(sizeof(struct dns_rr))+
+                                                                               SRV_RDATA_SIZE(
+                                                                                       *(struct srv_rdata*)l->rdata)));
+                               rec[r].tail_rr=&(rec[r].rr->next);
+                               rec[r].rr=rec[r].rr->next;
+                               break;
+                       case T_NAPTR:
+                               rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
+                               rec[r].rr->rdata=(void*)((char*)rec[r].rr+
+                                                               ROUND_POINTER(sizeof(struct dns_rr)));
+                               /* copy the whole srv_rdata block*/
+                               memcpy(rec[r].rr->rdata, l->rdata, 
+                                               NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata) );
+                               /* adjust the string pointer */
+                               ((struct naptr_rdata*)rec[r].rr->rdata)->flags=
+                                       translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->flags));
+                               ((struct naptr_rdata*)rec[r].rr->rdata)->services=
+                                       translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->services));
+                               ((struct naptr_rdata*)rec[r].rr->rdata)->regexp=
+                                       translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->regexp));
+                               ((struct naptr_rdata*)rec[r].rr->rdata)->repl=
+                                       translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
+                                                       (((struct naptr_rdata*)l->rdata)->repl));
+                               rec[r].rr->next=(void*)((char*)rec[r].rr+
+                                                       ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
+                                                                               NAPTR_RDATA_SIZE(
+                                                                                       *(struct naptr_rdata*)l->rdata)));
+                               rec[r].tail_rr=&(rec[r].rr->next);
+                               break;
+                       case T_CNAME:
+                               rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
+                               rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
+                               rec[r].rr->rdata=(void*)((char*)rec[r].rr
+                                                                       +sizeof(struct dns_rr));
+                               memcpy(rec[r].rr->rdata, l->rdata,
+                                                       CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
+                               rec[r].rr->next=(void*)((char*)rec[r].rr+
+                                                       ROUND_POINTER(sizeof(struct dns_rr)+
+                                                       CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata)));
+                               rec[r].tail_rr=&(rec[r].rr->next);
+                               rec[r].rr=rec[r].rr->next;
+                               break;
+                       default:
+                               /* do nothing */
+                               ;
+               }
+       }
+       for (r=0; r<no_records; r++){
+               *rec[r].tail_rr=0; /* terminate the list */
+               rec[r].e->expire=now+S_TO_TICKS(rec[r].max_ttl);
+       }
+       return rec[0].e;
+error:
+       for (r=0; r<no_records; r++){
+               dns_destroy_entry(rec[r].e);
+       }
+       return 0;
+}
+
+
+
+inline static struct dns_hash_entry* dns_get_entry(str* name, int type);
+
+
+#define CACHE_RELEVANT_RECS_ONLY
+
+#ifdef CACHE_RELEVANT_RECS_ONLY
+/* internal only: gets related entries from a rdata list, appends them
+ * to e (list) and returns:
+ *  - e if e is of the requested type
+ *  -  if e is a CNAME, tries to get to the end of the CNAME chain and returns
+ *      the final entry if the types match or 0 if the chain is unfinished
+ *  - 0 on error/not found
+ * records is modified (the used records are removed from the list and freed)
+ *
+ * WARNING: - records must be pkg_malloc'ed
+ * Notes:   - if the return is 0 and e->type==T_CNAME, the list will contain
+ *            the CNAME chain (the last element being the last CNAME)
+ *  */
+inline static struct dns_hash_entry* dns_get_related(struct dns_hash_entry* e,
+                                                                                                               int type,
+                                                                                                               struct rdata** records)
+{
+       struct dns_hash_entry* ret;
+       struct dns_hash_entry* l;
+       struct dns_hash_entry* t;
+       struct dns_hash_entry* lst_end;
+       struct dns_rr* rr;
+       static int cname_chain_len=0;
+       str tmp;
+       
+       ret=0;
+       l=e;
+       DBG("dns_get_related(%p (%.*s, %d), %d, *%p) (%d)\n", e,
+                       e->name_len, e->name, e->type, type, *records, cname_chain_len);
+       clist_init(l, next, prev);
+       if (type==e->type){
+               ret=e;
+               switch(e->type){
+                       case T_SRV:
+                               for (rr=e->rr_lst; rr && *records; rr=rr->next){
+                                       tmp.s=((struct srv_rdata*)rr->rdata)->name;
+                                       tmp.len=((struct srv_rdata*)rr->rdata)->name_len;
+                                       if (!(dns_flags&DNS_IPV6_ONLY)){
+                                               t=dns_cache_mk_rd_entry(&tmp, T_A, records);
+                                               if (t){
+                                                       if ((t->type==T_CNAME) && *records)
+                                                               dns_get_related(t, T_A, records);
+                                                       clist_append(l, t, next, prev);
+                                               }
+                                       }
+                                       if (!(dns_flags&DNS_IPV4_ONLY)){
+                                               t=dns_cache_mk_rd_entry(&tmp, T_AAAA, records);
+                                               if (t){
+                                                       if ((t->type==T_CNAME) && *records)
+                                                               dns_get_related(t, T_AAAA, records);
+                                                       clist_append(l, t, next, prev);
+                                               }
+                                       }
+                               }
+                               break;
+                       default:
+                               /* nothing extra */
+                               break;
+               }
+       }else if ((e->type==T_CNAME) && (cname_chain_len<MAX_CNAME_CHAIN)){
+               /* only one cname is allowed (rfc2181), so we ignore
+                * the others (we take only the first one) */
+               tmp.s=((struct cname_rdata*)e->rr_lst->rdata)->name;
+               tmp.len=((struct cname_rdata*)e->rr_lst->rdata)->name_len;
+               t=dns_cache_mk_rd_entry(&tmp, type, records);
+               if (t){
+                       if (*records){
+                               cname_chain_len++;
+                               ret=dns_get_related(t, type, records);
+                               cname_chain_len--;
+                               lst_end=t->prev;
+                               clist_append_sublist(l, t, lst_end, next, prev);
+                       }else{
+                               clist_append(l, t, next, prev);
+                       }
+               }
+       }
+       return ret;
+}
+#endif
+
+
+
+/* calls the external resolver and populates the cache with the result
+ * returns: 0 on error, pointer to hash entry on success
+ * WARNING: make sure you use dns_hash_entry_put() when you're
+ *  finished with the result)
+ * */
+inline static struct dns_hash_entry* dns_cache_do_request(str* name, int type)
+{
+       struct rdata* records;
+       struct dns_hash_entry* e;
+       struct dns_hash_entry* l;
+       struct dns_hash_entry* r;
+       struct dns_hash_entry* t;
+       struct ip_addr* ip;
+       str cname_val;
+       char name_buf[MAX_DNS_NAME];
+       
+       e=0;
+       l=0;
+       cname_val.s=0;
+       
+       if (type==T_A){
+               if ((ip=str2ip(name))!=0){
+                               e=dns_cache_mk_ip_entry(name, ip);
+                               if (e)
+                                       atomic_set(&e->refcnt, 1);/* because we ret. a ref. to it*/
+                               goto end; /* we do not cache obvious stuff */
+               }
+       }else if (type==T_AAAA){
+               if ((ip=str2ip6(name))!=0){
+                               e=dns_cache_mk_ip_entry(name, ip);
+                               if (e)
+                                       atomic_set(&e->refcnt, 1);/* because we ret. a ref. to it*/
+                               goto end;/* we do not cache obvious stuff */
+               }
+       }
+       if (name->len>=MAX_DNS_NAME){
+               LOG(L_ERR, "ERROR: dns_cache_do_request: name too long (%d chars)\n",
+                                       name->len);
+               goto end;
+       }
+       /* null terminate the string, needed by get_record */
+       memcpy(name_buf, name->s, name->len);
+       name_buf[name->len]=0;
+       records=get_record(name_buf, type, RES_AR);
+       if (records){
+#ifdef CACHE_RELEVANT_RECS_ONLY
+               e=dns_cache_mk_rd_entry(name, type, &records);
+               if (e){
+                       l=e;
+                       e=dns_get_related(l, type, &records);
+                       /* e should contain the searched entry (if found) and l
+                        * all the entries (e and related) */
+                       if (e){
+                               atomic_set(&e->refcnt, 1); /* 1 because we return a 
+                                                                                               ref. to it */
+                       }else{
+                               /* e==0 => l contains a  cname list => we use the last
+                                * cname from the chain for a new resolve attempt (l->prev) */
+                               /* only one cname record is allowed (rfc2181), so we ignore 
+                                * the others (we take only the first one) */
+                               cname_val.s=
+                                       ((struct cname_rdata*)l->prev->rr_lst->rdata)->name;
+                               cname_val.len=
+                                       ((struct cname_rdata*)l->prev->rr_lst->rdata)->name_len;
+                               DBG("dns_cache_do_request: cname detected: %.*s (%d)\n",
+                                               cname_val.len, cname_val.s, cname_val.len);
+                       }
+                       /* add all the records to the hash */
+                       l->prev->next=0; /* we break the double linked list for easier
+                                                               searching */
+                       LOCK_DNS_HASH(); /* optimization */
+                       for (r=l; r; r=t){
+                               t=r->next;
+                               dns_cache_add_unsafe(r); /* refcnt++ inside */
+                               if (atomic_get(&r->refcnt)==0){
+                                       /* if cache adding failed and nobody else is interested
+                                        * destroy this entry */
+                                       dns_destroy_entry(r);
+                               }
+                       }
+                       UNLOCK_DNS_HASH();
+                       /* if only cnames found => try to resolve the last one */
+                       if (cname_val.s){ 
+                               DBG("dns_cache_do_request: dns_get_entry(cname: %.*s (%d))\n",
+                                               cname_val.len, cname_val.s, cname_val.len);
+                               e=dns_get_entry(&cname_val, type);
+                       }
+               }
+#else
+               l=dns_cache_mk_rd_entry2(records);
+#endif
+               free_rdata_list(records);
+       }else if (dns_neg_cache_ttl){
+               e=dns_cache_mk_bad_entry(name, type, dns_neg_cache_ttl, DNS_BAD_NAME);
+               atomic_set(&e->refcnt, 1); /* 1 because we return a ref. to it */
+               dns_cache_add(e); /* refcnt++ inside*/
+               goto end;
+       }
+#ifndef CACHE_RELEVANT_RECS_ONLY
+       if (l){
+               /* add all the records to the cache, but return only the record
+                * we are looking for */
+               l->prev->next=0; /* we break the double linked list for easier
+                                                       searching */
+               LOCK_DNS_HASH(); /* optimization */
+               for (r=l; r; r=t){
+                       t=r->next;
+                       if (e==0){ /* no entry found yet */
+                               if (r->type==T_CNAME){
+                                       if ((r->name_len==name->len) && (r->rr_lst) &&
+                                                       (strncasecmp(r->name, name->s, name->len)==0)){
+                                               /* update the name with the name from the cname rec. */
+                                               cname_val.s=
+                                                               ((struct cname_rdata*)r->rr_lst->rdata)->name;
+                                               cname_val.len=
+                                                       ((struct cname_rdata*)r->rr_lst->rdata)->name_len;
+                                               name=&cname_val;
+                                       }
+                               }else if ((r->type==type) && (r->name_len==name->len) &&
+                                                       (strncasecmp(r->name, name->s, name->len)==0)){
+                                       e=r;
+                                       atomic_set(&e->refcnt, 1); /* 1 because we return a ref. 
+                                                                                                 to it */
+                               }
+                       }
+                       dns_cache_add_unsafe(r); /* refcnt++ inside */
+                       if (atomic_get(&r->refcnt)==0){
+                               /* if cache adding failed and nobody else is interested
+                                * destroy this entry */
+                               dns_destroy_entry(r);
+                       }
+               }
+               UNLOCK_DNS_HASH();
+               if ((e==0) && (cname_val.s)){ /* not found, but found a cname */
+                       /* only one cname is allowed (rfc2181), so we ignore the
+                        * others (we take only the first one) */
+                       e=dns_get_entry(&cname_val, type);
+               }
+       }
+#endif
+end:
+       return e;
+}
+
+
+
+/* tries to lookup (name, type) in the hash and if not found tries to make
+ *  a dns request
+ *  return: 0 on error, pointer to a dns_hash_entry on success
+ *  WARNING: when *   not needed anymore dns_hash_put() must be called! */
+inline static struct dns_hash_entry* dns_get_entry(str* name, int type)
+{
+       int h;
+       struct dns_hash_entry* e;
+       str cname_val;
+       int err;
+       static int rec_cnt=0; /* recursion protection */
+       
+       e=0;
+       if (rec_cnt>MAX_CNAME_CHAIN){
+               LOG(L_WARN, "WARNING: dns_get_entry: CNAME chain too long or"
+                               " recursive CNAMEs (\"%.*s\")\n", name->len, name->s);
+               goto error;
+       }
+       rec_cnt++;
+       e=dns_hash_get(name, type, &h, &err);
+       if ((e==0) && ((err) || ((e=dns_cache_do_request(name, type))==0))){
+               goto error;
+       }else if ((e->type==T_CNAME) && (type!=T_CNAME)){
+               /* cname found instead which couldn't be resolved with the cached
+                * info => try a dns request */
+               /* only one cname record is allowed (rfc2181), so we ignore 
+                * the others (we take only the first one) */
+               cname_val.s= ((struct cname_rdata*)e->rr_lst->rdata)->name;
+               cname_val.len=((struct cname_rdata*)e->rr_lst->rdata)->name_len;
+               dns_hash_put(e); /* not interested in the cname anymore */
+               if ((e=dns_cache_do_request(&cname_val, type))==0)
+                       goto error; /* could not resolve cname */
+       }
+       /* found */
+       if ((e->rr_lst==0) || e->err_flags){
+               /* negative cache => not resolvable */
+               dns_hash_put(e);
+               e=0;
+       }
+error:
+       rec_cnt--;
+       return e;
+}
+
+
+
+/* gets the first non-expired, good record starting with record no
+ * from the dns_hash_entry struct e
+ * params:       e   - dns_hash_entry struct
+ *               *no - it must contain the start record number (0 initially);
+ *                      it will be filled with the returned record number
+ *               now - current time/ticks value
+ * returns pointer to the rr on success and sets no to the rr number
+ *         0 on error and fills the error flags
+ *
+ * Example usage:
+ * list all non-expired non-bad-marked ips for name:
+ * e=dns_get_entry(name, T_A);
+ * if (e){
+ *    *no=0;
+ *    now=get_ticks_raw();
+ *    while(rr=dns_entry_get_rr(e, no, now){
+ *       DBG("address %d\n", *no);
+ *       *no++;  ( get the next address next time )
+ *     }
+ *  }
+ */
+inline static struct dns_rr* dns_entry_get_rr( struct dns_hash_entry* e,
+                                                                                        unsigned char* no, ticks_t now)
+{
+       struct dns_rr* rr;
+       int n;
+       int flags;
+       
+       flags=0;
+       for(rr=e->rr_lst, n=0;rr && (n<*no);rr=rr->next, n++);/* skip *no records*/
+       for(;rr;rr=rr->next){
+               if ((s_ticks_t)(now-e->expire)>=0) /* expired entry */
+                       continue;
+               if (rr->err_flags){ /* bad rr */
+                       continue;
+               }
+               /* everything is ok now */
+               *no=n;
+               return rr;
+       }
+       *no=n;
+       return 0;
+}
+
+
+
+/* gethostbyname compatibility: converts a dns_hash_entry structure 
+ * to a statical internal hostent structure
+ * returns a pointer to the internal hostent structure on success or
+ *          0 on error 
+ */
+struct hostent* dns_entry2he(struct dns_hash_entry* e)
+{
+       static struct hostent he;
+       static char hostname[256];
+       static char* p_aliases[1];
+       static char* p_addr[DNS_HE_MAX_ADDR+1];
+       static char address[16*DNS_HE_MAX_ADDR]; /* max 10 ipv6 addresses */
+       int af, len;
+       struct dns_rr* rr;
+       unsigned char rr_no;
+       ticks_t now;
+       int i;
+       
+       switch(e->type){
+               case T_A:
+                       af=AF_INET;
+                       len=4;
+                       break;
+               case T_AAAA:
+                       af=AF_INET6;
+                       len=16;
+                       break;
+               default:
+                       LOG(L_CRIT, "BUG: dns_entry2he: wrong entry type %d for %.*s\n",
+                                       e->type, e->name_len, e->name);
+                       return 0;
+       }
+       
+       
+       rr_no=0;
+       now=get_ticks_raw();
+       rr=dns_entry_get_rr(e, &rr_no, now);
+       for(i=0; rr && (i<DNS_HE_MAX_ADDR); i++, 
+                                                       rr=dns_entry_get_rr(e, &rr_no, now)){
+                               p_addr[i]=&address[i*len];
+                               memcpy(p_addr[i], ((struct a_rdata*)rr->rdata)->ip, len);
+       }
+       if (i==0){
+               DBG("DEBUG: dns_entry2he: no good records found (%d) for %.*s (%d)\n",
+                               rr_no, e->name_len, e->name, e->type);
+               return 0; /* no good record found */
+       }
+       
+       p_addr[i]=0; /* mark the end of the addresses */
+       p_aliases[0]=0; /* no aliases */
+       memcpy(hostname, e->name, e->name_len);
+       hostname[e->name_len]=0;
+       
+       he.h_addrtype=af;
+       he.h_length=len;
+       he.h_addr_list=p_addr;
+       he.h_aliases=p_aliases;
+       he.h_name=hostname;
+       
+       return &he;
+}
+
+
+
+/* gethostbyname compatibility: performs an a_lookup and returns a pointer 
+ * to a statical internal hostent structure
+ * returns 0 on success, <0 on error (see the error codes)
+ */
+struct hostent* dns_a_get_he(str* name)
+{
+       struct dns_hash_entry* e;
+       struct ip_addr* ip;
+       struct hostent* he;
+       
+       e=0;
+       if ((ip=str2ip(name))!=0){
+               return ip_addr2he(name, ip);
+       }
+       if ((e=dns_get_entry(name, T_A))==0)
+               return 0;
+       /* found */
+       he=dns_entry2he(e);
+       dns_hash_put(e);
+       return he;
+}
+
+
+
+/* gethostbyname compatibility: performs an aaaa_lookup and returns a pointer 
+ * to a statical internal hostent structure
+ * returns 0 on success, <0 on error (see the error codes)
+ */
+struct hostent* dns_aaaa_get_he(str* name)
+{
+       struct dns_hash_entry* e;
+       struct ip_addr* ip;
+       struct hostent* he;
+       
+       e=0;
+       if ((ip=str2ip6(name))!=0){
+               return ip_addr2he(name, ip);
+       }
+       if ((e=dns_get_entry(name, T_AAAA))==0)
+                       return 0;
+       /* found */
+       he=dns_entry2he(e);
+       dns_hash_put(e);
+       return he;
+}
+
+
+
+/* returns 0 on success, -1 on error (rr type does not contain an ip) */
+inline static int dns_rr2ip(int type, struct dns_rr* rr, struct ip_addr* ip)
+{
+       switch(type){
+               case T_A:
+                       ip->af=AF_INET;
+                       ip->len=4;
+                       memcpy(ip->u.addr, ((struct a_rdata*)rr->rdata)->ip, 4);
+                       return 0;
+                       break;
+               case T_AAAA:
+                       ip->af=AF_INET6;
+                       ip->len=16;
+                       memcpy(ip->u.addr, ((struct aaaa_rdata*)rr->rdata)->ip6, 16);
+                       return 0;
+                       break;
+       }
+       return -1;
+}
+
+
+
+/* gethostbyname compatibility:
+ * performs an a or aaaa dns lookup, returns 0 on error and a pointer to a
+ *          static hostent structure on success
+ *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
+ *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
+ *          - DNS_IPV4_ONLY: tries only an a_lookup
+ *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
+ */
+struct hostent* dns_get_he(str* name, int flags)
+{
+       struct hostent* he;
+       
+       
+       if ((flags&(DNS_IPV6_FIRST|DNS_IPV6_ONLY))){
+               he=dns_aaaa_get_he(name);
+               if (he) return he;
+       }else{
+               he=dns_a_get_he(name);
+               if (he) return he;
+       }
+       if (flags&DNS_IPV6_FIRST){
+               he=dns_a_get_he(name);
+       }else if (!(flags&(DNS_IPV6_ONLY|DNS_IPV4_ONLY))){
+               he=dns_aaaa_get_he(name);
+       }
+       return he;
+}
+
+
+
+/* sip_resolvehost helper: gets the first good  hostent/port combination
+ * returns 0 on error, pointer to static hostent structure on success
+ *           (and sets port)*/
+struct hostent* dns_srv_get_he(str* name, unsigned short* port, int flags)
+{
+       struct dns_hash_entry* e;
+       struct dns_rr* rr;
+       str rr_name;
+       struct hostent* he;
+       ticks_t now;
+       unsigned char rr_no;
+       
+       rr=0;
+       he=0;
+       now=get_ticks_raw();
+       if ((e=dns_get_entry(name, T_SRV))==0)
+                       goto error;
+       /* look inside the RRs for a good one (not expired or marked bad)  */
+       rr_no=0;
+       while( (rr=dns_entry_get_rr(e, &rr_no, now))!=0){
+               /* everything is ok now, we can try to resolve the ip */
+               rr_name.s=((struct srv_rdata*)rr->rdata)->name;
+               rr_name.len=((struct srv_rdata*)rr->rdata)->name_len;
+               if ((he=dns_get_he(&rr_name, flags))!=0){
+                               /* success, at least one good ip found */
+                               *port=((struct srv_rdata*)rr->rdata)->port;
+                               goto end;
+               }
+               rr_no++; /* try from the next record, the current one was not good */
+       }
+       /* if we reach this point => error, we couldn't find any good rr */
+end:
+       if (e) dns_hash_put(e);
+error:
+       return he;
+}
+
+
+
+struct hostent* dns_resolvehost(char* name)
+{
+       str host;
+       
+       if ((use_dns_cache==0) || (dns_hash==0)){ /* not init yet */
+               return _resolvehost(name);
+       }
+       host.s=name;
+       host.len=strlen(name);
+       return dns_get_he(&host, dns_flags);
+}
+
+
+
+/* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
+ * if *port!=0.
+ * when performing SRV lookup (*port==0) it will use proto to look for
+ * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
+ * returns: hostent struct & *port filled with the port from the SRV record;
+ *  0 on error
+ */
+struct hostent* dns_sip_resolvehost(str* name, unsigned short* port, int proto)
+{
+       struct hostent* he;
+       struct ip_addr* ip;
+       static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
+       int len;
+       str srv_name;
+
+       if ((use_dns_cache==0) || (dns_hash==0)){ 
+               /* not init or off => use normal, non-cached version */
+               return _sip_resolvehost(name, port, proto);
+       }
+       len=0;
+       /* try SRV if no port specified (draft-ietf-sip-srv-06) */
+       if ((port)&&(*port==0)){
+               *port=(proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we don't
+                                                                                                               find another */
+               if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME){
+                       LOG(L_WARN, "WARNING: dns_sip_resolvehost: domain name too long"
+                                               " (%d), unable to perform SRV lookup\n", name->len);
+               }else{
+                       /* check if it's an ip address */
+                       if ( ((ip=str2ip(name))!=0)
+#ifdef USE_IPV6
+                                 || ((ip=str2ip6(name))!=0)
+#endif
+                               ){
+                               /* we are lucky, this is an ip address */
+                               return ip_addr2he(name,ip);
+                       }
+                       
+                       switch(proto){
+                               case PROTO_NONE: /* no proto specified, use udp */
+                                       goto skip_srv;
+                               case PROTO_UDP:
+                                       memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
+                                       memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len);
+                                       tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0';
+                                       len=SRV_UDP_PREFIX_LEN + name->len;
+                                       break;
+                               case PROTO_TCP:
+                                       memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
+                                       memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len);
+                                       tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0';
+                                       len=SRV_TCP_PREFIX_LEN + name->len;
+                                       break;
+                               case PROTO_TLS:
+                                       memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
+                                       memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len);
+                                       tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0';
+                                       len=SRV_TLS_PREFIX_LEN + name->len;
+                                       break;
+                               default:
+                                       LOG(L_CRIT, "BUG: sip_resolvehost: unknown proto %d\n",
+                                                       proto);
+                                       return 0;
+                       }
+
+                       srv_name.s=tmp;
+                       srv_name.len=len;
+                       if ((he=dns_srv_get_he(&srv_name, port, dns_flags))!=0)
+                               return he;
+               }
+       }
+skip_srv:
+       if (name->len >= MAX_DNS_NAME) {
+               LOG(L_ERR, "dns_sip_resolvehost: domain name too long\n");
+               return 0;
+       }
+       he=dns_get_he(name, dns_flags);
+       return he;
+}
+
+
+
+/* performs an a lookup, fills the dns_entry pointer and the ip addr.
+ *  (with the first good ip). if *e ==0 does the a lookup, and changes it
+ *   to the result, if not it uses the current value and tries to use 
+ *   the rr_no record from it.
+ * params:  e - must contain the "in-use" dns_hash_entry pointer (from
+ *               a previous call) or *e==0 (for the first call)
+ *          name - host name for which we do the lookup (required only
+ *                  when *e==0)
+ *          ip   - will be filled with the first good resolved ip started
+ *                 at *rr_no
+ *          rr_no - record number to start searching for a good ip from
+ *                  (e.g. value from previous call + 1), filled on return
+ *                  with the number of the record corresponding to the 
+ *                  returned ip
+ * returns 0 on success, <0 on error (see the error codes),
+ *         fills e, ip and rr_no
+ *          On end of records (when use to iterate on all the ips) it
+ *          will return E_DNS_EOR (you should not log an error for this
+ *          value, is just a signal that the address list end has been reached)
+ * WARNING: dns_hash_put(*e) must be called when you don't need
+ *          the entry anymore and *e!=0 (failling to do so => mem. leak)
+ * Example:
+ *  dns_entry=0;
+ *  ret=dns_a_get_ip(&dns_entry, name, &ip, &rr_no);  -- get the first rr.
+ *  ...
+ *  rr_no++;
+ *  while((ret>=0) && dns_entry)
+ *     dns_a_get_ip(&dns_entry, name, &ip, &rr_no); -- get the next rr
+ *   if (ret!=-E_DNS_EOR) ERROR(....);
+ *  ...
+ *  dns_hash_put(dns_entry); -- finished with the entry
+ */
+int dns_a_resolve(struct dns_hash_entry** e, unsigned char* rr_no,
+                                       str* name, struct ip_addr* ip)
+{
+       struct dns_rr* rr;
+       int ret;
+       ticks_t now;
+       struct ip_addr* tmp;
+       
+       rr=0;
+       ret=-E_DNS_NO_IP; 
+       if (*e==0){ /* do lookup */
+               /* if ip don't set *e */
+               if ((tmp=str2ip(name))!=0){
+                       *ip=*tmp;
+                       *rr_no=0;
+                       return 0;
+               }
+               if ((*e=dns_get_entry(name, T_A))==0)
+                       goto error;
+               /* found */
+               *rr_no=0;
+               ret=-E_DNS_BAD_IP_ENTRY;
+       }
+       now=get_ticks_raw();
+       rr=dns_entry_get_rr(*e, rr_no, now);
+       if (rr){
+               /* everything is ok now, we can try to "convert" the ip */
+               dns_rr2ip((*e)->type, rr, ip);
+               ret=0;
+       }else{
+               ret=-E_DNS_EOR;
+       }
+error:
+       DBG("dns_a_resovle(%.*s, %d) returning %d\n",
+                       name->len, name->s, *rr_no, ret);
+       return ret;
+}
+
+
+
+/* lookup, fills the dns_entry pointer and the ip addr.
+ *  (with the first good ip). if *e ==0 does the a lookup, and changes it
+ *   to the result, if not it uses the current value and tries to use 
+ * Same as dns_a_resolve but for aaaa records (see above).
+ */
+int dns_aaaa_resolve(struct dns_hash_entry** e, unsigned char* rr_no, 
+                                               str* name, struct ip_addr* ip)
+{
+       struct dns_rr* rr;
+       int ret;
+       ticks_t now;
+       struct ip_addr* tmp;
+       
+       rr=0;
+       ret=-E_DNS_NO_IP; 
+       if (*e==0){ /* do lookup */
+               /* if ip don't set *e */
+               if ((tmp=str2ip6(name))!=0){
+                       *ip=*tmp;
+                       *rr_no=0;
+                       return 0;
+               }
+               if ((*e=dns_get_entry(name, T_AAAA))==0)
+                       goto error;
+               /* found */
+               *rr_no=0;
+               ret=-E_DNS_BAD_IP_ENTRY;
+       }
+       now=get_ticks_raw();
+       rr=dns_entry_get_rr(*e, rr_no, now);
+       if (rr){
+               /* everything is ok now, we can try to "convert" the ip */
+               dns_rr2ip((*e)->type, rr, ip);
+               ret=0;
+       }else{
+               ret=-E_DNS_EOR; /* no more records */
+       }
+error:
+       return ret;
+}
+
+
+
+/* performs an a or aaaa dns lookup, returns <0 on error (see the
+ *  dns error codes) and 0 on success
+ *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
+ *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
+ *          - DNS_IPV4_ONLY: tries only an a_lookup
+ *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
+ *  see dns_a_resolve() for the rest of the params., examples a.s.o
+ *  WARNING: don't forget dns_hash_put(*e) when e is not needed anymore
+ */
+int dns_ip_resolve(struct dns_hash_entry** e, unsigned char* rr_no, 
+                                       str* name, struct ip_addr* ip, int flags)
+{
+       int ret;
+       
+       if ((flags&(DNS_IPV6_FIRST|DNS_IPV6_ONLY))){
+               ret=dns_aaaa_resolve(e, rr_no, name, ip);
+               if (ret>=0) return ret;
+       }else{
+               ret=dns_a_resolve(e, rr_no, name, ip);
+               if (ret>=0) return ret;
+       }
+       if (flags&DNS_IPV6_FIRST){
+               ret=dns_a_resolve(e, rr_no, name, ip);
+       }else if (!(flags&(DNS_IPV6_ONLY|DNS_IPV4_ONLY))){
+               ret=dns_aaaa_resolve(e, rr_no, name, ip);
+       }
+       return ret;
+}
+
+
+
+/*  gets the first srv record starting at rr_no
+ *  (similar to dns_a_resolve but for srv, sets host, port)
+ */
+int dns_srv_resolve(struct dns_hash_entry** e, unsigned char* rr_no,
+                                       str* name, str* host, unsigned short* port)
+{
+       struct dns_rr* rr;
+       int ret;
+       ticks_t now;
+       
+       rr=0;
+       ret=-E_DNS_NO_SRV; 
+       if (*e==0){
+               if ((*e=dns_get_entry(name, T_SRV))==0)
+                       goto error;
+               /* found it */
+               *rr_no=0;
+               ret=-E_DNS_BAD_SRV_ENTRY;
+       }
+       now=get_ticks_raw();
+       rr=dns_entry_get_rr(*e, rr_no, now);
+       if (rr){
+               host->s=((struct srv_rdata*)rr->rdata)->name;
+               host->len=((struct srv_rdata*)rr->rdata)->name_len;
+               *port=((struct srv_rdata*)rr->rdata)->port;
+               ret=0;
+       }else{
+               ret=-E_DNS_EOR; /* no more records */
+       }
+error:
+       return ret;
+}
+
+
+
+/*  gets the first srv record starting at h->srv_no, resolve it
+ *   and get the first ip address (starting at h->ip_no)
+ *  (similar to dns_a_resolve but for srv, sets host, port)
+ *  WARNING: don't forget to init h prior to calling this function the first
+ *   time and dns_srv_handle_put(h), even if error is returned
+ */
+int dns_srv_resolve_ip(struct dns_srv_handle* h,
+                                       str* name, struct ip_addr* ip, unsigned short* port,
+                                       int flags)
+{
+       int ret;
+       str host;
+       
+       host.len=0;
+       host.s=0;
+       do{
+               if (h->a==0){ 
+                       if ((ret=dns_srv_resolve(&h->srv, &h->srv_no,
+                                                                                                       name, &host, port))<0)
+                               goto error;
+                       h->port=*port; /* store new port */
+               }else{
+                       *port=h->port; /* return the stored port */
+               }
+               if ((ret=dns_ip_resolve(&h->a, &h->ip_no, &host, ip, flags))<0){
+                       /* couldn't find any good ip for this record, try the next one */
+                       h->srv_no++;
+                       if (h->a){
+                               dns_hash_put(h->a);
+                               h->a=0;
+                       }
+               }else if (h->a==0){
+                       /* this was an ip, try the next srv record in the future */
+                       h->srv_no++;
+               }
+       }while(ret<0);
+error:
+       DBG("dns_srv_resolve_ip(\"%.*s\", %d, %d), ret=%d, ip=%s\n", 
+                       name->len, name->s, h->srv_no, h->ip_no, ret, ip_addr2a(ip));
+       return ret;
+}
+
+
+
+/* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
+ * if *port!=0.
+ * when performing SRV lookup (*port==0) it will use proto to look for
+ * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
+ * dns_res_h must be initialized prior to  calling this function and can be
+ * used to get the subsequent ips
+ * returns:  <0 on error
+ *            0 on success and it fills *ip, *port, dns_sip_resolve_h
+ * WARNING: when finished, dns_sip_resolve_put(dns_res_h) must be called!
+ */
+int dns_sip_resolve(struct dns_srv_handle* h,  str* name,
+                                               struct ip_addr* ip, unsigned short* port, int proto,
+                                               int flags)
+{
+       static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
+       int len;
+       str srv_name;
+       struct ip_addr* tmp_ip;
+       int ret;
+       struct hostent* he;
+
+       if (dns_hash==0){ /* not init => use normal, non-cached version */
+               LOG(L_WARN, "WARNING: dns_sip_resolve: called before dns cache"
+                                       " initialization\n");
+               h->srv=h->a=0;
+               he=_sip_resolvehost(name, port, proto);
+               if (he){
+                       hostent2ip_addr(ip, he, 0);
+                       return 0;
+               }
+               return -E_DNS_NO_SRV;
+       }
+       len=0;
+       if ((h->srv==0) && (h->a==0)){
+               h->port=(proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we
+                                                                                                               don't find another */
+               if ((port) && (*port==0)){
+                       /* try SRV if initial call & no port specified
+                        * (draft-ietf-sip-srv-06) */
+                       *port=h->port;
+                       if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME){
+                               LOG(L_WARN, "WARNING: dns_sip_resolvehost: domain name too"
+                                                       " long (%d), unable to perform SRV lookup\n",
+                                                       name->len);
+                       }else{
+                               /* check if it's an ip address */
+                               if ( ((tmp_ip=str2ip(name))!=0)
+#ifdef USE_IPV6
+                                         || ((tmp_ip=str2ip6(name))!=0)
+#endif
+                                       ){
+                                       /* we are lucky, this is an ip address */
+#ifdef USE_IPV6
+                                       if (((flags&DNS_IPV4_ONLY) && (tmp_ip->af==AF_INET6)) ||
+                                               ((flags&DNS_IPV6_ONLY) && (tmp_ip->af==AF_INET))){
+                                               return -E_DNS_AF_MISMATCH;
+                                       }
+#endif
+                                       *ip=*tmp_ip;
+                                       return 0;
+                               }
+                               
+                               switch(proto){
+                                       case PROTO_NONE: /* no proto specified, use udp */
+                                               goto skip_srv;
+                                       case PROTO_UDP:
+                                               memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
+                                               memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len);
+                                               tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0';
+                                               len=SRV_UDP_PREFIX_LEN + name->len;
+                                               break;
+                                       case PROTO_TCP:
+                                               memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
+                                               memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len);
+                                               tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0';
+                                               len=SRV_TCP_PREFIX_LEN + name->len;
+                                               break;
+                                       case PROTO_TLS:
+                                               memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
+                                               memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len);
+                                               tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0';
+                                               len=SRV_TLS_PREFIX_LEN + name->len;
+                                               break;
+                                       default:
+                                               LOG(L_CRIT, "BUG: sip_resolvehost: unknown proto %d\n",
+                                                                       proto);
+                                               return -E_DNS_CRITICAL;
+                               }
+                               srv_name.s=tmp;
+                               srv_name.len=len;
+                               
+                               if ((ret=dns_srv_resolve_ip(h, &srv_name, ip, port, flags))>=0)
+                               {
+                                       DBG("dns_sip_resolve(%.*s, %d, %d), srv0, ret=%d\n", 
+                                               name->len, name->s, h->srv_no, h->ip_no, ret);
+                                       return ret;
+                               }
+                       }
+               }
+       }else if (h->srv){
+                       srv_name.s=h->srv->name;
+                       srv_name.len=h->srv->name_len;
+                       /* continue srv resolving */
+                       ret=dns_srv_resolve_ip(h, &srv_name, ip, port, flags);
+                       DBG("dns_sip_resolve(%.*s, %d, %d), srv, ret=%d\n", 
+                                       name->len, name->s, h->srv_no, h->ip_no, ret);
+                       return ret;
+       }
+skip_srv:
+       if (name->len >= MAX_DNS_NAME) {
+               LOG(L_ERR, "dns_sip_resolve: domain name too long\n");
+               return -E_DNS_NAME_TOO_LONG;
+       }
+       ret=dns_ip_resolve(&h->a, &h->ip_no, name, ip, flags);
+       if (port)
+               *port=h->port;
+       DBG("dns_sip_resolve(%.*s, %d, %d), ip, ret=%d\n", 
+                       name->len, name->s, h->srv_no, h->ip_no, ret);
+       return ret;
+}
+
+
+
+/* performs an a lookup and fills ip with the first good ip address
+ * returns 0 on success, <0 on error (see the error codes)
+ */
+int dns_a_get_ip(str* name, struct ip_addr* ip)
+{
+       struct dns_hash_entry* e;
+       int ret;
+       unsigned char rr_no;
+       
+       e=0;
+       rr_no=0;
+       ret=dns_a_resolve(&e, &rr_no, name, ip);
+       if (e) dns_hash_put(e);
+       return ret;
+}
+
+
+
+int dns_aaaa_get_ip(str* name, struct ip_addr* ip)
+{
+       struct dns_hash_entry* e;
+       int ret;
+       unsigned char rr_no;
+       
+       e=0;
+       rr_no=0;
+       ret=dns_aaaa_resolve(&e, &rr_no, name, ip);
+       if (e) dns_hash_put(e);
+       return ret;
+}
+
+
+
+/* performs an a or aaaa dns lookup, returns <0 on error (see the
+ *  dns error codes) and 0 on success
+ *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
+ *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
+ *          - DNS_IPV4_ONLY: tries only an a_lookup
+ *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
+ */
+int dns_get_ip(str* name, struct ip_addr* ip, int flags)
+{
+       int ret;
+       struct dns_hash_entry* e;
+       unsigned char rr_no;
+       
+       e=0;
+       rr_no=0;
+       ret=dns_ip_resolve(&e, &rr_no, name, ip, flags);
+       if (e)
+               dns_hash_put(e);
+       return ret;
+}
+
+
+
+/* fast "inline" version, gets the first good ip:port */
+int dns_srv_get_ip(str* name, struct ip_addr* ip, unsigned short* port,
+                                               int flags)
+{
+       int ret;
+       struct dns_srv_handle h;
+       
+       dns_srv_handle_init(&h);
+       ret=dns_srv_resolve_ip(&h, name, ip, port, flags);
+       dns_srv_handle_put(&h);
+       return ret;
+}
+
+
+
+/* rpc functions */
+void dns_cache_mem_info(rpc_t* rpc, void* ctx)
+{
+       rpc->add(ctx, "dd",  *dns_cache_mem_used, dns_cache_max_mem);
+}
+
+
+void dns_cache_debug(rpc_t* rpc, void* ctx)
+{
+       int h;
+       struct dns_hash_entry* e;
+       ticks_t now;
+       
+       now=get_ticks_raw();
+       LOCK_DNS_HASH();
+               for (h=0; h<DNS_HASH_SIZE; h++){
+                       clist_foreach(&dns_hash[h], e, next){
+                               rpc->add(ctx, "sdddddd", 
+                                                               e->name, e->type, e->total_size, e->refcnt.val,
+                                                               (s_ticks_t)(e->expire-now)<0?-1:
+                                                                       TICKS_TO_S(e->expire-now),
+                                                               TICKS_TO_S(now-e->last_used),
+                                                               e->err_flags);
+                       }
+               }
+       UNLOCK_DNS_HASH();
+}
+
+
+
+/* rpc functions */
+void dns_cache_debug_all(rpc_t* rpc, void* ctx)
+{
+       int h;
+       struct dns_hash_entry* e;
+       struct dns_rr* rr;
+       struct ip_addr ip;
+       int i;
+       ticks_t now;
+       
+       now=get_ticks_raw();
+       LOCK_DNS_HASH();
+               for (h=0; h<DNS_HASH_SIZE; h++){
+                       clist_foreach(&dns_hash[h], e, next){
+                               for (i=0, rr=e->rr_lst; rr; i++, rr=rr->next){
+                                       rpc->add(ctx, "sddddddd", 
+                                                               e->name, e->type, i, e->total_size,
+                                                               e->refcnt.val,
+                                                               (s_ticks_t)(e->expire-now)<0?-1:
+                                                                       TICKS_TO_S(e->expire-now),
+                                                               TICKS_TO_S(now-e->last_used),
+                                                               e->err_flags);
+                                       switch(e->type){
+                                               case T_A:
+                                               case T_AAAA:
+                                                       if (dns_rr2ip(e->type, rr, &ip)==0){
+                                                               rpc->add(ctx, "ss", "ip", ip_addr2a(&ip) );
+                                                       }else{
+                                                               rpc->add(ctx, "ss", "ip", "<error: bad rr>");
+                                                       }
+                                                       break;
+                                               case T_SRV:
+                                                       rpc->add(ctx, "ss", "srv", 
+                                                                       ((struct srv_rdata*)(rr->rdata))->name);
+                                                       break;
+                                               case T_NAPTR:
+                                                       rpc->add(ctx, "ss", "naptr", 
+                                                                       ((struct naptr_rdata*)(rr->rdata))->flags);
+                                                       break;
+                                               case T_CNAME:
+                                                       rpc->add(ctx, "ss", "cname", 
+                                                                       ((struct cname_rdata*)(rr->rdata))->name);
+                                                       break;
+                                               default:
+                                                       rpc->add(ctx, "ss", "unknown", "?");
+                                       }
+                                       rpc->add(ctx, "dd",
+                                                               (s_ticks_t)(rr->expire-now)<0?-1:
+                                                                       TICKS_TO_S(rr->expire-now),
+                                                       rr->err_flags);
+                               }
+                       }
+               }
+       UNLOCK_DNS_HASH();
+}
+
+
+#endif
diff --git a/dns_cache.h b/dns_cache.h
new file mode 100644 (file)
index 0000000..49a2ea0
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ * $Id$
+ *
+ * resolver/dns related functions, dns cache and failover
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-07-13  created by andrei
+ */
+
+
+#ifndef __dns_cache_h
+#define __dns_cache_h
+
+#include "str.h"
+#include "timer.h"
+#include "ip_addr.h"
+#include "atomic_ops.h"
+#include "resolve.h"
+
+
+#if defined(USE_DNS_FAILOVER) && !defined(USE_DNS_CACHE)
+#error "DNS FAILOVER requires DNS CACHE support (define USE_DNS_CACHE)"
+#endif
+
+#define DNS_LU_LST
+
+/* dns functions return them as negative values (e.g. return -E_DNS_NO_IP)
+ * listed in the order of importance ( if more errors, only the most important
+ * is returned)
+ */
+enum dns_errors{
+                                       E_DNS_OK=0,
+                                       E_DNS_EOR, /* no more records (not an error)
+                                                     -- returned only by the dns_resolve* 
+                                                                 functions when called iteratively,; it
+                                                                 signals the end of the ip/records list */
+                                       E_DNS_UNKNOWN /* unkown error */,
+                                       E_DNS_INTERNAL_ERR /* internal error */,
+                                       E_DNS_BAD_SRV_ENTRY,
+                                       E_DNS_NO_SRV /* unresolvable srv record */,
+                                       E_DNS_BAD_IP_ENTRY,
+                                       E_DNS_NO_IP /* unresolvable a or aaaa records*/,
+                                       E_DNS_BAD_IP /* the ip is invalid */, 
+                                       E_DNS_BLACKLIST_IP /* the ip is blacklisted */, 
+                                       E_DNS_NAME_TOO_LONG /* try again with a shorter name */,
+                                       E_DNS_AF_MISMATCH /* ipv4 or ipv6 only requested, but 
+                                                                                name contains an ip addr. of the
+                                                                                opossite type */ ,
+                                       E_DNS_CRITICAL /* critical error, marks the end
+                                                                         of the error table (always last) */
+};
+
+
+extern int dns_flags; /* default flags used for dns lookup */
+
+/* return a short string, printable error description (err <=0) */
+const char* dns_strerror(int err);
+
+/* dns entry error flags */
+#define DNS_BAD_NAME     1 /* unresolvable */
+
+/* dns requests flags */
+#define DNS_NO_FLAGS   0
+#define DNS_IPV4_ONLY  1
+#define DNS_IPV6_ONLY  2
+#define DNS_IPV6_FIRST 4
+
+
+/* ip blacklist error flags */
+#define IP_ERR_BAD_DST      2 /* destination is marked as bad (e.g. bad ip) */
+#define IP_ERR_SND          3 /* send error while using this as destination */
+#define IP_ERR_TIMEOUT      4 /* timeout waiting for a response */
+#define IP_ERR_TCP_CON      5 /* could not establish tcp connection */
+
+
+/* stripped down dns rr */
+struct dns_rr{
+       struct dns_rr* next;
+       void* rdata; /* depends on the type */
+       /* name, type and class are not needed, contained in struct dns_query */
+       ticks_t expire; /* = ttl + crt_time */
+       unsigned char err_flags; /* if 0 everything is ok */
+
+};
+
+
+
+#ifdef DNS_LU_LST
+struct dns_lu_lst{  /* last used ordered list */
+       struct dns_lu_lst* next;
+       struct dns_lu_lst* prev;
+};
+#endif
+
+struct dns_hash_entry{
+       /* hash table links */
+       struct dns_hash_entry* next;
+       struct dns_hash_entry* prev;
+#ifdef DNS_LU_LST
+       struct dns_lu_lst last_used_lst;
+#endif
+       struct dns_rr* rr_lst;
+       atomic_t refcnt;
+       ticks_t last_used;
+       ticks_t expire; /* when the whole entry will expire */
+       int total_size;
+       unsigned short type;
+       unsigned char err_flags;
+       unsigned char name_len; /* can be maximum 255 bytes */
+       char name[1]; /* variable length, name, null terminated 
+                        (actual lenght = name_len +1)*/
+};
+
+
+
+struct dns_srv_handle{
+       struct dns_hash_entry* srv; /* srv entry */
+       struct dns_hash_entry* a;   /* a or aaaa current entry */
+       unsigned short port; /* current port */
+       unsigned char srv_no; /* current record no. in the srv entry */
+       unsigned char ip_no;   /* current record no. in the a/aaaa entry */
+};
+
+
+
+const char* dns_strerror(int err);
+
+int init_dns_cache();
+void destroy_dns_cache();
+
+
+void dns_hash_put(struct dns_hash_entry* e);
+void dns_hash_put_shm_unsafe(struct dns_hash_entry* e);
+
+inline static void dns_srv_handle_put(struct dns_srv_handle* h)
+{
+       if (h){
+               if (h->srv){
+                       dns_hash_put(h->srv);
+                       h->srv=0;
+               }
+               if (h->a){
+                       dns_hash_put(h->a);
+                       h->a=0;
+               }
+       }
+}
+
+
+
+/* use it when copying, it manually increases the ref cound */
+inline static void dns_srv_handle_ref(struct dns_srv_handle *h)
+{
+       if (h){
+               if (h->srv)
+                       atomic_inc(&h->srv->refcnt);
+               if (h->a)
+                       atomic_inc(&h->a->refcnt);
+       }
+}
+
+
+
+/* safe copy increases the refcnt, src must not change while in this function
+ * WARNING: the copy must be dns_srv_handle_put ! */
+inline static void dns_srv_handle_cpy(struct dns_srv_handle* dst,
+                                                                               struct dns_srv_handle* src)
+{
+       dns_srv_handle_ref(src);
+       *dst=*src;
+}
+
+
+
+/* same as above but assume shm_lock held (for internal tm use only) */
+inline static void dns_srv_handle_put_shm_unsafe(struct dns_srv_handle* h)
+{
+       if (h){
+               if (h->srv){
+                       dns_hash_put_shm_unsafe(h->srv);
+                       h->srv=0;
+               }
+               if (h->a){
+                       dns_hash_put_shm_unsafe(h->a);
+                       h->a=0;
+               }
+       }
+}
+
+
+
+/* get "next" ip next time a dns_srv_handle function is called
+ * params: h   - struct dns_srv_handler
+ *         err - return code of the last dns_*_resolve* call 
+ * returns: 0 if it doesn't make sense to try another record,
+ * 1 otherwise
+ */
+inline static int dns_srv_handle_next(struct dns_srv_handle* h, int err)
+{
+       if (err<0) return 0;
+       h->ip_no++;
+       return (h->srv || h->a);
+}
+
+
+
+inline static void dns_srv_handle_init(struct dns_srv_handle* h)
+{
+       h->srv=h->a=0;
+       h->srv_no=h->ip_no=0;
+}
+
+
+
+/* performes a srv query on name
+ * Params:  name  - srv query target (e.g. _sip._udp.foo.bar)
+ *          ip    - result: first good ip found
+ *          port  - result: corresponding port number
+ *          flags - resolve options (like ipv4 only, ipv6 prefered a.s.o)
+ * Returns: < 0 on error (can be passed to dns_strerror(), 0 on success
+ */
+int dns_srv_get_ip(str* name, struct ip_addr* ip, unsigned short* port,
+                                       int flags);
+
+/* performs an A, AAAA (or both) query/queries
+ * Params:  name  - query target (e.g. foo.bar)
+ *          ip    - result: first good ip found
+ *          flags - resolve options (like ipv4 only, ipv6 prefered a.s.o)
+ * Returns: < 0 on error (can be passed to dns_strerror(), 0 on success
+ */
+int dns_get_ip(str* name, struct ip_addr* ip, int flags);
+
+struct hostent* dns_srv_get_he(str* name, unsigned short* port, int flags);
+struct hostent* dns_get_he(str* name, int flags);
+
+
+/* resolve name to an ip, using srv record. Can be called multiple times
+ * to iterate on all the possible ips, e.g :
+ * dns_srv_handle_init(h);
+ * ret_code=dns_sip_resolve(h,...);
+ *  while( dns_srv_handle_next(h, ret_code){ ret_code=dns_sip_resolve(h...); }
+ * dns_srv_handle_put(h);
+ * WARNING: dns_srv_handle_init() must be called to initialize h and
+ *  dns_srv_handle_put(h) must be called when h is no longer needed
+ */
+int dns_sip_resolve(struct dns_srv_handle* h,  str* name, struct ip_addr* ip,
+                                       unsigned short* port, int proto, int flags);
+
+/* same as above, but fills su intead of changing port and filling an ip */ 
+inline static int dns_sip_resolve2su(struct dns_srv_handle* h,
+                                                                        union sockaddr_union* su,
+                                                                        str* name, unsigned short port,
+                                                                        int proto, int flags)
+{
+       struct ip_addr ip;
+       int ret;
+       
+       ret=dns_sip_resolve(h, name, &ip, &port, proto, flags);
+       if (ret>=0)
+               init_su(su, &ip, port);
+       return ret;
+}
+#endif
diff --git a/dns_wrappers.h b/dns_wrappers.h
new file mode 100644 (file)
index 0000000..c8c57ee
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ *
+ * resolver related functions
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-07-23  created by andrei
+ */
+
+#ifndef __dns_wrappers_h
+#define __dns_wrappers_h
+
+struct hostent* dns_resolvehost(char* name);
+struct hostent* dns_sip_resolvehost(str* name, unsigned short* port,
+                                                                               int proto);
+
+#endif
diff --git a/doc/dns.txt b/doc/dns.txt
new file mode 100644 (file)
index 0000000..70827e9
--- /dev/null
@@ -0,0 +1,173 @@
+# $Id$
+#
+# History:
+# --------
+# 2006-09-08  created by andrei
+#
+
+Overview
+
+ The dns subsystem in ser can either directly use libresolv and a combination
+  of the locally configured dns server, /etc/hosts and the local Network 
+  Information Service (NIS/YP a.s.o) or cache the query results (both positive
+  and negative) and look first in its internal cache.
+ When its internal dns cache is enabled, ser can also use dns failover: if
+  one destination resolves to multiple addresses ser can try all of them until
+  it finds one to which it can succesfully send the packet or it exhausts all 
+  of them. ser (tm to be more precise) uses the dns failover also when the
+  destination host doesn't send any reply to a forwarded invite within the
+  sip timeout interval (whose value can be configured using the tm fr_timer
+   parameter).
+
+
+DNS Cache and Failover Drawbacks
+
+ Using the dns cache and the dns failover has also some drawbacks: 
+
+  1. only the locally configured dns server (usually in /etc/resolv.conf) is
+  used for the requests (/etc/hosts and the local Network Information Service
+  are ignored). 
+     Workarround: disable the dns cache (use_dns_cache=off or
+  compile without -DUSE_DNS_CACHE).
+
+  2. the dns cache uses extra memory
+      Workarround: disable the dns cache.
+
+  3. the dns failover introduces a very small performance penalty 
+     Workarround: disable the dns failover (use_dns_failover=off).
+
+  4. the dns failover increases the memory usage (the internal structures
+  used to represent the transaction are bigger when the dns failover support is
+  compiled).
+     Workarround: compile without dns failover support (-DUSE_DNS_FAILOVER).
+  Turning it off from the config file is not enough in this case (the extra
+   memory will still be used).
+
+
+DNS Resolver Options
+
+ The DNS resolver options control how ser will interact with the external
+ DNS servers. These options (with the dns_try_ipv6 exception) are passed to
+ libresolv and are used each time a dns request is made.
+ The default values are system specific and generally depend on the
+ /etc/resolv.conf content. For servers doing a lot of DNS requests it is
+ highly recommended to change the default values in the ser config file
+  (even if using ser's internal dns cache).
+
+   dns_try_ipv6 = on | off - if on and ser listens on at least one ipv6 socket,
+      ipv6 (AAAA) lookups will be performed if the ipv4 (A) lookups fail. 
+      If off only ipv4 (A) lookups will be used.
+      Default: on if ser is compiled with ipv6 support.
+
+   dns_retr_time = time - time in s before retrying a dns request.
+      Default: system specific, depends also on the/etc/resolv.conf content
+      (usually 5 s).
+
+   dns_retr_no = no. - number of dns retransmissions before giving up.
+      Default: see above (usually 4)
+
+   dns_servers_no = no. - how many dns servers from the ones defined in 
+      /etc/resolv.conf will be used. Default: all of them.
+
+   dns_use_search_list= yes/no - if no, the search list in /etc/resolv.conf
+      will be ignored (=> fewer lookups => gives up faster).
+      Default: yes.
+      HINT: even if you don't have a search list defined, setting this option
+      to "no" will still be "faster", because an empty search list is in 
+      fact search "" (so even if the search list is empty/missing there will
+      still be 2 dns queries, eg. foo+'.' and foo+""+'.')
+
+ The maximum time a dns request can take (before failing) is:
+ (dns_retr_time*dns_retr_no)*(search_list_domains) If dns_try_ipv6 is yes,
+ mutliply it again by 2.
+
+ The option combination that produces the "fastest" dns resolver config
+  (the "faster" in the sense that it gives up the quickest) is:
+
+      dns_try_ipv6=no
+      dns_retr_time=1
+      dns_retr_no=1
+      dns_servers_no=1
+      dns_use_search_list=no
+
+ The recommended dns configuration is to have a "close" dns caching recursive
+ server configured in /etc/resolv.conf, set the dns resolver options in ser's
+ config as in the above example and enable the dns cache (in ser).
+ Pay particular attention to dns_servers_no and dns_use_search_list. It's a
+ good ideea to make sure you don't need / use the search list or more then one
+ dns server (to avoid unnecessary extra lookups).
+
+
+DNS Cache and Failover Config Variables
+
+   use_dns_cache = on | off - if off the dns cache won't be used (all dns
+      lookups will result into a dns request).  When on all the dns request
+      results will be cached.
+      WARNING: when enabled /etc/hosts will be completely bypassed, all the dns
+      request will go directly to the system configured (resolv.conf) dns
+      server.
+      Default: on.
+
+   use_dns_failover = on |off - if on and sending a request fails (due to not
+      being allowed from an onsend_route, send failure, blacklisted destination
+      or, when using tm, invite timeout), and the destination resolves to
+      multiple ip addresses and/or multiple SRV records, the send will be
+      re-tried using the next ip/record. In tm's case a new branch will be
+      created for each new send attempt.
+      Default: off.
+   Depends on use_dns_cache being on. If tm is used along with dns failover is
+   recommended to also turn on dst_blacklist.
+
+   dns_cache_flags = dns cache specific resolver flags, used for overriding
+     the default behaviour (low level).
+      Possible values:
+         1 - ipv4 only: only DNS A requests are performed, even if ser listens
+                        also on ipv6 addresses.
+         2 - ipv6 only: only DNS AAAA requests are performed. Ignored if
+                        dns_try_ipv6 is off or ser doesn't listen on any ipv6
+                        address.
+         4 - prefer ipv6: try first to resolve a host name to an ipv6 address
+                          (DNS AAAA request) and only if this fails try an ipv4
+                          address (DNS A request).
+                          By default the ipv4 addresses are preferred.
+      Default: 0
+
+   dns_cache_negative_ttl = time to live for negative results ("not found") in
+      seconds. Use 0 to disable.
+      Default: 60 s.
+
+   dns_cache_min_ttl = minimum accepted time to live for a record, in seconds.
+      If a record has a lower ttl, its value will be discarded and
+      dns_cache_min_ttl will be used instead.
+      Default: 0
+
+   dns_cache_max_ttl = maximum accepted time to live for a record, in seconds.
+      If a record has a higher ttl, its value will be discarded and
+      dns_cache_max_ttl will be used instead.
+      Default: MAXINT
+
+   dns_cache_mem = maximum memory used for the dns cache in Kb.
+      Default: 500 Kb
+
+   dns_cache_gc_interval = how often (in s) the dns cache will be garbage 
+      collected.
+      Default:  120 s.
+
+
+DNS Cache Compile Options
+
+   USE_DNS_CACHE - if defined the dns cache support will be compiled in 
+      (default). If not needed/wanted the dns_cache can be disabled from the
+      ser's config file. The only advantages for not compiling the dns cache
+      support is a slight decrease of the executable size and an extremely 
+      small performance increase (1 less comparison per dns request).
+
+   USE_DNS_FAILOVER - if defined the dns failover support will be compiled in.
+      (default). Compiling the dns failover support has a few disadvantages,
+      see the "Drawbacks" section.
+ Note: To remove a compile options,  edit ser's Makefile.defs and remove it 
+   form DEFS list. To add a compile options add it to the make command line,
+     e.g.: make proper; make all extra_defs=-DUSE_DNS_FAILOVER
+   or for a permanent solution, edit Makefile.defs and add it to DEFS 
+   (don't foget to prefix it with -D).
diff --git a/doc/dst_blacklist.txt b/doc/dst_blacklist.txt
new file mode 100644 (file)
index 0000000..516e116
--- /dev/null
@@ -0,0 +1,69 @@
+# $Id$
+#
+# History:
+# --------
+# 2006-09-08  created by andrei
+#
+
+Overview
+
+ The destination blacklist (dst_blacklist) is used to try to mark bad
+  destination and avoid possible future expensive send operation to them.
+ A destination is added to the blacklist when trying to send to it fails (e.g.
+ timeout while trying to send or connect on tcp), or when a sip timeout occurs
+ while trying to forward statefully an invite (using tm) and the remote side
+ doesn't send back any response.
+ The blacklist (if enabled) is checked before any send attempt.
+
+
+Drawbacks
+
+
+ Using the destination blacklist will cause some performance degradation,
+ especially on multi cpu machines. If you don't need it you can easily
+  disable it, either in ser's config or at compile time. Disabling it at
+  compile time is sligthly better (but not in a "measurable" way) then
+   disabling it at runtime, from the config file.
+ Whether the destination blacklist is better to be on or off depends a lot
+  on the setup. In general is better to turn it on when:
+   - sending to clients that don't respond is expensive (e.g. lots of clients
+   use tcp and they have the habit of silently discarding tcp traffic from time
+   to time)
+   - statefull forwarding is used (tm) and lower memory usage is desired
+   (a transaction will fail immediately if the destination is already 
+   blacklisted by a previous transaction to the same destination that failed
+   due to timeout)
+   - faster dns failover is desired, especially when statefull forwarding (tm)
+   and udp are used
+   - better chances of DOS survival are important
+
+
+Config Variables
+
+ use_dst_blacklist = on | off (default off) - enable the destination blacklist:
+  if on each failed send attempt will cause the destination to be blacklisted.
+  Before any send this blacklist will be checked and if a match is found the
+  send is no longer attempted (an error is returned immediately).
+  Note: using the blacklist incurs a small performance penalty.
+
+ dst_blacklist_mem = size in Kb (default 250 Kb) - maximum
+  shared memory ammount used for keeping the blacklisted destinations.
+
+ dst_blacklist_expire = time in s (default 60 s) - how much time a 
+  blacklisted destination will be kept in the blacklist (w/o any update).
+
+ dst_blacklist_gc_interval = time in s (default 60 s) - how often the 
+  garbage collection will run (eliminating old, expired entries).
+
+
+Compile Options
+
+ USE_DST_BLACKLIST - if defined the blacklist support will be compiled-in
+  (default).
+
+
+ Note: To remove a compile options,  edit ser's Makefile.defs and remove it 
+   form DEFS list. To add a compile options add it to the make command line,
+     e.g.: make proper; make all extra_defs=-DUSE_DNS_FAILOVER
+   or for a permanent solution, edit Makefile.defs and add it to DEFS 
+   (don't foget to prefix it with -D).
diff --git a/dst_blacklist.c b/dst_blacklist.c
new file mode 100644 (file)
index 0000000..9fb1fcc
--- /dev/null
@@ -0,0 +1,494 @@
+/*
+ * $Id$
+ *
+ * resolver related functions
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-07-29  created by andrei
+ */
+
+
+#ifdef USE_DST_BLACKLIST
+
+#include "dst_blacklist.h"
+#include "mem/shm_mem.h"
+#include "hashes.h"
+#include "locking.h"
+#include "timer.h"
+#include "timer_ticks.h"
+#include "ip_addr.h"
+#include "error.h"
+#include "rpc.h"
+
+
+
+
+struct dst_blst_entry{
+       struct dst_blst_entry* next;
+       ticks_t expire;
+       unsigned short port;
+       unsigned char proto;
+       unsigned char flags; /* contains the address type + error flags */
+       unsigned char ip[4]; /* 4 for ipv4, 16 for ipv6 */ 
+};
+
+#define DST_BLST_ENTRY_SIZE(b) \
+               (sizeof(struct dst_blst_entry)+((b).flags&BLST_IS_IPV6)*12)
+
+
+#define DST_BLST_HASH_SIZE             1024
+#define DEFAULT_BLST_TIMEOUT           60  /* 1 min. */
+#define DEFAULT_BLST_MAX_MEM   250 /* 1 Kb FIXME (debugging)*/
+#define DEFAULT_BLST_TIMER_INTERVAL            60 /* 1 min */
+
+
+static gen_lock_t* blst_lock=0;
+static struct timer_ln* blst_timer_h=0;
+
+static volatile unsigned int* blst_mem_used=0;
+unsigned int  blst_max_mem=DEFAULT_BLST_MAX_MEM; /* maximum memory used
+                                                                                                       for the blacklist entries*/
+unsigned int blst_timeout=DEFAULT_BLST_TIMEOUT;
+unsigned int blst_timer_interval=DEFAULT_BLST_TIMER_INTERVAL;
+struct dst_blst_entry** dst_blst_hash=0;
+
+
+#define LOCK_BLST()            lock_get(blst_lock)
+#define UNLOCK_BLST()  lock_release(blst_lock)
+
+
+inline static void blst_destroy_entry(struct dst_blst_entry* e)
+{
+       shm_free(e);
+}
+
+
+static ticks_t blst_timer(ticks_t ticks, struct timer_ln* tl, void* data);
+
+
+inline static void dst_blst_entry2ip(struct ip_addr* ip,
+                                                                               struct dst_blst_entry* e)
+{
+       if (e->flags & BLST_IS_IPV6){
+               ip->af=AF_INET6;
+               ip->len=16;
+       }else{
+               ip->af=AF_INET;
+               ip->len=4;
+       }
+       memcpy(ip->u.addr, e->ip, ip->len);
+}
+
+
+
+inline static unsigned short dst_blst_hash_no(unsigned char proto,
+                                                                                         struct ip_addr* ip,
+                                                                                         unsigned short port)
+{
+       str s1;
+       str s2;
+       
+       s1.s=(char*)ip->u.addr;
+       s1.len=ip->len;
+       s2.s=(char*)&port;
+       s2.len=sizeof(unsigned short);
+       return get_hash2_raw(&s1, &s2)%DST_BLST_HASH_SIZE;
+}
+
+
+
+void destroy_dst_blacklist()
+{
+       if (blst_timer_h){
+               timer_del(blst_timer_h);
+               timer_free(blst_timer_h);
+               blst_timer_h=0;
+       }
+       if (blst_lock){
+               lock_destroy(blst_lock);
+               lock_dealloc(blst_lock);
+               blst_lock=0;
+       }
+       if (dst_blst_hash){
+               shm_free(dst_blst_hash);
+               dst_blst_hash=0;
+       }
+       if (blst_mem_used){
+               shm_free((void*)blst_mem_used);
+               blst_mem_used=0;
+       }
+}
+
+
+
+int init_dst_blacklist()
+{
+       int ret;
+       
+       ret=-1;
+       blst_mem_used=shm_malloc(sizeof(*blst_mem_used));
+       if (blst_mem_used==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       dst_blst_hash=shm_malloc(sizeof(struct dst_blst_entry*) *
+                                                                                       DST_BLST_HASH_SIZE);
+       if (dst_blst_hash==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       blst_lock=lock_alloc();
+       if (blst_lock==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       if (lock_init(blst_lock)==0){
+               lock_dealloc(blst_lock);
+               blst_lock=0;
+               ret=-1;
+               goto error;
+       }
+       blst_timer_h=timer_alloc();
+       if (blst_timer_h==0){
+               ret=E_OUT_OF_MEM;
+               goto error;
+       }
+       /* fix options */
+       blst_max_mem<<=10; /* in Kb */ /* TODO: test with 0 */
+       if (blst_timer_interval){
+               timer_init(blst_timer_h, blst_timer, 0 ,0); /* slow timer */
+               if (timer_add(blst_timer_h, S_TO_TICKS(blst_timer_interval))<0){
+                       LOG(L_CRIT, "BUG: init_dst_blacklist: failed to add the timer\n");
+                       timer_free(blst_timer_h);
+                       blst_timer_h=0;
+                       goto error;
+               }
+       }
+       return 0;
+error:
+       destroy_dst_blacklist();
+       return ret;
+}
+
+
+/* must be called with the lock held
+ * struct dst_blst_entry** head, struct dst_blst_entry* e */
+#define dst_blacklist_lst_add(head, e)\
+do{ \
+       (e)->next=*(head); \
+       *(head)=(e); \
+}while(0)
+
+
+
+/* must be called with the lock held
+ * returns a pointer to the blacklist entry if found, 0 otherwise
+ * it also deletes expired elements (expire<=now) as it searches
+ * proto==PROTO_NONE = wildcard */
+inline static struct dst_blst_entry* _dst_blacklist_lst_find(
+                                                                                               struct dst_blst_entry** head,
+                                                                                               struct ip_addr* ip,
+                                                                                               unsigned char proto,
+                                                                                               unsigned short port,
+                                                                                               ticks_t now)
+{
+       struct dst_blst_entry** crt;
+       struct dst_blst_entry** tmp;
+       struct dst_blst_entry* e;
+       unsigned char type;
+       
+       type=(ip->af==AF_INET6)*BLST_IS_IPV6;
+       for (crt=head, tmp=&(*head)->next; *crt; crt=tmp, tmp=&(*crt)->next){
+               e=*crt;
+               /* remove old expired entries */
+               if ((s_ticks_t)(now-(*crt)->expire)>=0){
+                       *crt=(*crt)->next;
+                       *blst_mem_used-=DST_BLST_ENTRY_SIZE(*e);
+                       blst_destroy_entry(e);
+               }else if ((e->port==port) && ((e->flags & BLST_IS_IPV6)==type) &&
+                               ((e->proto==PROTO_NONE) || (proto==PROTO_NONE) ||
+                                       (e->proto==proto)) && 
+                                       (memcmp(ip->u.addr, e->ip, ip->len)==0)){
+                       return e;
+               }
+       }
+       return 0;
+}
+
+
+
+/* frees all the expired entries until either there are no more of them
+ *  or the total memory used is <= target (to free all of them use -1 for 
+ *  targer)
+ *  It must be called with LOCK_BLST held (or call dst_blacklist_clean_expired
+ *   instead).
+ *  params:   target  - free expired entries until no more then taget memory 
+ *                      is used  (use 0 to free all of them)
+ *            delta   - consider an entry expired if it expires after delta
+ *                      ticks from now
+ *            timeout - exit after timeout ticks
+ *
+ *  returns: number of deleted entries
+ *  This function should be called periodically from a timer
+ */
+inline static int _dst_blacklist_clean_expired_unsafe(unsigned int target,
+                                                                                               ticks_t delta,
+                                                                                               ticks_t timeout)
+{
+       static unsigned short start=0;
+       unsigned short h;
+       struct dst_blst_entry** crt;
+       struct dst_blst_entry** tmp;
+       struct dst_blst_entry* e;
+       ticks_t start_time;
+       ticks_t now;
+       int no=0;
+       
+       now=start_time=get_ticks_raw();
+       for(h=start; h!=(start+DST_BLST_HASH_SIZE); h++){
+               for (crt=&dst_blst_hash[h%DST_BLST_HASH_SIZE], tmp=&(*crt)->next;
+                               *crt; crt=tmp, tmp=&(*crt)->next){
+                       e=*crt;
+                       if ((s_ticks_t)(now+delta-(*crt)->expire)>=0){
+                               *crt=(*crt)->next;
+                               *blst_mem_used-=DST_BLST_ENTRY_SIZE(*e);
+                               blst_destroy_entry(e);
+                               no++;
+                               if (*blst_mem_used<=target)
+                                       goto skip;
+                       }
+               }
+               /* check for timeout only "between" hash cells */
+               now=get_ticks_raw();
+               if ((now-start_time)>=timeout){
+                       DBG("_dst_blacklist_clean_expired_unsafe: timeout: %d > %d\n",
+                                       TICKS_TO_MS(now-start_time), TICKS_TO_MS(timeout));
+                       goto skip;
+               }
+       }
+skip:
+       start=h; /* next time we start where we left */
+       return no;
+}
+
+
+
+/* frees all the expired entries until either there are no more of them
+ *  or the total memory used is <= target (to free all of them use -1 for 
+ *  targer)
+ *  params:   target  - free expired entries until no more then taget memory 
+ *                      is used  (use 0 to free all of them)
+ *            delta   - consider an entry expired if it expires after delta
+ *                      ticks from now
+ *            timeout - exit after timeout ticks
+ *
+ *  returns: number of deleted entries
+ *  This function should be called periodically from a timer
+ */
+inline static int dst_blacklist_clean_expired(unsigned int target,
+                                                                                               ticks_t delta,
+                                                                                               ticks_t timeout)
+{
+       int no;
+       
+       LOCK_BLST();
+               no=_dst_blacklist_clean_expired_unsafe(target, delta, timeout);
+       UNLOCK_BLST();
+       if (no){
+               DBG("dst_blacklist_clean_expired, %d entries removed\n", no);
+       }
+       return no;
+}
+
+
+
+/* timer */
+static ticks_t blst_timer(ticks_t ticks, struct timer_ln* tl, void* data)
+{
+       dst_blacklist_clean_expired(0, 0, 2); /*spend max. 2 ticks*/
+       return (ticks_t)(-1);
+}
+
+
+
+/* adds a proto ip:port combination to the blacklist
+ * returns 0 on success, -1 on error (blacklist full -- would use more then
+ *  blst:_max_mem, or out of shm. mem.)
+ */
+inline static int dst_blacklist_add_ip(unsigned char err_flags, 
+                                                                       unsigned char proto,
+                                                                       struct ip_addr* ip, unsigned short port)
+{
+       int size;
+       struct dst_blst_entry* e;
+       unsigned short hash;
+       ticks_t now;
+       int ret;
+       
+       ret=0;
+       if (ip->af==AF_INET){
+               err_flags&=~BLST_IS_IPV6; /* make sure the ipv6 flag is reset */
+               size=sizeof(struct dst_blst_entry);
+       }else{
+               err_flags|=BLST_IS_IPV6;
+               size=sizeof(struct dst_blst_entry)+12 /* ipv6 addr - 4 */;
+       }
+       now=get_ticks_raw();
+       hash=dst_blst_hash_no(proto, ip, port);
+       /* check if the entry already exists */
+       LOCK_BLST();
+               e=_dst_blacklist_lst_find(&dst_blst_hash[hash], ip, proto, port, now);
+               if (e){
+                       e->flags|=err_flags;
+                       e->expire=now+S_TO_TICKS(blst_timeout); /* update the timeout */
+               }else{
+                       if ((*blst_mem_used+size)>=blst_max_mem){
+                               /* first try to free some memory  (~ 12%), but don't
+                                * spend more then 250 ms*/
+                               _dst_blacklist_clean_expired_unsafe(*blst_mem_used/16*14, 0, 
+                                                                                                                       MS_TO_TICKS(250));
+                               if (*blst_mem_used+size>=blst_max_mem){
+                                       ret=-1;
+                                       goto error;
+                               }
+                       }
+                       e=shm_malloc(size);
+                       if (e==0){
+                               ret=E_OUT_OF_MEM;
+                               goto error;
+                       }
+                       *blst_mem_used+=size;
+                       e->flags=err_flags;
+                       e->proto=proto;
+                       e->port=port;
+                       memcpy(e->ip, ip->u.addr, ip->len);
+                       e->expire=now+S_TO_TICKS(blst_timeout); /* update the timeout */
+                       e->next=0;
+                       dst_blacklist_lst_add(&dst_blst_hash[hash], e);
+               }
+error:
+       UNLOCK_BLST();
+       return ret;
+}
+
+
+
+/* if no blacklisted returns 0, else returns the blacklist flags */
+inline static int dst_is_blacklisted_ip(unsigned char proto,
+                                                                               struct ip_addr* ip,
+                                                                               unsigned short port)
+{
+       struct dst_blst_entry* e;
+       unsigned short hash;
+       ticks_t now;
+       int ret=0;
+       
+       ret=0;
+       now=get_ticks_raw();
+       hash=dst_blst_hash_no(proto, ip, port);
+       LOCK_BLST();
+               e=_dst_blacklist_lst_find(&dst_blst_hash[hash], ip, proto, port, now);
+               if (e){
+                       ret=e->flags;
+               }
+       UNLOCK_BLST();
+       return ret;
+}
+
+
+
+int dst_blacklist_add(unsigned char err_flags,  struct dest_info* si)
+{
+       struct ip_addr ip;
+       su2ip_addr(&ip, &si->to);
+       return dst_blacklist_add_ip(err_flags, si->proto, &ip,
+                                                               su_getport(&si->to));
+}
+
+
+
+int dst_is_blacklisted(struct dest_info* si)
+{
+       struct ip_addr ip;
+       su2ip_addr(&ip, &si->to);
+       return dst_is_blacklisted_ip(si->proto, &ip, su_getport(&si->to));
+}
+
+
+
+/* rpc functions */
+void dst_blst_mem_info(rpc_t* rpc, void* ctx)
+{
+       rpc->add(ctx, "dd",  *blst_mem_used, blst_max_mem);
+}
+
+
+
+static char* get_proto_name(unsigned char proto)
+{
+       switch(proto){
+               case PROTO_NONE:
+                       return "*";
+               case PROTO_UDP:
+                       return "udp";
+               case PROTO_TCP:
+                       return "tcp";
+               case PROTO_TLS:
+                       return "tls";
+               default:
+                       return "unknown";
+       }
+}
+
+
+
+/* only for debugging, it helds the lock too long for "production" use */
+void dst_blst_debug(rpc_t* rpc, void* ctx)
+{
+       int h;
+       struct dst_blst_entry* e;
+       ticks_t now;
+       struct ip_addr ip;
+       
+       now=get_ticks_raw();
+       LOCK_BLST();
+               for(h=0; h<DST_BLST_HASH_SIZE; h++){
+                       for(e=dst_blst_hash[h]; e; e=e->next){
+                               dst_blst_entry2ip(&ip, e);
+                               rpc->add(ctx, "ssddd", get_proto_name(e->proto), 
+                                                                               ip_addr2a(&ip), e->port, 
+                                                                               (s_ticks_t)(now-e->expire)<=0?
+                                                                               TICKS_TO_S(e->expire-now):
+                                                                               -TICKS_TO_S(now-e->expire) ,
+                                                                               e->flags);
+                       }
+               }
+       UNLOCK_BLST();
+}
+
+#endif /* USE_DST_BLACKLIST */
+
diff --git a/dst_blacklist.h b/dst_blacklist.h
new file mode 100644 (file)
index 0000000..29bcbd5
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * $Id$
+ *
+ * resolver related functions
+ *
+ * Copyright (C) 2006 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* History:
+ * --------
+ *  2006-07-29  created by andrei
+ */
+
+#ifndef dst_black_list_h
+#define dst_black_list_h
+
+#include "ip_addr.h"
+
+/* flags: */
+#define BLST_IS_IPV6           1               /* set if the address is ipv6 */
+#define BLST_ERR_SEND          (1<<1)  /* set if  send is denied/failed */
+#define BLST_ERR_CONNECT       (1<<2)  /* set if connect failed (tcp/tls) */
+#define BLST_ICMP_RCVD         (1<<3)  /* set if icmp error */
+#define BLST_ERR_TIMEOUT       (1<<4)  /* set if sip timeout */
+#define BLST_RESERVED          (1<<5)  /* not used yet */
+#define BLST_ADM_PROHIBITED    (1<<6)  /* administratively prohibited */
+#define BLST_PERMANENT         (1<<7)  /* never deleted, never expires */
+
+int init_dst_blacklist();
+void destroy_dst_blacklist();
+
+int dst_blacklist_add(unsigned char err_flags, struct dest_info* si);
+
+int dst_is_blacklisted(struct dest_info* si);
+#endif
diff --git a/error.h b/error.h
index 4e7f211..ab4b95f 100644 (file)
--- a/error.h
+++ b/error.h
@@ -72,6 +72,8 @@
 
 /* error in server */
 #define E_BAD_SERVER     -500
+#define E_ADM_PROHIBITED  -510
+#define E_BLACKLISTED    -520
 
 
 #define MAX_REASON_LEN 128
index 3d47741..3197474 100644 (file)
--- a/forward.c
+++ b/forward.c
@@ -50,6 +50,8 @@
  *              pkg_malloc'ed (andrei)
  *  2006-04-12  forward_{request,reply} use now struct dest_info (andrei)
  *  2006-04-21  basic comp via param support (andrei)
+ *  2006-07-31  forward_request can resolve destination on its own, uses the 
+ *              dns cache and falls back on send error to other ips (andrei)
  */
 
 
 #include "name_alias.h"
 #include "socket_info.h"
 #include "onsend.h"
+#include "resolve.h"
+#ifdef USE_DNS_FAILOVER
+#include "dns_cache.h"
+#endif
+#ifdef USE_DST_BLACKLIST
+#include "dst_blacklist.h"
+#endif
 
 #ifdef DEBUG_DMALLOC
 #include <dmalloc.h>
@@ -265,32 +274,64 @@ found:
 /* forwards a request to dst
  * parameters:
  *   msg       - sip msg
+ *   dst       - destination name, if non-null it will be resolved and
+ *               send_info updated with the ip/port. Even if dst is non
+ *               null send_info must contain the protocol and if a non
+ *               default port or non srv. lookup is desired, the port must
+ *               be !=0 
+ *   port      - used only if dst!=0 (else the port in send_info->to is used)
  *   send_info - filled dest_info structure:
  *               if the send_socket memeber is null, a send_socket will be 
  *               choosen automatically
- * WARNING: don' forget to zero-fill all the  unused members (a non-zero 
+ * WARNING: don't forget to zero-fill all the  unused members (a non-zero 
  * random id along with proto==PROTO_TCP can have bad consequences, same for
- *   a bogus send_socket vaule)
+ *   a bogus send_socket value)
  */
-int forward_request(struct sip_msg* msg, struct dest_info* send_info)
+int forward_request(struct sip_msg* msg, str* dst, unsigned short port,
+                                                       struct dest_info* send_info)
 {
        unsigned int len;
        char* buf;
        char md5[MD5_LEN];
+       struct socket_info* orig_send_sock; /* initial send_sock */
+       int ret;
+       struct ip_addr ip; /* debugging only */
+#ifdef USE_DNS_FAILOVER
+       struct socket_info* prev_send_sock;
+       int err;
+       struct dns_srv_handle dns_srv_h;
        
-       buf=0;
+       prev_send_sock=0;
+       err=0;
+#endif
        
-       if (send_info->send_sock==0)
-               send_info->send_sock=get_send_socket(msg, &send_info->to,
-                                                                                               send_info->proto);
-       if (send_info->send_sock==0){
-               LOG(L_ERR, "forward_req: ERROR: cannot forward to af %d, proto %d "
-                               "no corresponding listening socket\n",
-                               send_info->to.s.sa_family, send_info->proto);
-               ser_error=E_NO_SOCKET;
-               goto error;
-       }
-
+       
+       buf=0;
+       orig_send_sock=send_info->send_sock;
+       ret=0;
+
+       if(dst){
+#ifdef USE_DNS_FAILOVER
+               if (use_dns_failover){
+                       dns_srv_handle_init(&dns_srv_h);
+                       err=dns_sip_resolve2su(&dns_srv_h, &send_info->to, dst, port,
+                                                                       send_info->proto, dns_flags);
+                       if (err!=0){
+                               LOG(L_ERR, "ERROR: forward_request: resolving \"%.*s\""
+                                               " failed: %s [%d]\n", dst->len, ZSW(dst->s),
+                                               dns_strerror(err), err);
+                               ret=E_BAD_ADDRESS;
+                               goto error;
+                       }
+               }else
+#endif
+               if (sip_hostport2su(&send_info->to, dst, port, send_info->proto)<0){
+                       LOG(L_ERR, "ERROR: forward_request: bad host name %.*s,"
+                                               " dropping packet\n", dst->len, ZSW(dst->s));
+                       ret=E_BAD_ADDRESS;
+                       goto error;
+               }
+       }/* dst */
        /* calculate branch for outbound request;  if syn_branch is turned off,
           calculate is from transaction key, i.e., as an md5 of From/To/CallID/
           CSeq exactly the same way as TM does; good for reboot -- than messages
@@ -306,48 +347,130 @@ int forward_request(struct sip_msg* msg, struct dest_info* send_info)
        } else {
                if (!char_msg_val( msg, md5 ))  { /* parses transaction key */
                        LOG(L_ERR, "ERROR: forward_request: char_msg_val failed\n");
+                       ret=E_UNSPEC;
                        goto error;
                }
                msg->hash_index=hash( msg->callid->body, get_cseq(msg)->number);
                if (!branch_builder( msg->hash_index, 0, md5, 0 /* 0-th branch */,
                                        msg->add_to_branch_s, &msg->add_to_branch_len )) {
                        LOG(L_ERR, "ERROR: forward_request: branch_builder failed\n");
+                       ret=E_UNSPEC;
                        goto error;
                }
        }
-
-       buf = build_req_buf_from_sip_req(msg, &len, send_info);
-       if (!buf){
-               LOG(L_ERR, "ERROR: forward_request: building failed\n");
-               goto error;
-       }
-        /* send it! */
-       DBG("Sending:\n%.*s.\n", (int)len, buf);
-       DBG("orig. len=%d, new_len=%d, proto=%d\n",
-                       msg->len, len, send_info->proto );
+       /* try to send the message until success or all the ips are exhausted
+        *  (if dns lookup is peformed && the dns cache used ) */
+#ifdef USE_DNS_FAILOVER
+       do{
+#endif
+               if (orig_send_sock==0) /* no forced send_sock => find it **/
+                       send_info->send_sock=get_send_socket(msg, &send_info->to,
+                                                                                               send_info->proto);
+               if (send_info->send_sock==0){
+                       LOG(L_ERR, "forward_req: ERROR: cannot forward to af %d, proto %d "
+                                               "no corresponding listening socket\n",
+                                               send_info->to.s.sa_family, send_info->proto);
+                       ret=ser_error=E_NO_SOCKET;
+#ifdef USE_DNS_FAILOVER
+                       /* continue, maybe we find a socket for some other ip */
+                       continue;
+#else
+                       goto error;
+#endif
+               }
        
-       if (run_onsend(msg, send_info, buf, len)==0){
-               LOG(L_INFO, "forward_request: request dropped (onsend_route)\n");
-               STATS_TX_DROPS;
-               ser_error=E_OK; /* no error */
-               goto error; /* error ? */
-       }
-       if (msg_send(send_info, buf, len)<0){
-               ser_error=E_SEND;
-               STATS_TX_DROPS;
+#ifdef USE_DNS_FAILOVER
+               if (prev_send_sock!=send_info->send_sock){
+                       /* rebuild the message only if the send_sock changed */
+                       prev_send_sock=send_info->send_sock;
+#endif
+                       if (buf) pkg_free(buf);
+                       buf = build_req_buf_from_sip_req(msg, &len, send_info);
+                       if (!buf){
+                               LOG(L_ERR, "ERROR: forward_request: building failed\n");
+                               ret=E_OUT_OF_MEM; /* most probable */
+                               goto error;
+                       }
+#ifdef USE_DNS_FAILOVER
+               }
+#endif
+                /* send it! */
+               DBG("Sending:\n%.*s.\n", (int)len, buf);
+               DBG("orig. len=%d, new_len=%d, proto=%d\n",
+                               msg->len, len, send_info->proto );
+       
+               if (run_onsend(msg, send_info, buf, len)==0){
+                       su2ip_addr(&ip, &send_info->to);
+                       LOG(L_INFO, "forward_request: request to %s:%d(%d) dropped"
+                                       " (onsend_route)\n", ip_addr2a(&ip),
+                                               su_getport(&send_info->to), send_info->proto);
+                       ser_error=E_OK; /* no error */
+                       ret=E_ADM_PROHIBITED;
+#ifdef USE_DNS_FAILOVER
+                       continue; /* try another ip */
+#else
+                       goto error; /* error ? */
+#endif
+               }
+#ifdef USE_DST_BLACKLIST
+               if (use_dst_blacklist){
+                       if (dst_is_blacklisted(send_info)){
+                               su2ip_addr(&ip, &send_info->to);
+                               LOG(L_ERR, "ERROR: blacklisted destination:%s:%d (%d)\n",
+                                                       ip_addr2a(&ip), su_getport(&send_info->to),
+                                                       send_info->proto);
+                               ret=ser_error=E_SEND;
+#ifdef USE_DNS_FAILOVER
+                               continue; /* try another ip */
+#else
+                               goto error;
+#endif
+                       }
+               }
+#endif
+               if (msg_send(send_info, buf, len)<0){
+                       ret=ser_error=E_SEND;
+#ifdef USE_DST_BLACKLIST
+                       if (use_dst_blacklist)
+                               dst_blacklist_add(BLST_ERR_SEND, send_info);
+#endif
+#ifdef USE_DNS_FAILOVER
+                       continue; /* try another ip */
+#else
+                       goto error;
+#endif
+               }else{
+                       ret=ser_error=E_OK;
+                       /* sent requests stats */
+                       STATS_TX_REQUEST(  msg->first_line.u.request.method_value );
+                       /* exit succcesfully */
+                       goto end;
+               }
+#ifdef USE_DNS_FAILOVER
+       }while(dst && use_dns_failover && dns_srv_handle_next(&dns_srv_h, err) && 
+                       ((err=dns_sip_resolve2su(&dns_srv_h, &send_info->to, dst, port,
+                                                                 send_info->proto, dns_flags))==0));
+       if ((err!=0) && (err!=-E_DNS_EOR)){
+               LOG(L_ERR, "ERROR:  resolving %.*s host name in uri"
+                                                       " failed: %s [%d] (dropping packet)\n",
+                                                                       dst->len, ZSW(dst->s),
+                                                                       dns_strerror(err), err);
+               ret=ser_error=E_BAD_ADDRESS;
                goto error;
        }
+#endif
        
-       /* sent requests stats */
-       STATS_TX_REQUEST(  msg->first_line.u.request.method_value );
-       
-       pkg_free(buf);
-       /* received_buf & line_buf will be freed in receive_msg by free_lump_list*/
-       return 0;
-
 error:
+       STATS_TX_DROPS;
+end:
+#ifdef USE_DNS_FAILOVER
+       if (dst && use_dns_failover){
+                               dns_srv_handle_put(&dns_srv_h);
+       }
+#endif
        if (buf) pkg_free(buf);
-       return -1;
+       /* received_buf & line_buf will be freed in receive_msg by free_lump_list*/
+       return ret;
 }
 
 
index 2341eea..e2d6da4 100644 (file)
--- a/forward.h
+++ b/forward.h
@@ -60,7 +60,8 @@ struct socket_info* get_send_socket(struct sip_msg* msg,
                                                                        union sockaddr_union* su, int proto);
 struct socket_info* get_out_socket(union sockaddr_union* to, int proto);
 int check_self(str* host, unsigned short port, unsigned short proto);
-int forward_request( struct sip_msg* msg,  struct dest_info* send_info);
+int forward_request( struct sip_msg* msg, str* dst,  unsigned short port,
+                                               struct dest_info* send_info);
 int update_sock_struct_from_via( union sockaddr_union* to,
                                                                 struct sip_msg* msg,
                                                                 struct via_body* via );
index 1048888..9aac991 100644 (file)
--- a/globals.h
+++ b/globals.h
@@ -123,6 +123,7 @@ extern int tos;
 /* extern int *pids; -moved to pt.h */
 
 extern int cfg_errors;
+extern int cfg_warnings;
 extern unsigned int msg_no;
 
 extern unsigned long shm_mem_size;
@@ -168,5 +169,22 @@ extern int dns_retr_time;
 extern int dns_retr_no;
 extern int dns_servers_no;
 extern int dns_search_list;
+#ifdef USE_DNS_CACHE
+extern int use_dns_cache; /* 1 if the cache is enabled, 0 otherwise */
+extern int use_dns_failover; /* 1 if failover is enabled, 0 otherwise */
+unsigned int dns_cache_max_mem; /* maximum memory used for the cached entries*/
+unsigned int dns_neg_cache_ttl; /* neg. cache ttl */
+unsigned int dns_cache_max_ttl; /* maximum ttl */
+unsigned int dns_cache_min_ttl; /* minimum ttl */
+unsigned int dns_timer_interval; /* gc timer interval in s */
+int dns_flags; /* default flags used for the  dns_*resolvehost 
+                    (compatibility wrappers) */
+#endif
+#ifdef USE_DST_BLACKLIST
+extern int use_dst_blacklist; /* 1 if the blacklist is enabled */
+unsigned int  blst_max_mem; /* maximum memory used for the blacklist entries*/
+unsigned int blst_timeout; /* blacklist entry ttl */
+unsigned int blst_timer_interval; /* blacklist gc timer interval (in s)*/
+#endif
 
 #endif
diff --git a/main.c b/main.c
index e9942bc..e6beab0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -61,6 +61,7 @@
  *               parent & once in the child to avoid a short window when one
  *               of them might use it "unset" (andrei)
  *  2005-07-25  use sigaction for setting the signal handlers (andrei)
+ *  2006-07-13  added dns cache/failover init. (andrei)
  */
 
 
 #include "core_cmd.h"
 #include "flags.h"
 #include "atomic_ops_init.h"
+#ifdef USE_DNS_CACHE
+#include "dns_cache.h"
+#endif
+#ifdef USE_DST_BLACKLIST
+#include "dst_blacklist.h"
+#endif
 
 #include "stats.h"
 
@@ -299,6 +306,13 @@ int reply_to_via=0;
 int mcast_loopback = 0;
 int mcast_ttl = -1; /* if -1, don't touch it, use the default (usually 1) */
 #endif /* USE_MCAST */
+#ifdef USE_DNS_CACHE
+int use_dns_cache=1; /* 1 if the cache is enabled, 0 otherwise */
+int use_dns_failover=0; /* 1 if failover is enabled, 0 otherwise */
+#endif
+#ifdef USE_DST_BLACKLIST
+int use_dst_blacklist=0; /* 1 if the blacklist is enabled */
+#endif
 
 int tos = IPTOS_LOWDELAY;
 
@@ -355,6 +369,8 @@ int process_count = 0;
 
 /* cfg parsing */
 int cfg_errors=0;
+int cfg_warnings=0;
+
 
 /* shared memory (in MB) */
 unsigned long shm_mem_size=SHM_MEM_SIZE * 1024 * 1024;
@@ -386,6 +402,12 @@ void cleanup(show_status)
                                         some process crashed and let it locked; this will
                                         allow an almost gracious shutdown */
        destroy_modules();
+#ifdef USE_DNS_CACHE
+       destroy_dns_cache();
+#endif
+#ifdef USE_DST_BLACKLIST
+       destroy_dst_blacklist();
+#endif
 #ifdef USE_TCP
        destroy_tcp();
 #endif
@@ -1210,13 +1232,16 @@ int main(int argc, char** argv)
        int ret;
        unsigned int seed;
        int rfd;
-       int debug_save, debug_flag = 0;
-       int dont_fork_cnt = 0;
+       int debug_save, debug_flag;
+       int dont_fork_cnt;
+       int socket_types;
 
        /*init*/
        creator_pid = getpid();
        ret=-1;
        my_argc=argc; my_argv=argv;
+       debug_flag=0;
+       dont_fork_cnt=0;
 
        /*init pkg mallocs (before parsing cfg or cmd line !)*/
        if (init_pkg_mallocs()==-1)
@@ -1361,6 +1386,9 @@ try_again:
                fprintf(stderr, "ERROR: bad config file (%d errors)\n", cfg_errors);
                goto error;
        }
+       if (cfg_warnings){
+               fprintf(stderr, "%d config warnings\n", cfg_warnings);
+       }
        if (debug_flag) debug = debug_save;
        print_rls();
 
@@ -1517,10 +1545,15 @@ try_again:
                        goto error;
                }
        }
-       if (fix_all_socket_lists()!=0){
+       if (fix_all_socket_lists(&socket_types)!=0){
                fprintf(stderr,  "failed to initialize list addresses\n");
                goto error;
        }
+       if (dns_try_ipv6 && !(socket_types & SOCKET_T_IPV6)){
+               /* if we are not listening on any ipv6 address => no point
+                * to try to resovle ipv6 addresses */
+               dns_try_ipv6=0;
+       }
        /* print all the listen addresses */
        printf("Listening on \n");
        print_all_socket_lists();
@@ -1560,7 +1593,20 @@ try_again:
                LOG(L_CRIT, "could not initialize timer, exiting...\n");
                goto error;
        }
-
+#ifdef USE_DNS_CACHE
+       if (init_dns_cache()<0){
+               LOG(L_CRIT, "could not initialize the dns cache, exiting...\n");
+               goto error;
+       }
+       if (use_dns_cache==0)
+               use_dns_failover=0; /* cannot work w/o dns_cache support */
+#endif
+#ifdef USE_DST_BLACKLIST
+       if (init_dst_blacklist()<0){
+               LOG(L_CRIT, "could not initialize the dst blacklist, exiting...\n");
+               goto error;
+       }
+#endif
        if (init_avps()<0) goto error;
        if (rpc_init_time() < 0) goto error;
 
index ad66e09..e94bda1 100644 (file)
@@ -41,6 +41,7 @@
  *             timer_link.payload removed (bogdan)
  * 2004-08-23  avp support added - move and remove avp list to/from
  *             transactions (bogdan)
+ * 2006-08-11  dns failover support (andrei)
  */
 
 #include <stdlib.h>
@@ -148,6 +149,22 @@ void free_cell( struct cell* dead_cell )
                if (rpl && rpl!=FAKED_REPLY && rpl->msg_flags&FL_SHM_CLONE) {
                        sip_msg_free_unsafe( rpl );
                }
+#ifdef USE_DNS_FAILOVER
+               if (dead_cell->uac[i].dns_h.a){
+                       DBG("branch %d -> dns_h.srv (%.*s) ref=%d,"
+                                                       " dns_h.a (%.*s) ref=%d\n", i,
+                                       dead_cell->uac[i].dns_h.srv?
+                                                               dead_cell->uac[i].dns_h.srv->name_len:0,
+                                       dead_cell->uac[i].dns_h.srv?
+                                                               dead_cell->uac[i].dns_h.srv->name:"",
+                                       dead_cell->uac[i].dns_h.srv?
+                                                               dead_cell->uac[i].dns_h.srv->refcnt.val:0,
+                                       dead_cell->uac[i].dns_h.a->name_len,
+                                       dead_cell->uac[i].dns_h.a->name,
+                                       dead_cell->uac[i].dns_h.a->refcnt.val);
+               }
+               dns_srv_handle_put_shm_unsafe(&dead_cell->uac[i].dns_h);
+#endif
        }
 
        /* collected to tags */
@@ -220,6 +237,9 @@ static void inline init_branches(struct cell *t)
                uac->request.branch = i;
                init_rb_timers(&uac->request);
                uac->local_cancel=uac->request;
+#ifdef USE_DNS_FAILOVER
+               dns_srv_handle_init(&uac->dns_h);
+#endif
        }
 }
 
index 324d8ab..0d1866a 100644 (file)
@@ -36,6 +36,7 @@
  *             with flags (bogdan)
  * 2004-08-23  avp support added - avp list linked in transaction (bogdan)
  * 2005-11-03  updated to the new timer interface (dropped tm timers) (andrei)
+ * 2006-08-11  dns failover support (andrei)
  */
 
 #include "defs.h"
@@ -65,7 +66,9 @@ struct retr_buf;
 #include "sip_msg.h"
 #include "t_reply.h"
 #include "t_hooks.h"
-#include "../../timer.h"
+#ifdef USE_DNS_FAILOVER
+#include "../../dns_cache.h"
+#endif
 
 #define LOCK_HASH(_h) lock_hash((_h))
 #define UNLOCK_HASH(_h) unlock_hash((_h))
@@ -146,6 +149,8 @@ typedef struct ua_server
 
 typedef struct ua_client
 {
+       /* if we store a reply (branch picking), this is where it is */
+       struct sip_msg  *reply;
        struct retr_buf  request;
        /* we maintain a separate copy of cancel rather than
           reuse the structure for original request; the 
@@ -156,9 +161,10 @@ typedef struct ua_client
        struct retr_buf local_cancel;
        /* pointer to retransmission buffer where uri is printed;
           good for generating ACK/CANCEL */
+#ifdef USE_DNS_FAILOVER
+       struct dns_srv_handle dns_h;
+#endif
        str              uri;
-       /* if we store a reply (branch picking), this is where it is */
-       struct sip_msg  *reply;
        /* if we don't store, we at least want to know the status */
        int             last_received;
 }ua_client_type;
index 980faee..94ed9a3 100644 (file)
@@ -44,6 +44,9 @@
  *              desable variable timer feature (bogdan)
  *  2005-12-11  t_relay doesn't return 0 (stop script) on send error 
  *              anymore (andrei)
+ *  2006-08-11  updated forward_request usage (andrei)
+ *              t_relay_to releases the transaction if t_forward_non_ack
+ *              fails and t_kill fails or this is a failed replication (andrei)
  */
 
 #include <limits.h>
@@ -198,11 +201,13 @@ int t_relay_to( struct sip_msg  *p_msg , struct proxy_l *proxy, int proto,
 {
        int ret;
        int new_tran;
-       str *uri;
        int reply_ret;
        /* struct hdr_field *hdr; */
        struct cell *t;
        struct dest_info dst;
+       unsigned short port;
+       str host;
+       short comp;
 
        ret=0;
        
@@ -233,19 +238,26 @@ int t_relay_to( struct sip_msg  *p_msg , struct proxy_l *proxy, int proto,
        if ( p_msg->REQ_METHOD==METHOD_ACK) {
                DBG( "SER: forwarding ACK  statelessly \n");
                if (proxy==0) {
-                       uri = GET_RURI(p_msg);
-                       if (uri2dst(&dst, p_msg, GET_NEXT_HOP(p_msg), proto)==0){
+                       init_dest_info(&dst);
+                       dst.proto=proto;
+                       if (get_uri_send_info(GET_NEXT_HOP(p_msg), &host, &port,
+                                                                       &dst.proto, &comp)!=0){
                                ret=E_BAD_ADDRESS;
                                goto done;
                        }
-                       ret=forward_request( p_msg , &dst) ;
+#ifdef USE_COMP
+                       dst.comp=comp;
+#endif
+                       /* dst->send_sock not set, but forward_request will take care
+                        * of it */
+                       ret=forward_request(p_msg, &host, port, &dst);
                } else {
                        init_dest_info(&dst);
                        dst.proto=get_proto(proto, proxy->proto);
                        proxy2su(&dst.to, proxy);
                        /* dst->send_sock not set, but forward_request will take care
                         * of it */
-                       ret=forward_request( p_msg , &dst) ;
+                       ret=forward_request( p_msg , 0, 0, &dst) ;
                }
                goto done;
        }
@@ -282,7 +294,10 @@ int t_relay_to( struct sip_msg  *p_msg , struct proxy_l *proxy, int proto,
                        }  else {
                                DBG("ERROR: generation of a stateful reply "
                                        "on error failed\n");
+                               t_release_transaction(t);
                        }
+               }else{
+                       t_release_transaction(t); /* kill it  silently */
                }
        } else {
                DBG( "SER: new transaction fwd'ed\n");
index 3125744..0806e3e 100644 (file)
  *  2006-02-07  named routes support (andrei)
  *  2006-04-18  add_uac simplified + switched to struct dest_info (andrei)
  *  2006-04-20  pint_uac_request uses now struct dest_info (andrei)
+ *  2006-08-11  dns failover support (andrei)
+ *              t_forward_non_ack won't start retransmission on send errors
+ *               anymore (WARNING: callers should release/kill the transaction
+ *               if error is returned) (andrei)
  */
 
 #include "defs.h"
 #include "t_fwd.h"
 #include "fix_lumps.h"
 #include "config.h"
+#ifdef USE_DNS_FAILOVER
+#include "../../dns_cache.h"
+#endif
+#ifdef USE_DST_BLACKLIST
+#include "../../dst_blacklist.h"
+#endif
+
+
 
 static int goto_on_branch = 0, branch_route = 0;
 
@@ -105,8 +117,14 @@ static char *print_uac_request( struct cell *t, struct sip_msg *i_req,
        char *buf, *shbuf;
        str* msg_uri;
        struct lump* add_rm_backup, *body_lumps_backup;
+       struct sip_uri parsed_uri_bak;
+       int parsed_uri_ok_bak;
+       str msg_uri_bak;
 
        shbuf=0;
+       msg_uri_bak.s=0; /* kill warnings */
+       msg_uri_bak.len=0;
+       parsed_uri_ok_bak=0;
 
        /* ... we calculate branch ... */       
        if (!t_calc_branch(t, branch, i_req->add_to_branch_s,
@@ -119,6 +137,9 @@ static char *print_uac_request( struct cell *t, struct sip_msg *i_req,
        /* ... update uri ... */
        msg_uri=GET_RURI(i_req);
        if ((msg_uri->s!=uri->s) || (msg_uri->len!=uri->len)){
+               msg_uri_bak=i_req->new_uri;
+               parsed_uri_ok_bak=i_req->parsed_uri_ok;
+               parsed_uri_bak=i_req->parsed_uri;
                i_req->new_uri=*uri;
                i_req->parsed_uri_ok=0;
        }
@@ -173,6 +194,12 @@ error01:
             /* Restore the lists from backups */
        i_req->add_rm = add_rm_backup;
        i_req->body_lumps = body_lumps_backup;
+       /* restore the new_uri from the backup */
+       if ((msg_uri->s!=uri->s) || (msg_uri->len!=uri->len)){
+               i_req->new_uri=msg_uri_bak;
+               i_req->parsed_uri=parsed_uri_bak;
+               i_req->parsed_uri_ok=parsed_uri_ok_bak;
+       }
 
  error00:
        return shbuf;
@@ -257,9 +284,15 @@ int add_uac( struct cell *t, struct sip_msg *request, str *uri, str* next_hop,
                get_send_socket( request, &t->uac[branch].request.dst.to,
                                                                t->uac[branch].request.dst.proto);
        }else {
+#ifdef USE_DNS_FAILOVER
+               if (uri2dst(&t->uac[branch].dns_h, &t->uac[branch].request.dst,
+                                       request, next_hop?next_hop:uri, proto) == 0)
+#else
                /* dst filled from the uri & request (send_socket) */
                if (uri2dst(&t->uac[branch].request.dst, request,
-                                               next_hop ? next_hop: uri, proto)==0){
+                                               next_hop ? next_hop: uri, proto)==0)
+#endif
+               {
                        ret=E_BAD_ADDRESS;
                        goto error;
                }
@@ -295,7 +328,7 @@ int add_uac( struct cell *t, struct sip_msg *request, str *uri, str* next_hop,
        if (proxy){
                proxy_mark(proxy, 1);
        }
-       /* done! */     
+       /* done! */
        ret=branch;
                
 error01:
@@ -303,6 +336,62 @@ error:
        return ret;
 }
 
+
+
+#ifdef USE_DNS_FAILOVER
+/* introduce a new uac to transaction, based on old_uac and a possible
+ *  new ip address (if the dns name resolves to more ips). If no more
+ *   ips are found => returns -1.
+ *  returns its branch id (>=0)
+   or error (<0); it doesn't send a message yet -- a reply to it
+   might interfere with the processes of adding multiple branches
+   if lock_replies is 1 replies will be locked for t until the new branch
+   is added (to prevent add branches races). Use 0 if the reply lock is
+   already held, e.g. in failure route/handlers (WARNING: using 1 in a 
+   failure route will cause a deadlock).
+*/
+int add_uac_dns_fallback( struct cell *t, struct sip_msg* msg, 
+                                                                       struct ua_client* old_uac,
+                                                                       int lock_replies)
+{
+       int ret;
+       
+       ret=-1;
+       if (use_dns_failover && dns_srv_handle_next(&old_uac->dns_h, 0)){
+                       if (lock_replies){
+                               /* use reply lock to guarantee nobody is adding a branch
+                                * in the same time */
+                               LOCK_REPLIES(t);
+                       }
+                       if (t->nr_of_outgoings >= MAX_BRANCHES){
+                               LOG(L_ERR, "ERROR: add_uac_dns_fallback: maximum number of "
+                                                       "branches exceeded\n");
+                               if (lock_replies)
+                                       UNLOCK_REPLIES(t);
+                               return E_CFG;
+                       }
+                       /* copy the dns handle into the new uac */
+                       dns_srv_handle_cpy(&t->uac[t->nr_of_outgoings].dns_h,
+                                                               &old_uac->dns_h);
+                       /* add_uac will use dns_h => next_hop will be ignored.
+                        * Unfortunately we can't reuse the old buffer, the branch id
+                        *  must be changed and the send_socket might be different =>
+                        *  re-create the whole uac */
+                       ret=add_uac(t,  msg, &old_uac->uri, 0, 0, 
+                                                       old_uac->request.dst.proto);
+                       if (ret<0){
+                               /* failed, delete the copied dns_h */
+                               dns_srv_handle_put(&t->uac[t->nr_of_outgoings].dns_h);
+                       }
+                       if (lock_replies){
+                               UNLOCK_REPLIES(t);
+                       }
+       }
+       return ret;
+}
+
+#endif
+
 int e2e_cancel_branch( struct sip_msg *cancel_msg, struct cell *t_cancel, 
        struct cell *t_invite, int branch )
 {
@@ -310,11 +399,16 @@ int e2e_cancel_branch( struct sip_msg *cancel_msg, struct cell *t_cancel,
        char *shbuf;
        unsigned int len;
 
+       ret=-1;
        if (t_cancel->uac[branch].request.buffer) {
                LOG(L_CRIT, "ERROR: e2e_cancel_branch: buffer rewrite attempt\n");
                ret=ser_error=E_BUG;
                goto error;
-       }       
+       }
+       if (t_invite->uac[branch].request.buffer==0){
+               /* inactive / deleted  branch */
+               goto error;
+       }
 
        /* note -- there is a gap in proxy stats -- we don't update 
           proxy stats with CANCEL (proxy->ok, proxy->tx, etc.)
@@ -440,6 +534,134 @@ void e2e_cancel( struct sip_msg *cancel_msg,
 }
 
 
+
+/* sends one uac/branch buffer and fallbacks to other ips if
+ *  the destination resolves to several addresses
+ *  Takes care of starting timers a.s.o. (on send success)
+ *  returns: -2 on error, -1 on drop,  current branch id on success,
+ *   new branch id on send error/blacklist, when failover is possible
+ *    (ret>=0 && ret!=branch)
+ *    if lock_replies is 1, the replies for t will be locked when adding
+ *     new branches (to prevent races). Use 0 from failure routes or other
+ *     places where the reply lock is already held, to avoid deadlocks. */
+int t_send_branch( struct cell *t, int branch, struct sip_msg* p_msg ,
+                                       struct proxy_l * proxy, int lock_replies)
+{
+       struct ip_addr ip; /* debugging */
+       int ret;
+       struct ua_client* uac;
+       
+       uac=&t->uac[branch];
+       ret=branch;
+       if (run_onsend(p_msg,   &uac->request.dst, uac->request.buffer,
+                                       uac->request.buffer_len)==0){
+               /* disable the current branch: set a "fake" timeout
+                *  reply code but don't set uac->reply, to avoid overriding 
+                *  a higly unlikely, perfectly timed fake reply (to a message
+                *   we never sent).
+                * (code=final reply && reply==0 => t_pick_branch won't ever pick it)*/
+                       uac->last_received=408;
+                       su2ip_addr(&ip, &uac->request.dst.to);
+                       DBG("t_send_branch: onsend_route dropped msg. to %s:%d (%d)\n",
+                                                       ip_addr2a(&ip), su_getport(&uac->request.dst.to),
+                                                       uac->request.dst.proto);
+#ifdef USE_DNS_FAILOVER
+                       /* if the destination resolves to more ips, add another
+                        *  branch/uac */
+                       if (use_dns_failover){
+                               ret=add_uac_dns_fallback(t, p_msg, uac, lock_replies);
+                               if (ret>=0){
+                                       su2ip_addr(&ip, &uac->request.dst.to);
+                                       DBG("t_send_branch: send on branch %d failed "
+                                                       "(onsend_route), trying another ip %s:%d (%d)\n",
+                                                       branch, ip_addr2a(&ip),
+                                                       su_getport(&uac->request.dst.to),
+                                                       uac->request.dst.proto);
+                                       /* success, return new branch */
+                                       return ret;
+                               }
+                       }
+#endif /* USE_DNS_FAILOVER*/
+               return -1; /* drop, try next branch */
+       }
+#ifdef USE_DST_BLACKLIST
+       if (use_dst_blacklist){
+               if (dst_is_blacklisted(&uac->request.dst)){
+                       su2ip_addr(&ip, &uac->request.dst.to);
+                       DBG("t_send_branch: blacklisted destination: %s:%d (%d)\n",
+                                                       ip_addr2a(&ip), su_getport(&uac->request.dst.to),
+                                                       uac->request.dst.proto);
+                       /* disable the current branch: set a "fake" timeout
+                        *  reply code but don't set uac->reply, to avoid overriding 
+                        *  a higly unlikely, perfectly timed fake reply (to a message
+                        *   we never sent).  (code=final reply && reply==0 => 
+                        *   t_pick_branch won't ever pick it)*/
+                       uac->last_received=408;
+#ifdef USE_DNS_FAILOVER
+                       /* if the destination resolves to more ips, add another
+                        *  branch/uac */
+                       if (use_dns_failover){
+                               ret=add_uac_dns_fallback(t, p_msg, uac, lock_replies);
+                               if (ret>=0){
+                                       su2ip_addr(&ip, &uac->request.dst.to);
+                                       DBG("t_send_branch: send on branch %d failed (blacklist),"
+                                                       " trying another ip %s:%d (%d)\n", branch,
+                                                       ip_addr2a(&ip), su_getport(&uac->request.dst.to),
+                                                       uac->request.dst.proto);
+                                       /* success, return new branch */
+                                       return ret;
+                               }
+                       }
+#endif /* USE_DNS_FAILOVER*/
+                       return -1; /* don't send */
+               }
+       }
+#endif /* USE_DST_BLACKLIST */
+       if (SEND_BUFFER( &uac->request)==-1) {
+               /* disable the current branch: set a "fake" timeout
+                *  reply code but don't set uac->reply, to avoid overriding 
+                *  a higly unlikely, perfectly timed fake reply (to a message
+                *  we never sent).
+                * (code=final reply && reply==0 => t_pick_branch won't ever pick it)*/
+               uac->last_received=408;
+               su2ip_addr(&ip, &uac->request.dst.to);
+               DBG("t_send_branch: send to %s:%d (%d) failed\n",
+                                                       ip_addr2a(&ip), su_getport(&uac->request.dst.to),
+                                                       uac->request.dst.proto);
+#ifdef USE_DST_BLACKLIST
+               if (use_dst_blacklist)
+                       dst_blacklist_add(BLST_ERR_SEND, &uac->request.dst);
+#endif
+#ifdef USE_DNS_FAILOVER
+               /* if the destination resolves to more ips, add another
+                *  branch/uac */
+               if (use_dns_failover){
+                       ret=add_uac_dns_fallback(t, p_msg, uac, lock_replies);
+                       if (ret>=0){
+                               /* success, return new branch */
+                               DBG("t_send_branch: send on branch %d failed, adding another"
+                                               " branch with another ip\n", branch);
+                               return ret;
+                       }
+               }
+#endif
+               LOG(L_ERR, "ERROR: t_send_branch: sending request on branch %d "
+                               "failed\n", branch);
+               if (proxy) { proxy->errors++; proxy->ok=0; }
+               return -2;
+       } else {
+               /* start retr. only if the send succeeded */
+               if (start_retr( &uac->request )!=0){
+                       LOG(L_CRIT, "BUG: t_send_branch: retr. already started for %p\n",
+                                       &uac->request);
+                       return -2;
+               }
+       }
+       return ret;
+}
+
+
+
 /* function returns:
  *       1 - forward successful
  *      -1 - error during forward
@@ -458,6 +680,7 @@ int t_forward_nonack( struct cell *t, struct sip_msg* p_msg ,
        struct cell *t_invite;
        int success_branch;
        int try_new;
+       int lock_replies;
        str dst_uri;
        struct socket_info* si, *backup_si;
 
@@ -545,7 +768,7 @@ int t_forward_nonack( struct cell *t, struct sip_msg* p_msg ,
        /* things went wrong ... no new branch has been fwd-ed at all */
        if (added_branches==0) {
                if (try_new==0) {
-                       LOG(L_ERR, "ERROR: t_forward_nonack: no branched for forwarding\n");
+                       LOG(L_ERR, "ERROR: t_forward_nonack: no branches for forwarding\n");
                        return -1;
                }
                LOG(L_ERR, "ERROR: t_forward_nonack: failure to add branches\n");
@@ -554,30 +777,26 @@ int t_forward_nonack( struct cell *t, struct sip_msg* p_msg ,
 
        /* send them out now */
        success_branch=0;
+       lock_replies= ! ((rmode==MODE_ONFAILURE) && (t==get_t()));
        for (i=first_branch; i<t->nr_of_outgoings; i++) {
                if (added_branches & (1<<i)) {
-                       if (run_onsend(p_msg,   &t->uac[i].request.dst,
-                                                                       t->uac[i].request.buffer,
-                                                                       t->uac[i].request.buffer_len)==0)
-                               continue; /* if onsend drop, try next branch */
                        
-                       if (SEND_BUFFER( &t->uac[i].request)==-1) {
-                               LOG(L_ERR, "ERROR: t_forward_nonack: sending request failed\n");
-                               if (proxy) { proxy->errors++; proxy->ok=0; }
-                       } else {
-                               success_branch++;
+                       branch_ret=t_send_branch(t, i, p_msg , proxy, lock_replies);
+                       if (branch_ret>=0){ /* some kind of success */
+                               if (branch_ret==i) /* success */
+                                       success_branch++;
+                               else /* new branch added */
+                                       added_branches |= 1<<branch_ret;
                        }
-                       if (start_retr( &t->uac[i].request )!=0)
-                               LOG(L_CRIT, "BUG: t_forward_non_ack: "
-                                               "failed to start retr. for %p\n", &t->uac[i].request);
                }
        }
        if (success_branch<=0) {
                ser_error=E_SEND;
+               /* the caller should take care and delete the transaction */
                return -1;
        }
        return 1;
-}      
+}
 
 int t_replicate(struct sip_msg *p_msg,  struct proxy_l *proxy, int proto )
 {
index facef27..8e3e670 100644 (file)
@@ -38,6 +38,7 @@
 #include "defs.h"
 
 #include "../../proxy.h"
+#include "h_table.h"
 
 typedef int (*tfwd_f)(struct sip_msg* p_msg , struct proxy_l * proxy );
 typedef int (*taddblind_f)( /*struct cell *t */ );
@@ -53,10 +54,17 @@ void e2e_cancel( struct sip_msg *cancel_msg, struct cell *t_cancel, struct cell
 int e2e_cancel_branch( struct sip_msg *cancel_msg, struct cell *t_cancel, struct cell *t_invite, int branch );
 int add_uac(   struct cell *t, struct sip_msg *request, str *uri, str* next_hop,
                                struct proxy_l *proxy, int proto );
+#ifdef USE_DNS_FAILOVER
+int add_uac_dns_fallback( struct cell *t, struct sip_msg* msg, 
+                                                                       struct ua_client* old_uac,
+                                                                       int lock_replies);
+#endif
 int add_blind_uac( /* struct cell *t */ );
 int t_forward_nonack( struct cell *t, struct sip_msg* p_msg,
                                                struct proxy_l * p, int proto);
 int t_forward_ack( struct sip_msg* p_msg );
+int t_send_branch( struct cell *t, int branch, struct sip_msg* p_msg ,
+                                       struct proxy_l * proxy, int lock_replies);
 
 
 #endif
index 4af72c6..1e4df70 100644 (file)
@@ -40,6 +40,8 @@
  * 2004-02-13: t->is_invite and t->local replaced with flags (bogdan)
  * 2006-04-21  build_uac_req, assemble_via use struct dest_info now;
  *              uri2sock replaced with uri2dst (andrei)
+ * 2006-08-11  build_dlg_ack: use the first dns ip for which a send_sock
+ *              is found (andrei)
  */
 
 #include "defs.h"
@@ -57,6 +59,9 @@
 #include "../../parser/contact/parse_contact.h"
 #include "t_msgbuilder.h"
 #include "uac.h"
+#ifdef USE_DNS_FAILOVER
+#include "../../dns_cache.h"
+#endif
 
 
 #define ROUTE_PREFIX "Route: "
@@ -359,7 +364,7 @@ static inline int get_contact_uri(struct sip_msg* msg, str* uri)
       * The function creates an ACK to 200 OK. Route set will be created
       * and parsed and next_hop parameter will contain the uri to which the
       * request should be send. The function is used by tm when it generates
-      * local ACK to 200 OK (on behalf of applications using uac
+      * local ACK to 200 OK (on behalf of applications using uac)
       */
 char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, unsigned int branch,
                    str* to, unsigned int *len, str *next_hop)
@@ -373,6 +378,9 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, unsigned int branch
        struct rte* list;
        str contact, ruri, *cont;
        struct dest_info dst;
+#ifdef USE_DNS_FAILOVER
+       struct dns_srv_handle dns_h;
+#endif
        
        if (get_contact_uri(rpl, &contact) < 0) {
                return 0;
@@ -399,11 +407,30 @@ char *build_dlg_ack(struct sip_msg* rpl, struct cell *Trans, unsigned int branch
        *len += ruri.len;
        
        
-            /* via */
-       if ((uri2dst(&dst, rpl, next_hop, PROTO_NONE)==0) || (dst.send_sock==0)){
+        /* via */
+#ifdef USE_DNS_FAILOVER
+       if (use_dns_failover){
+               dns_srv_handle_init(&dns_h);
+               if ((uri2dst(&dns_h , &dst, rpl, next_hop, PROTO_NONE)==0) ||
+                               (dst.send_sock==0)){
+                       dns_srv_handle_put(&dns_h);
+                       LOG(L_ERR, "build_dlg_ack: no socket found\n");
+                       goto error;
+               }
+               dns_srv_handle_put(&dns_h); /* not needed any more */
+       }else{
+               if ((uri2dst(0 , &dst, rpl, next_hop, PROTO_NONE)==0) ||
+                               (dst.send_sock==0)){
+                       LOG(L_ERR, "build_dlg_ack: no socket found\n");
+                       goto error;
+               }
+       }
+#else
+       if ( (uri2dst( &dst, rpl, next_hop, PROTO_NONE)==0) || (dst.send_sock==0)){
                        LOG(L_ERR, "build_dlg_ack: no socket found\n");
                goto error;
        }
+#endif
        
        if (!t_calc_branch(Trans,  branch, branch_buf, &branch_len)) goto error;
        branch_str.s = branch_buf;
index 450ff64..686a465 100644 (file)
@@ -68,6 +68,9 @@
  *               in t_retransmit_reply & reply_light (andrei)
  *  2005-11-09  updated to the new timers interface (andrei)
  *  2006-02-07  named routes support (andrei)
+ *  2006-09-13  t_pick_branch will skip also over branches with empty reply 
+ *              t_should_relay_response will re-pick the branch if failure 
+ *               route /handlers added new branches (andrei)
  */
 
 
@@ -317,15 +320,37 @@ static int send_local_ack(struct sip_msg* msg, str* next_hop,
                                                        char* ack, int ack_len)
 {
        struct dest_info dst;
+#ifdef USE_DNS_FAILOVER
+       struct dns_srv_handle dns_h;
+#endif
 
        if (!next_hop) {
                LOG(L_ERR, "send_local_ack: Invalid parameter value\n");
                return -1;
        }
+#ifdef USE_DNS_FAILOVER
+       if (use_dns_failover){
+               dns_srv_handle_init(&dns_h);
+               if ((uri2dst(&dns_h, &dst, msg,  next_hop, PROTO_NONE)==0) || 
+                               (dst.send_sock==0)){
+                       dns_srv_handle_put(&dns_h);
+                       LOG(L_ERR, "send_local_ack: no socket found\n");
+                       return -1;
+               }
+               dns_srv_handle_put(&dns_h); /* not needed anymore */
+       }else{
+               if ((uri2dst(0, &dst, msg,  next_hop, PROTO_NONE)==0) || 
+                               (dst.send_sock==0)){
+                       LOG(L_ERR, "send_local_ack: no socket found\n");
+                       return -1;
+               }
+       }
+#else
        if ((uri2dst(&dst, msg,  next_hop, PROTO_NONE)==0) || (dst.send_sock==0)){
                LOG(L_ERR, "send_local_ack: no socket found\n");
                return -1;
        }
+#endif
        return msg_send(&dst, ack, ack_len);
 }
 
@@ -701,7 +726,8 @@ int t_pick_branch(int inc_branch, int inc_code, struct cell *t, int *res_code)
                /* there is still an unfinished UAC transaction; wait now! */
                if ( t->uac[b].last_received<200 )
                        return -2;
-               if ( t->uac[b].last_received<lowest_s ) {
+               /* if reply is null => t_send_branch "faked" reply, skip over it */
+               if ( t->uac[b].last_received<lowest_s && t->uac[b].reply ) {
                        lowest_b =b;
                        lowest_s = t->uac[b].last_received;
                }
@@ -729,6 +755,7 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
        int branch_cnt;
        int picked_branch;
        int picked_code;
+       int new_branch;
        int inv_through;
 
        /* note: this code never lets replies to CANCEL go through;
@@ -807,9 +834,8 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
                /* run ON_FAILURE handlers ( route and callbacks) */
                if ( has_tran_tmcbs( Trans, TMCB_ON_FAILURE_RO|TMCB_ON_FAILURE)
                || Trans->on_negative ) {
-                       run_failure_handlers( Trans,
-                               Trans->uac[picked_branch].reply,
-                               picked_code);
+                       run_failure_handlers( Trans, Trans->uac[picked_branch].reply,
+                                                                       picked_code);
                }
 
                /* now reset it; after the failure logic, the reply may
@@ -834,11 +860,21 @@ static enum rps t_should_relay_response( struct cell *Trans , int new_code,
                        return RPS_COMPLETED;
                }
                /* look if the callback/failure_route introduced new branches ... */
-               if (branch_cnt<Trans->nr_of_outgoings)  {
-                       /* await then result of new branches */
-                       *should_store=1;
-                       *should_relay=-1;
-                       return RPS_STORE;
+               if (branch_cnt<Trans->nr_of_outgoings){
+                       /* the new branches might be already "finished" => we
+                        * must use t_pick_branch again */
+                       new_branch=t_pick_branch(branch, new_code, Trans, &picked_code);
+                       if (new_branch<0){
+                               if (new_branch==-2) { /* branches open yet */
+                                       *should_store=1;
+                                       *should_relay=-1;
+                                       return RPS_STORE;
+                               }
+                               /* error, use the old picked_branch */
+                       }else{
+                               /* found a new_branch */
+                               picked_branch=new_branch;
+                       }
                }
 
                /* really no more pending branches -- return lowest code */
index 0615aa0..2b509ee 100644 (file)
  *  2005-10-03  almost completely rewritten to use the new timers (andrei)
  *  2005-12-12  on final response marked the rb as removed to avoid deleting
  *              it from the timer handle; timer_allow_del()  (andrei)
+ *  2006-08-11  final_response_handler dns failover support for timeout-ed
+ *              invites (andrei)
  */
 
 #include "defs.h"
 #include "t_funcs.h"
 #include "t_reply.h"
 #include "t_cancel.h"
+#ifdef USE_DNS_FAILOVER
+#include "t_fwd.h" /* t_send_branch */
+#endif
+#ifdef USE_DST_BLACKLIST
+#include "../../dst_blacklist.h"
+#endif
 
 
 
@@ -221,6 +229,9 @@ inline static ticks_t  delete_cell( struct cell *p_cell, int unlock )
 
 
 
+
+/* generate a fake reply
+ * it assumes the REPLY_LOCK is already held and returns unlocked */
 static void fake_reply(struct cell *t, int branch, int code )
 {
        branch_bm_t cancel_bitmap;
@@ -311,6 +322,13 @@ inline static void final_response_handler( struct retr_buf* r_buf,
 {
        int silent;
        int reply_code;
+#ifdef USE_DNS_FAILOVER
+       /*int i; 
+       int added_branches;
+       */
+       int branch_ret;
+       int prev_branch;
+#endif
 
 #      ifdef EXTRA_DEBUG
        if (t->flags & T_IN_AGONY) 
@@ -380,11 +398,45 @@ inline static void final_response_handler(        struct retr_buf* r_buf,
        if (is_invite(t) && 
            r_buf->branch < MAX_BRANCHES && /* r_buf->branch is always >=0 */
            t->uac[r_buf->branch].last_received > 0) {
-               reply_code = 480; /* Request Terminated */
+               reply_code = 480; /* Temporarily Unavailable */
        } else {
                reply_code = 408; /* Request Timeout */
+#ifdef USE_DST_BLACKLIST
+               if (use_dst_blacklist)
+                       dst_blacklist_add( BLST_ERR_TIMEOUT, &r_buf->dst);
+#endif
+#ifdef USE_DNS_FAILOVER
+               /* if this is an invite, the destination resolves to more ips, and
+                *  it still hasn't passed more than fr_inv_timeout since we
+                *  started, add another branch/uac */
+               if (is_invite(t) && use_dns_failover &&
+                               ((get_ticks_raw()-(r_buf->fr_expire-t->fr_timeout)) <
+                                       t->fr_inv_timeout)){
+                       branch_ret=add_uac_dns_fallback(t, t->uas.request,
+                                                                                               &t->uac[r_buf->branch], 0);
+                       prev_branch=-1;
+                       while((branch_ret>=0) &&(branch_ret!=prev_branch)){
+                               prev_branch=branch_ret;
+                               branch_ret=t_send_branch(t, branch_ret, t->uas.request , 0, 0);
+                       }
+#if 0
+                       if (branch_ret>=0){
+                               added_branches=1<<branch_ret;
+                               /* success */
+                               for (i=branch_ret; i<t->nr_of_outgoings; i++) {
+                                       if (added_branches & (1<<i)) {
+                                               branch_ret=t_send_branch(t, i, t->uas.request , 0, 0);
+                                               if ((branch_ret>=0) && (branch_ret!=i)){
+                                                       /* no send, but new branch */
+                                                       added_branches |= 1<<branch_ret;
+                                               }
+                                       }
+                               }
+                       }
+#endif
+               }
+#endif
        }
-
        fake_reply(t, r_buf->branch, reply_code );
 }
 
index 3ac3347..f81de9f 100644 (file)
@@ -52,6 +52,7 @@
  *  2004-08-23  avp support in t_uac (bogdan)
  *  2005-12-16  t_uac will set the new_cell timers to the default values,
  *               fixes 0 fr_timer bug (andrei)
+ *  2006-08-11  t_uac uses dns failover until it finds a send socket (andrei)
  */
 
 #include <string.h>
@@ -173,6 +174,9 @@ static inline int t_uac_prepare(str* method, str* headers, str* body, dlg_t* dia
        char* buf;
         int buf_len, ret, flags;
        unsigned int hi;
+#ifdef USE_DNS_FAILOVER
+       struct dns_srv_handle dns_h;
+#endif
 
        ret=-1;
        /*if (dst_req) *dst_req = NULL;*/
@@ -186,13 +190,36 @@ static inline int t_uac_prepare(str* method, str* headers, str* body, dlg_t* dia
        DBG("DEBUG:tm:t_uac: next_hop=<%.*s>\n",dialog->hooks.next_hop->len,
                        dialog->hooks.next_hop->s);
        /* it's a new message, so we will take the default socket */
+#ifdef USE_DNS_FAILOVER
+       if (use_dns_failover){
+               dns_srv_handle_init(&dns_h);
+               if ((uri2dst(&dns_h, &dst, 0, dialog->hooks.next_hop, PROTO_NONE)==0)
+                               || (dst.send_sock==0)){
+                       dns_srv_handle_put(&dns_h);
+                       ser_error = E_NO_SOCKET;
+                       ret=ser_error;
+                       LOG(L_ERR, "t_uac: no socket found\n");
+                       goto error2;
+               }
+               dns_srv_handle_put(&dns_h); /* not needed anymore */
+       }else{
+               if ((uri2dst(0, &dst, 0, dialog->hooks.next_hop, PROTO_NONE)==0) ||
+                               (dst.send_sock==0)){
+                       ser_error = E_NO_SOCKET;
+                       ret=ser_error;
+                       LOG(L_ERR, "t_uac: no socket found\n");
+                       goto error2;
+               }
+       }
+#else
        if ((uri2dst(&dst, 0, dialog->hooks.next_hop, PROTO_NONE)==0) ||
                        (dst.send_sock==0)){
                ser_error = E_NO_SOCKET;
                ret=ser_error;
                LOG(L_ERR, "t_uac: no socket found\n");
                goto error2;
-       }       
+       }
+#endif
 
        new_cell = build_cell(0); 
        if (!new_cell) {
@@ -254,6 +281,7 @@ static inline int t_uac_prepare(str* method, str* headers, str* body, dlg_t* dia
        new_cell->nr_of_outgoings++;
        
        if (dst_req) *dst_req = request;
+       
        return 1;
 
  error1:
index 8ff2600..408b048 100644 (file)
@@ -36,6 +36,8 @@
  *  2003-07-07  get_proto takes now two protos as arguments (andrei)
  *              tls/sips support for get_proto & uri2proxy (andrei)
  *  2006-04-13  added uri2dst(), simplified uri2sock() (andrei)
+ *  2006-08-11  dns failover support: uri2dst uses the dns cache and tries to 
+ *               get the first ip for which there is a send sock. (andrei)
  */
 
 
@@ -54,6 +56,9 @@
 #include "../../mem/mem.h"
 #include "../../parser/msg_parser.h"
 #include "../../resolve.h"
+#ifdef USE_DNS_FAILOVER
+#include "../../dns_cache.h"
+#endif
 
 /* a forced_proto takes precedence if != PROTO_NONE */
 inline static enum sip_protos get_proto(enum sip_protos force_proto,
@@ -145,23 +150,94 @@ inline static struct proxy_l *uri2proxy( str *uri, int proto )
 
 
 
+/*
+ * parse uri and return send related information
+ * params: uri - uri in string form
+ *         host - filled with the uri host part
+ *         port - filled with the uri port
+ *         proto - if != PROTO_NONE, this protocol will be forced over the
+ *                 uri_proto, otherwise the uri proto will be used 
+ *                 (value/return)
+ *         comp - compression (if used)
+ * returns 0 on success, < 0 on error
+ */
+inline static int get_uri_send_info(str* uri, str* host, unsigned short* port,
+                                                                       short* proto, short* comp)
+{
+       struct sip_uri parsed_uri;
+       enum sip_protos uri_proto;
+       
+       if (parse_uri(uri->s, uri->len, &parsed_uri) < 0) {
+               LOG(L_ERR, "ERROR: get_uri_send_info: bad_uri: %.*s\n",
+                                       uri->len, uri->s );
+               return -1;
+       }
+       
+       if (parsed_uri.type==SIPS_URI_T){
+               if ((parsed_uri.proto!=PROTO_TCP) && (parsed_uri.proto!=PROTO_NONE)){
+                       LOG(L_ERR, "ERROR: get_uri_send_info: bad transport  for"
+                                               " sips uri: %d\n", parsed_uri.proto);
+                       return -1;
+               }else
+                       uri_proto=PROTO_TLS;
+       }else
+               uri_proto=parsed_uri.proto;
+       
+       *proto= get_proto(*proto, uri_proto);
+#ifdef USE_COMP
+       *comp=parsed_uri.comp;
+#endif
+#ifdef HONOR_MADDR
+       if (parsed_uri.maddr_val.s && parsed_uri.maddr_val.len) {
+               *host=parsed_uri.maddr;
+               DBG("maddr dst: %.*s:%d\n", parsed_uri.maddr_val.len, 
+                               parsed_uri.maddr_val.s, parsed_uri.port_no);
+       } else
+#endif
+               *host=parsed_uri.host;
+       *port=parsed_uri.port_no;
+       return 0;
+}
+
+
+
 /*
  * Convert a URI into a dest_info structure
- * params: msg - sip message used to set dst->send_sock, if 0 dst->send_sock
- *               will be set to the default w/o using msg->force_send_socket 
- *               (see get_send_socket()) 
- *         dst - will be filled
- *         uri - uri in str form
+ * If the uri host resolves to multiple ips and dns_h!=0 the first ip for 
+ *  which a send socket is found will be used. If no send_socket are found,
+ *  the first ip is selected.
+ *
+ * params: dns_h - pointer to a valid dns_srv_handle structure (intialized!) or
+ *                 null. If null or use_dns_failover==0 normal dns lookup will
+ *                 be performed (no failover).
+ *         dst   - will be filled
+ *         msg   -  sip message used to set dst->send_sock, if 0 dst->send_sock
+ *                 will be set to the default w/o using msg->force_send_socket 
+ *                 (see get_send_socket()) 
+ *         uri   - uri in str form
  *         proto - if != PROTO_NONE, this protocol will be forced over the
  *                 uri_proto, otherwise the uri proto will be used
  * returns 0 on error, dst on success
  */
+#ifdef USE_DNS_FAILOVER
+inline static struct dest_info *uri2dst(struct dns_srv_handle* dns_h,
+                                                                               struct dest_info* dst,
+                                                                               struct sip_msg *msg, str *uri, 
+                                                                                       int proto )
+#else
 inline static struct dest_info *uri2dst(struct dest_info* dst,
                                                                                struct sip_msg *msg, str *uri, 
                                                                                        int proto )
+#endif
 {
        struct sip_uri parsed_uri;
        enum sip_protos uri_proto;
+       str* host;
+#ifdef USE_DNS_FAILOVER
+       int ip_found;
+       union sockaddr_union to;
+       int err;
+#endif
 
        if (parse_uri(uri->s, uri->len, &parsed_uri) < 0) {
                LOG(L_ERR, "ERROR: uri2dst: bad_uri: %.*s\n",
@@ -186,16 +262,53 @@ inline static struct dest_info *uri2dst(struct dest_info* dst,
 #endif
 #ifdef HONOR_MADDR
        if (parsed_uri.maddr_val.s && parsed_uri.maddr_val.len) {
-               sip_hostport2su(&dst->to, &parsed_uri.maddr_val, parsed_uri.port_no, dst->proto);
-               DBG("maddr dst: %.*s:%d\n", parsed_uri.maddr_val.len, parsed_uri.maddr_val.s, parsed_uri.port_no);
+               host=&parsed_uri.maddr_val;
+               DBG("maddr dst: %.*s:%d\n", parsed_uri.maddr_val.len, 
+                                                               parsed_uri.maddr_val.s, parsed_uri.port_no);
        } else
 #endif
-       sip_hostport2su(&dst->to, &parsed_uri.host, parsed_uri.port_no,
-                                               dst->proto);
+               host=&parsed_uri.host;
+#ifdef USE_DNS_FAILOVER
+       if (use_dns_failover && dns_h){
+               ip_found=0;
+               do{
+                       /* try all the ips until we find a good send socket */
+                       err=dns_sip_resolve2su(dns_h, &to, host,
+                                                                       parsed_uri.port_no, dst->proto, dns_flags);
+                       if (err!=0){
+                               if (ip_found==0){
+                                       LOG(L_ERR, "ERROR: uri2dst: failed to resolve \"%.*s\" :"
+                                                               "%s (%d)\n", host->len, ZSW(host->s),
+                                                                       dns_strerror(err), err);
+                                       return 0; /* error, no ip found */
+                               }
+                               break;
+                       }
+                       if (ip_found==0){
+                               dst->to=to;
+                               ip_found=1;
+                       }
+                       dst->send_sock = get_send_socket(msg, &to, dst->proto);
+                       if (dst->send_sock){
+                               dst->to=to;
+                               return dst; /* found a good one */
+                       }
+               }while(dns_srv_handle_next(dns_h, err));
+               LOG(L_ERR, "ERROR: uri2sock: no corresponding socket for \"%.*s\" "
+                                       "af %d\n", host->len, ZSW(host->s), dst->to.s.sa_family);
+               /* try to continue */
+               return dst;
+       }
+#endif
+       if (sip_hostport2su(&dst->to, host, parsed_uri.port_no, dst->proto)!=0){
+               LOG(L_ERR, "ERROR: uri2dst: failed to resolve \"%.*s\"\n",
+                                       host->len, ZSW(host->s));
+               return 0;
+       }
        dst->send_sock = get_send_socket(msg, &dst->to, dst->proto);
        if (dst->send_sock==0) {
                LOG(L_ERR, "ERROR: uri2sock: no corresponding socket for af %d\n", 
-                               dst->to.s.sa_family);
+                                       dst->to.s.sa_family);
                /* ser_error = E_NO_SOCKET;*/
                /* try to continue */
        }
@@ -203,7 +316,7 @@ inline static struct dest_info *uri2dst(struct dest_info* dst,
 }
 
 
-
+#if 0
 /*
  * Convert a URI into the corresponding sockaddr_union (address to send to) and
  *  send socket_info (socket/address from which to send)
@@ -233,6 +346,6 @@ static inline struct socket_info *uri2sock(struct sip_msg* msg, str *uri,
        }
        return dst.send_sock;
 }
-
+#endif
 
 #endif /* _TM_UT_H */
index cfcb297..787878d 100644 (file)
@@ -46,7 +46,7 @@
 #define WARNING_PHRASE " \"Noisy feedback tells: "
 #define WARNING_PHRASE_LEN (sizeof(WARNING_PHRASE)-1)
 
-//#define MAX_CONTENT_LEN_BUF INT2STR_MAX_LEN /* see ut.h/int2str() */
+/*#define MAX_CONTENT_LEN_BUF INT2STR_MAX_LEN *//* see ut.h/int2str() */
 
 #include "parser/msg_parser.h"
 #include "ip_addr.h"
index d474ef2..4cbff55 100644 (file)
--- a/resolve.c
+++ b/resolve.c
@@ -32,6 +32,9 @@
  *  2005-07-11  added resolv_init (timeouts a.s.o) (andrei)
  *  2006-04-13  added sip_hostport2su()  (andrei)
  *  2006-07-13  rdata structures put on diet (andrei)
+ *  2006-07-17  rdata contains now also the record name (andrei)
+ *  2006-08-18  get_record can append also the additional records to the
+ *               returned list (andrei)
  */ 
 
 
 #include "ip_addr.h"
 #include "error.h"
 
+#ifdef USE_DNS_CACHE
+#include "dns_cache.h"
+#endif
+
 
 
 /* mallocs for local stuff */
 #define local_malloc pkg_malloc
 #define local_free   pkg_free
 
+#ifdef USE_IPV6
 int dns_try_ipv6=1; /* default on */
+#else
+int dns_try_ipv6=0; /* off, if no ipv6 support */
+#endif
 /* declared in globals.h */
 int dns_retr_time=-1;
 int dns_retr_no=-1;
@@ -118,7 +129,7 @@ unsigned char* dns_skipname(unsigned char* p, unsigned char* end)
                /* normal label */
                p+=*p+1;
        }
-       return (p>=end)?0:p;
+       return (p>end)?0:p;
 }
 
 
@@ -154,7 +165,7 @@ struct srv_rdata* dns_srv_parser( unsigned char* msg, unsigned char* end,
        char name[MAX_DNS_NAME];
        
        srv=0;
-       if ((rdata+6)>=end) goto error;
+       if ((rdata+6+1)>end) goto error;
        
        memcpy((void*)&priority, rdata, 2);
        memcpy((void*)&weight,   rdata+2, 2);
@@ -227,20 +238,20 @@ struct naptr_rdata* dns_naptr_parser( unsigned char* msg, unsigned char* end,
        char repl[MAX_DNS_NAME];
        
        naptr = 0;
-       if ((rdata + 7) >= end) goto error;
+       if ((rdata + 7 + 1)>end) goto error;
        
        memcpy((void*)&order, rdata, 2);
        memcpy((void*)&pref, rdata + 2, 2);
        flags_len = rdata[4];
-       if ((rdata + 7 +  flags_len) >= end)
+       if ((rdata + 7 + 1 +  flags_len) > end)
                goto error;
        flags=rdata+5;
        services_len = rdata[5 + flags_len];
-       if ((rdata + 7 + flags_len + services_len) >= end)
+       if ((rdata + 7 + 1 + flags_len + services_len) > end)
                goto error;
        services=rdata + 6 + flags_len;
        regexp_len = rdata[6 + flags_len + services_len];
-       if ((rdata + 7 + flags_len + services_len + regexp_len) >= end)
+       if ((rdata + 7 +1 + flags_len + services_len + regexp_len) > end)
                goto error;
        regexp=rdata + 7 + flags_len + services_len;
        rdata = rdata + 7 + flags_len + services_len + regexp_len;
@@ -318,7 +329,7 @@ struct a_rdata* dns_a_parser(unsigned char* rdata, unsigned char* end)
 {
        struct a_rdata* a;
        
-       if (rdata+4>=end) goto error;
+       if (rdata+4>end) goto error;
        a=(struct a_rdata*)local_malloc(sizeof(struct a_rdata));
        if (a==0){
                LOG(L_ERR, "ERROR: dns_a_parser: out of memory\n");
@@ -338,7 +349,7 @@ struct aaaa_rdata* dns_aaaa_parser(unsigned char* rdata, unsigned char* end)
 {
        struct aaaa_rdata* aaaa;
        
-       if (rdata+16>=end) goto error;
+       if (rdata+16>end) goto error;
        aaaa=(struct aaaa_rdata*)local_malloc(sizeof(struct aaaa_rdata));
        if (aaaa==0){
                LOG(L_ERR, "ERROR: dns_aaaa_parser: out of memory\n");
@@ -369,17 +380,17 @@ void free_rdata_list(struct rdata* head)
  * returns a dyn. alloc'ed struct rdata linked list with the parsed responses
  * or 0 on error
  * see rfc1035 for the query/response format */
-struct rdata* get_record(char* name, int type)
+struct rdata* get_record(char* name, int type, int flags)
 {
        int size;
+       int skip;
        int qno, answers_no;
        int r;
-       int ans_len;
        static union dns_query buff;
        unsigned char* p;
-       unsigned char* t;
        unsigned char* end;
-       static unsigned char answer[ANS_SIZE];
+       static char rec_name[MAX_DNS_NAME]; /* placeholder for the record name */
+       int rec_name_len;
        unsigned short rtype, class, rdlength;
        unsigned int ttl;
        struct rdata* head;
@@ -414,26 +425,35 @@ struct rdata* get_record(char* name, int type)
                for (;(p<end && (*p)); p++);
                p+=1+2+2; /* skip the ending  '\0, QCODE and QCLASS */
        #endif
-               if (p>=end) {
+               if (p>end) {
                        LOG(L_ERR, "ERROR: get_record: p>=end\n");
                        goto error;
                }
        };
        answers_no=ntohs((unsigned short)buff.hdr.ancount);
-       ans_len=ANS_SIZE;
-       t=answer;
+again:
        for (r=0; (r<answers_no) && (p<end); r++){
+#if 0
                /*  ignore it the default domain name */
                if ((p=dns_skipname(p, end))==0) {
                        LOG(L_ERR, "ERROR: get_record: skip_name=0 (#2)\n");
                        goto error;
                }
-               /*
-               skip=dn_expand(buff.buff, end, p, t, ans_len);
+#else
+               if ((skip=dn_expand(buff.buff, end, p, rec_name, MAX_DNS_NAME-1))==-1){
+                       LOG(L_ERR, "ERROR: get_record: dn_expand(rec_name) failed\n");
+                       goto error;
+               }
+#endif
                p+=skip;
-               */
+               rec_name_len=strlen(rec_name);
+               if (rec_name_len>255){
+                       LOG(L_ERR, "ERROR: get_record: dn_expand(rec_name): name too"
+                                       " long  (%d)\n", rec_name_len);
+                       goto error;
+               }
                /* check if enough space is left for type, class, ttl & size */
-               if ((p+2+2+4+2)>=end) goto error_boundary;
+               if ((p+2+2+4+2)>end) goto error_boundary;
                /* get type */
                memcpy((void*) &rtype, (void*)p, 2);
                rtype=ntohs(rtype);
@@ -450,18 +470,14 @@ struct rdata* get_record(char* name, int type)
                memcpy((void*)&rdlength, (void*)p, 2);
                rdlength=ntohs(rdlength);
                p+=2;
-               /* check for type */
-               /*
-               if (rtype!=type){
-                       LOG(L_ERR, "WARNING: get_record: wrong type in answer (%d!=%d)\n",
-                                       rtype, type);
+               if ((flags & RES_ONLY_TYPE) && (rtype!=type)){
+                       /* skip */
                        p+=rdlength;
                        continue;
                }
-               */
                /* expand the "type" record  (rdata)*/
                
-               rd=(struct rdata*) local_malloc(sizeof(struct rdata));
+               rd=(struct rdata*) local_malloc(sizeof(struct rdata)+rec_name_len+1-1);
                if (rd==0){
                        LOG(L_ERR, "ERROR: get_record: out of memory\n");
                        goto error;
@@ -470,6 +486,9 @@ struct rdata* get_record(char* name, int type)
                rd->class=class;
                rd->ttl=ttl;
                rd->next=0;
+               memcpy(rd->name, rec_name, rec_name_len);
+               rd->name[rec_name_len]=0;
+               rd->name_len=rec_name_len;
                switch(rtype){
                        case T_SRV:
                                srv_rd= dns_srv_parser(buff.buff, end, p);
@@ -529,13 +548,36 @@ struct rdata* get_record(char* name, int type)
                p+=rdlength;
                
        }
+       if (flags & RES_AR){
+               flags&=~RES_AR;
+               answers_no=ntohs((unsigned short)buff.hdr.nscount);
+               DBG("get_record: skipping %d NS (p=%p, end=%p)\n", answers_no, p, end);
+               for (r=0; (r<answers_no) && (p<end); r++){
+                       /* skip over the ns records */
+                       if ((p=dns_skipname(p, end))==0) {
+                               LOG(L_ERR, "ERROR: get_record: skip_name=0 (#3)\n");
+                               goto error;
+                       }
+                       /* check if enough space is left for type, class, ttl & size */
+                       if ((p+2+2+4+2)>end) goto error_boundary;
+                       memcpy((void*)&rdlength, (void*)p+2+2+4, 2);
+                       p+=2+2+4+2+ntohs(rdlength);
+               }
+               answers_no=ntohs((unsigned short)buff.hdr.arcount);
+               DBG("get_record: parsing %d ARs (p=%p, end=%p)\n", answers_no, p, end);
+               goto again; /* add also the additional records */
+       }
+                       
        return head;
 error_boundary:
                LOG(L_ERR, "ERROR: get_record: end of query buff reached\n");
                if (head) free_rdata_list(head);
                return 0;
 error_parse:
-               LOG(L_ERR, "ERROR: get_record: rdata parse error \n");
+               LOG(L_ERR, "ERROR: get_record: rdata parse error (%s, %d), %p-%p"
+                                               " rtype=%d, class=%d, ttl=%d, rdlength=%d \n",
+                               name, type,
+                               p, end, rtype, class, ttl, rdlength);
                if (rd) local_free(rd); /* rd->rdata=0 & rd is not linked yet into
                                                                   the list */
 error:
@@ -547,6 +589,8 @@ not_found:
 
 
 
+
+
 /* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
  * if *port!=0.
  * when performing SRV lookup (*port==0) it will use proto to look for
@@ -554,14 +598,14 @@ not_found:
  * returns: hostent struct & *port filled with the port from the SRV record;
  *  0 on error
  */
-struct hostent* sip_resolvehost(str* name, unsigned short* port, int proto)
+struct hostent* _sip_resolvehost(str* name, unsigned short* port, int proto)
 {
        struct hostent* he;
+       struct ip_addr* ip;
+       static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
        struct rdata* head;
        struct rdata* l;
        struct srv_rdata* srv;
-       struct ip_addr* ip;
-       static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
 
        /* try SRV if no port specified (draft-ietf-sip-srv-06) */
        if ((port)&&(*port==0)){
@@ -604,8 +648,7 @@ struct hostent* sip_resolvehost(str* name, unsigned short* port, int proto)
                                                        proto);
                                        return 0;
                        }
-
-                       head=get_record(tmp, T_SRV);
+                       head=get_record(tmp, T_SRV, RES_ONLY_TYPE);
                        for(l=head; l; l=l->next){
                                if (l->type!=T_SRV) continue; /*should never happen*/
                                srv=(struct srv_rdata*) l->rdata;
index b1a14f5..ff3b42d 100644 (file)
--- a/resolve.h
+++ b/resolve.h
@@ -31,6 +31,8 @@
  *  2003-04-12  support for resolving ipv6 address references added (andrei)
  *  2004-07-28  darwin needs nameser_compat.h (andrei)
  *  2006-07-13  rdata structures put on diet (andrei)
+ *  2006-07-17  rdata contains now also the record name (andrei)
+ *  2006-08-18  get_record uses flags (andrei)
  */
 
 
@@ -48,6 +50,9 @@
 #endif
 
 #include "ip_addr.h"
+#ifdef USE_DNS_CACHE
+#include "dns_wrappers.h"
+#endif
 
 
 #define MAX_QUERY_SIZE 8192
@@ -57,6 +62,9 @@
 #define MAX_DNS_STRING 255
 
 
+/* get_record flags */
+#define RES_ONLY_TYPE 1   /* return only the specified type records */
+#define RES_AR           2   /* return also the additional records */
 
 /* query union*/
 union dns_query{
@@ -72,7 +80,11 @@ struct rdata {
        unsigned int   ttl;
        void* rdata;
        struct rdata* next;
+       unsigned char name_len; /* name length w/o the terminating 0 */
+       char name[1]; /* null terminated name (len=name_len+1) */
 };
+/* real size of the structure */
+#define RDATA_SIZE(s) (sizeof(struct rdata)+(s).name_len) /* +1-1 */
 
 
 /* srv rec. struct*/
@@ -86,7 +98,7 @@ struct srv_rdata {
 
 
 /* real size of the structure */
-#define SRV_RDATA_SIZE (s) (sizeof(struct srv_rdata)+(s).name_len)
+#define SRV_RDATA_SIZE(s) (sizeof(struct srv_rdata)+(s).name_len)
 
 /* naptr rec. struct*/
 struct naptr_rdata {
@@ -107,7 +119,7 @@ struct naptr_rdata {
 };
 
 /* real size of the structure */
-#define NAPTR_RDATA_SIZE (s) (sizeof(struct naptr_rdata) \
+#define NAPTR_RDATA_SIZE(s) (sizeof(struct naptr_rdata) \
                                                                + (s).flags_len \
                                                                + (s).services_len \
                                                                + (s).regexp_len \
@@ -130,11 +142,11 @@ struct cname_rdata {
 };
 
 /* real size of the structure */
-#define CNAME_RDATA_SIZE (s) (sizeof(struct cname_rdata)+(s).name_len)
+#define CNAME_RDATA_SIZE(s) (sizeof(struct cname_rdata)+(s).name_len)
 
 
 
-struct rdata* get_record(char* name, int type);
+struct rdata* get_record(char* name, int type, int flags);
 void free_rdata_list(struct rdata* head);
 
 
@@ -299,14 +311,12 @@ error_char:
 
 
 
-struct hostent* sip_resolvehost(str* name, unsigned short* port, int proto);
-
+struct hostent* _sip_resolvehost(str* name, unsigned short* port, int proto);
 
 
-/* gethostbyname wrappers
- * use this, someday they will use a local cache */
 
-static inline struct hostent* resolvehost(char* name)
+/* gethostbyname wrapper, handles ip/ipv6 automatically */
+static inline struct hostent* _resolvehost(char* name)
 {
        static struct hostent* he=0;
 #ifdef HAVE_GETIPNODEBYNAME 
@@ -381,4 +391,17 @@ int resolv_init();
 
 int sip_hostport2su(union sockaddr_union* su, str* host, unsigned short port,
                                                int proto);
+
+
+
+/* wrappers */
+#ifdef USE_DNS_CACHE
+#define resolvehost dns_resolvehost
+#define sip_resolvehost dns_sip_resolvehost
+#else
+#define resolvehost _resolvehost
+#define sip_resolvehost _sip_resolvehost
+#endif
+
+
 #endif
index 33df222..7711c90 100644 (file)
@@ -517,8 +517,9 @@ error:
 
 
 /* fixes a socket list => resolve addresses, 
- * interface names, fills missing members, remove duplicates */
-static int fix_socket_list(struct socket_info **list)
+ * interface names, fills missing members, remove duplicates
+ * fills type_flags if not null with SOCKET_T_IPV4 and/or SOCKET_T_IPV6*/
+static int fix_socket_list(struct socket_info **list, int* type_flags)
 {
        struct socket_info* si;
        struct socket_info* l;
@@ -528,9 +529,10 @@ static int fix_socket_list(struct socket_info **list)
        struct hostent* he;
        char** h;
        
+       if (type_flags)
+               *type_flags=0;
        /* try to change all the interface names into addresses
         *  --ugly hack */
-       
        for (si=*list;si;){
                next=si->next;
                if (add_interfaces(si->name.s, AF_INET, si->port_no,
@@ -598,6 +600,9 @@ static int fix_socket_list(struct socket_info **list)
                        }
                hostent2ip_addr(&si->address, he, 0); /*convert to ip_addr 
                                                                                                                 format*/
+               if (type_flags){
+                       *type_flags|=(si->address.af==AF_INET)?SOCKET_T_IPV4:SOCKET_T_IPV6;
+               }
                if ((tmp=ip_addr2a(&si->address))==0) goto error;
                si->address_str.s=(char*)pkg_malloc(strlen(tmp)+1);
                if (si->address_str.s==0){
@@ -707,11 +712,15 @@ error:
 
 
 
-/* fix all 3 socket lists
+/* fix all 3 socket lists, fills socket_types if non-null
  * return 0 on success, -1 on error */
-int fix_all_socket_lists()
+int fix_all_socket_lists(int* socket_types)
 {
        struct utsname myname;
+       int flags;
+       
+       if (socket_types)
+               *socket_types=0;
        
        if ((udp_listen==0)
 #ifdef USE_TCP
@@ -752,23 +761,32 @@ int fix_all_socket_lists()
                        }
                }
        }
-       if (fix_socket_list(&udp_listen)!=0){
+       if (fix_socket_list(&udp_listen, &flags)!=0){
                LOG(L_ERR, "ERROR: fix_all_socket_lists: fix_socket_list"
                                " udp failed\n");
                goto error;
        }
+       if (flags && socket_types){
+               *socket_types|=flags|SOCKET_T_UDP;
+       }
 #ifdef USE_TCP
-       if (!tcp_disable && (fix_socket_list(&tcp_listen)!=0)){
+       if (!tcp_disable && (fix_socket_list(&tcp_listen, &flags)!=0)){
                LOG(L_ERR, "ERROR: fix_all_socket_lists: fix_socket_list"
                                " tcp failed\n");
                goto error;
        }
+       if (flags && socket_types){
+               *socket_types|=flags|SOCKET_T_TCP;
+       }
 #ifdef USE_TLS
-       if (!tls_disable && (fix_socket_list(&tls_listen)!=0)){
+       if (!tls_disable && (fix_socket_list(&tls_listen, &flags)!=0)){
                LOG(L_ERR, "ERROR: fix_all_socket_lists: fix_socket_list"
                                " tls failed\n");
                goto error;
        }
+       if (flags && socket_types){
+               *socket_types|=flags|SOCKET_T_TLS;
+       }
 #endif
 #endif
        if ((udp_listen==0)
index 48b1a99..cf8aae9 100644 (file)
@@ -52,9 +52,17 @@ struct socket_info* tls_listen;
 #endif
 
 
+/* flags for finding out the address types */
+#define SOCKET_T_IPV4 1
+#define SOCKET_T_IPV6 2
+#define SOCKET_T_UDP  4
+#define SOCKET_T_TCP  8
+#define SOCKET_T_TLS 16
+
+
 int add_listen_iface(char* name, unsigned short port, unsigned short proto,
                                                        enum si_flags flags);
-int fix_all_socket_lists();
+int fix_all_socket_lists(int* socket_types);
 void print_all_socket_lists();
 void print_aliases();
 
diff --git a/ut.h b/ut.h
index 8f08704..5555638 100644 (file)
--- a/ut.h
+++ b/ut.h
                ((_via)->hdr.s+(_via)->hdr.len)))
 
 
+
+/* rounds to sizeof(type), but type must have a 2^k size (e.g. short, int,
+ * long, void*) */
+#define ROUND2TYPE(s, type) \
+       (((s)+(sizeof(type)-1))&(~(sizeof(type)-1)))
+
+
+/* rounds to sizeof(char*) - the first 4 byte multiple on 32 bit archs
+ * and the first 8 byte multiple on 64 bit archs */
+#define ROUND_POINTER(s) ROUND2TYPE(s, char*)
+
+/* rounds to sizeof(long) - the first 4 byte multiple on 32 bit archs
+ * and the first 8 byte multiple on 64 bit archs  (equiv. to ROUND_POINTER)*/
+#define ROUND_LONG(s)  ROUND2TYPE(s, long)
+
+/* rounds to sizeof(int) - the first t byte multiple on 32 and 64  bit archs */
+#define ROUND_INT(s) ROUND2TYPE(s, int)
+
+/* rounds to sizeof(short) - the first 2 byte multiple */
+#define ROUND_SHORT(s) ROUND2TYPE(s, short)
+
+
+
 /* links a value to a msgid */
 struct msgid_var{
        union{
index 0a98fd1..09cb08b 100644 (file)
@@ -213,6 +213,15 @@ static struct cmd_alias cmd_aliases[]={
        {       "serversion",   "core.version",                 0                       },
        {       "who",                  "ctl.who",                              "[%v] %v: %v %v -> %v %v\n"},
        {       "listen",               "ctl.listen",                   "[%v] %v: %v %v\n"},
+       {       "dns_mem_info",         "dns.mem_info",                 "%v / %v\n"},
+       {       "dns_debug",    "dns.debug",                    
+                                       "%v (%v): size=%v ref=%v expire=%vs last=%vs ago f=%v\n"},
+       {       "dns_debug_all",        "dns.debug_all",                        
+                       "%v (%v) [%v]: size=%v ref=%v expire=%vs last=%vs ago f=%v\n"
+                       "\t\t%v:%v expire=%vs f=%v\n"},
+       {       "dst_blacklist_mem_info",       "dst_blacklist.mem_info",       "%v / %v\n"},
+       {       "dst_blacklist_debug",          "dst_blacklist.debug",  
+               "%v:%v:%v expire:%v flags: %v\n"},
        {0,0,0}
 };
 
index 3c076f2..7b5ca84 100644 (file)
--- a/version.h
+++ b/version.h
 #endif
 
 
+#ifdef USE_DNS_CACHE
+#define USE_DNS_CACHE_STR ", USE_DNS_CACHE"
+#else
+#define USE_DNS_CACHE_STR ""
+#endif
+
+#ifdef USE_DNS_FAILOVER
+#define USE_DNS_FAILOVER_STR ", USE_DNS_FAILOVER"
+#else
+#define USE_DNS_FAILOVER_STR ""
+#endif
+
+#ifdef USE_DST_BLACKLIST
+#define USE_DST_BLACKLIST_STR ", USE_DST_BLACKLIST"
+#else
+#define USE_DST_BLACKLIST_STR ""
+#endif
+
 #define SER_COMPILE_FLAGS \
        STATS_STR EXTRA_DEBUG_STR USE_IPV6_STR USE_TCP_STR USE_TLS_STR \
        DISABLE_NAGLE_STR USE_MCAST_STR NO_DEBUG_STR NO_LOG_STR DNS_IP_HACK_STR \
        USE_SHM_MEM_STR DBG_QM_MALLOC_STR DBG_F_MALLOC_STR DEBUG_DMALLOC_STR \
        TIMER_DEBUG_STR \
        FAST_LOCK_STR NOSMP_STR USE_PTHREAD_MUTEX_STR USE_POSIX_SEM_STR \
-       USE_SYSV_SEM_STR USE_COMP_STR
+       USE_SYSV_SEM_STR USE_COMP_STR USE_DNS_CACHE_STR USE_DNS_FAILOVER_STR \
+       USE_DST_BLACKLIST_STR
 
 
 #endif