Close #1602: configurable local port range for ICE transport.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@4343 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjlib/include/pj/sock.h b/pjlib/include/pj/sock.h
index 83d35db..4011f21 100644
--- a/pjlib/include/pj/sock.h
+++ b/pjlib/include/pj/sock.h
@@ -1166,6 +1166,24 @@
 				      pj_uint32_t addr,
 				      pj_uint16_t port);
 
+/**
+ * Bind the IP socket sockfd to the given address and a random port in the
+ * specified range.
+ *
+ * @param sockfd    	The socket desriptor.
+ * @param addr      	The local address and port to bind the socket to.
+ * @param port_range	The port range, relative the to start port number
+ * 			specified in port field in #addr. Note that if the
+ * 			port is zero, this param will be ignored.
+ * @param max_try   	Maximum retries.
+ *
+ * @return	    	Zero on success.
+ */
+PJ_DECL(pj_status_t) pj_sock_bind_random( pj_sock_t sockfd,
+				          const pj_sockaddr_t *addr,
+				          pj_uint16_t port_range,
+				          pj_uint16_t max_try);
+
 #if PJ_HAS_TCP
 /**
  * Listen for incoming connection. This function only applies to connection
diff --git a/pjlib/src/pj/sock_common.c b/pjlib/src/pj/sock_common.c
index 7aaf668..eb338e7 100644
--- a/pjlib/src/pj/sock_common.c
+++ b/pjlib/src/pj/sock_common.c
@@ -24,6 +24,7 @@
 #include <pj/ip_helper.h>
 #include <pj/os.h>
 #include <pj/addr_resolv.h>
+#include <pj/rand.h>
 #include <pj/string.h>
 #include <pj/compat/socket.h>
 
@@ -1040,6 +1041,44 @@
 }
 
 
+/*
+ * Bind socket at random port.
+ */
+PJ_DEF(pj_status_t) pj_sock_bind_random(  pj_sock_t sockfd,
+				          const pj_sockaddr_t *addr,
+				          pj_uint16_t port_range,
+				          pj_uint16_t max_try)
+{
+    pj_sockaddr bind_addr;
+    int addr_len;
+    pj_uint16_t base_port;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_CHECK_STACK();
+
+    PJ_ASSERT_RETURN(addr, PJ_EINVAL);
+
+    pj_sockaddr_cp(&bind_addr, addr);
+    addr_len = pj_sockaddr_get_len(addr);
+    base_port = pj_sockaddr_get_port(addr);
+
+    if (base_port == 0 || port_range == 0) {
+	return pj_sock_bind(sockfd, &bind_addr, addr_len);
+    }
+
+    for (; max_try; --max_try) {
+	pj_uint16_t port;
+	port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1));
+	pj_sockaddr_set_port(&bind_addr, port);
+	status = pj_sock_bind(sockfd, &bind_addr, addr_len);
+	if (status == PJ_SUCCESS)
+	    break;
+    }
+
+    return status;
+}
+
+
 /* Only need to implement these in DLL build */
 #if defined(PJ_DLL)
 
