- support for setting the source address in tcp_send() and tcpconn_get()
authorAndrei Pelinescu-Onciul <andrei@iptel.org>
Wed, 1 Aug 2007 00:05:40 +0000 (00:05 +0000)
committerAndrei Pelinescu-Onciul <andrei@iptel.org>
Wed, 1 Aug 2007 00:05:40 +0000 (00:05 +0000)
  (should allow for a better tcp force_send_socket() in the future)
- add multiple aliases for each connection, to cover all the search
 possiblities: (dst_ip, dst_port), (local_ip, dst_ip, dst_port),
  (local_ip, local_port, dst_ip, dst_port).
- improved connection hash function

action.c
forward.c
forward.h
ip_addr.h
msg_translator.c
tcp_conn.h
tcp_main.c
tcp_server.h

index 54d0e58..ab66353 100644 (file)
--- a/action.c
+++ b/action.c
@@ -272,7 +272,7 @@ int do_action(struct run_act_ctx* h, struct action* a, struct sip_msg* msg)
                                                /*tcp*/
                                                dst.proto=PROTO_TCP;
                                                dst.id=0;
-                                               ret=tcp_send(&dst, tmp, len);
+                                               ret=tcp_send(&dst, 0, tmp, len);
                                }
 #endif
                        }else{
index 8112503..0fa82f1 100644 (file)
--- a/forward.c
+++ b/forward.c
@@ -281,8 +281,8 @@ found:
  *               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
+ *               if the send_socket member is null, a send_socket will be 
+ *               chosen automatically
  * 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 value)
