blob: 6d4f2e8b61bee39c51f9454afe5ccc3c8bcf8051 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "server.h"
#include "test.h"
#define THIS_FILE "server.c"
#define MAX_STUN_PKT 1500
#define TURN_NONCE "thenonce"
static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status);
static pj_bool_t turn_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status);
static pj_bool_t alloc_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status);
pj_status_t create_test_server(pj_stun_config *stun_cfg,
pj_uint32_t flags,
const char *domain,
test_server **p_test_srv)
{
pj_pool_t *pool;
test_server *test_srv;
pj_sockaddr hostip;
char strbuf[100];
pj_status_t status;
PJ_ASSERT_RETURN(stun_cfg && domain && p_test_srv, PJ_EINVAL);
status = pj_gethostip(pj_AF_INET(), &hostip);
if (status != PJ_SUCCESS)
return status;
pool = pj_pool_create(mem, THIS_FILE, 512, 512, NULL);
test_srv = (test_server*) PJ_POOL_ZALLOC_T(pool, test_server);
test_srv->pool = pool;
test_srv->flags = flags;
test_srv->stun_cfg = stun_cfg;
pj_strdup2(pool, &test_srv->domain, domain);
test_srv->username = pj_str(TURN_USERNAME);
test_srv->passwd = pj_str(TURN_PASSWD);
pj_ioqueue_op_key_init(&test_srv->send_key, sizeof(test_srv->send_key));
if (flags & CREATE_DNS_SERVER) {
status = pj_dns_server_create(mem, test_srv->stun_cfg->ioqueue,
pj_AF_INET(), DNS_SERVER_PORT,
0, &test_srv->dns_server);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
}
/* Add DNS A record for the domain, for fallback */
if (flags & CREATE_A_RECORD_FOR_DOMAIN) {
pj_dns_parsed_rr rr;
pj_str_t res_name;
pj_in_addr ip_addr;
pj_strdup2(pool, &res_name, domain);
ip_addr = hostip.ipv4.sin_addr;
pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr);
pj_dns_server_add_rec(test_srv->dns_server, 1, &rr);
}
}
if (flags & CREATE_STUN_SERVER) {
pj_activesock_cb stun_sock_cb;
pj_sockaddr bound_addr;
pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb));
stun_sock_cb.on_data_recvfrom = &stun_on_data_recvfrom;
pj_sockaddr_in_init(&bound_addr.ipv4, NULL, STUN_SERVER_PORT);
status = pj_activesock_create_udp(pool, &bound_addr, NULL,
test_srv->stun_cfg->ioqueue,
&stun_sock_cb, test_srv,
&test_srv->stun_sock, NULL);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
}
status = pj_activesock_start_recvfrom(test_srv->stun_sock, pool,
MAX_STUN_PKT, 0);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
}
if (test_srv->dns_server && (flags & CREATE_STUN_SERVER_DNS_SRV)) {
pj_str_t res_name, target;
pj_dns_parsed_rr rr;
pj_in_addr ip_addr;
/* Add DNS entries:
* _stun._udp.domain 60 IN SRV 0 0 PORT stun.domain.
* stun.domain IN A 127.0.0.1
*/
pj_ansi_snprintf(strbuf, sizeof(strbuf),
"_stun._udp.%s", domain);
pj_strdup2(pool, &res_name, strbuf);
pj_ansi_snprintf(strbuf, sizeof(strbuf),
"stun.%s", domain);
pj_strdup2(pool, &target, strbuf);
pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0,
STUN_SERVER_PORT, &target);
pj_dns_server_add_rec(test_srv->dns_server, 1, &rr);
res_name = target;
ip_addr = hostip.ipv4.sin_addr;
pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr);
pj_dns_server_add_rec(test_srv->dns_server, 1, &rr);
}
}
if (flags & CREATE_TURN_SERVER) {
pj_activesock_cb turn_sock_cb;
pj_sockaddr bound_addr;
pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb));
turn_sock_cb.on_data_recvfrom = &turn_on_data_recvfrom;
pj_sockaddr_in_init(&bound_addr.ipv4, NULL, TURN_SERVER_PORT);
status = pj_activesock_create_udp(pool, &bound_addr, NULL,
test_srv->stun_cfg->ioqueue,
&turn_sock_cb, test_srv,
&test_srv->turn_sock, NULL);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
}
status = pj_activesock_start_recvfrom(test_srv->turn_sock, pool,
MAX_STUN_PKT, 0);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
}
if (test_srv->dns_server && (flags & CREATE_TURN_SERVER_DNS_SRV)) {
pj_str_t res_name, target;
pj_dns_parsed_rr rr;
pj_in_addr ip_addr;
/* Add DNS entries:
* _turn._udp.domain 60 IN SRV 0 0 PORT turn.domain.
* turn.domain IN A 127.0.0.1
*/
pj_ansi_snprintf(strbuf, sizeof(strbuf),
"_turn._udp.%s", domain);
pj_strdup2(pool, &res_name, strbuf);
pj_ansi_snprintf(strbuf, sizeof(strbuf),
"turn.%s", domain);
pj_strdup2(pool, &target, strbuf);
pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0,
TURN_SERVER_PORT, &target);
pj_dns_server_add_rec(test_srv->dns_server, 1, &rr);
res_name = target;
ip_addr = hostip.ipv4.sin_addr;
pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr);
pj_dns_server_add_rec(test_srv->dns_server, 1, &rr);
}
}
*p_test_srv = test_srv;
return PJ_SUCCESS;
}
void destroy_test_server(test_server *test_srv)
{
unsigned i;
PJ_ASSERT_ON_FAIL(test_srv, return);
for (i=0; i<test_srv->turn_alloc_cnt; ++i) {
pj_activesock_close(test_srv->turn_alloc[i].sock);
pj_pool_release(test_srv->turn_alloc[i].pool);
}
test_srv->turn_alloc_cnt = 0;
if (test_srv->turn_sock) {
pj_activesock_close(test_srv->turn_sock);
test_srv->turn_sock = NULL;
}
if (test_srv->stun_sock) {
pj_activesock_close(test_srv->stun_sock);
test_srv->stun_sock = NULL;
}
if (test_srv->dns_server) {
pj_dns_server_destroy(test_srv->dns_server);
test_srv->dns_server = NULL;
}
if (test_srv->pool) {
pj_pool_t *pool = test_srv->pool;
test_srv->pool = NULL;
pj_pool_release(pool);
}
}
static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status)
{
test_server *test_srv;
pj_stun_msg *req, *resp = NULL;
pj_pool_t *pool;
pj_ssize_t len;
if (status != PJ_SUCCESS)
return PJ_TRUE;
test_srv = (test_server*) pj_activesock_get_user_data(asock);
pool = pj_pool_create(test_srv->stun_cfg->pf, NULL, 512, 512, NULL);
status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size,
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
&req, NULL, NULL);
if (status != PJ_SUCCESS)
goto on_return;
if (req->hdr.type != PJ_STUN_BINDING_REQUEST) {
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST,
NULL, &resp);
goto send_pkt;
}
status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp);
if (status != PJ_SUCCESS)
goto on_return;
pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_MAPPED_ADDR,
PJ_TRUE, src_addr, addr_len);
send_pkt:
status = pj_stun_msg_encode(resp, (pj_uint8_t*)data, MAX_STUN_PKT,
0, NULL, &size);
if (status != PJ_SUCCESS)
goto on_return;
len = size;
status = pj_activesock_sendto(asock, &test_srv->send_key, data, &len,
0, src_addr, addr_len);
on_return:
pj_pool_release(pool);
return PJ_TRUE;
}
static pj_stun_msg* create_success_response(test_server *test_srv,
turn_allocation *alloc,
pj_stun_msg *req,
pj_pool_t *pool,
unsigned lifetime,
pj_str_t *auth_key)
{
pj_stun_msg *resp;
pj_str_t tmp;
pj_status_t status;
/* Create response */
status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp);
if (status != PJ_SUCCESS) {
return NULL;
}
/* Add TURN_NONCE */
pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, TURN_NONCE));
/* Add LIFETIME */
pj_stun_msg_add_uint_attr(pool, resp, PJ_STUN_ATTR_LIFETIME, lifetime);
if (lifetime != 0) {
/* Add RELAY-ADDRESS */
pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_RELAY_ADDR, PJ_TRUE, &alloc->alloc_addr,
pj_sockaddr_get_len(&alloc->alloc_addr));
/* Add XOR-MAPPED-ADDRESS */
pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, &alloc->client_addr,
pj_sockaddr_get_len(&alloc->client_addr));
}
/* Add blank MESSAGE-INTEGRITY */
pj_stun_msg_add_msgint_attr(pool, resp);
/* Set auth key */
pj_stun_create_key(pool, auth_key, &test_srv->domain, &test_srv->username,
PJ_STUN_PASSWD_PLAIN, &test_srv->passwd);
return resp;
}
static pj_bool_t turn_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status)
{
test_server *test_srv;
pj_pool_t *pool;
turn_allocation *alloc;
pj_stun_msg *req, *resp = NULL;
pj_str_t auth_key = { NULL, 0 };
char client_info[PJ_INET6_ADDRSTRLEN+10];
unsigned i;
pj_ssize_t len;
if (status != PJ_SUCCESS)
return PJ_TRUE;
pj_sockaddr_print(src_addr, client_info, sizeof(client_info), 3);
test_srv = (test_server*) pj_activesock_get_user_data(asock);
pool = pj_pool_create(test_srv->stun_cfg->pf, NULL, 512, 512, NULL);
status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size,
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET |
PJ_STUN_NO_FINGERPRINT_CHECK,
&req, NULL, NULL);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
PJ_LOG(1,("", "STUN message decode error from client %s: %s", client_info, errmsg));
goto on_return;
}
/* Find the client */
for (i=0; i<test_srv->turn_alloc_cnt; i++) {
if (pj_sockaddr_cmp(&test_srv->turn_alloc[i].client_addr, src_addr)==0)
break;
}
if (i==test_srv->turn_alloc_cnt) {
/* New client */
//pj_str_t ip_addr;
pj_stun_username_attr *uname;
pj_activesock_cb alloc_sock_cb;
turn_allocation *alloc;
/* Must be Allocate request */
if (req->hdr.type != PJ_STUN_ALLOCATE_REQUEST) {
PJ_LOG(1,(THIS_FILE, "Invalid %s %s from client %s",
pj_stun_get_method_name(req->hdr.type),
pj_stun_get_class_name(req->hdr.type),
client_info));
if (PJ_STUN_IS_REQUEST(req->hdr.type))
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, NULL, &resp);
goto send_pkt;
}
test_srv->turn_stat.rx_allocate_cnt++;
/* Skip if we're not responding to Allocate request */
if (!test_srv->turn_respond_allocate)
return PJ_TRUE;
/* Check if we have too many clients */
if (test_srv->turn_alloc_cnt == MAX_TURN_ALLOC) {
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_INSUFFICIENT_CAPACITY, NULL, &resp);
goto send_pkt;
}
/* Get USERNAME attribute */
uname = (pj_stun_username_attr*)
pj_stun_msg_find_attr(req, PJ_STUN_ATTR_USERNAME, 0);
/* Reject if it doesn't have MESSAGE-INTEGRITY or USERNAME attributes or
* the user is incorrect
*/
if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0) == NULL ||
uname==NULL || pj_stricmp2(&uname->value, TURN_USERNAME) != 0)
{
pj_str_t tmp;
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_UNAUTHORIZED, NULL, &resp);
pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_REALM, &test_srv->domain);
pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, TURN_NONCE));
goto send_pkt;
}
pj_bzero(&alloc_sock_cb, sizeof(alloc_sock_cb));
alloc_sock_cb.on_data_recvfrom = &alloc_on_data_recvfrom;
/* Create allocation */
alloc = &test_srv->turn_alloc[test_srv->turn_alloc_cnt];
alloc->perm_cnt = 0;
alloc->test_srv = test_srv;
pj_memcpy(&alloc->client_addr, src_addr, addr_len);
pj_ioqueue_op_key_init(&alloc->send_key, sizeof(alloc->send_key));
alloc->pool = pj_pool_create(test_srv->stun_cfg->pf, "alloc", 512, 512, NULL);
/* Create relay socket */
pj_sockaddr_in_init(&alloc->alloc_addr.ipv4, NULL, 0);
pj_gethostip(pj_AF_INET(), &alloc->alloc_addr);
status = pj_activesock_create_udp(alloc->pool, &alloc->alloc_addr, NULL,
test_srv->stun_cfg->ioqueue,
&alloc_sock_cb, alloc,
&alloc->sock, &alloc->alloc_addr);
if (status != PJ_SUCCESS) {
pj_pool_release(alloc->pool);
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp);
goto send_pkt;
}
//pj_sockaddr_set_str_addr(pj_AF_INET(), &alloc->alloc_addr, &ip_addr);
pj_activesock_set_user_data(alloc->sock, alloc);
status = pj_activesock_start_recvfrom(alloc->sock, alloc->pool, 1500, 0);
if (status != PJ_SUCCESS) {
pj_activesock_close(alloc->sock);
pj_pool_release(alloc->pool);
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp);
goto send_pkt;
}
/* Create Data indication */
status = pj_stun_msg_create(alloc->pool, PJ_STUN_DATA_INDICATION,
PJ_STUN_MAGIC, NULL, &alloc->data_ind);
if (status != PJ_SUCCESS) {
pj_activesock_close(alloc->sock);
pj_pool_release(alloc->pool);
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp);
goto send_pkt;
}
pj_stun_msg_add_sockaddr_attr(alloc->pool, alloc->data_ind,
PJ_STUN_ATTR_PEER_ADDR, PJ_TRUE,
&alloc->alloc_addr,
pj_sockaddr_get_len(&alloc->alloc_addr));
pj_stun_msg_add_binary_attr(alloc->pool, alloc->data_ind,
PJ_STUN_ATTR_DATA, (pj_uint8_t*)"", 1);
/* Create response */
resp = create_success_response(test_srv, alloc, req, pool, 600, &auth_key);
if (resp == NULL) {
pj_activesock_close(alloc->sock);
pj_pool_release(alloc->pool);
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp);
goto send_pkt;
}
++test_srv->turn_alloc_cnt;
} else {
alloc = &test_srv->turn_alloc[i];
if (req->hdr.type == PJ_STUN_ALLOCATE_REQUEST) {
test_srv->turn_stat.rx_allocate_cnt++;
/* Skip if we're not responding to Allocate request */
if (!test_srv->turn_respond_allocate)
return PJ_TRUE;
resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key);
} else if (req->hdr.type == PJ_STUN_REFRESH_REQUEST) {
pj_stun_lifetime_attr *lf_attr;
test_srv->turn_stat.rx_refresh_cnt++;
/* Skip if we're not responding to Refresh request */
if (!test_srv->turn_respond_refresh)
return PJ_TRUE;
lf_attr = (pj_stun_lifetime_attr*)
pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0);
if (lf_attr && lf_attr->value != 0) {
resp = create_success_response(test_srv, alloc, req, pool, 600, &auth_key);
pj_array_erase(test_srv->turn_alloc, sizeof(test_srv->turn_alloc[0]),
test_srv->turn_alloc_cnt, i);
--test_srv->turn_alloc_cnt;
} else
resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key);
} else if (req->hdr.type == PJ_STUN_SEND_INDICATION) {
pj_stun_peer_addr_attr *pa;
pj_stun_data_attr *da;
test_srv->turn_stat.rx_send_ind_cnt++;
pa = (pj_stun_peer_addr_attr*)
pj_stun_msg_find_attr(req, PJ_STUN_ATTR_PEER_ADDR, 0);
da = (pj_stun_data_attr*)
pj_stun_msg_find_attr(req, PJ_STUN_ATTR_DATA, 0);
if (pa && da) {
unsigned j;
char peer_info[PJ_INET6_ADDRSTRLEN];
pj_ssize_t sent;
pj_sockaddr_print(&pa->sockaddr, peer_info, sizeof(peer_info), 3);
for (j=0; j<alloc->perm_cnt; ++j) {
if (pj_sockaddr_cmp(&alloc->perm[j], &pa->sockaddr)==0)
break;
}
if (j==alloc->perm_cnt && alloc->perm_cnt < MAX_TURN_PERM) {
pj_sockaddr_cp(&alloc->perm[alloc->perm_cnt], &pa->sockaddr);
++alloc->perm_cnt;
PJ_LOG(5,("", "Permission %s added to client %s, perm_cnt=%d",
peer_info, client_info, alloc->perm_cnt));
}
PJ_LOG(5,(THIS_FILE, "Relaying %d bytes data from client %s to peer %s, "
"perm_cnt=%d",
da->length, client_info, peer_info, alloc->perm_cnt));
sent = da->length;
pj_activesock_sendto(alloc->sock, &alloc->send_key,
da->data, &sent, 0,
&pa->sockaddr,
pj_sockaddr_get_len(&pa->sockaddr));
} else {
PJ_LOG(1,(THIS_FILE, "Invalid Send Indication from %s", client_info));
}
} else if (PJ_STUN_IS_REQUEST(req->hdr.type)) {
pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, NULL, &resp);
}
}
send_pkt:
if (resp) {
status = pj_stun_msg_encode(resp, (pj_uint8_t*)data, MAX_STUN_PKT,
0, &auth_key, &size);
if (status != PJ_SUCCESS)
goto on_return;
len = size;
status = pj_activesock_sendto(asock, &test_srv->send_key, data, &len,
0, src_addr, addr_len);
}
on_return:
pj_pool_release(pool);
return PJ_TRUE;
}
/* On received data from peer */
static pj_bool_t alloc_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status)
{
turn_allocation *alloc;
pj_stun_peer_addr_attr *pa;
pj_stun_data_attr *da;
char peer_info[PJ_INET6_ADDRSTRLEN+10];
char client_info[PJ_INET6_ADDRSTRLEN+10];
pj_uint8_t buffer[1500];
pj_ssize_t sent;
unsigned i;
if (status != PJ_SUCCESS)
return PJ_TRUE;
alloc = (turn_allocation*) pj_activesock_get_user_data(asock);
pj_sockaddr_print(&alloc->client_addr, client_info, sizeof(client_info), 3);
pj_sockaddr_print(src_addr, peer_info, sizeof(peer_info), 3);
/* Check that this peer has a permission */
for (i=0; i<alloc->perm_cnt; ++i) {
if (pj_sockaddr_get_len(&alloc->perm[i]) == (unsigned)addr_len &&
pj_memcmp(pj_sockaddr_get_addr(&alloc->perm[i]),
pj_sockaddr_get_addr(src_addr),
addr_len) == 0)
{
break;
}
}
if (i==alloc->perm_cnt) {
PJ_LOG(5,("", "Client %s received %d bytes unauthorized data from peer %s",
client_info, size, peer_info));
if (alloc->perm_cnt == 0)
PJ_LOG(5,("", "Client %s has no permission", client_info));
return PJ_TRUE;
}
/* Format a Data indication */
pa = (pj_stun_peer_addr_attr*)
pj_stun_msg_find_attr(alloc->data_ind, PJ_STUN_ATTR_PEER_ADDR, 0);
da = (pj_stun_data_attr*)
pj_stun_msg_find_attr(alloc->data_ind, PJ_STUN_ATTR_DATA, 0);
pj_assert(pa && da);
pj_sockaddr_cp(&pa->sockaddr, src_addr);
da->data = data;
da->length = size;
/* Encode Data indication */
status = pj_stun_msg_encode(alloc->data_ind, buffer, sizeof(buffer), 0,
NULL, &size);
if (status != PJ_SUCCESS)
return PJ_TRUE;
/* Send */
sent = size;
PJ_LOG(5,("", "Forwarding %d bytes data from peer %s to client %s",
sent, peer_info, client_info));
pj_activesock_sendto(alloc->test_srv->turn_sock, &alloc->send_key, buffer,
&sent, 0, &alloc->client_addr,
pj_sockaddr_get_len(&alloc->client_addr));
return PJ_TRUE;
}