diff --git a/pjnath/include/pjnath/stun_sock.h b/pjnath/include/pjnath/stun_sock.h
index db7d1d0..f9e9be2 100644
--- a/pjnath/include/pjnath/stun_sock.h
+++ b/pjnath/include/pjnath/stun_sock.h
@@ -238,11 +238,21 @@
      * address is zero, socket will be bound to INADDR_ANY. If the address
      * is non-zero, socket will be bound to this address only, and the
      * transport will have only one address alias (the \a alias_cnt field
-     * in #pj_stun_sock_info structure.
+     * in #pj_stun_sock_info structure. If the port is set to zero, the
+     * socket will bind at any port (chosen by the OS).
      */
     pj_sockaddr bound_addr;
 
     /**
+     * Specify the port range for STUN socket binding, relative to the start
+     * port number specified in \a bound_addr. Note that this setting is only
+     * applicable when the start port number is non zero.
+     *
+     * Default value is zero.
+     */
+    pj_uint16_t	port_range;
+
+    /**
      * Specify the STUN keep-alive duration, in seconds. The STUN transport
      * does keep-alive by sending STUN Binding request to the STUN server. 
      * If this value is zero, the PJ_STUN_KEEP_ALIVE_SEC value will be used.
diff --git a/pjnath/include/pjnath/turn_sock.h b/pjnath/include/pjnath/turn_sock.h
index c125085..f756a3b 100644
--- a/pjnath/include/pjnath/turn_sock.h
+++ b/pjnath/include/pjnath/turn_sock.h
@@ -141,6 +141,23 @@
      */
     pj_bool_t qos_ignore_error;
 
+    /**
+     * Specify the interface where the socket should be bound to. If the
+     * address is zero, socket will be bound to INADDR_ANY. If the address
+     * is non-zero, socket will be bound to this address only. If the port is
+     * set to zero, the socket will bind at any port (chosen by the OS).
+     */
+    pj_sockaddr bound_addr;
+
+    /**
+     * Specify the port range for TURN socket binding, relative to the start
+     * port number specified in \a bound_addr. Note that this setting is only
+     * applicable when the start port number is non zero.
+     *
+     * Default value is zero.
+     */
+    pj_uint16_t	port_range;
+
 } pj_turn_sock_cfg;
 
 
diff --git a/pjnath/src/pjnath/stun_sock.c b/pjnath/src/pjnath/stun_sock.c
index 7f6aacd..725c4c8 100644
--- a/pjnath/src/pjnath/stun_sock.c
+++ b/pjnath/src/pjnath/stun_sock.c
@@ -32,6 +32,8 @@
 #include <pj/rand.h>
 
 
+enum { MAX_BIND_RETRY = 100 };
+
 struct pj_stun_sock
 {
     char		*obj_name;	/* Log identification	    */
@@ -162,7 +164,9 @@
     pj_pool_t *pool;
     pj_stun_sock *stun_sock;
     pj_stun_sock_cfg default_cfg;
+    pj_sockaddr bound_addr;
     unsigned i;
+    pj_uint16_t max_bind_retry;
     pj_status_t status;
 
     PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL);
@@ -211,17 +215,17 @@
 	goto on_error;
 
     /* Bind socket */
-    if (pj_sockaddr_has_addr(&cfg->bound_addr)) {
-	status = pj_sock_bind(stun_sock->sock_fd, &cfg->bound_addr,
-			      pj_sockaddr_get_len(&cfg->bound_addr));
-    } else {
-	pj_sockaddr bound_addr;
-
-	pj_sockaddr_init(af, &bound_addr, NULL, 0);
-	status = pj_sock_bind(stun_sock->sock_fd, &bound_addr,
-			      pj_sockaddr_get_len(&bound_addr));
+    max_bind_retry = MAX_BIND_RETRY;
+    if (cfg->port_range && cfg->port_range < max_bind_retry)
+	max_bind_retry = cfg->port_range;
+    pj_sockaddr_init(af, &bound_addr, NULL, 0);
+    if (cfg->bound_addr.addr.sa_family == pj_AF_INET() || 
+	cfg->bound_addr.addr.sa_family == pj_AF_INET6())
+    {
+	pj_sockaddr_cp(&bound_addr, &cfg->bound_addr);
     }
-
+    status = pj_sock_bind_random(stun_sock->sock_fd, &bound_addr,
+				 cfg->port_range, max_bind_retry);
     if (status != PJ_SUCCESS)
 	goto on_error;
 
diff --git a/pjnath/src/pjnath/turn_sock.c b/pjnath/src/pjnath/turn_sock.c
index 919c083..08831a4 100644
--- a/pjnath/src/pjnath/turn_sock.c
+++ b/pjnath/src/pjnath/turn_sock.c
@@ -32,6 +32,10 @@
     TIMER_DESTROY
 };
 
