More ticket #485: client and server self tested

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1879 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c
index 02b017c..63598bb 100644
--- a/pjnath/src/pjnath/stun_session.c
+++ b/pjnath/src/pjnath/stun_session.c
@@ -267,6 +267,9 @@
 
     *notify_user = PJ_TRUE;
 
+    if (response==NULL)
+	return PJ_SUCCESS;
+
     if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM)
 	return PJ_SUCCESS;
     
diff --git a/pjnath/src/pjnath/turn_session.c b/pjnath/src/pjnath/turn_session.c
index e5540b0..e5364ed 100644
--- a/pjnath/src/pjnath/turn_session.c
+++ b/pjnath/src/pjnath/turn_session.c
@@ -188,6 +188,7 @@
     sess->tp_type = tp_type;
     sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC;
     sess->user_data = user_data;
+    sess->next_ch = PJ_TURN_CHANNEL_MIN;
 
     /* Copy callback */
     pj_memcpy(&sess->cb, cb, sizeof(*cb));
@@ -325,7 +326,7 @@
     case PJ_TURN_STATE_READY:
 	/* Send REFRESH with LIFETIME=0 */
 	can_destroy = PJ_FALSE;
-	sess->pending_destroy = PJ_TRUE;
+	send_refresh(sess, 0);
 	break;
     case PJ_TURN_STATE_DEALLOCATING:
 	can_destroy = PJ_FALSE;
@@ -778,7 +779,7 @@
     /* Add CHANNEL-NUMBER attribute */
     pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
 			      PJ_STUN_ATTR_CHANNEL_NUMBER,
-			      PJ_STUN_SET_CH_NB(sess->next_ch));
+			      PJ_STUN_SET_CH_NB(ch_num));
 
     /* Add PEER-ADDRESS attribute */
     pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
@@ -1023,7 +1024,23 @@
     }
     
     /* Save relayed address */
-    pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, sizeof(pj_sockaddr));
+    if (raddr_attr) {
+	/* If we already have relay address, check if the relay address 
+	 * in the response matches our relay address.
+	 */
+	if (pj_sockaddr_has_addr(&sess->relay_addr)) {
+	    if (pj_sockaddr_cmp(&sess->relay_addr, &raddr_attr->sockaddr)) {
+		on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+				pj_cstr(&s, "Error: different RELAY-ADDRESS is"
+					    "returned by server"));
+		return;
+	    }
+	} else {
+	    /* Otherwise save the relayed address */
+	    pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, 
+		      sizeof(pj_sockaddr));
+	}
+    }
 
     /* Success */
 
@@ -1180,6 +1197,8 @@
     pj_stun_peer_addr_attr *peer_attr;
     pj_stun_data_attr *data_attr;
 
+    PJ_UNUSED_ARG(pkt);
+    PJ_UNUSED_ARG(pkt_len);
     PJ_UNUSED_ARG(src_addr);
     PJ_UNUSED_ARG(src_addr_len);
 
@@ -1209,7 +1228,7 @@
 
     /* Notify application */
     if (sess->cb.on_rx_data) {
-	(*sess->cb.on_rx_data)(sess, pkt, pkt_len, 
+	(*sess->cb.on_rx_data)(sess, data_attr->data, data_attr->length, 
 			       &peer_attr->sockaddr,
 			       pj_sockaddr_get_len(&peer_attr->sockaddr));
     }
@@ -1298,13 +1317,15 @@
 	}
 
 	if (bind_channel) {
+	    pj_uint32_t hval = 0;
 	    /* Register by channel number */
 	    pj_assert(peer->ch_id != PJ_TURN_INVALID_CHANNEL && peer->bound);
-	    pj_assert(pj_hash_get(sess->peer_table, &peer->ch_id, 
-				  sizeof(peer->ch_id), NULL)==0);
 
-	    pj_hash_set(sess->pool, sess->peer_table, &peer->ch_id,
-			sizeof(peer->ch_id), 0, peer);
+	    if (pj_hash_get(sess->peer_table, &peer->ch_id, 
+			    sizeof(peer->ch_id), &hval)==0) {
+		pj_hash_set(sess->pool, sess->peer_table, &peer->ch_id,
+			    sizeof(peer->ch_id), hval, peer);
+	    }
 	}
     }
 
@@ -1405,6 +1426,7 @@
 	    delay.sec = sess->ka_interval;
 	    delay.msec = 0;
 
+	    sess->timer.id = TIMER_KEEP_ALIVE;
 	    pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay);
 	}
 
@@ -1414,6 +1436,9 @@
 	/* Time to destroy */
 	pj_lock_release(sess->lock);
 	do_destroy(sess);
