/*-------------------------------------------------------------------------
   STUN function copy right information

   Copyright (C) 2006-2008. Tang, Li <tangli@palmmicro.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This software 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-------------------------------------------------------------------------*/

// stun.fwdnet.net:3478
// stun.sipgate.net:10000

/*
Public STUN servers:
stun.ekiga.net
stun.fwdnet.net (no XOR_MAPPED_ADDRESS support) 
stun.ideasip.com (no XOR_MAPPED_ADDRESS support) 
stun01.sipphone.com (no DNS SRV record) 
stun.softjoys.com (no DNS SRV record) (no XOR_MAPPED_ADDRESS support) 
stun.voipbuster.com (no DNS SRV record) (no XOR_MAPPED_ADDRESS support) 
stun.voxgratia.org (no DNS SRV record) (no XOR_MAPPED_ADDRESS support) 
stun.xten.com 
stunserver.org (see their usage policy)
stun.sipgate.net:10000 
numb.viagenie.ca (http://numb.viagenie.ca) (no XOR_MAPPED_ADDRESS support) 

Various types of NAT (still according to the RFC):

Full Cone:A full cone NAT is one where all requests from the same internal 
	  IP address and port are mapped to the same external IP address 
	  and port. Furthermore, any external host can send a packet to 
	  the internal host, by sending a packet to the mapped external address. 

Restricted Cone: A restricted cone NAT is one where all requests from the 
	         same internal IP address and port are mapped to the same
		 external IP address and port. Unlike a full cone NAT, an 
		 external host (with IP address X) can send a packet to the 
		 internal host only if the internal host had previously sent 
		 a packet to IP address X. 

Port Restricted Cone: A port restricted cone NAT is like a restricted cone NAT, 
		      but the restriction includes port numbers. Specifically, 
		      an external host can send a packet, with source IP address X 
	              and source port P, to the internal host only if the internal 
                      host had previously sent a packet to IP address X and port P. 

Symmetric: A symmetric NAT is one where all requests from the same internal IP address 
           and port, to a specific destination IP address and port, are mapped to the 
           same external IP address and port. If the same host sends a packet with the
           same source address and port, but to a different destination, a different 
           mapping is used. Furthermore, only the external host that receives a packet 
           can send a UDP packet back to the internal host. 
*/


#include "version.h"
#include "type.h"

#ifdef CALL_SIP

#include "ar168.h"
#include "core.h"
#include "apps.h"
#include "tcpip.h"
#include "bank2.h"

// NAT type
#define NAT_TYPE_UNKNOWN			0
#define NAT_TYPE_BLOCKED			1
#define NAT_TYPE_OPEN_INTERNET		2
#define NAT_TYPE_SYM_FIREWALL		3
#define NAT_TYPE_FULL_CONE			4
#define NAT_TYPE_RESTRICTED			5
#define NAT_TYPE_PORT_RESTRICTED	6	
#define NAT_TYPE_SYMMETRIC			7

#define MSG_FIELD_TYPE		0
#define MSG_FIELD_LENGTH	2
#define MSG_FIELD_TRANS_ID	4
#define MSG_FIELD_DATA		20

#define MSG_TYPE_BREQ		0x0001  // Binding Request
#define MSG_TYPE_BRESP		0x0101  // Binding Response
#define MSG_TYPE_BERESP		0x0111  // Binding Error Response
#define MSG_TYPE_SSREQ		0x0002  // Shared Secret Request
#define MSG_TYPE_SSRESP		0x0102  // Shared Secret Response
#define MSG_TYPE_SSERESP	0x0112  // Shared Secret Error Response

#define MSG_ATTR_TYPE		0
#define MSG_ATTR_LENGTH		2
#define MSG_ATTR_VALUE		4

#define ATTR_MAPPED_ADDRESS		0x01
#define ATTR_RESPONSE_ADDRESS	0x02
#define ATTR_CHANGE_REQUEST		0x03
#define ATTR_SOURCE_ADDRESS		0x04
#define ATTR_CHANGED_ADDRESS	0x05
#define ATTR_USERNAME			0x06
#define ATTR_PASSWORD			0x07
#define ATTR_MESSAGE_INTEGRITY  0x08
#define ATTR_ERROR_CODE			0x09
#define ATTR_UNKNOWN_ATTRIBUTES 0x0a
#define ATTR_REFLECTED_FROM		0x0b

#define CHANGE_IP_MASK		0x04
#define CHANGE_PORT_MASK	0x02

#define STUN_STATE_IDLE			0
#define STUN_STATE_FIRST_TEST1	1
#define STUN_STATE_TEST2		2
#define STUN_STATE_SECOND_TEST1	3
#define STUN_STATE_TEST3		4
#define STUN_STATE_CHECKING		5

#define STUN_TRANS_ID_LEN	16
#define STUN_SEND_LEN		28

