/* * Copyright (C) 2009-2010 Mamadou Diop. * * Contact: Mamadou Diop * * 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 * * @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;