-    }    
+    } else {
+	pj_assert(!"Unknown timer event");
+	pj_lock_release(sess->lock);
+    }
 }
 
diff --git a/pjnath/src/pjnath/turn_udp.c b/pjnath/src/pjnath/turn_udp.c
index 8d5ff9d..a9fd470 100644
--- a/pjnath/src/pjnath/turn_udp.c
+++ b/pjnath/src/pjnath/turn_udp.c
@@ -19,10 +19,17 @@
 #include <pjnath/turn_udp.h>
 #include <pj/assert.h>
 #include <pj/errno.h>
+#include <pj/lock.h>
 #include <pj/log.h>
 #include <pj/pool.h>
 #include <pj/ioqueue.h>
 
+enum
+{
+    TIMER_NONE,
+    TIMER_DESTROY
+};
+
 struct pj_turn_udp
 {
     pj_pool_t		*pool;
@@ -30,6 +37,12 @@
     pj_turn_udp_cb	 cb;
     void		*user_data;
 
+    pj_lock_t		*lock;
+
+    pj_bool_t		 destroy_request;
+    pj_timer_heap_t	*timer_heap;
+    pj_timer_entry	 timer;
+
     pj_sock_t		 sock;
     pj_ioqueue_key_t	*key;
     pj_ioqueue_op_key_t	 read_key;
@@ -64,6 +77,10 @@
                              pj_ssize_t bytes_read);
 
 
+static void destroy(pj_turn_udp *udp_rel);
+static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e);
+
+
 /*
  * Create.
  */
@@ -93,23 +110,22 @@
 	pj_memcpy(&udp_rel->cb, cb, sizeof(*cb));
     }
 
-    /* Init TURN session */
-    pj_bzero(&sess_cb, sizeof(sess_cb));
-    sess_cb.on_send_pkt = &turn_on_send_pkt;
-    sess_cb.on_channel_bound = &turn_on_channel_bound;
-    sess_cb.on_rx_data = &turn_on_rx_data;
-    sess_cb.on_state = &turn_on_state;
-    status = pj_turn_session_create(cfg, pool->obj_name, af, PJ_TURN_TP_UDP,
-				    &sess_cb, udp_rel, 0, &udp_rel->sess);
+    /* Create lock */
+    status = pj_lock_create_recursive_mutex(pool, pool->obj_name, 
+					    &udp_rel->lock);
     if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
+	destroy(udp_rel);
 	return status;
     }
 
+    /* Init timer */
+    udp_rel->timer_heap = cfg->timer_heap;
+    pj_timer_entry_init(&udp_rel->timer, TIMER_NONE, udp_rel, &timer_cb);
+
     /* Init socket */
     status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &udp_rel->sock);
     if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
+	destroy(udp_rel);
 	return status;
     }
 
@@ -118,7 +134,7 @@
     status = pj_sock_bind(udp_rel->sock, &udp_rel->src_addr, 
 			  pj_sockaddr_get_len(&udp_rel->src_addr));
     if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
+	destroy(udp_rel);
 	return status;
     }
 
@@ -129,7 +145,20 @@
 				      udp_rel->sock, udp_rel, 
 				      &ioq_cb, &udp_rel->key);
     if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
+	destroy(udp_rel);
+	return status;
+    }
+
+    /* Init TURN session */
+    pj_bzero(&sess_cb, sizeof(sess_cb));
+    sess_cb.on_send_pkt = &turn_on_send_pkt;
+    sess_cb.on_channel_bound = &turn_on_channel_bound;
+    sess_cb.on_rx_data = &turn_on_rx_data;
+    sess_cb.on_state = &turn_on_state;
+    status = pj_turn_session_create(cfg, pool->obj_name, af, PJ_TURN_TP_UDP,
+				    &sess_cb, udp_rel, 0, &udp_rel->sess);
+    if (status != PJ_SUCCESS) {
+	destroy(udp_rel);
 	return status;
     }
 
@@ -144,13 +173,33 @@
 /*
  * Destroy.
  */
-PJ_DEF(void) pj_turn_udp_destroy(pj_turn_udp *udp_rel)
+static void destroy(pj_turn_udp *udp_rel)
 {
+    if (udp_rel->lock) {
+	pj_lock_acquire(udp_rel->lock);
+    }
+
     if (udp_rel->sess) {
+	pj_turn_session_set_user_data(udp_rel->sess, NULL);
 	pj_turn_session_destroy(udp_rel->sess);
 	udp_rel->sess = NULL;
     }
 
+    if (udp_rel->key) {
+	pj_ioqueue_unregister(udp_rel->key);
+	udp_rel->key = NULL;
+	udp_rel->sock = 0;
+    } else if (udp_rel->sock) {
+	pj_sock_close(udp_rel->sock);
+	udp_rel->sock = 0;
+    }
+
+    if (udp_rel->lock) {
+	pj_lock_release(udp_rel->lock);
+	pj_lock_destroy(udp_rel->lock);
+	udp_rel->lock = NULL;
+    }
+
     if (udp_rel->pool) {
 	pj_pool_t *pool = udp_rel->pool;
 	udp_rel->pool = NULL;
@@ -158,6 +207,46 @@
     }
 }
 