UDP_SOCKET _pStunSocket;
UCHAR _iStunNatType;
UCHAR _iStunState;
UCHAR _iStunTask;
UCHAR _iStunRetry;
UCHAR _iStunTimer;
UCHAR _pStunTranId[STUN_TRANS_ID_LEN];
UCHAR _pStunServerIP[IP_ALEN];
UCHAR _pStunMappedAddr[IP_ALEN];
USHORT _sStunMappedPort;
UCHAR _pStunChangedAddr[IP_ALEN];
USHORT _sStunChangedPort;

/**********************************************************************
 ** STUN functions                                                   **
 **********************************************************************
 */
void StunInit()
{
	_pStunSocket = NULL;
	_iStunTask = STUN_TASK_NONE;
	_iStunState = STUN_STATE_IDLE;
	memset(_pStunTranId, 0, STUN_TRANS_ID_LEN);
	memset(_pStunMappedAddr, 0, IP_ALEN);
	memset(_pStunChangedAddr, 0, IP_ALEN);
	_sStunMappedPort = 0;
	_sStunChangedPort = 0;
}

void stun_send_req()
{
	PCHAR pCur;

	pCur = Adapter_pUdpBuf;
	// Header
	USHORT2PCHAR(MSG_TYPE_BREQ, pCur);
	pCur[2] = 0;
	pCur[3] = 8;
	pCur += 4;
	memcpy(pCur, _pStunTranId, STUN_TRANS_ID_LEN);
	pCur += STUN_TRANS_ID_LEN;
	// Change-Request Attribute
	memset(pCur, 0, 8);
	pCur[1] = ATTR_CHANGE_REQUEST;
	pCur[3] = 4;

	if (_iStunState == STUN_STATE_TEST2)
	{
		// change IP and port
		pCur[7] |= CHANGE_IP_MASK;
		pCur[7] |= CHANGE_PORT_MASK;
	}
	else if (_iStunState == STUN_STATE_TEST3)
	{
		// change port only
		pCur[7] |= CHANGE_PORT_MASK;
	}

	if (_iStunState == STUN_STATE_SECOND_TEST1 || _iStunState == STUN_STATE_TEST3)
	{
		UdpSendTo(_pStunSocket, STUN_SEND_LEN, _pStunChangedAddr, _sStunChangedPort);
	}
	else
	{
		UdpSendTo(_pStunSocket, STUN_SEND_LEN, _pStunServerIP, OptionsGetShort(OPT_STUN_PORT));
	}
}

void stun_start_test()
{
	rand_array(_pStunTranId, STUN_TRANS_ID_LEN);
	_iStunRetry = 0;
	_iStunTimer = 0;
	stun_send_req();
}


void stun_start(PCHAR pServerIP)
{
	if (pServerIP)
	{
		UdpDebugString("first test1");
		memcpy4(_pStunServerIP, pServerIP);
		_iStunState = STUN_STATE_FIRST_TEST1;
		stun_start_test();
	}
	else
	{
		UdpDebugString("Nat type: unknown");
		_iStunNatType = NAT_TYPE_UNKNOWN;
		TaskStunDone(NULL, 0, _iStunTask);
	}
}

void StunMap(UCHAR iTask, UDP_SOCKET pSocket)
{
	UCHAR pDns[MAX_DNS_LEN];
	UCHAR pDstIP[IP_ALEN];

	_iStunTask = iTask;
	_pStunSocket = pSocket;
	if (iTask == STUN_TASK_LOGON)
	{
		_iStunNatType = NAT_TYPE_UNKNOWN;
		OptionsGetString(pDns, OPT_STUN_SERVER, MAX_DNS_LEN);
		if (strlen(pDns))
		{
			if (str2ip(pDns, pDstIP))
			{
				stun_start(pDstIP);
			}
			else
			{
				TaskDnsQuery(pDns, DNS_TYPE_A, NET_BANK_OFFSET, (USHORT)stun_start);
			}
		}
		else
		{
			stun_start(NULL);
		}
	}
	else
	{
		if (_iStunNatType == NAT_TYPE_UNKNOWN)
		{
			TaskStunDone(NULL, 0, _iStunTask);
		}
		else
		{
			_iStunState = STUN_STATE_CHECKING;
			stun_start_test();
		}
	}
}

