571 lines
17 KiB
C
571 lines
17 KiB
C
/*
|
|
* Copyright (C) 2009-2010 Mamadou Diop.
|
|
*
|
|
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
|
|
*
|
|
* This file is part of Open Source Doubango Framework.
|
|
*
|
|
* DOUBANGO 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* DOUBANGO 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 DOUBANGO.
|
|
*
|
|
*/
|
|
|
|
/**@file tnet_nat.c
|
|
* @brief NAT Traversal helper functions using STUN, TURN and ICE.
|
|
*
|
|
* @author Mamadou Diop <diopmamadou(at)doubango.org>
|
|
*
|
|
* @date Created: Sat Nov 8 16:54:58 2009 mdiop
|
|
*/
|
|
#include "tnet_nat.h"
|
|
|
|
#include "tnet_endianness.h"
|
|
|
|
#include "tsk_string.h"
|
|
#include "tsk_memory.h"
|
|
|
|
#include "tsk_debug.h"
|
|
|
|
/**@defgroup tnet_nat_group NAT Traversal API (STUN, TURN and ICE).
|
|
*/
|
|
|
|
|
|
/**@ingroup tnet_nat_group
|
|
* Creates new NAT context.
|
|
*/
|
|
tnet_nat_context_handle_t* tnet_nat_context_create(tnet_socket_type_t socket_type, const char* username, const char* password)
|
|
{
|
|
return tsk_object_new(tnet_nat_context_def_t, socket_type, username, password);
|
|
}
|
|
|
|
/**
|
|
* Predicate function to find turn allocation by id.
|
|
*
|
|
* @param [in,out] item The current list item.
|
|
* @param [in,out] id A pointer to the allocation identifier.
|
|
*
|
|
* @return Zero if current list item hold an allocation with the same id and -1 otherwise.
|
|
**/
|
|
int __pred_find_turn_allocation(const tsk_list_item_t* item, const void* id)
|
|
{
|
|
if(item)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
if(allocation)
|
|
{
|
|
tnet_turn_allocation_id_t alloc_id = *((tnet_turn_allocation_id_t*)id);
|
|
return (allocation->id == alloc_id) ? 0 : -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Predicate function to find stun binding by id.
|
|
*
|
|
* @param [in,out] item The current list item.
|
|
* @param [in,out] id A pointer to the binding identifier.
|
|
*
|
|
* @return Zero if current list item hold a binding with the same id and -1 otherwise.
|
|
**/
|
|
int __pred_find_stun_binding(const tsk_list_item_t* item, const void* id)
|
|
{
|
|
if(item)
|
|
{
|
|
tnet_stun_binding_t *binding = item->data;
|
|
if(binding)
|
|
{
|
|
tnet_stun_binding_id_t binding_id = *((tnet_stun_binding_id_t*)id);
|
|
return (binding->id == binding_id) ? 0 : -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Predicate function to find TURN channel binding by id.
|
|
*
|
|
* @param [in,out] item The current list item.
|
|
* @param [in,out] id A pointer to the TURN channel binding identifier.
|
|
*
|
|
* @return Zero if current list item hold a TURN channel binding with the same id and -1 otherwise.
|
|
**/
|
|
int __pred_find_turn_channel_binding(const tsk_list_item_t* item, const void* id)
|
|
{
|
|
if(item)
|
|
{
|
|
tnet_turn_channel_binding_t *binding = item->data;
|
|
if(binding)
|
|
{
|
|
tnet_turn_channel_binding_id_t binding_id = *((tnet_turn_channel_binding_id_t*)id);
|
|
return (binding->id == binding_id) ? 0 : -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Formats binary IP address as string.
|
|
*
|
|
* @param in_ip The binary IP address to format (in Host byte order).
|
|
* @param family The address family.
|
|
* @param [in,out] out_ip The output string
|
|
*
|
|
* @return Zero if current list item hold a binding with the same id and -1 otherwise.
|
|
**/
|
|
int tnet_stun_address_tostring(const uint8_t in_ip[16], tnet_stun_addr_family_t family, char** out_ip)
|
|
{
|
|
/*if(family == stun_ipv6){
|
|
tsk_sprintf(out_ip, "%x:%x:%x:%x:%x:%x:%x:%x",
|
|
tnet_ntohs_2(&in_ip[0]), tnet_ntohs_2(&in_ip[2]), tnet_ntohs_2(&in_ip[4]), tnet_ntohs_2(&in_ip[6]),
|
|
tnet_ntohs_2(&in_ip[8]), tnet_ntohs_2(&in_ip[10]), tnet_ntohs_2(&in_ip[12]), tnet_ntohs_2(&in_ip[14]));
|
|
}
|
|
else if(family == stun_ipv4){
|
|
tsk_sprintf(out_ip, "%u.%u.%u.%u", (address>>24)&0xFF, (address>>16)&0xFF, (address>>8)&0xFF, (address>>0)&0xFF);
|
|
|
|
return 0;
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Unsupported address family: %u.", family);
|
|
}*/
|
|
if(family == stun_ipv6){
|
|
tsk_sprintf(out_ip, "%x:%x:%x:%x:%x:%x:%x:%x",
|
|
TSK_TO_UINT16(&in_ip[0]), TSK_TO_UINT16(&in_ip[2]), TSK_TO_UINT16(&in_ip[4]), TSK_TO_UINT16(&in_ip[6]),
|
|
TSK_TO_UINT16(&in_ip[8]), TSK_TO_UINT16(&in_ip[10]), TSK_TO_UINT16(&in_ip[12]), TSK_TO_UINT16(&in_ip[14]));
|
|
}
|
|
else if(family == stun_ipv4){
|
|
tsk_sprintf(out_ip, "%u.%u.%u.%u", in_ip[0], in_ip[1], in_ip[2], in_ip[3]);
|
|
|
|
return 0;
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Unsupported address family: %u.", family);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Sets the address of the STUN/TURN server.
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param [in,out] server_address The address of server.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
**/
|
|
int tnet_nat_set_server_address(tnet_nat_context_handle_t* self, const char* server_address)
|
|
{
|
|
tnet_nat_context_t* context = self;
|
|
|
|
if(context){
|
|
tsk_strupdate(&(context->server_address), server_address);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Sets the address and port of the STUN/TURN server.
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param [in,out] server_address The address of server.
|
|
* @param server_port The server port.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
**/
|
|
int tnet_nat_set_server(tnet_nat_context_handle_t* self, const char* server_address, tnet_port_t server_port)
|
|
{
|
|
tnet_nat_context_t* context = self;
|
|
|
|
if(context){
|
|
tsk_strupdate(&(context->server_address), server_address);
|
|
context->server_port = server_port;
|
|
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Creates and sends a STUN2 binding request to the STUN/TURN server in order to get the server reflexive
|
|
* address associated to this file descriptor (or socket). The caller should call @ref tnet_nat_stun_unbind to destroy the binding.
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param localFD The local file descriptor (or socket) for which to get the reflexive server address.
|
|
*
|
|
* @return A valid binding id if succeed and @ref TNET_STUN_INVALID_BINDING_ID otherwise. If the returned id is valid then
|
|
* the newly created binding will contain the server-reflexive address associated to the local file descriptor.
|
|
*
|
|
* @sa @ref tnet_nat_stun_unbind.
|
|
**/
|
|
tnet_stun_binding_id_t tnet_nat_stun_bind(const tnet_nat_context_handle_t* self, const tnet_fd_t localFD)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
if(context){
|
|
return tnet_stun_bind(context, localFD);
|
|
}
|
|
return TNET_STUN_INVALID_BINDING_ID;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
* Gets the server reflexive address associated to this STUN2 binding.
|
|
*
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param id The id of the STUN2 binding conetxt (obtained using @ref tnet_nat_stun_bind) holding the server-reflexive address.
|
|
* @param [in,out] ipaddress The reflexive IP address. It is up the the caller to free the returned string
|
|
* @param [in,out] port The reflexive port.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
**/
|
|
int tnet_nat_stun_get_reflexive_address(const tnet_nat_context_handle_t* self, tnet_stun_binding_id_t id, char** ipaddress, tnet_port_t *port)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
if(context){
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->stun_bindings, __pred_find_stun_binding, &id);
|
|
if(item && item->data){
|
|
tnet_stun_binding_t *binding = item->data;
|
|
/*STUN2: XOR-MAPPED-ADDRESS */
|
|
if(binding->xmaddr){
|
|
int ret = 0;
|
|
if(ipaddress){
|
|
ret = tnet_stun_address_tostring(binding->xmaddr->xaddress, binding->xmaddr->family, ipaddress);
|
|
}
|
|
if(port){
|
|
*port = /*tnet_ntohs*/(binding->xmaddr->xport);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*STUN1: MAPPED-ADDRESS*/
|
|
if(binding->maddr){
|
|
int ret = 0;
|
|
if(ipaddress){
|
|
ret = tnet_stun_address_tostring(binding->maddr->address, binding->maddr->family, ipaddress);
|
|
}
|
|
if(port){
|
|
*port = /*tnet_ntohs*/(binding->maddr->port);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Removes a STUN2 binding from the NAT context.
|
|
*
|
|
* @param [in,out] self The NAT context from which to remove the STUN2 binding.
|
|
* @param id The id of the STUN2 binding to remove.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
*
|
|
*
|
|
* @sa @ref tnet_nat_stun_bind.
|
|
**/
|
|
int tnet_nat_stun_unbind(const tnet_nat_context_handle_t* self, tnet_stun_binding_id_t id)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
if(context)
|
|
{
|
|
tsk_list_remove_item_by_pred(context->stun_bindings, __pred_find_stun_binding, &id);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Creates TURN allocation as per draft-ietf-behave-turn-16 subclause 6. This function will also
|
|
* send an allocation request to the server (subclause 6.1).
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param localFD The local file descriptor.
|
|
*
|
|
* @return A valid TURN allocation id if succeed and @ref TNET_TURN_INVALID_ALLOCATION_ID otherwise.
|
|
*
|
|
* @sa @ref tnet_nat_turn_unallocate.
|
|
**/
|
|
tnet_turn_allocation_id_t tnet_nat_turn_allocate(const tnet_nat_context_handle_t* self, const tnet_fd_t localFD)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(self)
|
|
{
|
|
return tnet_turn_allocate(self, localFD, context->socket_type);
|
|
}
|
|
|
|
return TNET_TURN_INVALID_ALLOCATION_ID;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
* Gets the STUN server-refelexive IP address and port associated to this TURN allocation.
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param id The id of the TURN allocation for which to to get server-reflexive IP address and port.
|
|
* @param [in,out] ipaddress The server-reflexive IP address.
|
|
* @param [in,out] port The server-reflexive port.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
**/
|
|
int tnet_nat_turn_get_reflexive_address(const tnet_nat_context_handle_t* self, tnet_turn_allocation_id_t id, char** ipaddress, tnet_port_t *port)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
if(context)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->allocations, __pred_find_turn_allocation, &id);
|
|
if(item && item->data)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
/*STUN2: XOR-MAPPED-ADDRESS */
|
|
if(allocation->xmaddr)
|
|
{
|
|
int ret = tnet_stun_address_tostring(allocation->xmaddr->xaddress, allocation->xmaddr->family, ipaddress);
|
|
*port = /*tnet_ntohs*/(allocation->xmaddr->xport);
|
|
return ret;
|
|
}
|
|
|
|
/*STUN1: MAPPED-ADDRESS*/
|
|
if(allocation->maddr)
|
|
{
|
|
int ret = tnet_stun_address_tostring(allocation->maddr->address, allocation->maddr->family, ipaddress);
|
|
*port = /*tnet_ntohs*/(allocation->maddr->port);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Refresh a TURN allocation previously created using @ref tnet_nat_turn_allocate.
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param id The id of the TURN allocation to refresh.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
**/
|
|
int tnet_nat_turn_allocation_refresh(const tnet_nat_context_handle_t* self, tnet_turn_allocation_id_t id)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(context)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->allocations, __pred_find_turn_allocation, &id);
|
|
if(item && item->data)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
return tnet_turn_allocation_refresh(self, allocation);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*
|
|
* Unallocate/remove a TURN allocation from the server.
|
|
*
|
|
* @param [in,out] self The NAT context from which to remove the allocation.
|
|
* @param id The id of the TURN allocation to remove.
|
|
*
|
|
* @return Zero if succeed and non zero error code otherwise.
|
|
*
|
|
* @sa @ref tnet_nat_turn_allocate.
|
|
**/
|
|
int tnet_nat_turn_unallocate(const tnet_nat_context_handle_t* self, tnet_turn_allocation_id_t id)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(context)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->allocations, __pred_find_turn_allocation, &id);
|
|
if(item && item->data)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
return tnet_turn_unallocate(self, allocation);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
* Creates TURN channel binding as per draft-ietf-behave-turn-16 sublause 11 and send it to the
|
|
* server as per subclause 11.1.
|
|
*
|
|
*
|
|
* @param [in,out] self The NAT context.
|
|
* @param id The id of the TURN allocation associated to this binding.
|
|
* @param [in,out] peer The XOR remote peer for the channel binding.
|
|
*
|
|
* @return A valid TURN channel binding id if succeed and @ref TNET_TURN_INVALID_CHANNEL_BINDING_ID otherwise.
|
|
**/
|
|
tnet_turn_channel_binding_id_t tnet_nat_turn_channel_bind(const tnet_nat_context_handle_t* self, tnet_turn_allocation_id_t id, struct sockaddr_storage *peer)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(context)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->allocations, __pred_find_turn_allocation, &id);
|
|
if(item && item->data)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
return tnet_turn_channel_bind(self, allocation, peer);
|
|
}
|
|
}
|
|
return TNET_TURN_INVALID_CHANNEL_BINDING_ID;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*/
|
|
int tnet_nat_turn_channel_refresh(const tnet_nat_context_handle_t* self, tnet_turn_channel_binding_id_t id)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(context)
|
|
{
|
|
tsk_list_item_t* curr;
|
|
tsk_list_foreach(curr, context->allocations)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(((tnet_turn_allocation_t *)curr->data)->channel_bindings, __pred_find_turn_channel_binding, &id);
|
|
if(item && item->data)
|
|
{
|
|
return tnet_turn_channel_refresh(context, (tnet_turn_channel_binding_t *)item->data);
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*/
|
|
int tnet_nat_turn_channel_send(const tnet_nat_context_handle_t* self, tnet_turn_channel_binding_id_t id, const void* data, tsk_size_t size, int indication)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(context && data && size)
|
|
{
|
|
tsk_list_item_t* curr;
|
|
tsk_list_foreach(curr, context->allocations)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(((tnet_turn_allocation_t *)curr->data)->channel_bindings, __pred_find_turn_channel_binding, &id);
|
|
if(item && item->data)
|
|
{
|
|
return tnet_turn_channel_senddata(context, (tnet_turn_channel_binding_t *)item->data, data, size, indication);
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**@ingroup tnet_nat_group
|
|
*/
|
|
int tnet_nat_turn_add_permission(const tnet_nat_context_handle_t* self, tnet_turn_allocation_id_t id, const char* ipaddress, uint32_t timeout)
|
|
{
|
|
const tnet_nat_context_t* context = self;
|
|
|
|
if(self)
|
|
{
|
|
const tsk_list_item_t* item = tsk_list_find_item_by_pred(context->allocations, __pred_find_turn_allocation, &id);
|
|
if(item && item->data)
|
|
{
|
|
tnet_turn_allocation_t *allocation = item->data;
|
|
return tnet_turn_add_permission(self, allocation, ipaddress, timeout);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
//=================================================================================================
|
|
// NAT CONTEXT object definition
|
|
//
|
|
static tsk_object_t* tnet_nat_context_ctor(tsk_object_t * self, va_list * app)
|
|
{
|
|
tnet_nat_context_t *context = self;
|
|
if(context){
|
|
context->socket_type = va_arg(*app, tnet_socket_type_t);
|
|
|
|
context->username = tsk_strdup(va_arg(*app, const char*));
|
|
context->password = tsk_strdup(va_arg(*app, const char*));
|
|
|
|
context->server_port = TNET_NAT_TCP_UDP_DEFAULT_PORT;
|
|
|
|
/* 7.2.1. Sending over UDP
|
|
In fixed-line access links, a value of 500 ms is RECOMMENDED.
|
|
*/
|
|
context->RTO = TNET_NAT_DEFAULT_RTO;
|
|
|
|
/* 7.2.1. Sending over UDP
|
|
Rc SHOULD be configurable and SHOULD have a default of 7.
|
|
*/
|
|
context->Rc = TNET_NAT_DEFAULT_RC;
|
|
|
|
context->software = tsk_strdup(TNET_SOFTWARE);
|
|
//context->enable_evenport = 1;
|
|
//context->enable_fingerprint = 1;
|
|
context->enable_integrity = 0;
|
|
context->enable_dontfrag = 0;//TNET_SOCKET_TYPE_IS_DGRAM(context->socket_type) ? 1 : 0;
|
|
|
|
context->allocations = tsk_list_create();
|
|
context->stun_bindings = tsk_list_create();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static tsk_object_t* tnet_nat_context_dtor(tsk_object_t * self)
|
|
{
|
|
tnet_nat_context_t *context = self;
|
|
if(context){
|
|
TSK_FREE(context->username);
|
|
TSK_FREE(context->password);
|
|
TSK_FREE(context->software);
|
|
TSK_FREE(context->server_address);
|
|
|
|
TSK_OBJECT_SAFE_FREE(context->allocations);
|
|
TSK_OBJECT_SAFE_FREE(context->stun_bindings);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
static const tsk_object_def_t tnet_nat_context_def_s =
|
|
{
|
|
sizeof(tnet_nat_context_t),
|
|
tnet_nat_context_ctor,
|
|
tnet_nat_context_dtor,
|
|
tsk_null,
|
|
};
|
|
const tsk_object_def_t *tnet_nat_context_def_t = &tnet_nat_context_def_s;
|