+PJ_DEF(void) pj_turn_udp_destroy(pj_turn_udp *udp_rel)
+{
+    pj_lock_acquire(udp_rel->lock);
+    udp_rel->destroy_request = PJ_TRUE;
+
+    if (udp_rel->sess) {
+	pj_turn_session_destroy(udp_rel->sess);
+	/* This will ultimately call our state callback, and when
+	 * session state is DESTROYING we will schedule a timer to
+	 * destroy ourselves.
+	 */
+	pj_lock_release(udp_rel->lock);
+    } else {
+	pj_lock_release(udp_rel->lock);
+	destroy(udp_rel);
+    }
+
+}
+
+/* Timer callback */
+static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+    pj_turn_udp *udp_rel = (pj_turn_udp*)e->user_data;
+    int eid = e->id;
+
+    PJ_UNUSED_ARG(th);
+
+    e->id = TIMER_NONE;
+
+    switch (eid) {
+    case TIMER_DESTROY:
+	destroy(udp_rel);
+	break;
+    default:
+	pj_assert(!"Invalid timer id");
+	break;
+    }
+}
+
+
 /*
  * Set user data.
  */
@@ -271,6 +360,7 @@
     pj_status_t status;
 
     udp_rel = (pj_turn_udp*) pj_ioqueue_get_user_data(key);
+    pj_lock_acquire(udp_rel->lock);
 
     do {
 	/* Report incoming packet to TURN session */
@@ -300,6 +390,7 @@
     } while (status != PJ_EPENDING && status != PJ_ECANCELLED &&
 	     ++retry < MAX_RETRY);
 
+    pj_lock_release(udp_rel->lock);
 }
 
 
@@ -316,6 +407,12 @@
 			   pj_turn_session_get_user_data(sess);
     pj_ssize_t len = pkt_len;
 
+    if (udp_rel == NULL) {
+	/* We've been destroyed */
+	pj_assert(!"We should shutdown gracefully");
+	return PJ_EINVALIDOP;
+    }
+
     return pj_sock_sendto(udp_rel->sock, pkt, &len, 0,
 			  dst_addr, dst_addr_len);
 }
@@ -347,6 +444,11 @@
 {
     pj_turn_udp *udp_rel = (pj_turn_udp*) 
 			   pj_turn_session_get_user_data(sess);
+    if (udp_rel == NULL) {
+	/* We've been destroyed */
+	return;
+    }
+
     if (udp_rel->cb.on_rx_data) {
 	(*udp_rel->cb.on_rx_data)(udp_rel, pkt, pkt_len, 
 				  peer_addr, addr_len);
@@ -363,12 +465,27 @@
 {
     pj_turn_udp *udp_rel = (pj_turn_udp*) 
 			   pj_turn_session_get_user_data(sess);
+    if (udp_rel == NULL) {
+	/* We've been destroyed */
+	return;
+    }
+
     if (udp_rel->cb.on_state) {
 	(*udp_rel->cb.on_state)(udp_rel, old_state, new_state);
     }
 
-    if (new_state > PJ_TURN_STATE_READY) {
-	udp_rel->sess = NULL;
+    if (new_state >= PJ_TURN_STATE_DESTROYING && udp_rel->sess) {
+	if (udp_rel->destroy_request) {
+	    pj_time_val delay = {0, 0};
+
+	    pj_turn_session_set_user_data(udp_rel->sess, NULL);
+
+	    udp_rel->timer.id = TIMER_DESTROY;
+	    pj_timer_heap_schedule(udp_rel->timer_heap, &udp_rel->timer, 
+				   &delay);
+	} else {
+	    udp_rel->sess = NULL;
+	}
     }
 }
 
diff --git a/pjnath/src/pjturn-client/client_main.c b/pjnath/src/pjturn-client/client_main.c
index 114f47c..470ad25 100644
--- a/pjnath/src/pjturn-client/client_main.c
+++ b/pjnath/src/pjturn-client/client_main.c
@@ -61,7 +61,6 @@
     char    *realm;
     char    *user_name;
     char    *password;
-    char    *nonce;
     pj_bool_t use_fingerprint;
 } o;
 
@@ -135,7 +134,7 @@
 	port = pj_sockaddr_get_port(&addr);
 
 	CHECK( pj_gethostip(pj_AF_INET(), &g.peer[i].addr) );
-	pj_sockaddr_set_port(&g.peer[0].addr, port);
+	pj_sockaddr_set_port(&g.peer[i].addr, port);
 
     }
 
@@ -265,7 +264,7 @@
 	cred.data.static_cred.username = pj_str(o.user_name);
 	cred.data.static_cred.data_type = 0;
 	cred.data.static_cred.data = pj_str(o.password);
-	cred.data.static_cred.nonce = pj_str(o.nonce);
+	//cred.data.static_cred.nonce = pj_str(o.nonce);
     } else {
 	PJ_LOG(2,(THIS_FILE, "Warning: no credential is set"));
     }