+
+enum { MAX_BIND_RETRY = 100 };
+
+
 #define INIT	0x1FFFFFFF
 
 struct pj_turn_sock
@@ -102,6 +106,7 @@
     cfg->qos_ignore_error = PJ_TRUE;
 }
 
+
 /*
  * Create.
  */
@@ -725,6 +730,8 @@
 	int sock_type;
 	pj_sock_t sock;
 	pj_activesock_cb asock_cb;
+	pj_sockaddr bound_addr, *cfg_bind_addr;
+	pj_uint16_t max_bind_retry;
 
 	/* Close existing connection, if any. This happens when
 	 * we're switching to alternate TURN server when either TCP
@@ -750,7 +757,29 @@
 	    return;
 	}
 
-        /* Apply QoS, if specified */
+	/* Bind socket */
+	cfg_bind_addr = &turn_sock->setting.bound_addr;
+	max_bind_retry = MAX_BIND_RETRY;
+	if (turn_sock->setting.port_range &&
+	    turn_sock->setting.port_range < max_bind_retry)
+	{
+	    max_bind_retry = turn_sock->setting.port_range;
+	}
+	pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0);
+	if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || 
+	    cfg_bind_addr->addr.sa_family == pj_AF_INET6())
+	{
+	    pj_sockaddr_cp(&bound_addr, cfg_bind_addr);
+	}
+	status = pj_sock_bind_random(sock, &bound_addr,
+				     turn_sock->setting.port_range,
+				     max_bind_retry);
+	if (status != PJ_SUCCESS) {
+	    pj_turn_sock_destroy(turn_sock);
+	    return;
+	}
+
+	/* Apply QoS, if specified */
 	status = pj_sock_apply_qos2(sock, turn_sock->setting.qos_type,
 				    &turn_sock->setting.qos_params, 
 				    (turn_sock->setting.qos_ignore_error?2:1),
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 5114189..e3d9dd3 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -2153,6 +2153,15 @@
     unsigned		port;
 
     /**
+     * Specify the port range for socket binding, relative to the start
+     * port number specified in \a port. Note that this setting is only
+     * applicable when the start port number is non zero.
+     *
+     * Default value is zero.
+     */
+    unsigned		port_range;
+
+    /**
      * Optional address to advertise as the address of this transport.
      * Application can specify any address or hostname for this field,
      * for example it can point to one of the interface address in the
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index f891201..26a6cb7 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -718,6 +718,13 @@
     if (acc_cfg->ice_cfg.ice_max_host_cands >= 0)
 	ice_cfg.stun.max_host_cands = acc_cfg->ice_cfg.ice_max_host_cands;
 
+    /* Copy binding port setting to STUN setting */
+    pj_sockaddr_init(ice_cfg.af, &ice_cfg.stun.cfg.bound_addr,
+		     &cfg->bound_addr, cfg->port);
+    ice_cfg.stun.cfg.port_range = cfg->port_range;
+    if (cfg->port != 0 && ice_cfg.stun.cfg.port_range == 0)
+	ice_cfg.stun.cfg.port_range = pjsua_var.ua_cfg.max_calls * 10;
+
     /* Copy QoS setting to STUN setting */
     ice_cfg.stun.cfg.qos_type = cfg->qos_type;
     pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
@@ -743,6 +750,13 @@
 	ice_cfg.turn.cfg.qos_type = cfg->qos_type;
 	pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
 		  sizeof(cfg->qos_params));
+
+	/* Copy binding port setting to TURN setting */
+	pj_sockaddr_init(ice_cfg.af, &ice_cfg.turn.cfg.bound_addr,
+			 &cfg->bound_addr, cfg->port);
+	ice_cfg.turn.cfg.port_range = cfg->port_range;
+	if (cfg->port != 0 && ice_cfg.turn.cfg.port_range == 0)
+	    ice_cfg.turn.cfg.port_range = pjsua_var.ua_cfg.max_calls * 10;
     }
 
     /* Configure packet size for STUN and TURN sockets */