doubango/tinyNET/src/tnet_nat.c
c732d49e
 #if HAVE_CRT
 #define _CRTDBG_MAP_ALLOC 
 #include <stdlib.h> 
 #include <crtdbg.h>
 #endif //HAVE_CRT
 /*
 * Copyright (C) 2010-2011 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.
  *
  */
 #include "tnet_nat.h"
 #include "stun/tnet_stun_types.h"
 #include "stun/tnet_stun_utils.h"
 #include "stun/tnet_stun_pkt.h"
 #include "stun/tnet_stun_attr.h"
 #include "stun/tnet_stun_binding.h"
 
 #include "tnet_endianness.h"
 #include "tnet_socket.h"
 
 #include "tsk_string.h"
 #include "tsk_memory.h"
 
 #include "tsk_debug.h"
 
 /**@defgroup tnet_nat_group NAT Traversal API (STUN, TURN and ICE).
 */
 
 typedef struct tnet_nat_ctx_s
 {
 	TSK_DECLARE_OBJECT;
 
 	tnet_socket_type_t socket_type;
 
 	char* username; /**< The username to use to authenticate against the TURN/STUN server. */
 	char* password;	/**< The password to use to authenticate against the TURN/STUN server. */
 
 	char* server_address; /**< TURN/STUN server address (could be FQDN or IP) */
 	tnet_port_t server_port; /**< TURN/STUN server port. */
 
 	uint16_t RTO; /**< Estimate of the round-trip time (RTT) in millisecond. */
 	uint16_t Rc; /**< Number of retransmissions for UDP in millisecond. */
 
 	unsigned use_dnsquery:1; /**< Indicates whether to use DNS SRV query to find the stun/turn ip address. */
 	
 	tnet_stun_bindings_L_t *stun_bindings; /**< List of all STUN2 bindings associated to this context. */
 }
 tnet_nat_ctx_t;
 
 
 /**@ingroup tnet_nat_group
 * Creates new NAT context.
 */
 struct tnet_nat_ctx_s* tnet_nat_context_create(tnet_socket_type_t socket_type, const char* pc_username, const char* pc_password)
 {
 	extern const tsk_object_def_t *tnet_nat_context_def_t;
 	struct tnet_nat_ctx_s* p_ctx;
 
 	if (!(p_ctx = tsk_object_new(tnet_nat_context_def_t)) || !(p_ctx->stun_bindings = tsk_list_create())) {
 		TSK_OBJECT_SAFE_FREE(p_ctx);
 		TSK_DEBUG_ERROR("Failed to create NAT context");
 		return tsk_null;
 	}
 
 	p_ctx->socket_type = socket_type;
 	p_ctx->username = tsk_strdup(pc_username);
 	p_ctx->password = tsk_strdup(pc_password);
 	p_ctx->server_port = kStunPortDefaultTcpUdp;
 	/*	7.2.1.  Sending over UDP
 		In fixed-line access links, a value of 500 ms is RECOMMENDED.
 	*/
 	p_ctx->RTO = kStunRTO;
 	/*	7.2.1.  Sending over UDP
 		Rc SHOULD be configurable and SHOULD have a default of 7.
 	*/
 	p_ctx->Rc = kStunRC;
 
 	return p_ctx;
 }
 
 /** 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 *p_bind = item->data;
 		if (p_bind) {
 			tnet_stun_binding_id_t binding_id = *((tnet_stun_binding_id_t*)id);
 			return (p_bind->id == binding_id) ? 0 : -1;
 		}
 	}
 	return -1;
 }
 
 /**@ingroup tnet_nat_group
  *
  * Sets the address of the STUN/TURN server.
  *
  * @param [in,out]	p_self			The NAT context. 
  * @param [in,out]	pc_server_address	The address of server. 
  *
  * @return	Zero if succeed and non zero error code otherwise. 
 **/
 int tnet_nat_set_server_address(struct tnet_nat_ctx_s* p_self, const char* pc_server_address)
 {
 	if (!p_self) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 	tsk_strupdate(&(p_self->server_address), pc_server_address);
 	return 0;
 }
 
 /**@ingroup tnet_nat_group
  *
  * Sets the address and port of the STUN/TURN server.
  *
  * @param [in,out]	p_self			The NAT context. 
  * @param [in,out]	pc_server_address	The address of server. 
  * @param	u_server_port				The server port. 
  *
  * @return	Zero if succeed and non zero error code otherwise.  
 **/
 int tnet_nat_set_server(struct tnet_nat_ctx_s* p_self, const char* pc_server_address,  tnet_port_t u_server_port)
 {
 	if (!p_self) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 	tsk_strupdate(&(p_self->server_address), pc_server_address);
 	p_self->server_port = u_server_port;
 	return 0;	
 }
 
 /**@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]	p_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 kStunBindingInvalidId 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 struct tnet_nat_ctx_s* p_self, const tnet_fd_t localFD)
 {
 	tnet_stun_binding_id_t id = kStunBindingInvalidId;
 	tnet_stun_binding_t *p_binding = tsk_null;
 	int ret;
 	if (!p_self || localFD == TNET_INVALID_FD) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		goto bail;
 	}
     if ((ret = tnet_stun_binding_create(localFD, p_self->socket_type, p_self->server_address, p_self->server_port, p_self->username, p_self->password, &p_binding))) {
         goto bail;
     }
     if ((ret = tnet_nat_stun_send_bind(p_self, p_binding))) {
         goto bail;
     }
     id = p_binding->id;
     tsk_list_push_back_data(p_self->stun_bindings, (void**)&p_binding);
 
 bail:
 	TSK_OBJECT_SAFE_FREE(p_binding);
     return id;
 }
 
 
 /**@ingroup tnet_nat_group
  * 	Internal function to send a STUN2 binding request over the network.
  *
  * @param [in,out]	p_self	The NAT context holding the user preferences.
  * @param [in,out]	p_binding	The STUN binding object used to create the message to send.
  *
  * @return	Zero if succeed and non-zero error code otherwise.
 **/
 int tnet_nat_stun_send_bind(const struct tnet_nat_ctx_s* pc_self, struct tnet_stun_binding_s *p_binding)
 {
 	int ret = -1;
     tnet_stun_pkt_resp_t *p_pkt_resp = tsk_null;
     tnet_stun_pkt_req_t *p_pkt_req = tsk_null;
 
 	if (!pc_self || !p_binding) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	if (!TNET_SOCKET_TYPE_IS_DGRAM(p_binding->socket_type)) {
 		TSK_DEBUG_ERROR("Only DGRAM could be used for STUN transport");
 		return -2;
 	}
 
     if ((ret = tnet_stun_binding_create_req(p_binding, &p_pkt_req))) {
         goto bail;
     }
 
     /*	RFC 5389 - 10.2.1.1.  First Request
     	If the client has not completed a successful request/response
     	transaction with the server (as identified by hostname, if the DNS
     	procedures of Section 9 are used, else IP address if not), it SHOULD
     	omit the USERNAME, MESSAGE-INTEGRITY, REALM, and NONCE attributes.
     	In other words, the very first request is sent as if there were no
     	authentication or message integrity applied.
     */
 stun_phase0: {
 		if ((ret = tnet_stun_utils_send_unreliably(p_binding->localFD, pc_self->RTO, pc_self->Rc, p_pkt_req, (struct sockaddr*)&p_binding->addr_server, &p_pkt_resp))) {
 			goto bail;
 		}
 
         if (p_pkt_resp) {
             if (TNET_STUN_PKT_RESP_IS_ERROR(p_pkt_resp)) {
 				uint16_t u_code;
 				if ((ret = tnet_stun_pkt_get_errorcode(p_pkt_resp, &u_code))) {
 					goto bail;
 				}
 				if (u_code == kStunErrCodeUnauthorized || u_code == kStunErrCodeStaleNonce) {
 					if (u_code == kStunErrCodeUnauthorized) {
 						// Make sure this is not an authentication failure (#2 401)
 						// Do not send another req to avoid endless messages
 						if ((tnet_stun_pkt_attr_exists(p_pkt_req, tnet_stun_attr_type_message_integrity))) { // already has a MESSAGE-INTEGRITY?
 							TSK_DEBUG_ERROR("STUN authentication failed");
 							goto bail;
 						}
 					}
 					if ((ret = tnet_stun_pkt_auth_prepare_2(p_pkt_req, p_binding->p_username, p_binding->p_password, p_pkt_resp))) {
 						goto bail;
 					}
 					// Try to send again now that authinfo is up2date
 					goto stun_phase0;
 				}
 				else if (u_code == kStunErrCodeUnknownAttributes) {
 					if((ret = tnet_stun_pkt_process_err420(p_pkt_req, p_pkt_resp))) {
 						goto bail;
 					}
 					// Try to send again now that authinfo is up2date
 					goto stun_phase0;
 				}
 				ret = -3;
             }
             else {
 				const tnet_stun_attr_address_t* pc_addr;
 				if ((ret = tnet_stun_pkt_attr_find_first(p_pkt_resp, tnet_stun_attr_type_xor_mapped_address, (const tnet_stun_attr_t**)&pc_addr)) == 0 && pc_addr) {
 					TSK_OBJECT_SAFE_FREE(p_binding->p_xmaddr);
 					p_binding->p_xmaddr = tsk_object_ref(TSK_OBJECT(pc_addr));
 				}
 				if ((ret = tnet_stun_pkt_attr_find_first(p_pkt_resp, tnet_stun_attr_type_mapped_address, (const tnet_stun_attr_t**)&pc_addr)) == 0 && pc_addr) {
 					TSK_OBJECT_SAFE_FREE(p_binding->p_maddr);
 					p_binding->p_maddr = tsk_object_ref(TSK_OBJECT(pc_addr));
 				}
 			}
         }
     }
     /* END OF stun_phase0 */
 
 bail:
     TSK_OBJECT_SAFE_FREE(p_pkt_resp);
     TSK_OBJECT_SAFE_FREE(p_pkt_req);
 
     return ret;
 }
 
 /**@ingroup tnet_nat_group
  * Gets the server reflexive address associated to this STUN2 binding.
  *
  *
  * @param [in,out]	p_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]	pp_ip	The reflexive IP address. It is up the the caller to free the returned string 
  * @param [in,out]	pu_port		The reflexive port. 
  *
  * @return	Zero if succeed and non zero error code otherwise. 
 **/
 int tnet_nat_stun_get_reflexive_address(const struct tnet_nat_ctx_s* p_self, tnet_stun_binding_id_t id, char** pp_ip, tnet_port_t *pu_port)
 {
 	const tsk_list_item_t* item;
 	if (!p_self) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 	if (!pp_ip && !pu_port) {
 		return 0;
 	}
 	
 	if ((item = tsk_list_find_item_by_pred(p_self->stun_bindings, __pred_find_stun_binding, &id)) && item->data) {
 		const tnet_stun_binding_t *pc_bind = (const tnet_stun_binding_t *)item->data;
 		const struct tnet_stun_attr_address_s *pc_addr = pc_bind->p_xmaddr ? pc_bind->p_xmaddr : pc_bind->p_maddr;
 		if (pc_addr) {
 			tnet_ip_t ip;
 			int ret;
 			if ((ret = tnet_stun_utils_inet_ntop((pc_addr->e_family == tnet_stun_address_family_ipv6), &pc_addr->address, &ip))) {
 				return ret;
 			}
 			if (pp_ip) {
 				tsk_strupdate(pp_ip, ip);
 			}
 			if (pu_port) {
 				*pu_port = pc_addr->u_port;
 			}
 			return 0;
 		}
 	}
 	return -2;
 }
 
 /**@ingroup tnet_nat_group
  *
  * Removes a STUN2 binding from the NAT context.
  *
  * @param [in,out]	p_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 struct tnet_nat_ctx_s* p_self, tnet_stun_binding_id_t id)
 {
 	if (!p_self) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 	tsk_list_remove_item_by_pred(p_self->stun_bindings, __pred_find_stun_binding, &id);
 	return 0;	
 }
 
 
 //=================================================================================================
 //	NAT CONTEXT object definition
 //
 static tsk_object_t* tnet_nat_context_ctor(tsk_object_t * self, va_list * app)
 {
 	tnet_nat_ctx_t *p_ctx = (tnet_nat_ctx_t*)self;
 	if (p_ctx) {
 	}
 	return self;
 }
 static tsk_object_t* tnet_nat_context_dtor(tsk_object_t * self)
 { 
 	tnet_nat_ctx_t *p_ctx = (tnet_nat_ctx_t*)self;
 	if (p_ctx) {
 		TSK_FREE(p_ctx->username);
 		TSK_FREE(p_ctx->password);
 		TSK_FREE(p_ctx->server_address);
 
 		TSK_OBJECT_SAFE_FREE(p_ctx->stun_bindings);
 		TSK_DEBUG_INFO("*** NAT context destroyed ***");
 	}
 
 	return self;
 }
 static const tsk_object_def_t tnet_nat_context_def_s = 
 {
 	sizeof(tnet_nat_ctx_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;