@@ -286,7 +285,6 @@
 {
     if (g.udp_rel) {
 	pj_turn_udp_destroy(g.udp_rel);
-	g.udp_rel = NULL;
     }
 }
 
@@ -309,10 +307,16 @@
 static void turn_on_state(pj_turn_udp *udp_rel, pj_turn_state_t old_state,
 			  pj_turn_state_t new_state)
 {
+    PJ_LOG(3,(THIS_FILE, "State %s --> %s", pj_turn_state_name(old_state), 
+	      pj_turn_state_name(new_state)));
+
     if (new_state == PJ_TURN_STATE_READY) {
 	pj_turn_session_info info;
 	pj_turn_udp_get_info(udp_rel, &info);
 	pj_memcpy(&g.relay_addr, &info.relay_addr, sizeof(pj_sockaddr));
+    } else if (new_state > PJ_TURN_STATE_READY && g.udp_rel) {
+	PJ_LOG(3,(THIS_FILE, "Relay shutting down.."));
+	g.udp_rel = NULL;
     }
 }
 
@@ -379,10 +383,10 @@
     printf("| Relay addr: %-21s |                                |\n",
 	   relay_addr);
     puts("|                                   | 0  Send data to relay address  |");
-    puts("| A      Allocate relay             +--------------------------------+	");
-    puts("| S[01]  Send data to peer 0/1      |             PEER-1             |");
-    puts("| B[01]  BindChannel to peer 0/1    |                                |");
-    printf("| X      Delete allocation          | Address: %-21s |\n",
+    puts("| a      Allocate relay             +--------------------------------+	");
+    puts("| s,ss   Send data to peer 0/1      |             PEER-1             |");
+    puts("| b,bb   BindChannel to peer 0/1    |                                |");
+    printf("| x      Delete allocation          | Address: %-21s |\n",
 	  peer1_addr);
     puts("+-----------------------------------+                                |");
     puts("| q  Quit                           | 1  Send data to relay adderss  |");
@@ -405,19 +409,19 @@
 	fgets(input, sizeof(input), stdin);
 	
 	switch (input[0]) {
-	case 'A':
+	case 'a':
 	    create_relay();
 	    break;
-	case 'S':
+	case 's':
 	    if (g.udp_rel == NULL) {
 		puts("Error: no relay");
 		continue;
 	    }
-	    if (input[1] != '0' && input[1] != '1') {
-		puts("Usage: S0 or S1");
-		continue;
-	    }
-	    peer = &g.peer[input[1]-'0'];
+	    if (input[1]!='s')
+		peer = &g.peer[0];
+	    else
+		peer = &g.peer[1];
+
 	    strcpy(input, "Hello from client");
 	    status = pj_turn_udp_sendto(g.udp_rel, input, strlen(input)+1, 
 					&peer->addr, 
@@ -425,22 +429,22 @@
 	    if (status != PJ_SUCCESS)
 		my_perror("turn_udp_sendto() failed", status);
 	    break;
-	case 'B':
+	case 'b':
 	    if (g.udp_rel == NULL) {
 		puts("Error: no relay");
 		continue;
 	    }
-	    if (input[1] != '0' && input[1] != '1') {
-		puts("Usage: B0 or B1");
-		continue;
-	    }
-	    peer = &g.peer[input[1]-'0'];
+	    if (input[1]!='b')
+		peer = &g.peer[0];
+	    else
+		peer = &g.peer[1];
+
 	    status = pj_turn_udp_bind_channel(g.udp_rel, &peer->addr,
 					      pj_sockaddr_get_len(&peer->addr));
 	    if (status != PJ_SUCCESS)
 		my_perror("turn_udp_bind_channel() failed", status);
 	    break;
-	case 'X':
+	case 'x':
 	    if (g.udp_rel == NULL) {
 		puts("Error: no relay");
 		continue;
@@ -449,7 +453,7 @@
 	    break;
 	case '0':
 	case '1':
-	    peer = &g.peer[input[1]-'0'];
+	    peer = &g.peer[input[0]-'0'];
 	    sprintf(input, "Hello from peer%d", input[0]-'0');
 	    len = strlen(input)+1;
 	    pj_sock_sendto(peer->sock, input, &len, 0, &g.relay_addr, 
@@ -473,7 +477,6 @@
     puts(" --realm, -r       Set realm of the credential");
     puts(" --username, -u    Set username of the credential");
     puts(" --password, -p    Set password of the credential");
-    puts(" --nonce, -N       Set NONCE");   
     puts(" --fingerprint, -F Use fingerprint for outgoing requests");
     puts(" --help, -h");
 }
@@ -484,7 +487,6 @@
 	{ "realm",	1, 0, 'r'},
 	{ "username",	1, 0, 'u'},
 	{ "password",	1, 0, 'p'},
-	{ "nonce",	1, 0, 'N'},
 	{ "fingerprint",0, 0, 'F'},
 	{ "data",	1, 0, 'D'},
 	{ "help",	0, 0, 'h'}
@@ -504,9 +506,6 @@
 	case 'p':
 	    o.password = pj_optarg;
 	    break;
-	case 'N':
-	    o.nonce = pj_optarg;
-	    break;
 	case 'h':
 	    usage();
 	    return 0;
@@ -537,8 +536,8 @@
     if ((status=init()) != 0)
 	goto on_return;
     
-    if ((status=create_relay()) != 0)
-	goto on_return;
+    //if ((status=create_relay()) != 0)
+    //	goto on_return;
     
     console_main();
 
diff --git a/pjnath/src/pjturn-srv/allocation.c b/pjnath/src/pjturn-srv/allocation.c
index 14ed228..339b292 100644
--- a/pjnath/src/pjturn-srv/allocation.c
+++ b/pjnath/src/pjturn-srv/allocation.c
@@ -668,7 +668,7 @@
 
     if (status != PJ_SUCCESS) {
 	/* Unable to allocate port */
-	PJ_LOG(4,(THIS_FILE, "bind() failed: err %d", 
+	PJ_LOG(4,(THIS_FILE, "Unable to allocate relay, giving up: err %d", 
 		  status));
 	pj_sock_close(relay->tp.sock);
 	relay->tp.sock = PJ_INVALID_SOCKET;
@@ -688,6 +688,11 @@
     if (!pj_sockaddr_has_addr(&relay->hkey.addr)) {
 	pj_sockaddr_copy_addr(&relay->hkey.addr, &alloc->listener->addr);
     }
+    if (!pj_sockaddr_has_addr(&relay->hkey.addr)) {
+	pj_sockaddr tmp_addr;
+	pj_gethostip(af, &tmp_addr);
+	pj_sockaddr_copy_addr(&relay->hkey.addr, &tmp_addr);
+    }
 
     /* Init ioqueue */
     pj_bzero(&icb, sizeof(icb));
@@ -751,14 +756,18 @@
 	interval = 0;
     }
 
-    /* Add LIFETIME. */
-    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
-			      PJ_STUN_ATTR_LIFETIME, interval);
+    /* Add LIFETIME if this is not ChannelBind. */
+    if (PJ_STUN_GET_METHOD(tdata->msg->hdr.type)!=PJ_STUN_CHANNEL_BIND_METHOD){
+	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+				  PJ_STUN_ATTR_LIFETIME, interval);
 
-    /* Add BANDWIDTH */
-    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
-			      PJ_STUN_ATTR_BANDWIDTH,
-			      alloc->bandwidth);
+	/* Add BANDWIDTH if lifetime is not zero */
+	if (interval != 0) {
+	    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+				      PJ_STUN_ATTR_BANDWIDTH,
+				      alloc->bandwidth);
+	}
+    }
 
     status = pj_stun_session_send_msg(alloc->sess, PJ_TRUE, 
 				      &alloc->hkey.clt_addr,  
@@ -773,8 +782,8 @@
 
 /* Create new permission */
 static pj_turn_permission *create_permission(pj_turn_allocation *alloc,
-					    const pj_sockaddr_t *peer_addr,
-					    unsigned addr_len)
+					     const pj_sockaddr_t *peer_addr,
+					     unsigned addr_len)
 {
     pj_turn_permission *perm;
 
@@ -794,6 +803,10 @@
     pj_gettimeofday(&perm->expiry);
     perm->expiry.sec += PJ_TURN_PERM_TIMEOUT;
 
+    /* Register to hash table */
+    pj_hash_set(alloc->pool, alloc->peer_table, &perm->hkey.peer_addr, 
+	        pj_sockaddr_get_len(&perm->hkey.peer_addr), 0, perm);
+
     return perm;
 }
 
@@ -804,14 +817,14 @@
     pj_time_val now;
 
     pj_gettimeofday(&now);
-    if (PJ_TIME_VAL_LT(perm->expiry, now)) {
+    if (PJ_TIME_VAL_GT(perm->expiry, now)) {
 	/* Permission has not expired */
 	return perm;
     }
 
     /* Remove from permission hash table */
-    pj_hash_set(NULL, alloc->peer_table, &perm->hkey, sizeof(perm->hkey),
-		0, NULL);
+    pj_hash_set(NULL, alloc->peer_table, &perm->hkey.peer_addr, 
+	        pj_sockaddr_get_len(&perm->hkey.peer_addr), 0, NULL);
 
     /* Remove from channel hash table, if assigned a channel number */
     if (perm->channel != PJ_TURN_INVALID_CHANNEL) {
@@ -828,16 +841,12 @@
 			  const pj_sockaddr_t *peer_addr,
 			  unsigned addr_len)
 {
-    pj_turn_permission_key key;
     pj_turn_permission *perm;
 
-    pj_bzero(&key, sizeof(key));
-    pj_memcpy(&key, peer_addr, addr_len);
-
     /* Lookup in peer hash table */
-    perm = (pj_turn_permission*) pj_hash_get(alloc->peer_table, &key,
-					    sizeof(key), NULL);
-    return check_permission_expiry(perm);
+    perm = (pj_turn_permission*) pj_hash_get(alloc->peer_table, peer_addr,
+					     addr_len, NULL);
+    return perm ? check_permission_expiry(perm) : NULL;
 }
 
 /* Lookup permission in hash table by the channel number */
@@ -849,9 +858,9 @@
     pj_turn_permission *perm;
 
     /* Lookup in peer hash table */
-    perm = (pj_turn_permission*) pj_hash_get(alloc->peer_table, &chnum16,
+    perm = (pj_turn_permission*) pj_hash_get(alloc->ch_table, &chnum16,
 					    sizeof(chnum16), NULL);
-    return check_permission_expiry(perm);
+    return perm ? check_permission_expiry(perm) : NULL;
 }
 
 /* Update permission because of data from client to peer. 
@@ -930,8 +939,8 @@
 	if (!perm) {
 	    /* Discard */
 	    PJ_LOG(4,(alloc->obj_name, 
-		      "ChannelData from %s discarded: not found",
-		      alloc->info));
+		      "ChannelData from %s discarded: ch#0x%x not found",
+		      alloc->info, pj_ntohs(cd->ch_number)));
 	    goto on_return;
 	}
 
