Index: pjproject/trunk/pjnath/src/pjnath/stun_session.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/stun_session.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjnath/stun_session.c	(revision 1879)
@@ -268,4 +268,7 @@
     *notify_user = PJ_TRUE;
 
+    if (response==NULL)
+	return PJ_SUCCESS;
+
     if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM)
 	return PJ_SUCCESS;
Index: pjproject/trunk/pjnath/src/pjnath/turn_session.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 1879)
@@ -189,4 +189,5 @@
     sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC;
     sess->user_data = user_data;
+    sess->next_ch = PJ_TURN_CHANNEL_MIN;
 
     /* Copy callback */
@@ -326,5 +327,5 @@
 	/* Send REFRESH with LIFETIME=0 */
 	can_destroy = PJ_FALSE;
-	sess->pending_destroy = PJ_TRUE;
+	send_refresh(sess, 0);
 	break;
     case PJ_TURN_STATE_DEALLOCATING:
@@ -779,5 +780,5 @@
     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 */
@@ -1024,5 +1025,21 @@
     
     /* 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 */
@@ -1181,4 +1198,6 @@
     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);
@@ -1210,5 +1229,5 @@
     /* 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));
@@ -1299,11 +1318,13 @@
 
 	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);
+	    }
 	}
     }
@@ -1406,4 +1427,5 @@
 	    delay.msec = 0;
 
+	    sess->timer.id = TIMER_KEEP_ALIVE;
 	    pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay);
 	}
@@ -1415,5 +1437,8 @@
 	pj_lock_release(sess->lock);
 	do_destroy(sess);
-    }    
-}
-
+    } else {
+	pj_assert(!"Unknown timer event");
+	pj_lock_release(sess->lock);
+    }
+}
+
Index: pjproject/trunk/pjnath/src/pjnath/turn_udp.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/turn_udp.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjnath/turn_udp.c	(revision 1879)
@@ -20,7 +20,14 @@
 #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
@@ -30,4 +37,10 @@
     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;
@@ -65,4 +78,8 @@
 
 
+static void destroy(pj_turn_udp *udp_rel);
+static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e);
+
+
 /*
  * Create.
@@ -92,4 +109,43 @@
     if (cb) {
 	pj_memcpy(&udp_rel->cb, cb, sizeof(*cb));
+    }
+
+    /* Create lock */
+    status = pj_lock_create_recursive_mutex(pool, pool->obj_name, 
+					    &udp_rel->lock);
+    if (status != PJ_SUCCESS) {
+	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) {
+	destroy(udp_rel);
+	return status;
+    }
+
+    /* Bind to any */
+    pj_sockaddr_init(af, &udp_rel->src_addr, NULL, 0);
+    status = pj_sock_bind(udp_rel->sock, &udp_rel->src_addr, 
+			  pj_sockaddr_get_len(&udp_rel->src_addr));
+    if (status != PJ_SUCCESS) {
+	destroy(udp_rel);
+	return status;
+    }
+
+    /* Register to ioqeuue */
+    pj_bzero(&ioq_cb, sizeof(ioq_cb));
+    ioq_cb.on_read_complete = &on_read_complete;
+    status = pj_ioqueue_register_sock(udp_rel->pool, cfg->ioqueue, 
+				      udp_rel->sock, udp_rel, 
+				      &ioq_cb, &udp_rel->key);
+    if (status != PJ_SUCCESS) {
+	destroy(udp_rel);
+	return status;
     }
 
@@ -103,32 +159,5 @@
 				    &sess_cb, udp_rel, 0, &udp_rel->sess);
     if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
-	return status;
-    }
-
-    /* Init socket */
-    status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &udp_rel->sock);
-    if (status != PJ_SUCCESS) {
-	pj_turn_udp_destroy(udp_rel);
-	return status;
-    }
-
-    /* Bind to any */
-    pj_sockaddr_init(af, &udp_rel->src_addr, NULL, 0);
-    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);
-	return status;
-    }
-
-    /* Register to ioqeuue */
-    pj_bzero(&ioq_cb, sizeof(ioq_cb));
-    ioq_cb.on_read_complete = &on_read_complete;
-    status = pj_ioqueue_register_sock(udp_rel->pool, cfg->ioqueue, 
-				      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;
     }
@@ -145,9 +174,29 @@
  * 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;
     }
 
@@ -158,4 +207,44 @@
     }
 }