index e2d6da4..89ff605 100644 (file)
--- a/forward.h
+++ b/forward.h
@@ -112,7 +112,7 @@ static inline int msg_send(struct dest_info* dst, char* buf, int len)
                                        " support is disabled\n");
                        goto error;
                }else{
-                       if (tcp_send(dst, buf, len)<0){
+                       if (tcp_send(dst, 0, buf, len)<0){
                                STATS_TX_DROPS;
                                LOG(L_ERR, "msg_send: ERROR: tcp_send failed\n");
                                goto error;
@@ -127,7 +127,7 @@ static inline int msg_send(struct dest_info* dst, char* buf, int len)
                                        " support is disabled\n");
                        goto error;
                }else{
-                       if (tcp_send(dst, buf, len)<0){
+                       if (tcp_send(dst, 0, buf, len)<0){
                                STATS_TX_DROPS;
                                LOG(L_ERR, "msg_send: ERROR: tcp_send failed\n");
                                goto error;
index a5b1698..7a46b2e 100644 (file)
--- a/ip_addr.h
+++ b/ip_addr.h
@@ -32,6 +32,7 @@
  *  2003-04-06  all ports are stored/passed in host byte order now (andrei)
  *  2006-04-20  comp support in recv_info and dest_info (andrei)
  *  2006-04-21  added init_dst_from_rcv (andrei)
+ *  2007-06-26  added ip_addr_mk_any() (andrei)
  */
 
 #ifndef ip_addr_h
@@ -43,6 +44,7 @@
 #include <netinet/in.h>
 #include <netdb.h>
 #include "str.h"
+#include "compiler_opt.h"
 
 
 #include "dprint.h"
@@ -204,6 +206,31 @@ inline static int ip_addr_any(struct ip_addr* ip)
 
 
 
+/* creates an ANY ip_addr (filled with 0, af and len properly set) */
+inline static void ip_addr_mk_any(int af, struct ip_addr* ip)
+{
+       ip->af=af;
+       if (likely(af==AF_INET)){
+               ip->len=4;
+               ip->u.addr32[0]=0;
+       }
+#ifdef USE_IPV6
+       else{
+               ip->len=16;
+#if (defined (ULONG_MAX) && ULONG_MAX > 4294967295) || defined LP64
+               /* long is 64 bits */
+               ip->u.addrl[0]=0;
+               ip->u.addrl[1]=0;
+#else
+               ip->u.addr32[0]=0;
+               ip->u.addr32[1]=0;
+               ip->u.addr32[2]=0;
+               ip->u.addr32[3]=0;
+#endif /* ULONG_MAX */
+       }
+#endif
+}
+
 /* returns 1 if ip & net.mask == net.ip ; 0 otherwise & -1 on error 
        [ diff. address families ]) */
 inline static int matchnet(struct ip_addr* ip, struct net* net)
index 926e1de..ed3a378 100644 (file)
@@ -196,7 +196,7 @@ static int check_via_address(struct ip_addr* ip, str *name,
        if (resolver&DO_DNS){
                DBG("check_via_address: doing dns lookup\n");
                /* try all names ips */
-               he=sip_resolvehost(name, &port, 0); /* FIXME proto? */
+               he=sip_resolvehost(name, &port, 0); /* don't use naptr */
                if (he && ip->af==he->h_addrtype){
                        for(i=0;he && he->h_addr_list[i];i++){
                                if ( memcmp(&he->h_addr_list[i], ip->u.addr, ip->len)==0)
index 5b064b1..f33c46b 100644 (file)
@@ -31,6 +31,8 @@
  *  2003-06-30  added tcp_connection flags & state (andrei) 
  *  2003-10-27  tcp port aliases support added (andrei)
  *  2006-10-13  added tcp_req_states for STUN (vlada)
+ *  2007-07-26  improved tcp connection hash function; increased aliases
+ *               hash size (andrei)
  */
 
 
@@ -43,7 +45,8 @@
 #include "atomic_ops.h"
 #include "timer_ticks.h"
 
-#define TCP_CON_MAX_ALIASES 4 /* maximum number of port aliases */
+/* maximum number of port aliases x search wildcard possibilities */
+#define TCP_CON_MAX_ALIASES (4*3) 
 
 #define TCP_BUF_SIZE   4096 
 #define DEFAULT_TCP_CONNECTION_LIFETIME 120 /* in  seconds */
@@ -180,25 +183,41 @@ struct tcp_connection{
 #define TCPCONN_LOCK lock_get(tcpconn_lock);
 #define TCPCONN_UNLOCK lock_release(tcpconn_lock);
 
-#define TCP_ALIAS_HASH_SIZE 1024
+#define TCP_ALIAS_HASH_SIZE 4096
 #define TCP_ID_HASH_SIZE 1024
 
-static inline unsigned tcp_addr_hash(struct ip_addr* ip, unsigned short port)
+/* hash (dst_ip, dst_port, local_ip, local_port) */
+static inline unsigned tcp_addr_hash(  struct ip_addr* ip, 
+                                                                               unsigned short port,
+                                                                               struct ip_addr* l_ip,
+                                                                               unsigned short l_port)
 {
-       if(ip->len==4) return (ip->u.addr32[0]^port)&(TCP_ALIAS_HASH_SIZE-1);
+       unsigned h;
+
+       if(ip->len==4)
+               h=(ip->u.addr32[0]^port)^(l_ip->u.addr32[0]^l_port);
        else if (ip->len==16) 
-                       return (ip->u.addr32[0]^ip->u.addr32[1]^ip->u.addr32[2]^
-                                       ip->u.addr32[3]^port) & (TCP_ALIAS_HASH_SIZE-1);
+               h= (ip->u.addr32[0]^ip->u.addr32[1]^ip->u.addr32[2]^
+                               ip->u.addr32[3]^port) ^
+                       (l_ip->u.addr32[0]^l_ip->u.addr32[1]^l_ip->u.addr32[2]^
+                               l_ip->u.addr32[3]^l_port);
        else{
                LOG(L_CRIT, "tcp_addr_hash: BUG: bad len %d for an ip address\n",
                                ip->len);
                return 0;
        }
+       /* make sure the first bits are influenced by all 32
+        * (the first log2(TCP_ALIAS_HASH_SIZE) bits should be a mix of all
+        *  32)*/
+       h ^= h>>17;
+       h ^= h>>7;
+       return h & (TCP_ALIAS_HASH_SIZE-1);
 }
 
 #define tcp_id_hash(id) (id&(TCP_ID_HASH_SIZE-1))
 
 struct tcp_connection* tcpconn_get(int id, struct ip_addr* ip, int port,
+                                                                       union sockaddr_union* local_addr,
                                                                        ticks_t timeout);
 
 #endif
index 38a4c4b..32f23ca 100644 (file)
@@ -75,6 +75,8 @@
  *               result in inf. lifetime) (andrei)
  *  2007-07-25  tcpconn_connect can now bind the socket on a specified
  *                source addr/port (andrei)
+ *  2007-07-26   tcp_send() and tcpconn_get() can now use a specified source
+ *                addr./port (andrei)
  */
 
 
@@ -202,6 +204,11 @@ static io_wait_h io_h;
 
 
 
+inline static int _tcpconn_add_alias_unsafe(struct tcp_connection* c, int port,
+                                                                               struct ip_addr* l_ip, int l_port);
+
+
+
 /* sets source address used when opening new sockets and no source is specified
  *  (by default the address is choosen by the kernel)
  * Should be used only on init.
@@ -589,23 +596,36 @@ error:
 
 /* adds a tcp connection to the tcpconn hashes
  * Note: it's called _only_ from the tcp_main process */
-struct tcp_connection*  tcpconn_add(struct tcp_connection *c)
+inline static struct tcp_connection*  tcpconn_add(struct tcp_connection *c)
 {
+       struct ip_addr zero_ip;
 
-       if (c){
+       if (likely(c)){
+               ip_addr_mk_any(c->rcv.src_ip.af, &zero_ip);
                c->id_hash=tcp_id_hash(c->id);
-               c->con_aliases[0].hash=tcp_addr_hash(&c->rcv.src_ip, c->rcv.src_port);
+               c->aliases=0;
                TCPCONN_LOCK;
                /* add it at the begining of the list*/
                tcpconn_listadd(tcpconn_id_hash[c->id_hash], c, id_next, id_prev);
-               /* set the first alias */
-               c->con_aliases[0].port=c->rcv.src_port;
-               c->con_aliases[0].parent=c;
-               tcpconn_listadd(tcpconn_aliases_hash[c->con_aliases[0].hash],
-                                                       &c->con_aliases[0], next, prev);
-               c->aliases++;
+               /* set the aliases */
+               /* first alias is for (peer_ip, peer_port, 0 ,0) -- for finding
+                *  any connection to peer_ip, peer_port
+                * the second alias is for (peer_ip, peer_port, local_addr, 0) -- for
+                *  finding any conenction to peer_ip, peer_port from local_addr 
+                * the third alias is for (peer_ip, peer_port, local_addr, local_port) 
+                *   -- for finding if a fully specified connection exists */
+               _tcpconn_add_alias_unsafe(c, c->rcv.src_port, &zero_ip, 0);
+               _tcpconn_add_alias_unsafe(c, c->rcv.src_port, &c->rcv.dst_ip, 0);
+               _tcpconn_add_alias_unsafe(c, c->rcv.src_port, &c->rcv.dst_ip,
+                                                                                                               c->rcv.dst_port);
+               /* ignore add_alias errors, there are some valid cases when one
+                *  of the add_alias would fail (e.g. first add_alias for 2 connections
+                *   with the same destination but different src. ip*/
                TCPCONN_UNLOCK;
-               DBG("tcpconn_add: hashes: %d, %d\n", c->con_aliases[0].hash,
+               DBG("tcpconn_add: hashes: %d:%d:%d, %d\n",
+                                                                                               c->con_aliases[0].hash,
+                                                                                               c->con_aliases[1].hash,
+                                                                                               c->con_aliases[2].hash,
                                                                                                c->id_hash);
                return c;
        }else{
@@ -651,15 +671,20 @@ void tcpconn_rm(struct tcp_connection* c)
 }
 
 
-/* finds a connection, if id=0 uses the ip addr & port (host byte order)
+/* finds a connection, if id=0 uses the ip addr, port, local_ip and local port
+ *  (host byte order) and tries to find the connection that matches all of
+ *   them. Wild cards can be used for local_ip and local_port (a 0 filled
+ *   ip address and/or a 0 local port).
  * WARNING: unprotected (locks) use tcpconn_get unless you really
  * know what you are doing */
-struct tcp_connection* _tcpconn_find(int id, struct ip_addr* ip, int port)
+struct tcp_connection* _tcpconn_find(int id, struct ip_addr* ip, int port,
+                                                                               struct ip_addr* l_ip, int l_port)
 {
 
        struct tcp_connection *c;
        struct tcp_conn_alias* a;
        unsigned hash;
+       int is_local_ip_any;
        
 #ifdef EXTRA_DEBUG
        DBG("tcpconn_find: %d  port %d\n",id, port);
@@ -675,7 +700,8 @@ struct tcp_connection* _tcpconn_find(int id, struct ip_addr* ip, int port)
                        if ((id==c->id)&&(c->state!=S_CONN_BAD)) return c;
                }
        }else if (ip){
-               hash=tcp_addr_hash(ip, port);
+               hash=tcp_addr_hash(ip, port, l_ip, l_port);
+               is_local_ip_any=ip_addr_any(l_ip);
                for (a=tcpconn_aliases_hash[hash]; a; a=a->next){
 #ifdef EXTRA_DEBUG
                        DBG("a=%p, c=%p, c->id=%d, alias port= %d port=%d\n", a, a->parent,
@@ -683,7 +709,11 @@ struct tcp_connection* _tcpconn_find(int id, struct ip_addr* ip, int port)
                        print_ip("ip=",&a->parent->rcv.src_ip,"\n");
 #endif
                        if ( (a->parent->state!=S_CONN_BAD) && (port==a->port) &&
-                                       (ip_addr_cmp(ip, &a->parent->rcv.src_ip)) )
+                                       ((l_port==0) || (l_port==a->parent->rcv.dst_port)) &&
+                                       (ip_addr_cmp(ip, &a->parent->rcv.src_ip)) &&
+                                       (is_local_ip_any ||
+                                               ip_addr_cmp(l_ip, &a->parent->rcv.dst_ip))
+                               )
                                return a->parent;
                }
        }
@@ -692,13 +722,30 @@ struct tcp_connection* _tcpconn_find(int id, struct ip_addr* ip, int port)
 
 
 
-/* _tcpconn_find with locks and timeout */
+/* _tcpconn_find with locks and timeout
+ * local_addr contains the desired local ip:port. If null any local address 
+ * will be used.  IN*ADDR_ANY or 0 port are wild cards.
+ */
 struct tcp_connection* tcpconn_get(int id, struct ip_addr* ip, int port,
+                                                                       union sockaddr_union* local_addr,
                                                                        ticks_t timeout)
 {
        struct tcp_connection* c;
+       struct ip_addr local_ip;
+       int local_port;
+       
+       local_port=0;
+       if (ip){
+               if (local_addr){
+                       su2ip_addr(&local_ip, local_addr);
+                       local_port=su_getport(local_addr);
+               }else{
+                       ip_addr_mk_any(ip->af, &local_ip);
+                       local_port=0;
+               }
+       }
        TCPCONN_LOCK;
-       c=_tcpconn_find(id, ip, port);
+       c=_tcpconn_find(id, ip, port, &local_ip, local_port);
        if (c){ 
                        atomic_inc(&c->refcnt);
                        c->timeout=get_ticks_raw()+timeout;
@@ -709,26 +756,29 @@ struct tcp_connection* tcpconn_get(int id, struct ip_addr* ip, int port,
 
 
 
-/* add port as an alias for the "id" connection
- * returns 0 on success,-1 on failure */
-int tcpconn_add_alias(int id, int port, int proto)
+/* add c->dst:port, local_addr as an alias for the "id" connection, 
+ * returns 0 on success, <0 on failure ( -1  - null c, -2 too many aliases,
+ *  -3 alias already present and pointing to another connection)
+ * WARNING: must be called with TCPCONN_LOCK held */
+inline static int _tcpconn_add_alias_unsafe(struct tcp_connection* c, int port,
+                                                                               struct ip_addr* l_ip, int l_port)
 {
-       struct tcp_connection* c;
        unsigned hash;
        struct tcp_conn_alias* a;
+       int is_local_ip_any;
        
        a=0;
-       /* fix the port */
-       port=port?port:((proto==PROTO_TLS)?SIPS_PORT:SIP_PORT);
-       TCPCONN_LOCK;
-       /* check if alias already exists */
-       c=_tcpconn_find(id, 0, 0);
+       is_local_ip_any=ip_addr_any(l_ip);
        if (c){
-               hash=tcp_addr_hash(&c->rcv.src_ip, port);
+               hash=tcp_addr_hash(&c->rcv.src_ip, port, l_ip, l_port);
                /* search the aliases for an already existing one */
                for (a=tcpconn_aliases_hash[hash]; a; a=a->next){
                        if ( (a->parent->state!=S_CONN_BAD) && (port==a->port) &&
-                                       (ip_addr_cmp(&c->rcv.src_ip, &a->parent->rcv.src_ip)) ){
+                                       ( (l_port==0) || (l_port==a->parent->rcv.dst_port)) &&
+                                       (ip_addr_cmp(&c->rcv.src_ip, &a->parent->rcv.src_ip)) &&
+                                       ( is_local_ip_any || 
+                                         ip_addr_cmp(&a->parent->rcv.dst_ip, l_ip))
+                                       ){
                                /* found */
                                if (a->parent!=c) goto error_sec;
                                else goto ok;
@@ -743,38 +793,88 @@ int tcpconn_add_alias(int id, int port, int proto)
                c->aliases++;
        }else goto error_not_found;
 ok:
-       TCPCONN_UNLOCK;
 #ifdef EXTRA_DEBUG
-       if (a) DBG("tcpconn_add_alias: alias already present\n");
-       else   DBG("tcpconn_add_alias: alias port %d for hash %d, id %d\n",
+       if (a) DBG("_tcpconn_add_alias_unsafe: alias already present\n");
+       else   DBG("_tcpconn_add_alias_unsafe: alias port %d for hash %d, id %d\n",
                        port, hash, c->id);
 #endif
        return 0;
 error_aliases:
-       TCPCONN_UNLOCK;
-       LOG(L_ERR, "ERROR: tcpconn_add_alias: too many aliases for connection %p"
-                               " (%d)\n", c, c->id);
+       /* too many aliases */
+       return -2;
+error_not_found:
+       /* null connection */
        return -1;
+error_sec:
+       /* alias already present and pointing to a different connection
+        * (hijack attempt?) */
+       return -3;
+}
+
+
+
+/* add port as an alias for the "id" connection, 
+ * returns 0 on success,-1 on failure */
+int tcpconn_add_alias(int id, int port, int proto)
+{
+       struct tcp_connection* c;
+       int ret;
+       struct ip_addr zero_ip;
+       
+       /* fix the port */
+       port=port?port:((proto==PROTO_TLS)?SIPS_PORT:SIP_PORT);
+       TCPCONN_LOCK;
+       /* check if alias already exists */
+       c=_tcpconn_find(id, 0, 0, 0, 0);
+       if (c){
+               ip_addr_mk_any(c->rcv.src_ip.af, &zero_ip);
+               
+               /* alias src_ip:port, 0, 0 */
+               ret=_tcpconn_add_alias_unsafe(c, port,  &zero_ip, 0);
+               if (ret<0 && ret!=-3) goto error;
+               /* alias src_ip:port, local_ip, 0 */
+               ret=_tcpconn_add_alias_unsafe(c, port,  &c->rcv.dst_ip, 0);
+               if (ret<0 && ret!=-3) goto error;
+               /* alias src_ip:port, local_ip, local_port */
+               ret=_tcpconn_add_alias_unsafe(c, port,  &c->rcv.dst_ip,
+                                                                                                                       c->rcv.dst_port);
+               if (ret<0) goto error;
+       }else goto error_not_found;
+       TCPCONN_UNLOCK;
+       return 0;
 error_not_found:
        TCPCONN_UNLOCK;
        LOG(L_ERR, "ERROR: tcpconn_add_alias: no connection found for id %d\n",id);
        return -1;
-error_sec:
+error:
        TCPCONN_UNLOCK;
-       LOG(L_ERR, "ERROR: tcpconn_add_alias: possible port hijack attempt\n");
-       LOG(L_ERR, "ERROR: tcpconn_add_alias: alias already present and points"
-                       " to another connection (%d : %d and %d : %d)\n",
-                       a->parent->id,  port, c->id, port);
+       switch(ret){
+               case -2:
+                       LOG(L_ERR, "ERROR: tcpconn_add_alias: too many aliases"
+                                       " for connection %p (%d)\n", c, c->id);
+                       break;
+               case -3:
+                       LOG(L_ERR, "ERROR: tcpconn_add_alias: possible port"
+                                       " hijack attempt\n");
+                       LOG(L_ERR, "ERROR: tcpconn_add_alias: alias for %d port %d already"
+                                               " present and points to another connection \n",
+                                               c->id, port);
+                       break;
+               default:
+                       LOG(L_ERR, "ERROR: tcpconn_add_alias: unkown error %d\n", ret);
+       }
        return -1;
 }
 
 
 
 /* finds a tcpconn & sends on it
- * uses the dst members to, proto (TCP|TLS) and id
+ * uses the dst members to, proto (TCP|TLS) and id and tries to send
+ *  from the "from" address (if non null and id==0)
  * returns: number of bytes written (>=0) on success
  *          <0 on error */
-int tcp_send(struct dest_info* dst, char* buf, unsigned len)
+int tcp_send(struct dest_info* dst, union sockaddr_union* from,
+                                       char* buf, unsigned len)
 {
        struct tcp_connection *c;
        struct tcp_connection *tmp;
@@ -783,14 +883,13 @@ int tcp_send(struct dest_info* dst, char* buf, unsigned len)
        int fd;
        long response[2];
        int n;
-       union sockaddr_union* from;
        
        port=su_getport(&dst->to);
        if (port){
                su2ip_addr(&ip, &dst->to);
-               c=tcpconn_get(dst->id, &ip, port, tcp_con_lifetime); 
+               c=tcpconn_get(dst->id, &ip, port, from, tcp_con_lifetime); 
        }else if (dst->id){
-               c=tcpconn_get(dst->id, 0, 0, tcp_con_lifetime);
+               c=tcpconn_get(dst->id, 0, 0, 0, tcp_con_lifetime);
        }else{
                LOG(L_CRIT, "BUG: tcp_send called with null id & to\n");
                return -1;
@@ -800,7 +899,7 @@ int tcp_send(struct dest_info* dst, char* buf, unsigned len)
                if (c==0) {
                        if (port){
                                /* try again w/o id */
-                               c=tcpconn_get(0, &ip, port, tcp_con_lifetime);
+                               c=tcpconn_get(0, &ip, port, from, tcp_con_lifetime);
                                goto no_id;
                        }else{
                                LOG(L_ERR, "ERROR: tcp_send: id %d not found, dropping\n",
@@ -813,7 +912,7 @@ no_id:
                if (c==0){
                        DBG("tcp_send: no open tcp connection found, opening new one\n");
                        /* create tcp connection */
-                               from=0;
+                       if (from==0){
                                /* check to see if we have to use a specific source addr. */
                                switch (dst->to.s.sa_family) {
                                        case AF_INET:
@@ -828,6 +927,7 @@ no_id:
                                                /* error, bad af, ignore ... */
                                                break;
                                }
+                       }
                        if ((c=tcpconn_connect(&dst->to, from, dst->proto))==0){
                                LOG(L_ERR, "ERROR: tcp_send: connect failed\n");
                                return -1;
index 9c1588b..979d2e7 100644 (file)
@@ -34,7 +34,8 @@
 
 /* "public" functions*/
 
-int tcp_send(struct dest_info* dst, char* buf, unsigned len);
+int tcp_send(struct dest_info* dst, union sockaddr_union* from,
+                               char* buf, unsigned len);
 
 int tcpconn_add_alias(int id, int port, int proto);