@@ -991,7 +1000,7 @@
 	cd->length = pj_htons((pj_uint16_t)len);
 
 	/* Copy data */
-	pj_memcpy(rel->tp.rx_pkt+sizeof(pj_turn_channel_data), pkt, len);
+	pj_memcpy(rel->tp.tx_pkt+sizeof(pj_turn_channel_data), pkt, len);
 
 	/* Send to client */
 	pj_turn_listener_sendto(alloc->listener, rel->tp.tx_pkt,
@@ -1009,6 +1018,18 @@
 	    alloc_err(alloc, "Error creating Data indication", status);
 	    return;
 	}
+
+	pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, 
+				      PJ_STUN_ATTR_PEER_ADDR, PJ_TRUE,
+				      src_addr, pj_sockaddr_get_len(src_addr));
+	pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg,
+				    PJ_STUN_ATTR_DATA, 
+				    (const pj_uint8_t*)pkt, len);
+
+	pj_stun_session_send_msg(alloc->sess, PJ_FALSE, 
+				 &alloc->hkey.clt_addr, 
+				 pj_sockaddr_get_len(&alloc->hkey.clt_addr), 
+				 tdata);
     }
 }
 
@@ -1186,6 +1207,9 @@
 	    /* Refresh permission */
 	    refresh_permission(p1);
 