BOOLEAN StunRun(UDP_SOCKET pUcb)
{
	PCHAR pCur;
	USHORT sCurLen, sMsgLen, sMsgType, sMappedPort, sChangedPort;
	PCHAR pMappedAddr;
	PCHAR pChangedAddr;
	PCHAR pData;

	if (_iStunState == STUN_STATE_IDLE)		return FALSE;
	if (pUcb != _pStunSocket)		return FALSE;

	pData = Adapter_pUdpBuf;

	if (memcmp((PCHAR)(pData + MSG_FIELD_TRANS_ID), _pStunTranId, STUN_TRANS_ID_LEN))		return FALSE;

	sMsgType = PCHAR2USHORT((PCHAR)(pData + MSG_FIELD_TYPE));
	if (sMsgType == MSG_TYPE_BERESP)
	{
		TaskStunDone(NULL, 0, _iStunTask);
		return TRUE;
	}
	if (sMsgType != MSG_TYPE_BRESP)		return TRUE;
	if (pUcb->sLen < MSG_FIELD_DATA)			return TRUE;

	pMappedAddr = NULL;
	pChangedAddr = NULL;
	sMappedPort = 0;
	sChangedPort = 0;
	sMsgLen = PCHAR2USHORT((PCHAR)(pData + MSG_FIELD_LENGTH));
	sMsgLen += MSG_FIELD_DATA;
	sCurLen = MSG_FIELD_DATA;
	while (sCurLen < sMsgLen)
	{
		pCur = (PCHAR)(pData + sCurLen);
		sCurLen += MSG_ATTR_VALUE;
		sCurLen += PCHAR2USHORT((PCHAR)(pCur + MSG_ATTR_LENGTH));
		if (pCur[0] != 0)	continue;
		if (pCur[1] == ATTR_MAPPED_ADDRESS)
		{
			pCur += MSG_ATTR_VALUE;
			pMappedAddr = (PCHAR)(pCur + 4);
			sMappedPort = PCHAR2USHORT((PCHAR)(pCur + 2));
		}
		else if (pCur[1] == ATTR_CHANGED_ADDRESS)
		{
			pCur += MSG_ATTR_VALUE;
			pChangedAddr = (PCHAR)(pCur + 4);
			sChangedPort = PCHAR2USHORT((PCHAR)(pCur + 2));
		}
	}

	if (_iStunState == STUN_STATE_CHECKING)
	{
		if (pMappedAddr)
		{
			memcpy4(_pStunMappedAddr, pMappedAddr);
			if (_iStunNatType == NAT_TYPE_FULL_CONE || _iStunNatType == NAT_TYPE_RESTRICTED || _iStunNatType == NAT_TYPE_PORT_RESTRICTED)
			{
				_sStunMappedPort = sMappedPort;
				TaskStunDone(_pStunMappedAddr, _sStunMappedPort, _iStunTask);
			}
			else
			{
				TaskStunDone(_pStunMappedAddr, 0, _iStunTask);
			}
		}
		else
		{
			TaskStunDone(NULL, 0, _iStunTask);
		}
	}
	else if (_iStunState == STUN_STATE_FIRST_TEST1)
	{
		if (pMappedAddr)
		{
			memcpy4(_pStunMappedAddr, pMappedAddr);
			_sStunMappedPort = sMappedPort;
		}
		if (pChangedAddr)
		{
			memcpy4(_pStunChangedAddr, pChangedAddr);
			_sStunChangedPort = sChangedPort;
		}
		UdpDebugString("test2");
		_iStunState = STUN_STATE_TEST2;
		stun_start_test();
	}
	else if (_iStunState == STUN_STATE_SECOND_TEST1)
	{
		if (memcmp(_pStunMappedAddr, pMappedAddr, IP_ALEN) || _sStunMappedPort != sMappedPort)
		{
			UdpDebugString("Nat type: symmetric");
			_iStunNatType = NAT_TYPE_SYMMETRIC;
			TaskStunDone(_pStunMappedAddr, 0, _iStunTask);
		}
		else
		{
			UdpDebugString("test3");
			_iStunState = STUN_STATE_TEST3;
			stun_start_test();
		}
	}
	else if (_iStunState == STUN_STATE_TEST2)
	{
		UdpDebugString("Nat type: full cone");
		_iStunNatType = NAT_TYPE_FULL_CONE;
		TaskStunDone(_pStunMappedAddr, _sStunMappedPort, _iStunTask);
	}
	else if (_iStunState == STUN_STATE_TEST3)
	{
		UdpDebugString("Nat type: resricted");
		_iStunNatType = NAT_TYPE_RESTRICTED;
		TaskStunDone(_pStunMappedAddr, _sStunMappedPort, _iStunTask);
	}
	return TRUE;
}

void StunTimer()
{
	if (_iStunState == STUN_STATE_IDLE)		return;

	_iStunTimer ++;
	if (_iStunTimer > 2)
	{
		_iStunTimer = 0;
		_iStunRetry ++;
		if (_iStunRetry > 2)
		{
			switch (_iStunState)
			{
			case STUN_STATE_CHECKING:
				TaskStunDone(NULL, 0, _iStunTask);
				break;

			case STUN_STATE_FIRST_TEST1:
			case STUN_STATE_SECOND_TEST1:
				UdpDebugString("Nat type: unknown");
				_iStunNatType = NAT_TYPE_UNKNOWN;
				TaskStunDone(NULL, 0, _iStunTask);
				break;

			case STUN_STATE_TEST2:
				UdpDebugString("second test1");
				_iStunState = STUN_STATE_SECOND_TEST1;
				stun_start_test();
				break;

			case STUN_STATE_TEST3:
				UdpDebugString("Nat type: port restricted");
				_iStunNatType = NAT_TYPE_PORT_RESTRICTED;
				TaskStunDone(_pStunMappedAddr, _sStunMappedPort, _iStunTask);
				break;
			}
		}
		else
		{
			stun_send_req();
		}
	}
}

#endif