+
+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;
+    }
+}
+
 
 /*
@@ -272,4 +361,5 @@
 
     udp_rel = (pj_turn_udp*) pj_ioqueue_get_user_data(key);
+    pj_lock_acquire(udp_rel->lock);
 
     do {
@@ -301,4 +391,5 @@
 	     ++retry < MAX_RETRY);
 
+    pj_lock_release(udp_rel->lock);
 }
 
@@ -317,4 +408,10 @@
     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);
@@ -348,4 +445,9 @@
     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, 
@@ -364,12 +466,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;
+	}
+    }
+}
+
+
Index: pjproject/trunk/pjnath/src/pjturn-client/client_main.c
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-client/client_main.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-client/client_main.c	(revision 1879)
@@ -62,5 +62,4 @@
     char    *user_name;
     char    *password;
-    char    *nonce;
     pj_bool_t use_fingerprint;
 } o;
@@ -136,5 +135,5 @@
 
 	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);
 
     }
@@ -266,5 +265,5 @@
 	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"));
@@ -287,5 +286,4 @@
     if (g.udp_rel) {
 	pj_turn_udp_destroy(g.udp_rel);
-	g.udp_rel = NULL;
     }
 }
@@ -310,8 +308,14 @@
 			  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;
     }
 }
@@ -380,8 +384,8 @@
 	   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("+-----------------------------------+                                |");
@@ -406,17 +410,17 @@
 	
 	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, 
@@ -426,14 +430,14 @@
 		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));
@@ -441,5 +445,5 @@
 		my_perror("turn_udp_bind_channel() failed", status);
 	    break;
-	case 'X':
+	case 'x':
 	    if (g.udp_rel == NULL) {
 		puts("Error: no relay");
@@ -450,5 +454,5 @@
 	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;
@@ -474,5 +478,4 @@
     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");
@@ -485,5 +488,4 @@
 	{ "username",	1, 0, 'u'},
 	{ "password",	1, 0, 'p'},
-	{ "nonce",	1, 0, 'N'},
 	{ "fingerprint",0, 0, 'F'},
 	{ "data",	1, 0, 'D'},
@@ -505,7 +507,4 @@
 	    o.password = pj_optarg;
 	    break;
-	case 'N':
-	    o.nonce = pj_optarg;
-	    break;
 	case 'h':
 	    usage();
@@ -538,6 +537,6 @@
 	goto on_return;
     
-    if ((status=create_relay()) != 0)
-	goto on_return;
+    //if ((status=create_relay()) != 0)
+    //	goto on_return;
     
     console_main();
Index: pjproject/trunk/pjnath/src/pjturn-srv/allocation.c
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/allocation.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/allocation.c	(revision 1879)
@@ -669,5 +669,5 @@
     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);
@@ -689,4 +689,9 @@
 	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 */
@@ -752,12 +757,16 @@
     }
 
-    /* Add LIFETIME. */
-    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 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 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, 
@@ -774,6 +783,6 @@
 /* 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;
@@ -795,4 +804,8 @@
     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;
 }
@@ -805,5 +818,5 @@
 
     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;
@@ -811,6 +824,6 @@
 
     /* 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 */
@@ -829,14 +842,10 @@
 			  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;
 }
 
@@ -850,7 +859,7 @@
 
     /* 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;
 }
 
@@ -931,6 +940,6 @@
 	    /* 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;
 	}
@@ -992,5 +1001,5 @@
 
 	/* 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 */
@@ -1010,4 +1019,16 @@
 	    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);
     }
 }
@@ -1187,4 +1208,7 @@
 	    refresh_permission(p1);
 
+	    /* Send response */
+	    send_reply_ok(alloc, rdata);
+
 	    /* Done */
 	    return PJ_SUCCESS;
@@ -1212,4 +1236,9 @@
 	/* 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 */
Index: pjproject/trunk/pjnath/src/pjturn-srv/auth.c
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/auth.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/auth.c	(revision 1879)
@@ -114,9 +114,9 @@
  * 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);
@@ -128,5 +128,5 @@
 	return PJ_FALSE;
 
-    return PJ_SUCCESS;
+    return PJ_TRUE;
 }
 
Index: pjproject/trunk/pjnath/src/pjturn-srv/auth.h
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/auth.h	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/auth.h	(revision 1879)
@@ -106,9 +106,9 @@
  *			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__ */
Index: pjproject/trunk/pjnath/src/pjturn-srv/main.c
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/main.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/main.c	(revision 1879)
@@ -22,4 +22,6 @@
 #define REALM	"pjsip.org"
 
+static pj_caching_pool g_cp;
+
 int err(const char *title, pj_status_t status)
 {
@@ -31,7 +33,89 @@
 }
 
+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;
@@ -45,9 +129,9 @@
     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);
@@ -63,13 +147,9 @@
 
     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();
 
Index: pjproject/trunk/pjnath/src/pjturn-srv/server.c
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/server.c	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/server.c	(revision 1879)
@@ -136,5 +136,5 @@
     /* 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;
@@ -266,4 +266,16 @@
     }
 
+    /* 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) {
@@ -277,16 +289,4 @@
 	}
     }
-
-    /* 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) */
Index: pjproject/trunk/pjnath/src/pjturn-srv/turn.h
===================================================================
--- pjproject/trunk/pjnath/src/pjturn-srv/turn.h	(revision 1878)
+++ pjproject/trunk/pjnath/src/pjturn-srv/turn.h	(revision 1879)
@@ -191,5 +191,5 @@
 
     /** 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;