+	    /* Send response */
+	    send_reply_ok(alloc, rdata);
+
 	    /* Done */
 	    return PJ_SUCCESS;
 	}
@@ -1212,6 +1236,11 @@
 	/* Assign channel number to permission */
 	p2->channel = PJ_STUN_GET_CH_NB(ch_attr->value);
 
+	/* Register to hash table */
+	pj_assert(sizeof(p2->channel==2));
+	pj_hash_set(alloc->pool, alloc->ch_table, &p2->channel, 
+		    sizeof(p2->channel), 0, p2);
+
 	/* Update */
 	refresh_permission(p2);
 
diff --git a/pjnath/src/pjturn-srv/auth.c b/pjnath/src/pjturn-srv/auth.c
index 3071221..8e6c9c5 100644
--- a/pjnath/src/pjturn-srv/auth.c
+++ b/pjnath/src/pjturn-srv/auth.c
@@ -113,11 +113,11 @@
  * in the message can be accepted. If this callback returns
  * PJ_FALSE, 438 (Stale Nonce) response will be created.
  */
-PJ_DEF(pj_status_t) pj_turn_verify_nonce(const pj_stun_msg *msg,
-					 void *user_data,
-					 const pj_str_t *realm,
-					 const pj_str_t *username,
-					 const pj_str_t *nonce)
+PJ_DEF(pj_bool_t) pj_turn_verify_nonce(const pj_stun_msg *msg,
+				       void *user_data,
+				       const pj_str_t *realm,
+				       const pj_str_t *username,
+				       const pj_str_t *nonce)
 {
     PJ_UNUSED_ARG(msg);
     PJ_UNUSED_ARG(user_data);
@@ -127,6 +127,6 @@
     if (pj_stricmp2(nonce, THE_NONCE))
 	return PJ_FALSE;
 
-    return PJ_SUCCESS;
+    return PJ_TRUE;
 }
 
