Browse Source

- support for setting the source address in tcp_send() and tcpconn_get()
(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

Andrei Pelinescu-Onciul 18 years ago
parent
commit
a288ee348d
8 changed files with 205 additions and 58 deletions
  1. 1 1
      action.c
  2. 2 2
      forward.c
  3. 2 2
      forward.h
  4. 27 0
      ip_addr.h
  5. 1 1
      msg_translator.c
  6. 25 6
      tcp_conn.h
  7. 145 45
      tcp_main.c
  8. 2 1
      tcp_server.h

+ 1 - 1
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{

+ 2 - 2
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)

+ 2 - 2
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;

+ 27 - 0
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)

+ 1 - 1
msg_translator.c

@@ -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)

+ 25 - 6
tcp_conn.h

@@ -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

+ 145 - 45
tcp_main.c

@@ -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;

+ 2 - 1
tcp_server.h

@@ -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);