diff --git a/pjnath/src/pjturn-srv/auth.h b/pjnath/src/pjturn-srv/auth.h
index db92805..2e342a7 100644
--- a/pjnath/src/pjturn-srv/auth.h
+++ b/pjnath/src/pjturn-srv/auth.h
@@ -105,11 +105,11 @@
  * @return		The callback MUST return non-zero if the 
  *			NONCE can be accepted.
  */
-PJ_DECL(pj_status_t) pj_turn_verify_nonce(const pj_stun_msg *msg,
-					  void *user_data,
-					  const pj_str_t *realm,
-					  const pj_str_t *username,
-					  const pj_str_t *nonce);
+PJ_DECL(pj_bool_t) pj_turn_verify_nonce(const pj_stun_msg *msg,
+					void *user_data,
+					const pj_str_t *realm,
+					const pj_str_t *username,
+					const pj_str_t *nonce);
 
 #endif	/* __PJ_TURN_SRV_AUTH_H__ */
 
diff --git a/pjnath/src/pjturn-srv/main.c b/pjnath/src/pjturn-srv/main.c
index 60c420b..164b3c2 100644
--- a/pjnath/src/pjturn-srv/main.c
+++ b/pjnath/src/pjturn-srv/main.c
@@ -21,6 +21,8 @@
 
 #define REALM	"pjsip.org"
 
+static pj_caching_pool g_cp;
+
 int err(const char *title, pj_status_t status)
 {
     char errmsg[PJ_ERR_MSG_SIZE];
@@ -30,9 +32,91 @@
     return 1;
 }
 
+static void dump_status(pj_turn_srv *srv)
+{
+    char addr[80];
+    pj_hash_iterator_t itbuf, *it;
+    pj_time_val now;
+    unsigned i;
+
+    for (i=0; i<srv->core.lis_cnt; ++i) {
+	pj_turn_listener *lis = srv->core.listener[i];
+	printf("Server address : %s\n", lis->info);
+    }
+
+    printf("Worker threads : %d\n", srv->core.thread_cnt);
+    printf("Total mem usage: %d.%03dMB\n", g_cp.used_size / 1000000, 
+	   (g_cp.used_size % 1000000)/1000);
+    printf("UDP port range : %u %u %u (next/min/max)\n", srv->ports.next_udp,
+	   srv->ports.min_udp, srv->ports.max_udp);
+    printf("TCP port range : %u %u %u (next/min/max)\n", srv->ports.next_tcp,
+	   srv->ports.min_tcp, srv->ports.max_tcp);
+    printf("Clients #      : %u\n", pj_hash_count(srv->tables.alloc));
+
+    puts("");
+
+    if (pj_hash_count(srv->tables.alloc)==0) {
+	return;
+    }
+
+    puts("#    Client addr.          Alloc addr.            Username Lftm Expy #prm #chl");
+    puts("------------------------------------------------------------------------------");
+
+    pj_gettimeofday(&now);
+
+    it = pj_hash_first(srv->tables.alloc, &itbuf);
+    i=1;
+    while (it) {
+	pj_turn_allocation *alloc = (pj_turn_allocation*) 
+				    pj_hash_this(srv->tables.alloc, it);
+	printf("%-3d %-22s %-22s %-8.*s %-4d %-4d %-4d %-4d\n",
+	       i,
+	       alloc->info,
+	       pj_sockaddr_print(&alloc->relay.hkey.addr, addr, sizeof(addr), 3),
+	       (int)alloc->cred.data.static_cred.username.slen,
+	       (int)alloc->cred.data.static_cred.username.ptr,
+	       alloc->relay.lifetime,
+	       alloc->relay.expiry.sec - now.sec,
+	       pj_hash_count(alloc->peer_table), 
+	       pj_hash_count(alloc->ch_table));
+	it = pj_hash_next(srv->tables.alloc, it);
+	++i;
+    }
+}
+
+static void menu(void)
+{
+    puts("");
+    puts("Menu:");
+    puts(" d   Dump status");
+    puts(" q   Quit");
+    printf(">> ");
+}
+
+static void console_main(pj_turn_srv *srv)
+{
+    pj_bool_t quit = PJ_FALSE;
+
+    while (!quit) {
+	char line[10];
+	
+	menu();
+	    
+	fgets(line, sizeof(line), stdin);
+
+	switch (line[0]) {
+	case 'd':
+	    dump_status(srv);
+	    break;
+	case 'q':
+	    quit = PJ_TRUE;
+	    break;
+	}
+    }
+}
+
 int main()
 {
-    pj_caching_pool cp;
     pj_turn_srv *srv;
     pj_turn_listener *listener;
     pj_status_t status;
@@ -44,11 +128,11 @@
     pjlib_util_init();
     pjnath_init();
 
-    pj_caching_pool_init(&cp, NULL, 0);
+    pj_caching_pool_init(&g_cp, NULL, 0);
 
     pj_turn_auth_init(REALM);
 
-    status = pj_turn_srv_create(&cp.factory, &srv);
+    status = pj_turn_srv_create(&g_cp.factory, &srv);
     if (status != PJ_SUCCESS)
 	return err("Error creating server", status);
 
@@ -62,15 +146,11 @@
 	return err("Error adding listener", status);
 
     puts("Server is running");
-    puts("Press <ENTER> to quit");
 
-    {
-	char line[10];
-	fgets(line, sizeof(line), stdin);
-    }
+    console_main(srv);
 
     pj_turn_srv_destroy(srv);
-    pj_caching_pool_destroy(&cp);
+    pj_caching_pool_destroy(&g_cp);
     pj_shutdown();
 
     return 0;
diff --git a/pjnath/src/pjturn-srv/server.c b/pjnath/src/pjturn-srv/server.c
index b22cc53..66f1c6a 100644
--- a/pjnath/src/pjturn-srv/server.c
+++ b/pjnath/src/pjturn-srv/server.c
@@ -135,7 +135,7 @@
 
     /* Init ports settings */
     srv->ports.min_udp = srv->ports.next_udp = MIN_PORT;
-    srv->ports.max_tcp = MAX_PORT;
+    srv->ports.max_udp = MAX_PORT;
     srv->ports.min_tcp = srv->ports.next_tcp = MIN_PORT;
     srv->ports.max_tcp = MAX_PORT;
 
@@ -265,6 +265,18 @@
 	}
     }
 
+    /* Destroy all allocations FIRST */
+    if (srv->tables.alloc) {
+	it = pj_hash_first(srv->tables.alloc, &itbuf);
+	while (it != NULL) {
+	    pj_turn_allocation *alloc = (pj_turn_allocation*)
+					pj_hash_this(srv->tables.alloc, it);
+	    pj_hash_iterator_t *next = pj_hash_next(srv->tables.alloc, it);
+	    pj_turn_allocation_destroy(alloc);
+	    it = next;
+	}
+    }
+    
     /* Destroy all listeners and STUN sessions associated with them. */
     for (i=0; i<srv->core.lis_cnt; ++i) {
 	if (srv->core.listener[i]) {
@@ -277,18 +289,6 @@
 	}
     }
 
-    /* Destroy all allocations */
-    if (srv->tables.alloc) {
-	it = pj_hash_first(srv->tables.alloc, &itbuf);
-	while (it != NULL) {
-	    pj_turn_allocation *alloc = (pj_turn_allocation*)
-					pj_hash_this(srv->tables.alloc, it);
-	    pj_turn_allocation_destroy(alloc);
-	    it = pj_hash_next(srv->tables.alloc, it);
-	}
-    }
-    
-
     /* Destroy hash tables (well, sort of) */
     if (srv->tables.alloc) {
 	srv->tables.alloc = NULL;
diff --git a/pjnath/src/pjturn-srv/turn.h b/pjnath/src/pjturn-srv/turn.h
index 2eb9925..337e874 100644
--- a/pjnath/src/pjturn-srv/turn.h
+++ b/pjnath/src/pjturn-srv/turn.h
@@ -190,7 +190,7 @@
     pj_turn_allocation	*allocation;
 
     /** Optional channel number, or PJ_TURN_INVALID_CHANNEL if channel number
-     *  is not requested for this permission.
+     *  is not requested for this permission. 
      */
     pj_uint16_t		channel;