summaryrefslogtreecommitdiffstats
path: root/src/stun
diff options
context:
space:
mode:
Diffstat (limited to 'src/stun')
-rw-r--r--src/stun/Makefile.am15
-rw-r--r--src/stun/stun.cxx2587
-rw-r--r--src/stun/stun.h405
-rw-r--r--src/stun/stun_transaction.cpp680
-rw-r--r--src/stun/stun_transaction.h159
-rw-r--r--src/stun/udp.cxx349
-rw-r--r--src/stun/udp.h156
7 files changed, 4351 insertions, 0 deletions
diff --git a/src/stun/Makefile.am b/src/stun/Makefile.am
new file mode 100644
index 0000000..f52face
--- /dev/null
+++ b/src/stun/Makefile.am
@@ -0,0 +1,15 @@
+AM_CPPFLAGS = \
+ -Wall \
+ -I$(top_srcdir)/src \
+ $(CCRTP_CFLAGS)\
+ $(XML2_CFLAGS)
+
+noinst_LIBRARIES = libstun.a
+
+libstun_a_SOURCES =\
+ stun.cxx\
+ stun_transaction.cpp\
+ udp.cxx\
+ stun.h\
+ stun_transaction.h\
+ udp.h
diff --git a/src/stun/stun.cxx b/src/stun/stun.cxx
new file mode 100644
index 0000000..f5e9bc9
--- /dev/null
+++ b/src/stun/stun.cxx
@@ -0,0 +1,2587 @@
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <cstdlib>
+#include <errno.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <net/if.h>
+
+#endif
+
+
+#if defined(__sparc__) || defined(WIN32)
+#define NOSSL
+#endif
+#define NOSSL
+
+#include "udp.h"
+#include "stun.h"
+
+// Twinkle
+#include "util.h"
+#include "sockets/socket.h"
+#include "audits/memman.h"
+
+
+using namespace std;
+
+
+static void
+computeHmac(char* hmac, const char* input, int length, const char* key, int keySize);
+
+static bool
+stunParseAtrAddress( char* body, unsigned int hdrLen, StunAtrAddress4& result )
+{
+ if ( hdrLen != 8 )
+ {
+ clog << "hdrLen wrong for Address" <<endl;
+ return false;
+ }
+ result.pad = *body++;
+ result.family = *body++;
+ if (result.family == IPv4Family)
+ {
+ UInt16 nport;
+ memcpy(&nport, body, 2); body+=2;
+ result.ipv4.port = ntohs(nport);
+
+ UInt32 naddr;
+ memcpy(&naddr, body, 4); body+=4;
+ result.ipv4.addr = ntohl(naddr);
+ return true;
+ }
+ else if (result.family == IPv6Family)
+ {
+ clog << "ipv6 not supported" << endl;
+ }
+ else
+ {
+ clog << "bad address family: " << result.family << endl;
+ }
+
+ return false;
+}
+
+static bool
+stunParseAtrChangeRequest( char* body, unsigned int hdrLen, StunAtrChangeRequest& result )
+{
+ if ( hdrLen != 4 )
+ {
+ clog << "hdr length = " << hdrLen << " expecting " << sizeof(result) << endl;
+
+ clog << "Incorrect size for ChangeRequest" << endl;
+ return false;
+ }
+ else
+ {
+ memcpy(&result.value, body, 4);
+ result.value = ntohl(result.value);
+ return true;
+ }
+}
+
+static bool
+stunParseAtrError( char* body, unsigned int hdrLen, StunAtrError& result )
+{
+ if ( hdrLen >= sizeof(result) )
+ {
+ clog << "head on Error too large" << endl;
+ return false;
+ }
+ else
+ {
+ memcpy(&result.pad, body, 2); body+=2;
+ result.pad = ntohs(result.pad);
+ result.errorClass = *body++;
+ result.number = *body++;
+
+ result.sizeReason = hdrLen - 4;
+ memcpy(&result.reason, body, result.sizeReason);
+ result.reason[result.sizeReason] = 0;
+ return true;
+ }
+}
+
+static bool
+stunParseAtrUnknown( char* body, unsigned int hdrLen, StunAtrUnknown& result )
+{
+ if ( hdrLen >= sizeof(result) )
+ {
+ return false;
+ }
+ else
+ {
+ if (hdrLen % 4 != 0) return false;
+ result.numAttributes = hdrLen / 4;
+ for (int i=0; i<result.numAttributes; i++)
+ {
+ memcpy(&result.attrType[i], body, 2); body+=2;
+ result.attrType[i] = ntohs(result.attrType[i]);
+ }
+ return true;
+ }
+}
+
+
+static bool
+stunParseAtrString( char* body, unsigned int hdrLen, StunAtrString& result )
+{
+ if ( hdrLen >= STUN_MAX_STRING )
+ {
+ clog << "String is too large" << endl;
+ return false;
+ }
+ else
+ {
+ if (hdrLen % 4 != 0)
+ {
+ clog << "Bad length string " << hdrLen << endl;
+ return false;
+ }
+
+ result.sizeValue = hdrLen;
+ memcpy(&result.value, body, hdrLen);
+ result.value[hdrLen] = 0;
+ return true;
+ }
+}
+
+
+static bool
+stunParseAtrIntegrity( char* body, unsigned int hdrLen, StunAtrIntegrity& result )
+{
+ if ( hdrLen != 20)
+ {
+ clog << "MessageIntegrity must be 20 bytes" << endl;
+ return false;
+ }
+ else
+ {
+ memcpy(&result.hash, body, hdrLen);
+ return true;
+ }
+}
+
+
+bool
+stunParseMessage( char* buf, unsigned int bufLen, StunMessage& msg, bool verbose)
+{
+ if (verbose) clog << "Received stun message: " << bufLen << " bytes" << endl;
+ memset(&msg, 0, sizeof(msg));
+
+ if (sizeof(StunMsgHdr) > bufLen)
+ {
+ return false;
+ }
+
+ memcpy(&msg.msgHdr, buf, sizeof(StunMsgHdr));
+ msg.msgHdr.msgType = ntohs(msg.msgHdr.msgType);
+ msg.msgHdr.msgLength = ntohs(msg.msgHdr.msgLength);
+
+ if (msg.msgHdr.msgLength + sizeof(StunMsgHdr) != bufLen)
+ {
+ clog << "Message header length doesn't match message size: " << msg.msgHdr.msgLength << " - " << bufLen << endl;
+ return false;
+ }
+
+ char* body = buf + sizeof(StunMsgHdr);
+ unsigned int size = msg.msgHdr.msgLength;
+
+ //clog << "bytes after header = " << size << endl;
+
+ while ( size > 0 )
+ {
+ // !jf! should check that there are enough bytes left in the buffer
+
+ StunAtrHdr* attr = reinterpret_cast<StunAtrHdr*>(body);
+
+ unsigned int attrLen = ntohs(attr->length);
+ int atrType = ntohs(attr->type);
+
+ //if (verbose) clog << "Found attribute type=" << AttrNames[atrType] << " length=" << attrLen << endl;
+ if ( attrLen+4 > size )
+ {
+ clog << "claims attribute is larger than size of message " <<"(attribute type="<<atrType<<")"<< endl;
+ return false;
+ }
+
+ body += 4; // skip the length and type in attribute header
+ size -= 4;
+
+ switch ( atrType )
+ {
+ case MappedAddress:
+ msg.hasMappedAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.mappedAddress )== false )
+ {
+ clog << "problem parsing MappedAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "MappedAddress = " << msg.mappedAddress.ipv4 << endl;
+ }
+
+ break;
+
+ case ResponseAddress:
+ msg.hasResponseAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.responseAddress )== false )
+ {
+ clog << "problem parsing ResponseAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "ResponseAddress = " << msg.responseAddress.ipv4 << endl;
+ }
+ break;
+
+ case ChangeRequest:
+ msg.hasChangeRequest = true;
+ if (stunParseAtrChangeRequest( body, attrLen, msg.changeRequest) == false)
+ {
+ clog << "problem parsing ChangeRequest" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "ChangeRequest = " << msg.changeRequest.value << endl;
+ }
+ break;
+
+ case SourceAddress:
+ msg.hasSourceAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.sourceAddress )== false )
+ {
+ clog << "problem parsing SourceAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "SourceAddress = " << msg.sourceAddress.ipv4 << endl;
+ }
+ break;
+
+ case ChangedAddress:
+ msg.hasChangedAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.changedAddress )== false )
+ {
+ clog << "problem parsing ChangedAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "ChangedAddress = " << msg.changedAddress.ipv4 << endl;
+ }
+ break;
+
+ case Username:
+ msg.hasUsername = true;
+ if (stunParseAtrString( body, attrLen, msg.username) == false)
+ {
+ clog << "problem parsing Username" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "Username = " << msg.username.value << endl;
+ }
+
+ break;
+
+ case Password:
+ msg.hasPassword = true;
+ if (stunParseAtrString( body, attrLen, msg.password) == false)
+ {
+ clog << "problem parsing Password" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "Password = " << msg.password.value << endl;
+ }
+ break;
+
+ case MessageIntegrity:
+ msg.hasMessageIntegrity = true;
+ if (stunParseAtrIntegrity( body, attrLen, msg.messageIntegrity) == false)
+ {
+ clog << "problem parsing MessageIntegrity" << endl;
+ return false;
+ }
+ else
+ {
+ //if (verbose) clog << "MessageIntegrity = " << msg.messageIntegrity.hash << endl;
+ }
+
+ // read the current HMAC
+ // look up the password given the user of given the transaction id
+ // compute the HMAC on the buffer
+ // decide if they match or not
+ break;
+
+ case ErrorCode:
+ msg.hasErrorCode = true;
+ if (stunParseAtrError(body, attrLen, msg.errorCode) == false)
+ {
+ clog << "problem parsing ErrorCode" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "ErrorCode = " << int(msg.errorCode.errorClass)
+ << " " << int(msg.errorCode.number)
+ << " " << msg.errorCode.reason << endl;
+ }
+
+ break;
+
+ case UnknownAttribute:
+ msg.hasUnknownAttributes = true;
+ if (stunParseAtrUnknown(body, attrLen, msg.unknownAttributes) == false)
+ {
+ clog << "problem parsing UnknownAttribute" << endl;
+ return false;
+ }
+ break;
+
+ case ReflectedFrom:
+ msg.hasReflectedFrom = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.reflectedFrom ) == false )
+ {
+ clog << "problem parsing ReflectedFrom" << endl;
+ return false;
+ }
+ break;
+
+ case XorMappedAddress:
+ msg.hasXorMappedAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.xorMappedAddress ) == false )
+ {
+ clog << "problem parsing XorMappedAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "XorMappedAddress = " << msg.mappedAddress.ipv4 << endl;
+ }
+ break;
+
+ case XorOnly:
+ msg.xorOnly = true;
+ if (verbose)
+ {
+ clog << "xorOnly = true" << endl;
+ }
+ break;
+
+ case ServerName:
+ msg.hasServerName = true;
+ if (stunParseAtrString( body, attrLen, msg.serverName) == false)
+ {
+ clog << "problem parsing ServerName" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "ServerName = " << msg.serverName.value << endl;
+ }
+ break;
+
+ case SecondaryAddress:
+ msg.hasSecondaryAddress = true;
+ if ( stunParseAtrAddress( body, attrLen, msg.secondaryAddress ) == false )
+ {
+ clog << "problem parsing secondaryAddress" << endl;
+ return false;
+ }
+ else
+ {
+ if (verbose) clog << "SecondaryAddress = " << msg.secondaryAddress.ipv4 << endl;
+ }
+ break;
+
+ default:
+ if (verbose) clog << "Unknown attribute: " << atrType << endl;
+ if ( atrType <= 0x7FFF )
+ {
+ return false;
+ }
+ }
+
+ body += attrLen;
+ size -= attrLen;
+ }
+
+ return true;
+}
+
+
+static char*
+encode16(char* buf, UInt16 data)
+{
+ UInt16 ndata = htons(data);
+ memcpy(buf, reinterpret_cast<void*>(&ndata), sizeof(UInt16));
+ return buf + sizeof(UInt16);
+}
+
+static char*
+encode32(char* buf, UInt32 data)
+{
+ UInt32 ndata = htonl(data);
+ memcpy(buf, reinterpret_cast<void*>(&ndata), sizeof(UInt32));
+ return buf + sizeof(UInt32);
+}
+
+
+static char*
+encode(char* buf, const char* data, unsigned int length)
+{
+ memcpy(buf, data, length);
+ return buf + length;
+}
+
+
+static char*
+encodeAtrAddress4(char* ptr, UInt16 type, const StunAtrAddress4& atr)
+{
+ ptr = encode16(ptr, type);
+ ptr = encode16(ptr, 8);
+ *ptr++ = atr.pad;
+ *ptr++ = IPv4Family;
+ ptr = encode16(ptr, atr.ipv4.port);
+ ptr = encode32(ptr, atr.ipv4.addr);
+
+ return ptr;
+}
+
+static char*
+encodeAtrChangeRequest(char* ptr, const StunAtrChangeRequest& atr)
+{
+ ptr = encode16(ptr, ChangeRequest);
+ ptr = encode16(ptr, 4);
+ ptr = encode32(ptr, atr.value);
+ return ptr;
+}
+
+static char*
+encodeAtrError(char* ptr, const StunAtrError& atr)
+{
+ ptr = encode16(ptr, ErrorCode);
+ ptr = encode16(ptr, 6 + atr.sizeReason);
+ ptr = encode16(ptr, atr.pad);
+ *ptr++ = atr.errorClass;
+ *ptr++ = atr.number;
+ ptr = encode(ptr, atr.reason, atr.sizeReason);
+ return ptr;
+}
+
+
+static char*
+encodeAtrUnknown(char* ptr, const StunAtrUnknown& atr)
+{
+ ptr = encode16(ptr, UnknownAttribute);
+ ptr = encode16(ptr, 2+2*atr.numAttributes);
+ for (int i=0; i<atr.numAttributes; i++)
+ {
+ ptr = encode16(ptr, atr.attrType[i]);
+ }
+ return ptr;
+}
+
+
+static char*
+encodeXorOnly(char* ptr)
+{
+ ptr = encode16(ptr, XorOnly );
+ return ptr;
+}
+
+
+static char*
+encodeAtrString(char* ptr, UInt16 type, const StunAtrString& atr)
+{
+ assert(atr.sizeValue % 4 == 0);
+
+ ptr = encode16(ptr, type);
+ ptr = encode16(ptr, atr.sizeValue);
+ ptr = encode(ptr, atr.value, atr.sizeValue);
+ return ptr;
+}
+
+
+static char*
+encodeAtrIntegrity(char* ptr, const StunAtrIntegrity& atr)
+{
+ ptr = encode16(ptr, MessageIntegrity);
+ ptr = encode16(ptr, 20);
+ ptr = encode(ptr, atr.hash, sizeof(atr.hash));
+ return ptr;
+}
+
+
+unsigned int
+stunEncodeMessage( const StunMessage& msg,
+ char* buf,
+ unsigned int bufLen,
+ const StunAtrString& password,
+ bool verbose)
+{
+ assert(bufLen >= sizeof(StunMsgHdr));
+ char* ptr = buf;
+
+ ptr = encode16(ptr, msg.msgHdr.msgType);
+ char* lengthp = ptr;
+ ptr = encode16(ptr, 0);
+ ptr = encode(ptr, reinterpret_cast<const char*>(msg.msgHdr.id.octet), sizeof(msg.msgHdr.id));
+
+ if (verbose) clog << "Encoding stun message: " << endl;
+ if (msg.hasMappedAddress)
+ {
+ if (verbose) clog << "Encoding MappedAddress: " << msg.mappedAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4 (ptr, MappedAddress, msg.mappedAddress);
+ }
+ if (msg.hasResponseAddress)
+ {
+ if (verbose) clog << "Encoding ResponseAddress: " << msg.responseAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4(ptr, ResponseAddress, msg.responseAddress);
+ }
+ if (msg.hasChangeRequest)
+ {
+ if (verbose) clog << "Encoding ChangeRequest: " << msg.changeRequest.value << endl;
+ ptr = encodeAtrChangeRequest(ptr, msg.changeRequest);
+ }
+ if (msg.hasSourceAddress)
+ {
+ if (verbose) clog << "Encoding SourceAddress: " << msg.sourceAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4(ptr, SourceAddress, msg.sourceAddress);
+ }
+ if (msg.hasChangedAddress)
+ {
+ if (verbose) clog << "Encoding ChangedAddress: " << msg.changedAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4(ptr, ChangedAddress, msg.changedAddress);
+ }
+ if (msg.hasUsername)
+ {
+ if (verbose) clog << "Encoding Username: " << msg.username.value << endl;
+ ptr = encodeAtrString(ptr, Username, msg.username);
+ }
+ if (msg.hasPassword)
+ {
+ if (verbose) clog << "Encoding Password: " << msg.password.value << endl;
+ ptr = encodeAtrString(ptr, Password, msg.password);
+ }
+ if (msg.hasErrorCode)
+ {
+ if (verbose) clog << "Encoding ErrorCode: class="
+ << int(msg.errorCode.errorClass)
+ << " number=" << int(msg.errorCode.number)
+ << " reason="
+ << msg.errorCode.reason
+ << endl;
+
+ ptr = encodeAtrError(ptr, msg.errorCode);
+ }
+ if (msg.hasUnknownAttributes)
+ {
+ if (verbose) clog << "Encoding UnknownAttribute: ???" << endl;
+ ptr = encodeAtrUnknown(ptr, msg.unknownAttributes);
+ }
+ if (msg.hasReflectedFrom)
+ {
+ if (verbose) clog << "Encoding ReflectedFrom: " << msg.reflectedFrom.ipv4 << endl;
+ ptr = encodeAtrAddress4(ptr, ReflectedFrom, msg.reflectedFrom);
+ }
+ if (msg.hasXorMappedAddress)
+ {
+ if (verbose) clog << "Encoding XorMappedAddress: " << msg.xorMappedAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4 (ptr, XorMappedAddress, msg.xorMappedAddress);
+ }
+ if (msg.xorOnly)
+ {
+ if (verbose) clog << "Encoding xorOnly: " << endl;
+ ptr = encodeXorOnly( ptr );
+ }
+ if (msg.hasServerName)
+ {
+ if (verbose) clog << "Encoding ServerName: " << msg.serverName.value << endl;
+ ptr = encodeAtrString(ptr, ServerName, msg.serverName);
+ }
+ if (msg.hasSecondaryAddress)
+ {
+ if (verbose) clog << "Encoding SecondaryAddress: " << msg.secondaryAddress.ipv4 << endl;
+ ptr = encodeAtrAddress4 (ptr, SecondaryAddress, msg.secondaryAddress);
+ }
+
+ if (password.sizeValue > 0)
+ {
+ if (verbose) clog << "HMAC with password: " << password.value << endl;
+
+ StunAtrIntegrity integrity;
+ computeHmac(integrity.hash, buf, int(ptr-buf) , password.value, password.sizeValue);
+ ptr = encodeAtrIntegrity(ptr, integrity);
+ }
+ if (verbose) clog << endl;
+
+ encode16(lengthp, UInt16(ptr - buf - sizeof(StunMsgHdr)));
+ return int(ptr - buf);
+}
+
+int
+stunRand()
+{
+ // return 32 bits of random stuff
+ assert( sizeof(int) == 4 );
+ static bool init=false;
+ if ( !init )
+ {
+ init = true;
+
+ UInt64 tick;
+
+// Twinkle: removed platform dependent code (except WIN32 code)
+
+#if defined(WIN32)
+ volatile unsigned int lowtick=0,hightick=0;
+ __asm
+ {
+ rdtsc
+ mov lowtick, eax
+ mov hightick, edx
+ }
+ tick = hightick;
+ tick <<= 32;
+ tick |= lowtick;
+#else
+ tick = time(NULL);
+#endif
+ int seed = int(tick);
+#ifdef WIN32
+ srand(seed);
+#else
+ srandom(seed);
+#endif
+ }
+
+#ifdef WIN32
+ assert( RAND_MAX == 0x7fff );
+ int r1 = rand();
+ int r2 = rand();
+
+ int ret = (r1<<16) + r2;
+
+ return ret;
+#else
+ return random();
+#endif
+}
+
+
+/// return a random number to use as a port
+static int
+randomPort()
+{
+ int min=0x4000;
+ int max=0x7FFF;
+
+ int ret = stunRand();
+ ret = ret|min;
+ ret = ret&max;
+
+ return ret;
+}
+
+
+#ifdef NOSSL
+static void
+computeHmac(char* hmac, const char* input, int length, const char* key, int sizeKey)
+{
+ strncpy(hmac,"hmac-not-implemented",20);
+}
+#else
+#include <openssl/hmac.h>
+
+static void
+computeHmac(char* hmac, const char* input, int length, const char* key, int sizeKey)
+{
+ unsigned int resultSize=0;
+ HMAC(EVP_sha1(),
+ key, sizeKey,
+ reinterpret_cast<const unsigned char*>(input), length,
+ reinterpret_cast<unsigned char*>(hmac), &resultSize);
+ assert(resultSize == 20);
+}
+#endif
+
+
+static void
+toHex(const char* buffer, int bufferSize, char* output)
+{
+ static char hexmap[] = "0123456789abcdef";
+
+ const char* p = buffer;
+ char* r = output;
+ for (int i=0; i < bufferSize; i++)
+ {
+ unsigned char temp = *p++;
+
+ int hi = (temp & 0xf0)>>4;
+ int low = (temp & 0xf);
+
+ *r++ = hexmap[hi];
+ *r++ = hexmap[low];
+ }
+ *r = 0;
+}
+
+void
+stunCreateUserName(const StunAddress4& source, StunAtrString* username)
+{
+ UInt64 time = stunGetSystemTimeSecs();
+ time -= (time % 20*60);
+ //UInt64 hitime = time >> 32;
+ UInt64 lotime = time & 0xFFFFFFFF;
+
+ char buffer[1024];
+ sprintf(buffer,
+ "%08x:%08x:%08x:",
+ UInt32(source.addr),
+ UInt32(stunRand()),
+ UInt32(lotime));
+ assert( strlen(buffer) < 1024 );
+
+ assert(strlen(buffer) + 41 < STUN_MAX_STRING);
+
+ char hmac[20];
+ char key[] = "Jason";
+ computeHmac(hmac, buffer, strlen(buffer), key, strlen(key) );
+ char hmacHex[41];
+ toHex(hmac, 20, hmacHex );
+ hmacHex[40] =0;
+
+ strcat(buffer,hmacHex);
+
+ int l = strlen(buffer);
+ assert( l+1 < STUN_MAX_STRING );
+ assert( l%4 == 0 );
+
+ username->sizeValue = l;
+ memcpy(username->value,buffer,l);
+ username->value[l]=0;
+
+ //if (verbose) clog << "computed username=" << username.value << endl;
+}
+
+void
+stunCreatePassword(const StunAtrString& username, StunAtrString* password)
+{
+ char hmac[20];
+ char key[] = "Fluffy";
+ //char buffer[STUN_MAX_STRING];
+ computeHmac(hmac, username.value, strlen(username.value), key, strlen(key));
+ toHex(hmac, 20, password->value);
+ password->sizeValue = 40;
+ password->value[40]=0;
+
+ //clog << "password=" << password->value << endl;
+}
+
+
+UInt64
+stunGetSystemTimeSecs()
+{
+ UInt64 time=0;
+#if defined(WIN32)
+ SYSTEMTIME t;
+ // CJ TODO - this probably has bug on wrap around every 24 hours
+ GetSystemTime( &t );
+ time = (t.wHour*60+t.wMinute)*60+t.wSecond;
+#else
+ struct timeval now;
+ gettimeofday( &now , NULL );
+ //assert( now );
+ time = now.tv_sec;
+#endif
+ return time;
+}
+
+
+ostream& operator<< ( ostream& strm, const UInt128& r )
+{
+ strm << int(r.octet[0]);
+ for ( int i=1; i<16; i++ )
+ {
+ strm << ':' << int(r.octet[i]);
+ }
+
+ return strm;
+}
+
+ostream&
+operator<<( ostream& strm, const StunAddress4& addr)
+{
+ UInt32 ip = addr.addr;
+ strm << ((int)(ip>>24)&0xFF) << ".";
+ strm << ((int)(ip>>16)&0xFF) << ".";
+ strm << ((int)(ip>> 8)&0xFF) << ".";
+ strm << ((int)(ip>> 0)&0xFF) ;
+
+ strm << ":" << addr.port;
+
+ return strm;
+}
+
+
+// returns true if it scucceeded
+bool
+stunParseHostName( char* peerName,
+ UInt32& ip,
+ UInt16& portVal,
+ UInt16 defaultPort )
+{
+ in_addr sin_addr;
+
+ char host[512];
+ strncpy(host,peerName,512);
+ host[512-1]='\0';
+ char* port = NULL;
+
+ int portNum = defaultPort;
+
+ // pull out the port part if present.
+ char* sep = strchr(host,':');
+
+ if ( sep == NULL )
+ {
+ portNum = defaultPort;
+ }
+ else
+ {
+ *sep = '\0';
+ port = sep + 1;
+ // set port part
+
+ char* endPtr=NULL;
+
+ portNum = strtol(port,&endPtr,10);
+
+ if ( endPtr != NULL )
+ {
+ if ( *endPtr != '\0' )
+ {
+ portNum = defaultPort;
+ }
+ }
+ }
+
+ if ( portNum < 1024 ) return false;
+ if ( portNum >= 0xFFFF ) return false;
+
+ // figure out the host part
+ struct hostent* h;
+
+#ifdef WIN32
+ assert( strlen(host) >= 1 );
+ if ( isdigit( host[0] ) )
+ {
+ // assume it is a ip address
+ unsigned long a = inet_addr(host);
+ //cerr << "a=0x" << hex << a << dec << endl;
+
+ ip = ntohl( a );
+ }
+ else
+ {
+ // assume it is a host name
+ h = gethostbyname( host );
+
+ if ( h == NULL )
+ {
+ int err = getErrno();
+ std::cerr << "error was " << err << std::endl;
+ assert( err != WSANOTINITIALISED );
+
+ ip = ntohl( 0x7F000001L );
+
+ return false;
+ }
+ else
+ {
+ sin_addr = *(struct in_addr*)h->h_addr;
+ ip = ntohl( sin_addr.s_addr );
+ }
+ }
+
+#else
+ h = gethostbyname( host );
+ if ( h == NULL )
+ {
+ int err = getErrno();
+ std::cerr << "error was " << err << std::endl;
+ ip = ntohl( 0x7F000001L );
+ return false;
+ }
+ else
+ {
+ sin_addr = *(struct in_addr*)h->h_addr;
+ ip = ntohl( sin_addr.s_addr );
+ }
+#endif
+
+ portVal = portNum;
+
+ return true;
+}
+
+
+bool
+stunParseServerName( char* name, StunAddress4& addr)
+{
+ assert(name);
+
+ // TODO - put in DNS SRV stuff.
+
+ bool ret = stunParseHostName( name, addr.addr, addr.port, 3478);
+ if ( ret != true )
+ {
+ addr.port=0xFFFF;
+ }
+ return ret;
+}
+
+
+static void
+stunCreateErrorResponse(StunMessage& response, int cl, int number, const char* msg)
+{
+ response.msgHdr.msgType = BindErrorResponseMsg;
+ response.hasErrorCode = true;
+ response.errorCode.errorClass = cl;
+ response.errorCode.number = number;
+ strcpy(response.errorCode.reason, msg);
+}
+
+// Twinkle
+StunMessage *stunBuildError(const StunMessage &m, int code, const char *reason) {
+ StunMessage *err = new StunMessage();
+ MEMMAN_NEW(err);
+
+ stunCreateErrorResponse(*err, code / 100, code % 100, reason);
+
+ for ( int i=0; i<16; i++ )
+ {
+ err->msgHdr.id.octet[i] = m.msgHdr.id.octet[i];
+ }
+
+ return err;
+}
+
+string stunMsg2Str(const StunMessage &m) {
+ string s = "STUN ";
+
+ // Message type
+ if (m.msgHdr.msgType == BindRequestMsg) {
+ s += "Bind Request";
+ } else if (m.msgHdr.msgType == BindResponseMsg) {
+ s += "Bind Response";
+ } else if (m.msgHdr.msgType == BindErrorResponseMsg) {
+ s += "Bind Error Response";
+ } else if (m.msgHdr.msgType == SharedSecretRequestMsg) {
+ s += "Shared Secret Request";
+ } else if (m.msgHdr.msgType == SharedSecretResponseMsg) {
+ s += "Shared Secret Response";
+ } else if (m.msgHdr.msgType == SharedSecretErrorResponseMsg) {
+ s += "Shared Secret Error Response";
+ }
+ s += "\n";
+
+ // Message ID
+ s += "ID = ";
+ for ( int i=0; i<16; i++ ) {
+ char buf[3];
+ snprintf(buf, 3, "%X", m.msgHdr.id.octet[i]);
+ s += buf;
+ }
+ s += "\n";
+
+ if (m.hasMappedAddress) {
+ s += "Mapped Addres = ";
+ s += h_ip2str(m.mappedAddress.ipv4.addr);
+ s += ':';
+ s += int2str(m.mappedAddress.ipv4.port);
+ s += "\n";
+ }
+
+
+ if (m.hasResponseAddress) {
+ s += "Response Addres = ";
+ s += h_ip2str(m.responseAddress.ipv4.addr);
+ s += ':';
+ s += int2str(m.responseAddress.ipv4.port);
+ s += "\n";
+ }
+
+ if (m.hasChangeRequest) {
+ s += "Change Request = ";
+ bool change_flags = false;
+ if (m.changeRequest.value & ChangeIpFlag) {
+ s += "change IP";
+ change_flags = true;
+ }
+ if (m.changeRequest.value & ChangePortFlag) {
+ if (change_flags) s += ", ";
+ s += "change port";
+ change_flags = true;
+ }
+ if (!change_flags) s += "none";
+ s += "\n";
+ }
+
+ if (m.hasSourceAddress) {
+ s += "Source Addres = ";
+ s += h_ip2str(m.sourceAddress.ipv4.addr);
+ s += ':';
+ s += int2str(m.sourceAddress.ipv4.port);
+ s += "\n";
+ }
+
+ if (m.hasChangedAddress) {
+ s += "Changed Addres = ";
+ s += h_ip2str(m.changedAddress.ipv4.addr);
+ s += ':';
+ s += int2str(m.changedAddress.ipv4.port);
+ s += "\n";
+ }
+
+ if (m.hasErrorCode) {
+ s += "Error Code = ";
+ s += int2str(m.errorCode.errorClass * 100 + m.errorCode.number);
+ s += ' ';
+ s += m.errorCode.reason;
+ s += "\n";
+ }
+
+ return s;
+}
+
+bool stunEqualId(const StunMessage &m1, const StunMessage &m2) {
+ for ( int i=0; i<16; i++ )
+ {
+ if (m1.msgHdr.id.octet[i] != m2.msgHdr.id.octet[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+string stunNatType2Str(NatType t) {
+ switch (t) {
+ case StunTypeUnknown:
+ return "Unknow";
+ case StunTypeOpen:
+ return "Open";
+ case StunTypeConeNat:
+ return "Full cone";
+ case StunTypeRestrictedNat:
+ return "Restriced cone";
+ case StunTypePortRestrictedNat:
+ return "Port restricted cone";
+ case StunTypeSymNat:
+ return "Symmetric";
+ case StunTypeSymFirewall:
+ return "Symmetric firewall;";
+ case StunTypeBlocked:
+ return "Blocked.";
+ case StunTypeFailure:
+ return "Failed";
+ default:
+ return "NULL";
+ }
+}
+// Twinkle
+
+#if 0
+static void
+stunCreateSharedSecretErrorResponse(StunMessage& response, int cl, int number, const char* msg)
+{
+ response.msgHdr.msgType = SharedSecretErrorResponseMsg;
+ response.hasErrorCode = true;
+ response.errorCode.errorClass = cl;
+ response.errorCode.number = number;
+ strcpy(response.errorCode.reason, msg);
+}
+#endif
+
+static void
+stunCreateSharedSecretResponse(const StunMessage& request, const StunAddress4& source, StunMessage& response)
+{
+ response.msgHdr.msgType = SharedSecretResponseMsg;
+ response.msgHdr.id = request.msgHdr.id;
+
+ response.hasUsername = true;
+ stunCreateUserName( source, &response.username);
+
+ response.hasPassword = true;
+ stunCreatePassword( response.username, &response.password);
+}
+
+
+// This funtion takes a single message sent to a stun server, parses
+// and constructs an apropriate repsonse - returns true if message is
+// valid
+bool
+stunServerProcessMsg( char* buf,
+ unsigned int bufLen,
+ StunAddress4& from,
+ StunAddress4& secondary,
+ StunAddress4& myAddr,
+ StunAddress4& altAddr,
+ StunMessage* resp,
+ StunAddress4* destination,
+ StunAtrString* hmacPassword,
+ bool* changePort,
+ bool* changeIp,
+ bool verbose)
+{
+
+ // set up information for default response
+
+ memset( resp, 0 , sizeof(*resp) );
+
+ *changeIp = false;
+ *changePort = false;
+
+ StunMessage req;
+ bool ok = stunParseMessage( buf,bufLen, req, verbose);
+
+ if (!ok) // Complete garbage, drop it on the floor
+ {
+ if (verbose) clog << "Request did not parse" << endl;
+ return false;
+ }
+ if (verbose) clog << "Request parsed ok" << endl;
+
+ StunAddress4 mapped = req.mappedAddress.ipv4;
+ StunAddress4 respondTo = req.responseAddress.ipv4;
+ UInt32 flags = req.changeRequest.value;
+
+ switch (req.msgHdr.msgType)
+ {
+ case SharedSecretRequestMsg:
+ if(verbose) clog << "Received SharedSecretRequestMsg on udp. send error 433." << endl;
+ // !cj! - should fix so you know if this came over TLS or UDP
+ stunCreateSharedSecretResponse(req, from, *resp);
+ //stunCreateSharedSecretErrorResponse(*resp, 4, 33, "this request must be over TLS");
+ return true;
+
+ case BindRequestMsg:
+ if (!req.hasMessageIntegrity)
+ {
+ if (verbose) clog << "BindRequest does not contain MessageIntegrity" << endl;
+
+ if (0) // !jf! mustAuthenticate
+ {
+ if(verbose) clog << "Received BindRequest with no MessageIntegrity. Sending 401." << endl;
+ stunCreateErrorResponse(*resp, 4, 1, "Missing MessageIntegrity");
+ return true;
+ }
+ }
+ else
+ {
+ if (!req.hasUsername)
+ {
+ if (verbose) clog << "No UserName. Send 432." << endl;
+ stunCreateErrorResponse(*resp, 4, 32, "No UserName and contains MessageIntegrity");
+ return true;
+ }
+ else
+ {
+ if (verbose) clog << "Validating username: " << req.username.value << endl;
+ // !jf! could retrieve associated password from provisioning here
+ if (strcmp(req.username.value, "test") == 0)
+ {
+ if (0)
+ {
+ // !jf! if the credentials are stale
+ stunCreateErrorResponse(*resp, 4, 30, "Stale credentials on BindRequest");
+ return true;
+ }
+ else
+ {
+ if (verbose) clog << "Validating MessageIntegrity" << endl;
+ // need access to shared secret
+
+ unsigned char hmac[20];
+#ifndef NOSSL
+ unsigned int hmacSize=20;
+
+ HMAC(EVP_sha1(),
+ "1234", 4,
+ reinterpret_cast<const unsigned char*>(buf), bufLen-20-4,
+ hmac, &hmacSize);
+ assert(hmacSize == 20);
+#endif
+
+ if (memcmp(buf, hmac, 20) != 0)
+ {
+ if (verbose) clog << "MessageIntegrity is bad. Sending " << endl;
+ stunCreateErrorResponse(*resp, 4, 3, "Unknown username. Try test with password 1234");
+ return true;
+ }
+
+ // need to compute this later after message is filled in
+ resp->hasMessageIntegrity = true;
+ assert(req.hasUsername);
+ resp->hasUsername = true;
+ resp->username = req.username; // copy username in
+ }
+ }
+ else
+ {
+ if (verbose) clog << "Invalid username: " << req.username.value << "Send 430." << endl;
+ }
+ }
+ }
+
+ // TODO !jf! should check for unknown attributes here and send 420 listing the
+ // unknown attributes.
+
+ if ( respondTo.port == 0 ) respondTo = from;
+ if ( mapped.port == 0 ) mapped = from;
+
+ *changeIp = ( flags & ChangeIpFlag )?true:false;
+ *changePort = ( flags & ChangePortFlag )?true:false;
+
+ if (verbose)
+ {
+ clog << "Request is valid:" << endl;
+ clog << "\t flags=" << flags << endl;
+ clog << "\t changeIp=" << *changeIp << endl;
+ clog << "\t changePort=" << *changePort << endl;
+ clog << "\t from = " << from << endl;
+ clog << "\t respond to = " << respondTo << endl;
+ clog << "\t mapped = " << mapped << endl;
+ }
+
+ // form the outgoing message
+ resp->msgHdr.msgType = BindResponseMsg;
+ for ( int i=0; i<16; i++ )
+ {
+ resp->msgHdr.id.octet[i] = req.msgHdr.id.octet[i];
+ }
+
+ if ( req.xorOnly == false )
+ {
+ resp->hasMappedAddress = true;
+ resp->mappedAddress.ipv4.port = mapped.port;
+ resp->mappedAddress.ipv4.addr = mapped.addr;
+ }
+
+ if (1) // do xorMapped address or not
+ {
+ resp->hasXorMappedAddress = true;
+ UInt16 id16 = req.msgHdr.id.octet[7]<<8
+ | req.msgHdr.id.octet[6];
+ UInt32 id32 = req.msgHdr.id.octet[7]<<24
+ | req.msgHdr.id.octet[6]<<16
+ | req.msgHdr.id.octet[5]<<8
+ | req.msgHdr.id.octet[4];;
+ resp->xorMappedAddress.ipv4.port = mapped.port^id16;
+ resp->xorMappedAddress.ipv4.addr = mapped.addr^id32;
+ }
+
+ resp->hasSourceAddress = true;
+ resp->sourceAddress.ipv4.port = (*changePort) ? altAddr.port : myAddr.port;
+ resp->sourceAddress.ipv4.addr = (*changeIp) ? altAddr.addr : myAddr.addr;
+
+ resp->hasChangedAddress = true;
+ resp->changedAddress.ipv4.port = altAddr.port;
+ resp->changedAddress.ipv4.addr = altAddr.addr;
+
+ if ( secondary.port != 0 )
+ {
+ resp->hasSecondaryAddress = true;
+ resp->secondaryAddress.ipv4.port = secondary.port;
+ resp->secondaryAddress.ipv4.addr = secondary.addr;
+ }
+
+ if ( req.hasUsername && req.username.sizeValue > 0 )
+ {
+ // copy username in
+ resp->hasUsername = true;
+ assert( req.username.sizeValue % 4 == 0 );
+ assert( req.username.sizeValue < STUN_MAX_STRING );
+ memcpy( resp->username.value, req.username.value, req.username.sizeValue );
+ resp->username.sizeValue = req.username.sizeValue;
+ }
+
+ if (1) // add ServerName
+ {
+ resp->hasServerName = true;
+ const char serverName[] = "Vovida.org " STUN_VERSION; // must pad to mult of 4
+
+ assert( sizeof(serverName) < STUN_MAX_STRING );
+ //cerr << "sizeof serverName is " << sizeof(serverName) << endl;
+ assert( sizeof(serverName)%4 == 0 );
+ memcpy( resp->serverName.value, serverName, sizeof(serverName));
+ resp->serverName.sizeValue = sizeof(serverName);
+ }
+
+ if ( req.hasMessageIntegrity & req.hasUsername )
+ {
+ // this creates the password that will be used in the HMAC when then
+ // messages is sent
+ stunCreatePassword( req.username, hmacPassword );
+ }
+
+ if (req.hasUsername && (req.username.sizeValue > 64 ) )
+ {
+ UInt32 source;
+ assert( sizeof(int) == sizeof(UInt32) );
+
+ sscanf(req.username.value, "%x", &source);
+ resp->hasReflectedFrom = true;
+ resp->reflectedFrom.ipv4.port = 0;
+ resp->reflectedFrom.ipv4.addr = source;
+ }
+
+ destination->port = respondTo.port;
+ destination->addr = respondTo.addr;
+
+ return true;
+
+ default:
+ if (verbose) clog << "Unknown or unsupported request " << endl;
+ return false;
+ }
+
+ assert(0);
+ return false;
+}
+
+bool
+stunInitServer(StunServerInfo& info, const StunAddress4& myAddr, const StunAddress4& altAddr, int startMediaPort, bool verbose )
+{
+ assert( myAddr.port != 0 );
+ assert( altAddr.port!= 0 );
+ assert( myAddr.addr != 0 );
+ //assert( altAddr.addr != 0 );
+
+ info.myAddr = myAddr;
+ info.altAddr = altAddr;
+
+ info.myFd = INVALID_SOCKET;
+ info.altPortFd = INVALID_SOCKET;
+ info.altIpFd = INVALID_SOCKET;
+ info.altIpPortFd = INVALID_SOCKET;
+
+ memset(info.relays, 0, sizeof(info.relays));
+ if (startMediaPort > 0)
+ {
+ info.relay = true;
+
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ relay->relayPort = startMediaPort+i;
+ relay->fd = 0;
+ relay->expireTime = 0;
+ }
+ }
+ else
+ {
+ info.relay = false;
+ }
+
+ if ((info.myFd = openPort(myAddr.port, myAddr.addr,verbose)) == INVALID_SOCKET)
+ {
+ clog << "Can't open " << myAddr << endl;
+ stunStopServer(info);
+
+ return false;
+ }
+ //if (verbose) clog << "Opened " << myAddr.addr << ":" << myAddr.port << " --> " << info.myFd << endl;
+
+ if ((info.altPortFd = openPort(altAddr.port,myAddr.addr,verbose)) == INVALID_SOCKET)
+ {
+ clog << "Can't open " << myAddr << endl;
+ stunStopServer(info);
+ return false;
+ }
+ //if (verbose) clog << "Opened " << myAddr.addr << ":" << altAddr.port << " --> " << info.altPortFd << endl;
+
+
+ info.altIpFd = INVALID_SOCKET;
+ if ( altAddr.addr != 0 )
+ {
+ if ((info.altIpFd = openPort( myAddr.port, altAddr.addr,verbose)) == INVALID_SOCKET)
+ {
+ clog << "Can't open " << altAddr << endl;
+ stunStopServer(info);
+ return false;
+ }
+ //if (verbose) clog << "Opened " << altAddr.addr << ":" << myAddr.port << " --> " << info.altIpFd << endl;;
+ }
+
+ info.altIpPortFd = INVALID_SOCKET;
+ if ( altAddr.addr != 0 )
+ { if ((info.altIpPortFd = openPort(altAddr.port, altAddr.addr,verbose)) == INVALID_SOCKET)
+ {
+ clog << "Can't open " << altAddr << endl;
+ stunStopServer(info);
+ return false;
+ }
+ //if (verbose) clog << "Opened " << altAddr.addr << ":" << altAddr.port << " --> " << info.altIpPortFd << endl;;
+ }
+
+ return true;
+}
+
+void
+stunStopServer(StunServerInfo& info)
+{
+ if (info.myFd > 0) closesocket(info.myFd);
+ if (info.altPortFd > 0) closesocket(info.altPortFd);
+ if (info.altIpFd > 0) closesocket(info.altIpFd);
+ if (info.altIpPortFd > 0) closesocket(info.altIpPortFd);
+
+ if (info.relay)
+ {
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ if (relay->fd)
+ {
+ closesocket(relay->fd);
+ relay->fd = 0;
+ }
+ }
+ }
+}
+
+
+bool
+stunServerProcess(StunServerInfo& info, bool verbose)
+{
+ char msg[STUN_MAX_MESSAGE_SIZE];
+ int msgLen = sizeof(msg);
+
+ bool ok = false;
+ bool recvAltIp =false;
+ bool recvAltPort = false;
+
+ fd_set fdSet;
+#ifdef WIN32
+ unsigned int maxFd=0;
+#else
+ int maxFd=0;
+#endif
+ FD_ZERO(&fdSet);
+ FD_SET(info.myFd,&fdSet);
+ if ( info.myFd >= maxFd ) maxFd=info.myFd+1;
+ FD_SET(info.altPortFd,&fdSet);
+ if ( info.altPortFd >= maxFd ) maxFd=info.altPortFd+1;
+
+ if ( info.altIpFd != INVALID_SOCKET )
+ {
+ FD_SET(info.altIpFd,&fdSet);
+ if (info.altIpFd>=maxFd) maxFd=info.altIpFd+1;
+ }
+ if ( info.altIpPortFd != INVALID_SOCKET )
+ {
+ FD_SET(info.altIpPortFd,&fdSet);
+ if (info.altIpPortFd>=maxFd) maxFd=info.altIpPortFd+1;
+ }
+
+ if (info.relay)
+ {
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ if (relay->fd)
+ {
+ FD_SET(relay->fd, &fdSet);
+ if (relay->fd >= maxFd) maxFd=relay->fd+1;
+ }
+ }
+ }
+
+ if ( info.altIpFd != INVALID_SOCKET )
+ {
+ FD_SET(info.altIpFd,&fdSet);
+ if (info.altIpFd>=maxFd) maxFd=info.altIpFd+1;
+ }
+ if ( info.altIpPortFd != INVALID_SOCKET )
+ {
+ FD_SET(info.altIpPortFd,&fdSet);
+ if (info.altIpPortFd>=maxFd) maxFd=info.altIpPortFd+1;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000;
+
+ int e = select( maxFd, &fdSet, NULL,NULL, &tv );
+ if (e < 0)
+ {
+ int err = getErrno();
+ clog << "Error on select: " << strerror(err) << endl;
+ }
+ else if (e >= 0)
+ {
+ StunAddress4 from;
+
+ // do the media relaying
+ if (info.relay)
+ {
+ time_t now = time(0);
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ if (relay->fd)
+ {
+ if (FD_ISSET(relay->fd, &fdSet))
+ {
+ char msg[MAX_RTP_MSG_SIZE];
+ int msgLen = sizeof(msg);
+
+ StunAddress4 rtpFrom;
+ ok = getMessage( relay->fd, msg, &msgLen, &rtpFrom.addr, &rtpFrom.port ,verbose);
+ if (ok)
+ {
+ sendMessage(info.myFd, msg, msgLen, relay->destination.addr, relay->destination.port, verbose);
+ relay->expireTime = now + MEDIA_RELAY_TIMEOUT;
+ if ( verbose ) clog << "Relay packet on "
+ << relay->fd
+ << " from " << rtpFrom
+ << " -> " << relay->destination
+ << endl;
+ }
+ }
+ else if (now > relay->expireTime)
+ {
+ closesocket(relay->fd);
+ relay->fd = 0;
+ }
+ }
+ }
+ }
+
+
+ if (FD_ISSET(info.myFd,&fdSet))
+ {
+ if (verbose) clog << "received on A1:P1" << endl;
+ recvAltIp = false;
+ recvAltPort = false;
+ ok = getMessage( info.myFd, msg, &msgLen, &from.addr, &from.port,verbose );
+ }
+ else if (FD_ISSET(info.altPortFd, &fdSet))
+ {
+ if (verbose) clog << "received on A1:P2" << endl;
+ recvAltIp = false;
+ recvAltPort = true;
+ ok = getMessage( info.altPortFd, msg, &msgLen, &from.addr, &from.port,verbose );
+ }
+ else if ( (info.altIpFd!=INVALID_SOCKET) && FD_ISSET(info.altIpFd,&fdSet))
+ {
+ if (verbose) clog << "received on A2:P1" << endl;
+ recvAltIp = true;
+ recvAltPort = false;
+ ok = getMessage( info.altIpFd, msg, &msgLen, &from.addr, &from.port ,verbose);
+ }
+ else if ( (info.altIpPortFd!=INVALID_SOCKET) && FD_ISSET(info.altIpPortFd, &fdSet))
+ {
+ if (verbose) clog << "received on A2:P2" << endl;
+ recvAltIp = true;
+ recvAltPort = true;
+ ok = getMessage( info.altIpPortFd, msg, &msgLen, &from.addr, &from.port,verbose );
+ }
+ else
+ {
+ return true;
+ }
+
+ int relayPort = 0;
+ if (info.relay)
+ {
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ if (relay->destination.addr == from.addr &&
+ relay->destination.port == from.port)
+ {
+ relayPort = relay->relayPort;
+ relay->expireTime = time(0) + MEDIA_RELAY_TIMEOUT;
+ break;
+ }
+ }
+
+ if (relayPort == 0)
+ {
+ for (int i=0; i<MAX_MEDIA_RELAYS; ++i)
+ {
+ StunMediaRelay* relay = &info.relays[i];
+ if (relay->fd == 0)
+ {
+ if ( verbose ) clog << "Open relay port " << relay->relayPort << endl;
+
+ relay->fd = openPort(relay->relayPort, info.myAddr.addr, verbose);
+ relay->destination.addr = from.addr;
+ relay->destination.port = from.port;
+ relay->expireTime = time(0) + MEDIA_RELAY_TIMEOUT;
+ relayPort = relay->relayPort;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !ok )
+ {
+ if ( verbose ) clog << "Get message did not return a valid message" <<endl;
+ return true;
+ }
+
+ if ( verbose ) clog << "Got a request (len=" << msgLen << ") from " << from << endl;
+
+ if ( msgLen <= 0 )
+ {
+ return true;
+ }
+
+ bool changePort = false;
+ bool changeIp = false;
+
+ StunMessage resp;
+ StunAddress4 dest;
+ StunAtrString hmacPassword;
+ hmacPassword.sizeValue = 0;
+
+ StunAddress4 secondary;
+ secondary.port = 0;
+ secondary.addr = 0;
+
+ if (info.relay && relayPort)
+ {
+ secondary = from;
+
+ from.addr = info.myAddr.addr;
+ from.port = relayPort;
+ }
+
+ ok = stunServerProcessMsg( msg, msgLen, from, secondary,
+ recvAltIp ? info.altAddr : info.myAddr,
+ recvAltIp ? info.myAddr : info.altAddr,
+ &resp,
+ &dest,
+ &hmacPassword,
+ &changePort,
+ &changeIp,
+ verbose );
+
+ if ( !ok )
+ {
+ if ( verbose ) clog << "Failed to parse message" << endl;
+ return true;
+ }
+
+ char buf[STUN_MAX_MESSAGE_SIZE];
+ int len = sizeof(buf);
+
+ len = stunEncodeMessage( resp, buf, len, hmacPassword,verbose );
+
+ if ( dest.addr == 0 ) ok=false;
+ if ( dest.port == 0 ) ok=false;
+
+ if ( ok )
+ {
+ assert( dest.addr != 0 );
+ assert( dest.port != 0 );
+
+ StunSocket sendFd;
+
+ bool sendAltIp = recvAltIp; // send on the received IP address
+ bool sendAltPort = recvAltPort; // send on the received port
+
+ if ( changeIp ) sendAltIp = !sendAltIp; // if need to change IP, then flip logic
+ if ( changePort ) sendAltPort = !sendAltPort; // if need to change port, then flip logic
+
+ if ( !sendAltPort )
+ {
+ if ( !sendAltIp )
+ {
+ sendFd = info.myFd;
+ }
+ else
+ {
+ sendFd = info.altIpFd;
+ }
+ }
+ else
+ {
+ if ( !sendAltIp )
+ {
+ sendFd = info.altPortFd;
+ }
+ else
+ {
+ sendFd = info.altIpPortFd;
+ }
+ }
+
+ if ( sendFd != INVALID_SOCKET )
+ {
+ sendMessage( sendFd, buf, len, dest.addr, dest.port, verbose );
+ }
+ }
+ }
+
+ return true;
+}
+
+int
+stunFindLocalInterfaces(UInt32* addresses,int maxRet)
+{
+#if defined(WIN32) || defined(__sparc__)
+ return 0;
+#else
+ struct ifconf ifc;
+
+ int s = socket( AF_INET, SOCK_DGRAM, 0 );
+ int len = 100 * sizeof(struct ifreq);
+
+ char buf[ len ];
+
+ ifc.ifc_len = len;
+ ifc.ifc_buf = buf;
+
+ int e = ioctl(s,SIOCGIFCONF,&ifc);
+ char *ptr = buf;
+ int tl = ifc.ifc_len;
+ int count=0;
+
+ while ( (tl > 0) && ( count < maxRet) )
+ {
+ struct ifreq* ifr = (struct ifreq *)ptr;
+
+ int si = sizeof(ifr->ifr_name) + sizeof(struct sockaddr);
+ tl -= si;
+ ptr += si;
+ //char* name = ifr->ifr_ifrn.ifrn_name;
+ //cerr << "name = " << name << endl;
+
+ struct ifreq ifr2;
+ ifr2 = *ifr;
+
+ e = ioctl(s,SIOCGIFADDR,&ifr2);
+ if ( e == -1 )
+ {
+ break;
+ }
+
+ //cerr << "ioctl addr e = " << e << endl;
+
+ struct sockaddr a = ifr2.ifr_addr;
+ struct sockaddr_in* addr = (struct sockaddr_in*) &a;
+
+ UInt32 ai = ntohl( addr->sin_addr.s_addr );
+ if (int((ai>>24)&0xFF) != 127)
+ {
+ addresses[count++] = ai;
+ }
+
+#if 0
+ cerr << "Detected interface "
+ << int((ai>>24)&0xFF) << "."
+ << int((ai>>16)&0xFF) << "."
+ << int((ai>> 8)&0xFF) << "."
+ << int((ai )&0xFF) << endl;
+#endif
+ }
+
+ closesocket(s);
+
+ return count;
+#endif
+}
+
+
+void
+stunBuildReqSimple( StunMessage* msg,
+ const StunAtrString& username,
+ bool changePort, bool changeIp, unsigned int id )
+{
+ assert( msg );
+ memset( msg , 0 , sizeof(*msg) );
+
+ msg->msgHdr.msgType = BindRequestMsg;
+
+ for ( int i=0; i<16; i=i+4 )
+ {
+ assert(i+3<16);
+ int r = stunRand();
+ msg->msgHdr.id.octet[i+0]= r>>0;
+ msg->msgHdr.id.octet[i+1]= r>>8;
+ msg->msgHdr.id.octet[i+2]= r>>16;
+ msg->msgHdr.id.octet[i+3]= r>>24;
+ }
+
+ if ( id != 0 )
+ {
+ msg->msgHdr.id.octet[0] = id;
+ }
+
+ msg->hasChangeRequest = true;
+ msg->changeRequest.value =(changeIp?ChangeIpFlag:0) |
+ (changePort?ChangePortFlag:0);
+
+ if ( username.sizeValue > 0 )
+ {
+ msg->hasUsername = true;
+ msg->username = username;
+ }
+}
+
+
+static void
+stunSendTest( StunSocket myFd, StunAddress4& dest,
+ const StunAtrString& username, const StunAtrString& password,
+ int testNum, bool verbose )
+{
+ assert( dest.addr != 0 );
+ assert( dest.port != 0 );
+
+ bool changePort=false;
+ bool changeIP=false;
+ bool discard=false;
+
+ switch (testNum)
+ {
+ case 1:
+ case 10:
+ case 11:
+ break;
+ case 2:
+ //changePort=true;
+ changeIP=true;
+ break;
+ case 3:
+ changePort=true;
+ break;
+ case 4:
+ changeIP=true;
+ break;
+ case 5:
+ discard=true;
+ break;
+ default:
+ cerr << "Test " << testNum <<" is unkown\n";
+ assert(0);
+ }
+
+ StunMessage req;
+ memset(&req, 0, sizeof(StunMessage));
+
+ stunBuildReqSimple( &req, username,
+ changePort , changeIP ,
+ testNum );
+
+ char buf[STUN_MAX_MESSAGE_SIZE];
+ int len = STUN_MAX_MESSAGE_SIZE;
+
+ len = stunEncodeMessage( req, buf, len, password,verbose );
+
+ if ( verbose )
+ {
+ clog << "About to send msg of len " << len << " to " << dest << endl;
+ }
+
+ sendMessage( myFd, buf, len, dest.addr, dest.port, verbose );
+
+ // add some delay so the packets don't get sent too quickly
+#ifdef WIN32 // !cj! TODO - should fix this up in windows
+ clock_t now = clock();
+ assert( CLOCKS_PER_SEC == 1000 );
+ while ( clock() <= now+10 ) { };
+#else
+ usleep(10*1000);
+#endif
+
+}
+
+
+void
+stunGetUserNameAndPassword( const StunAddress4& dest,
+ StunAtrString* username,
+ StunAtrString* password)
+{
+ // !cj! This is totally bogus - need to make TLS connection to dest and get a
+ // username and password to use
+ stunCreateUserName(dest, username);
+ stunCreatePassword(*username, password);
+}
+
+
+void
+stunTest( StunAddress4& dest, int testNum, bool verbose, StunAddress4* sAddr )
+{
+ assert( dest.addr != 0 );
+ assert( dest.port != 0 );
+
+ int port = randomPort();
+ UInt32 interfaceIp=0;
+ if (sAddr)
+ {
+ interfaceIp = sAddr->addr;
+ if ( sAddr->port != 0 )
+ {
+ port = sAddr->port;
+ }
+ }
+ StunSocket myFd = openPort(port,interfaceIp,verbose);
+
+ StunAtrString username;
+ StunAtrString password;
+
+ username.sizeValue = 0;
+ password.sizeValue = 0;
+
+#ifdef USE_TLS
+ stunGetUserNameAndPassword( dest, username, password );
+#endif
+
+ stunSendTest( myFd, dest, username, password, testNum, verbose );
+
+ char msg[STUN_MAX_MESSAGE_SIZE];
+ int msgLen = STUN_MAX_MESSAGE_SIZE;
+
+ StunAddress4 from;
+ getMessage( myFd,
+ msg,
+ &msgLen,
+ &from.addr,
+ &from.port,verbose );
+
+ StunMessage resp;
+ memset(&resp, 0, sizeof(StunMessage));
+
+ if ( verbose ) clog << "Got a response" << endl;
+ bool ok = stunParseMessage( msg,msgLen, resp,verbose );
+
+ if ( verbose )
+ {
+ clog << "\t ok=" << ok << endl;
+ clog << "\t id=" << resp.msgHdr.id << endl;
+ clog << "\t mappedAddr=" << resp.mappedAddress.ipv4 << endl;
+ clog << "\t changedAddr=" << resp.changedAddress.ipv4 << endl;
+ clog << endl;
+ }
+
+ if (sAddr)
+ {
+ sAddr->port = resp.mappedAddress.ipv4.port;
+ sAddr->addr = resp.mappedAddress.ipv4.addr;
+ }
+}
+
+
+NatType
+stunNatType( StunAddress4& dest,
+ bool verbose,
+ bool* preservePort, // if set, is return for if NAT preservers ports or not
+ bool* hairpin, // if set, is the return for if NAT will hairpin packets
+ int port, // port to use for the test, 0 to choose random port
+ StunAddress4* sAddr // NIC to use
+ )
+{
+ assert( dest.addr != 0 );
+ assert( dest.port != 0 );
+
+ if ( hairpin )
+ {
+ *hairpin = false;
+ }
+
+ if ( port == 0 )
+ {
+ port = randomPort();
+ }
+ UInt32 interfaceIp=0;
+ if (sAddr)
+ {
+ interfaceIp = sAddr->addr;
+ }
+ StunSocket myFd1 = openPort(port,interfaceIp,verbose);
+ StunSocket myFd2 = openPort(port+1,interfaceIp,verbose);
+
+ if ( ( myFd1 == INVALID_SOCKET) || ( myFd2 == INVALID_SOCKET) )
+ {
+ cerr << "Some problem opening port/interface to send on" << endl;
+ return StunTypeFailure;
+ }
+
+ assert( myFd1 != INVALID_SOCKET );
+ assert( myFd2 != INVALID_SOCKET );
+
+ bool respTestI=false;
+ bool isNat=true;
+ StunAddress4 testIchangedAddr;
+ StunAddress4 testImappedAddr;
+ bool respTestI2=false;
+ bool mappedIpSame = true;
+ StunAddress4 testI2mappedAddr;
+ StunAddress4 testI2dest=dest;
+ bool respTestII=false;
+ bool respTestIII=false;
+
+ bool respTestHairpin=false;
+
+ memset(&testImappedAddr,0,sizeof(testImappedAddr));
+
+ StunAtrString username;
+ StunAtrString password;
+
+ username.sizeValue = 0;
+ password.sizeValue = 0;
+
+#ifdef USE_TLS
+ stunGetUserNameAndPassword( dest, username, password );
+#endif
+
+ //stunSendTest( myFd1, dest, username, password, 1, verbose );
+ int count=0;
+ while ( count < 7 )
+ {
+ struct timeval tv;
+ fd_set fdSet;
+#ifdef WIN32
+ unsigned int fdSetSize;
+#else
+ int fdSetSize;
+#endif
+ FD_ZERO(&fdSet); fdSetSize=0;
+ FD_SET(myFd1,&fdSet); fdSetSize = (myFd1+1>fdSetSize) ? myFd1+1 : fdSetSize;
+ FD_SET(myFd2,&fdSet); fdSetSize = (myFd2+1>fdSetSize) ? myFd2+1 : fdSetSize;
+ tv.tv_sec=0;
+ tv.tv_usec=150*1000; // 150 ms
+ if ( count == 0 ) tv.tv_usec=0;
+
+ int err = select(fdSetSize, &fdSet, NULL, NULL, &tv);
+ int e = getErrno();
+ if ( err == SOCKET_ERROR )
+ {
+ // error occured
+ cerr << "Error " << e << " " << strerror(e) << " in select" << endl;
+ closesocket(myFd1);
+ closesocket(myFd2);
+ return StunTypeFailure;
+ }
+ else if ( err == 0 )
+ {
+ // timeout occured
+ count++;
+
+ if ( !respTestI )
+ {
+ stunSendTest( myFd1, dest, username, password, 1 ,verbose );
+ }
+
+ if ( (!respTestI2) && respTestI )
+ {
+ // check the address to send to if valid
+ if ( ( testI2dest.addr != 0 ) &&
+ ( testI2dest.port != 0 ) )
+ {
+ stunSendTest( myFd1, testI2dest, username, password, 10 ,verbose);
+ }
+ }
+
+ if ( !respTestII )
+ {
+ stunSendTest( myFd2, dest, username, password, 2 ,verbose );
+ }
+
+ if ( !respTestIII )
+ {
+ stunSendTest( myFd2, dest, username, password, 3 ,verbose );
+ }
+
+ if ( respTestI && (!respTestHairpin) )
+ {
+ if ( ( testImappedAddr.addr != 0 ) &&
+ ( testImappedAddr.port != 0 ) )
+ {
+ stunSendTest( myFd1, testImappedAddr, username, password, 11 ,verbose );
+ }
+ }
+ }
+ else
+ {
+ //if (verbose) clog << "-----------------------------------------" << endl;
+ assert( err>0 );
+ // data is avialbe on some fd
+
+ for ( int i=0; i<2; i++)
+ {
+ StunSocket myFd;
+ if ( i==0 )
+ {
+ myFd=myFd1;
+ }
+ else
+ {
+ myFd=myFd2;
+ }
+
+ if ( myFd!=INVALID_SOCKET )
+ {
+ if ( FD_ISSET(myFd,&fdSet) )
+ {
+ char msg[STUN_MAX_MESSAGE_SIZE];
+ int msgLen = sizeof(msg);
+
+ StunAddress4 from;
+
+ getMessage( myFd,
+ msg,
+ &msgLen,
+ &from.addr,
+ &from.port,verbose );
+
+ StunMessage resp;
+ memset(&resp, 0, sizeof(StunMessage));
+
+ stunParseMessage( msg,msgLen, resp,verbose );
+
+ if ( verbose )
+ {
+ clog << "Received message of type " << resp.msgHdr.msgType
+ << " id=" << (int)(resp.msgHdr.id.octet[0]) << endl;
+ }
+
+ switch( resp.msgHdr.id.octet[0] )
+ {
+ case 1:
+ {
+ if ( !respTestI )
+ {
+
+ testIchangedAddr.addr = resp.changedAddress.ipv4.addr;
+ testIchangedAddr.port = resp.changedAddress.ipv4.port;
+ testImappedAddr.addr = resp.mappedAddress.ipv4.addr;
+ testImappedAddr.port = resp.mappedAddress.ipv4.port;
+
+ if ( preservePort )
+ {
+ *preservePort = ( testImappedAddr.port == port );
+ }
+
+ testI2dest.addr = resp.changedAddress.ipv4.addr;
+
+ if (sAddr)
+ {
+ sAddr->port = testImappedAddr.port;
+ sAddr->addr = testImappedAddr.addr;
+ }
+
+ count = 0;
+ }
+ respTestI=true;
+ }
+ break;
+ case 2:
+ {
+ respTestII=true;
+ }
+ break;
+ case 3:
+ {
+ respTestIII=true;
+ }
+ break;
+ case 10:
+ {
+ if ( !respTestI2 )
+ {
+ testI2mappedAddr.addr = resp.mappedAddress.ipv4.addr;
+ testI2mappedAddr.port = resp.mappedAddress.ipv4.port;
+
+ mappedIpSame = false;
+ if ( (testI2mappedAddr.addr == testImappedAddr.addr ) &&
+ (testI2mappedAddr.port == testImappedAddr.port ))
+ {
+ mappedIpSame = true;
+ }
+
+
+ }
+ respTestI2=true;
+ }
+ break;
+ case 11:
+ {
+
+ if ( hairpin )
+ {
+ *hairpin = true;
+ }
+ respTestHairpin = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // see if we can bind to this address
+ //cerr << "try binding to " << testImappedAddr << endl;
+ StunSocket s = openPort( 0/*use ephemeral*/, testImappedAddr.addr, false );
+ if ( s != INVALID_SOCKET )
+ {
+ closesocket(s);
+ isNat = false;
+ //cerr << "binding worked" << endl;
+ }
+ else
+ {
+ isNat = true;
+ //cerr << "binding failed" << endl;
+ }
+
+ closesocket(myFd1);
+ closesocket(myFd2);
+
+ if (verbose)
+ {
+ clog << "test I = " << respTestI << endl;
+ clog << "test II = " << respTestII << endl;
+ clog << "test III = " << respTestIII << endl;
+ clog << "test I(2) = " << respTestI2 << endl;
+ clog << "is nat = " << isNat <<endl;
+ clog << "mapped IP same = " << mappedIpSame << endl;
+ }
+
+ // implement logic flow chart from draft RFC
+ if ( respTestI )
+ {
+ if ( isNat )
+ {
+ if (respTestII)
+ {
+ return StunTypeConeNat;
+ }
+ else
+ {
+ if ( mappedIpSame )
+ {
+ if ( respTestIII )
+ {
+ return StunTypeRestrictedNat;
+ }
+ else
+ {
+ return StunTypePortRestrictedNat;
+ }
+ }
+ else
+ {
+ return StunTypeSymNat;
+ }
+ }
+ }
+ else
+ {
+ if (respTestII)
+ {
+ return StunTypeOpen;
+ }
+ else
+ {
+ return StunTypeSymFirewall;
+ }
+ }
+ }
+ else
+ {
+ return StunTypeBlocked;
+ }
+
+ return StunTypeUnknown;
+}
+
+
+int
+stunOpenStunSocket( StunAddress4& dest, StunAddress4* mapAddr,
+ int port, StunAddress4* srcAddr,
+ bool verbose )
+{
+ assert( dest.addr != 0 );
+ assert( dest.port != 0 );
+ assert( mapAddr );
+
+ if ( port == 0 )
+ {
+ port = randomPort();
+ }
+ unsigned int interfaceIp = 0;
+ if ( srcAddr )
+ {
+ interfaceIp = srcAddr->addr;
+ }
+
+ StunSocket myFd = openPort(port,interfaceIp,verbose);
+ if (myFd == INVALID_SOCKET)
+ {
+ return myFd;
+ }
+
+ char msg[STUN_MAX_MESSAGE_SIZE];
+ int msgLen = sizeof(msg);
+
+ StunAtrString username;
+ StunAtrString password;
+
+ username.sizeValue = 0;
+ password.sizeValue = 0;
+
+#ifdef USE_TLS
+ stunGetUserNameAndPassword( dest, username, password );
+#endif
+
+ stunSendTest(myFd, dest, username, password, 1, 0/*false*/ );
+
+ StunAddress4 from;
+
+ getMessage( myFd, msg, &msgLen, &from.addr, &from.port,verbose );
+
+ StunMessage resp;
+ memset(&resp, 0, sizeof(StunMessage));
+
+ bool ok = stunParseMessage( msg, msgLen, resp,verbose );
+ if (!ok)
+ {
+ return -1;
+ }
+
+ StunAddress4 mappedAddr = resp.mappedAddress.ipv4;
+ StunAddress4 changedAddr = resp.changedAddress.ipv4;
+
+ //clog << "--- stunOpenStunSocket --- " << endl;
+ //clog << "\treq id=" << req.id << endl;
+ //clog << "\tresp id=" << id << endl;
+ //clog << "\tmappedAddr=" << mappedAddr << endl;
+
+ *mapAddr = mappedAddr;
+
+ return myFd;
+}
+
+
+bool
+stunOpenStunSocketPair( StunAddress4& dest, StunAddress4* mapAddr,
+ int* fd1, int* fd2,
+ int port, StunAddress4* srcAddr,
+ bool verbose )
+{
+ assert( dest.addr!= 0 );
+ assert( dest.port != 0 );
+ assert( mapAddr );
+
+ const int NUM=3;
+
+ if ( port == 0 )
+ {
+ port = randomPort();
+ }
+
+ *fd1=-1;
+ *fd2=-1;
+
+ char msg[STUN_MAX_MESSAGE_SIZE];
+ int msgLen =sizeof(msg);
+
+ StunAddress4 from;
+ int fd[NUM];
+ int i;
+
+ unsigned int interfaceIp = 0;
+ if ( srcAddr )
+ {
+ interfaceIp = srcAddr->addr;
+ }
+
+ for( i=0; i<NUM; i++)
+ {
+ fd[i] = openPort( (port == 0) ? 0 : (port + i),
+ interfaceIp, verbose);
+ if (fd[i] < 0)
+ {
+ while (i > 0)
+ {
+ closesocket(fd[--i]);
+ }
+ return false;
+ }
+ }
+
+ StunAtrString username;
+ StunAtrString password;
+
+ username.sizeValue = 0;
+ password.sizeValue = 0;
+
+#ifdef USE_TLS
+ stunGetUserNameAndPassword( dest, username, password );
+#endif
+
+ for( i=0; i<NUM; i++)
+ {
+ stunSendTest(fd[i], dest, username, password, 1/*testNum*/, verbose );
+ }
+
+ StunAddress4 mappedAddr[NUM];
+ for( i=0; i<NUM; i++)
+ {
+ msgLen = sizeof(msg)/sizeof(*msg);
+ getMessage( fd[i],
+ msg,
+ &msgLen,
+ &from.addr,
+ &from.port ,verbose);
+
+ StunMessage resp;
+ memset(&resp, 0, sizeof(StunMessage));
+
+ bool ok = stunParseMessage( msg, msgLen, resp, verbose );
+ if (!ok)
+ {
+ return false;
+ }
+
+ mappedAddr[i] = resp.mappedAddress.ipv4;
+ StunAddress4 changedAddr = resp.changedAddress.ipv4;
+ }
+
+ if (verbose)
+ {
+ clog << "--- stunOpenStunSocketPair --- " << endl;
+ for( i=0; i<NUM; i++)
+ {
+ clog << "\t mappedAddr=" << mappedAddr[i] << endl;
+ }
+ }
+
+ if ( mappedAddr[0].port %2 == 0 )
+ {
+ if ( mappedAddr[0].port+1 == mappedAddr[1].port )
+ {
+ *mapAddr = mappedAddr[0];
+ *fd1 = fd[0];
+ *fd2 = fd[1];
+ closesocket( fd[2] );
+ return true;
+ }
+ }
+ else
+ {
+ if (( mappedAddr[1].port %2 == 0 )
+ && ( mappedAddr[1].port+1 == mappedAddr[2].port ))
+ {
+ *mapAddr = mappedAddr[1];
+ *fd1 = fd[1];
+ *fd2 = fd[2];
+ closesocket( fd[0] );
+ return true;
+ }
+ }
+
+ // something failed, close all and return error
+ for( i=0; i<NUM; i++)
+ {
+ closesocket( fd[i] );
+ }
+
+ return false;
+}
+
+/* ====================================================================
+ * The Vovida Software License, Version 1.0
+ *
+ * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The names "VOCAL", "Vovida Open Communication Application Library",
+ * and "Vovida Open Communication Application Library (VOCAL)" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact vocal@vovida.org.
+ *
+ * 4. Products derived from this software may not be called "VOCAL", nor
+ * may "VOCAL" appear in their name, without prior written
+ * permission of Vovida Networks, Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
+ * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
+ * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by Vovida
+ * Networks, Inc. and many individuals on behalf of Vovida Networks,
+ * Inc. For more information on Vovida Networks, Inc., please see
+ * <http://www.vovida.org/>.
+ *
+ */
+
+// Local Variables:
+// mode:c++
+// c-file-style:"ellemtel"
+// c-file-offsets:((case-label . +))
+// indent-tabs-mode:nil
+// End:
+
+
diff --git a/src/stun/stun.h b/src/stun/stun.h
new file mode 100644
index 0000000..1746ce5
--- /dev/null
+++ b/src/stun/stun.h
@@ -0,0 +1,405 @@
+#ifndef STUN_H
+#define STUN_H
+
+#include <iostream>
+#include <time.h>
+#include <string>
+
+using namespace std;
+
+// if you change this version, change in makefile too
+#define STUN_VERSION "0.94"
+
+#define STUN_MAX_STRING 256
+#define STUN_MAX_UNKNOWN_ATTRIBUTES 8
+#define STUN_MAX_MESSAGE_SIZE 2048
+
+#define STUN_PORT 3478
+
+// define some basic types
+typedef unsigned char UInt8;
+typedef unsigned short UInt16;
+typedef unsigned int UInt32;
+#if defined( WIN32 )
+typedef unsigned __int64 UInt64;
+#else
+typedef unsigned long long UInt64;
+#endif
+typedef struct { unsigned char octet[16]; } UInt128;
+
+/// define a structure to hold a stun address
+const UInt8 IPv4Family = 0x01;
+const UInt8 IPv6Family = 0x02;
+
+// define flags
+const UInt32 ChangeIpFlag = 0x04;
+const UInt32 ChangePortFlag = 0x02;
+
+// define stun attribute
+const UInt16 MappedAddress = 0x0001;
+const UInt16 ResponseAddress = 0x0002;
+const UInt16 ChangeRequest = 0x0003;
+const UInt16 SourceAddress = 0x0004;
+const UInt16 ChangedAddress = 0x0005;
+const UInt16 Username = 0x0006;
+const UInt16 Password = 0x0007;
+const UInt16 MessageIntegrity = 0x0008;
+const UInt16 ErrorCode = 0x0009;
+const UInt16 UnknownAttribute = 0x000A;
+const UInt16 ReflectedFrom = 0x000B;
+const UInt16 XorMappedAddress = 0x0020;
+const UInt16 XorOnly = 0x0021;
+const UInt16 ServerName = 0x0022;
+const UInt16 SecondaryAddress = 0x0050; // Non standard extention
+
+// define types for a stun message
+const UInt16 BindRequestMsg = 0x0001;
+const UInt16 BindResponseMsg = 0x0101;
+const UInt16 BindErrorResponseMsg = 0x0111;
+const UInt16 SharedSecretRequestMsg = 0x0002;
+const UInt16 SharedSecretResponseMsg = 0x0102;
+const UInt16 SharedSecretErrorResponseMsg = 0x0112;
+
+typedef struct
+{
+ UInt16 msgType;
+ UInt16 msgLength;
+ UInt128 id;
+} StunMsgHdr;
+
+
+typedef struct
+{
+ UInt16 type;
+ UInt16 length;
+} StunAtrHdr;
+
+typedef struct
+{
+ UInt16 port;
+ UInt32 addr;
+} StunAddress4;
+
+typedef struct
+{
+ UInt8 pad;
+ UInt8 family;
+ StunAddress4 ipv4;
+} StunAtrAddress4;
+
+typedef struct
+{
+ UInt32 value;
+} StunAtrChangeRequest;
+
+typedef struct
+{
+ UInt16 pad; // all 0
+ UInt8 errorClass;
+ UInt8 number;
+ char reason[STUN_MAX_STRING];
+ UInt16 sizeReason;
+} StunAtrError;
+
+typedef struct
+{
+ UInt16 attrType[STUN_MAX_UNKNOWN_ATTRIBUTES];
+ UInt16 numAttributes;
+} StunAtrUnknown;
+
+typedef struct
+{
+ char value[STUN_MAX_STRING];
+ UInt16 sizeValue;
+} StunAtrString;
+
+typedef struct
+{
+ char hash[20];
+} StunAtrIntegrity;
+
+typedef enum
+{
+ HmacUnkown=0,
+ HmacOK,
+ HmacBadUserName,
+ HmacUnkownUserName,
+ HmacFailed,
+} StunHmacStatus;
+
+typedef struct
+{
+ StunMsgHdr msgHdr;
+
+ bool hasMappedAddress;
+ StunAtrAddress4 mappedAddress;
+
+ bool hasResponseAddress;
+ StunAtrAddress4 responseAddress;
+
+ bool hasChangeRequest;
+ StunAtrChangeRequest changeRequest;
+
+ bool hasSourceAddress;
+ StunAtrAddress4 sourceAddress;
+
+ bool hasChangedAddress;
+ StunAtrAddress4 changedAddress;
+
+ bool hasUsername;
+ StunAtrString username;
+
+ bool hasPassword;
+ StunAtrString password;
+
+ bool hasMessageIntegrity;
+ StunAtrIntegrity messageIntegrity;
+
+ bool hasErrorCode;
+ StunAtrError errorCode;
+
+ bool hasUnknownAttributes;
+ StunAtrUnknown unknownAttributes;
+
+ bool hasReflectedFrom;
+ StunAtrAddress4 reflectedFrom;
+
+ bool hasXorMappedAddress;
+ StunAtrAddress4 xorMappedAddress;
+
+ bool xorOnly;
+
+ bool hasServerName;
+ StunAtrString serverName;
+
+ bool hasSecondaryAddress;
+ StunAtrAddress4 secondaryAddress;
+} StunMessage;
+
+
+// Define enum with different types of NAT
+typedef enum
+{
+ StunTypeUnknown=0,
+ StunTypeOpen,
+ StunTypeConeNat,
+ StunTypeRestrictedNat,
+ StunTypePortRestrictedNat,
+ StunTypeSymNat,
+ StunTypeSymFirewall,
+ StunTypeBlocked,
+ StunTypeFailure
+} NatType;
+
+#ifdef WIN32
+typedef SOCKET StunSocket;
+#else
+typedef int StunSocket;
+#endif
+
+#define MAX_MEDIA_RELAYS 500
+#define MAX_RTP_MSG_SIZE 1500
+#define MEDIA_RELAY_TIMEOUT 3*60
+
+typedef struct
+{
+ int relayPort; // media relay port
+ int fd; // media relay file descriptor
+ StunAddress4 destination; // NAT IP:port
+ time_t expireTime; // if no activity after time, close the socket
+} StunMediaRelay;
+
+typedef struct
+{
+ StunAddress4 myAddr;
+ StunAddress4 altAddr;
+ StunSocket myFd;
+ StunSocket altPortFd;
+ StunSocket altIpFd;
+ StunSocket altIpPortFd;
+ bool relay; // true if media relaying is to be done
+ StunMediaRelay relays[MAX_MEDIA_RELAYS];
+} StunServerInfo;
+
+bool
+stunParseMessage( char* buf,
+ unsigned int bufLen,
+ StunMessage& message,
+ bool verbose );
+
+void
+stunBuildReqSimple( StunMessage* msg,
+ const StunAtrString& username,
+ bool changePort, bool changeIp, unsigned int id=0 );
+
+unsigned int
+stunEncodeMessage( const StunMessage& message,
+ char* buf,
+ unsigned int bufLen,
+ const StunAtrString& password,
+ bool verbose);
+
+void
+stunCreateUserName(const StunAddress4& addr, StunAtrString* username);
+
+void
+stunGetUserNameAndPassword( const StunAddress4& dest,
+ StunAtrString* username,
+ StunAtrString* password);
+
+void
+stunCreatePassword(const StunAtrString& username, StunAtrString* password);
+
+// Twinkle
+// Build an error response
+StunMessage *stunBuildError(const StunMessage &m, int code, const char *reason);
+
+// Create a string representation of a STUN message
+string stunMsg2Str(const StunMessage &m);
+
+bool stunEqualId(const StunMessage &m1, const StunMessage &m2);
+
+string stunNatType2Str(NatType t);
+// Twinkle
+
+int
+stunRand();
+
+UInt64
+stunGetSystemTimeSecs();
+
+/// find the IP address of a the specified stun server - return false is fails parse
+bool
+stunParseServerName( char* serverName, StunAddress4& stunServerAddr);
+
+bool
+stunParseHostName( char* peerName,
+ UInt32& ip,
+ UInt16& portVal,
+ UInt16 defaultPort );
+
+/// return true if all is OK
+/// Create a media relay and do the STERN thing if startMediaPort is non-zero
+bool
+stunInitServer(StunServerInfo& info,
+ const StunAddress4& myAddr,
+ const StunAddress4& altAddr,
+ int startMediaPort,
+ bool verbose);
+
+void
+stunStopServer(StunServerInfo& info);
+
+/// return true if all is OK
+bool
+stunServerProcess(StunServerInfo& info, bool verbose);
+
+/// returns number of address found - take array or addres
+int
+stunFindLocalInterfaces(UInt32* addresses, int maxSize );
+
+void
+stunTest( StunAddress4& dest, int testNum, bool verbose, StunAddress4* srcAddr=0 );
+
+NatType
+stunNatType( StunAddress4& dest, bool verbose,
+ bool* preservePort=0, // if set, is return for if NAT preservers ports or not
+ bool* hairpin=0 , // if set, is the return for if NAT will hairpin packets
+ int port=0, // port to use for the test, 0 to choose random port
+ StunAddress4* sAddr=0 // NIC to use
+ );
+
+/// prints a StunAddress
+std::ostream&
+operator<<( std::ostream& strm, const StunAddress4& addr);
+
+std::ostream&
+operator<< ( std::ostream& strm, const UInt128& );
+
+
+bool
+stunServerProcessMsg( char* buf,
+ unsigned int bufLen,
+ StunAddress4& from,
+ StunAddress4& myAddr,
+ StunAddress4& altAddr,
+ StunMessage* resp,
+ StunAddress4* destination,
+ StunAtrString* hmacPassword,
+ bool* changePort,
+ bool* changeIp,
+ bool verbose);
+
+int
+stunOpenStunSocket( StunAddress4& dest,
+ StunAddress4* mappedAddr,
+ int port=0,
+ StunAddress4* srcAddr=0,
+ bool verbose=false );
+
+bool
+stunOpenStunSocketPair( StunAddress4& dest, StunAddress4* mappedAddr,
+ int* fd1, int* fd2,
+ int srcPort=0, StunAddress4* srcAddr=0,
+ bool verbose=false);
+
+#endif
+
+
+/* ====================================================================
+ * The Vovida Software License, Version 1.0
+ *
+ * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The names "VOCAL", "Vovida Open Communication Application Library",
+ * and "Vovida Open Communication Application Library (VOCAL)" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact vocal@vovida.org.
+ *
+ * 4. Products derived from this software may not be called "VOCAL", nor
+ * may "VOCAL" appear in their name, without prior written
+ * permission of Vovida Networks, Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
+ * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
+ * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by Vovida
+ * Networks, Inc. and many individuals on behalf of Vovida Networks,
+ * Inc. For more information on Vovida Networks, Inc., please see
+ * <http://www.vovida.org/>.
+ *
+ */
+
+// Local Variables:
+// mode:c++
+// c-file-style:"ellemtel"
+// c-file-offsets:((case-label . +))
+// indent-tabs-mode:nil
+// End:
+
diff --git a/src/stun/stun_transaction.cpp b/src/stun/stun_transaction.cpp
new file mode 100644
index 0000000..9514ba4
--- /dev/null
+++ b/src/stun/stun_transaction.cpp
@@ -0,0 +1,680 @@
+/*
+ Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com>
+
+ 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 <cstring>
+#include "stun_transaction.h"
+#include "events.h"
+#include "log.h"
+#include "phone.h"
+#include "sys_settings.h"
+#include "transaction_mgr.h"
+#include "translator.h"
+#include "util.h"
+#include "audits/memman.h"
+
+extern t_transaction_mgr *transaction_mgr;
+extern t_event_queue *evq_trans_layer;
+extern t_event_queue *evq_trans_mgr;
+extern t_event_queue *evq_sender;
+extern t_phone *phone;
+
+
+bool get_stun_binding(t_user *user_config, unsigned short src_port, unsigned long &mapped_ip,
+ unsigned short &mapped_port, int &err_code, string &err_reason)
+{
+ list<t_ip_port> destinations =
+ user_config->get_stun_server().get_h_ip_srv("udp");
+
+ if (destinations.empty()) {
+ // Cannot resolve STUN server address.
+ log_file->write_header("::get_stun_binding", LOG_NORMAL, LOG_CRITICAL);
+ log_file->write_raw("Failed to resolve: ");
+ log_file->write_raw(user_config->get_stun_server().encode());
+ log_file->write_endl();
+ log_file->write_raw("Return internal STUN bind error: 404 Not Found");
+ log_file->write_endl();
+ log_file->write_footer();
+
+ err_code = 404;
+ err_reason = "Not Found";
+ return false;
+ }
+
+ int num_transmissions = 0;
+ int wait_intval = DUR_STUN_START_INTVAL;
+
+ t_socket_udp sock(src_port);
+ sock.connect(destinations.front().ipaddr, destinations.front().port);
+
+ // Build STUN request
+ char buf[STUN_MAX_MESSAGE_SIZE + 1];
+ StunMessage req_bind;
+ StunAtrString stun_null_str;
+ stun_null_str.sizeValue = 0;
+ stunBuildReqSimple(&req_bind, stun_null_str, false, false);
+ char req_msg[STUN_MAX_MESSAGE_SIZE];
+ int req_msg_size = stunEncodeMessage(req_bind, req_msg,
+ STUN_MAX_MESSAGE_SIZE, stun_null_str, false);
+
+ // Send STUN request and retransmit till a response is received.
+ while (num_transmissions < STUN_MAX_TRANSMISSIONS) {
+ bool ret;
+
+ try {
+ sock.send(req_msg, req_msg_size);
+ }
+ catch (int err) {
+ // Socket error (probably ICMP error)
+ // Failover to next destination
+ log_file->write_report("Send failed. Failover to next destination.",
+ "::get_stun_binding");
+
+ destinations.pop_front();
+ if (destinations.empty()) {
+ log_file->write_report("No next destination for failover.",
+ "::get_stun_binding");
+ break;
+ }
+
+ num_transmissions = 0;
+ wait_intval = DUR_STUN_START_INTVAL;
+ sock.connect(destinations.front().ipaddr, destinations.front().port);
+ continue;
+ }
+
+ log_file->write_header("::get_stun_binding", LOG_STUN);
+ log_file->write_raw("Send to: ");
+ log_file->write_raw(h_ip2str(destinations.front().ipaddr));
+ log_file->write_raw(":");
+ log_file->write_raw(destinations.front().port);
+ log_file->write_endl();
+ log_file->write_raw(stunMsg2Str(req_bind));
+ log_file->write_footer();
+
+ try {
+ ret = sock.select_read(wait_intval);
+ }
+ catch (int err) {
+ // Socket error (probably ICMP error)
+ // Failover to next destination
+ log_file->write_report("Select failed. Failover to next destination.",
+ "::get_stun_binding");
+
+ destinations.pop_front();
+ if (destinations.empty()) {
+ log_file->write_report("No next destination for failover.",
+ "::get_stun_binding");
+ break;
+ }
+
+ num_transmissions = 0;
+ wait_intval = DUR_STUN_START_INTVAL;
+ sock.connect(destinations.front().ipaddr, destinations.front().port);
+ continue;
+ }
+
+ if (!ret) {
+ // Time out
+ num_transmissions++;
+ if (wait_intval < DUR_STUN_MAX_INTVAL) {
+ wait_intval *= 2;
+ }
+ continue;
+ }
+
+ // A message has been received
+ int resp_msg_size;
+ try {
+ resp_msg_size = sock.recv(buf, STUN_MAX_MESSAGE_SIZE + 1);
+ }
+ catch (int err) {
+ // Socket error (probably ICMP error)
+ // Failover to next destination
+ log_file->write_report("Recv failed. Failover to next destination.",
+ "::get_stun_binding");
+
+ destinations.pop_front();
+ if (destinations.empty()) {
+ log_file->write_report("No next destination for failover.",
+ "::get_stun_binding");
+ break;
+ }
+
+ num_transmissions = 0;
+ wait_intval = DUR_STUN_START_INTVAL;
+ sock.connect(destinations.front().ipaddr, destinations.front().port);
+ continue;
+ }
+
+ StunMessage resp_bind;
+
+ if (!stunParseMessage(buf, resp_msg_size, resp_bind, false)) {
+ log_file->write_report(
+ "Received faulty STUN message", "::get_stun_binding",
+ LOG_STUN);
+ num_transmissions++;
+ if (wait_intval < DUR_STUN_MAX_INTVAL) {
+ wait_intval *= 2;
+ }
+ continue;
+ }
+
+ log_file->write_header("::get_stun_binding", LOG_STUN);
+ log_file->write_raw("Received from: ");
+ log_file->write_raw(h_ip2str(destinations.front().ipaddr));
+ log_file->write_raw(":");
+ log_file->write_raw(destinations.front().port);
+ log_file->write_endl();
+ log_file->write_raw(stunMsg2Str(resp_bind));
+ log_file->write_footer();
+
+ // Check if id in msgHdr matches
+ if (!stunEqualId(resp_bind, req_bind)) {
+ num_transmissions++;
+ if (wait_intval < DUR_STUN_MAX_INTVAL) {
+ wait_intval *= 2;
+ }
+ continue;
+ }
+
+ if (resp_bind.msgHdr.msgType == BindResponseMsg &&
+ resp_bind.hasMappedAddress) {
+ // Bind response received
+ mapped_ip = resp_bind.mappedAddress.ipv4.addr;
+ mapped_port = resp_bind.mappedAddress.ipv4.port;
+ return true;
+ }
+
+ if (resp_bind.msgHdr.msgType == BindErrorResponseMsg &&
+ resp_bind.hasErrorCode)
+ {
+ // Bind error received
+ err_code = resp_bind.errorCode.errorClass * 100 +
+ resp_bind.errorCode.number;
+ char s[STUN_MAX_STRING + 1];
+ strncpy(s, resp_bind.errorCode.reason, STUN_MAX_STRING);
+ s[STUN_MAX_STRING] = 0;
+ err_reason = s;
+ return false;
+ }
+
+ // A wrong response has been received.
+ log_file->write_report(
+ "Invalid STUN response received", "::get_stun_binding",
+ LOG_NORMAL);
+
+ err_code = 500;
+ err_reason = "Server Error";
+ return false;
+ }
+
+ // Request timed out
+ log_file->write_report("STUN request timeout", "::get_stun_binding",
+ LOG_NORMAL);
+
+ err_code = 408;
+ err_reason = "Request Timeout";
+ return false;
+}
+
+bool stun_discover_nat(t_phone_user *pu, string &err_msg) {
+ t_user *user_config = pu->get_user_profile();
+
+ // By default enable STUN. If for some reason we cannot perform
+ // NAT discovery, then enable STUN. It will not harm, but only
+ // create non-needed STUN transactions if we are not behind a NAT.
+ pu->use_stun = true;
+ pu->use_nat_keepalive = true;
+
+ list<t_ip_port> destinations =
+ user_config->get_stun_server().get_h_ip_srv("udp");
+
+ if (destinations.empty()) {
+ // Cannot resolve STUN server address.
+ log_file->write_header("::main", LOG_NORMAL, LOG_CRITICAL);
+ log_file->write_raw("Failed to resolve: ");
+ log_file->write_raw(user_config->get_stun_server().encode());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ err_msg = TRANSLATE("Cannot resolve STUN server: %1");
+ err_msg = replace_first(err_msg, "%1", user_config->get_stun_server().encode());
+ return false;
+ }
+
+ while (!destinations.empty()) {
+ StunAddress4 stun_ip4;
+ stun_ip4.addr = destinations.front().ipaddr;
+ stun_ip4.port = destinations.front().port;
+
+ NatType nat_type = stunNatType(stun_ip4, false);
+ log_file->write_header("::main");
+ log_file->write_raw("STUN NAT type discovery for ");
+ log_file->write_raw(user_config->get_profile_name());
+ log_file->write_endl();
+ log_file->write_raw("NAT type: ");
+ log_file->write_raw(stunNatType2Str(nat_type));
+ log_file->write_endl();
+ log_file->write_footer();
+
+ switch (nat_type) {
+ case StunTypeOpen:
+ // STUN is not needed.
+ pu->use_stun = false;
+ pu->use_nat_keepalive = false;
+ return true;
+ case StunTypeSymNat:
+ err_msg = TRANSLATE("You are behind a symmetric NAT.\nSTUN will not work.\nConfigure a public IP address in the user profile\nand create the following static bindings (UDP) in your NAT.");
+ err_msg += "\n\n";
+ err_msg += TRANSLATE("public IP: %1 --> private IP: %2 (SIP signaling)");
+ err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_sip_port()));
+ err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_sip_port()));
+ err_msg += "\n";
+ err_msg += TRANSLATE("public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP)");
+ err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_rtp_port()));
+ err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_rtp_port() + 5));
+ err_msg = replace_first(err_msg, "%3", int2str(sys_config->get_rtp_port()));
+ err_msg = replace_first(err_msg, "%4", int2str(sys_config->get_rtp_port() + 5));
+
+ pu->use_stun = false;
+ pu->use_nat_keepalive = false;
+ return false;
+ case StunTypeSymFirewall:
+ // STUN is not needed as we are on a pubic IP.
+ // NAT keep alive is needed however to keep the firewall open.
+ pu->use_stun = false;
+ return true;
+ case StunTypeBlocked:
+ destinations.pop_front();
+
+ // The code for NAT type discovery does not handle
+ // ICMP errors. So if the conclusion is that the network
+ // connection is blocked, it might be due to a down STUN
+ // server. Try alternative destination if avaliable.
+
+ if (destinations.empty()) {
+ err_msg = TRANSLATE("Cannot reach the STUN server: %1");
+ err_msg = replace_first(err_msg, "%1",
+ user_config->get_stun_server().encode());
+ err_msg += "\n\n";
+ err_msg += TRANSLATE("If you are behind a firewall then you need to open the following UDP ports.");
+ err_msg += "\n";
+ err_msg += TRANSLATE("Port %1 (SIP signaling)");
+ err_msg = replace_first(err_msg, "%1",
+ int2str(sys_config->get_sip_port()));
+ err_msg += "\n";
+ err_msg += TRANSLATE("Ports %1-%2 (RTP/RTCP)");
+ err_msg = replace_first(err_msg, "%1",
+ int2str(sys_config->get_rtp_port()));
+ err_msg = replace_first(err_msg, "%2",
+ int2str(sys_config->get_rtp_port() + 5));
+
+ return false;
+ }
+
+ log_file->write_report("Failover to next destination.",
+ "::stun_discover_nat");
+ break;
+ case StunTypeFailure:
+ destinations.pop_front();
+ log_file->write_report("Failover to next destination.",
+ "::stun_discover_nat");
+ break;
+ default:
+ // Use STUN.
+ return true;
+ }
+ }
+
+ err_msg = TRANSLATE("NAT type discovery via STUN failed.");
+ return false;
+}
+
+
+// Main function for STUN listener thread for media STUN requests.
+void *stun_listen_main(void *arg) {
+ char buf[STUN_MAX_MESSAGE_SIZE + 1];
+ int data_size;
+
+ t_socket_udp *sock = (t_socket_udp *)arg;
+
+ while(true) {
+ try {
+ data_size = sock->recv(buf, STUN_MAX_MESSAGE_SIZE + 1);
+ } catch (int err) {
+ string msg("Failed to receive STUN response for media.\n");
+ msg += get_error_str(err);
+ log_file->write_report(msg, "::stun_listen_main",
+ LOG_NORMAL, LOG_CRITICAL);
+
+ // The request will timeout, no need to send a response now.
+
+ return NULL;
+ }
+
+ StunMessage m;
+
+ if (!stunParseMessage(buf, data_size, m, false)) {
+ log_file->write_report("Faulty STUN message", "::stun_listen_main");
+ continue;
+ }
+
+ log_file->write_header("::stun_listen_main", LOG_STUN);
+ log_file->write_raw("Received: ");
+ log_file->write_raw(stunMsg2Str(m));
+ log_file->write_footer();
+
+ evq_trans_mgr->push_stun_response(&m, 0, 0);
+ }
+}
+
+//////////////////////////////////////////////
+// Base STUN transaction
+//////////////////////////////////////////////
+
+t_mutex t_stun_transaction::mtx_class;
+t_tid t_stun_transaction::next_id = 1;
+
+t_stun_transaction::t_stun_transaction(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst)
+{
+ mtx_class.lock();
+ id = next_id++;
+ if (next_id == 65535) next_id = 1;
+ mtx_class.unlock();
+
+ state = TS_NULL;
+ request = new StunMessage(*r);
+ MEMMAN_NEW(request);
+ tuid = _tuid;
+
+ dur_req_timeout = DUR_STUN_START_INTVAL;
+ num_transmissions = 0;
+
+ destinations = dst;
+
+ user_config = user->copy();
+}
+
+t_stun_transaction::~t_stun_transaction() {
+ MEMMAN_DELETE(request);
+ delete request;
+ MEMMAN_DELETE(user_config);
+ delete user_config;
+}
+
+t_tid t_stun_transaction::get_id(void) const {
+ return id;
+}
+
+t_trans_state t_stun_transaction::get_state(void) const {
+ return state;
+}
+
+void t_stun_transaction::start_timer_req_timeout(void) {
+ timer_req_timeout = transaction_mgr->start_stun_timer(dur_req_timeout,
+ STUN_TMR_REQ_TIMEOUT, id);
+
+ // RFC 3489 9.3
+ // Double the retransmision interval till a maximum
+ if (dur_req_timeout < DUR_STUN_MAX_INTVAL) {
+ dur_req_timeout = 2 * dur_req_timeout;
+ }
+}
+
+void t_stun_transaction::stop_timer_req_timeout(void) {
+ if (timer_req_timeout) {
+ transaction_mgr->stop_timer(timer_req_timeout);
+ timer_req_timeout = 0;
+ }
+}
+
+void t_stun_transaction::process_response(StunMessage *r) {
+ stop_timer_req_timeout();
+ evq_trans_layer->push_stun_response(r, tuid, id);
+ state = TS_TERMINATED;
+}
+
+void t_stun_transaction::process_icmp(const t_icmp_msg &icmp) {
+ stop_timer_req_timeout();
+
+ log_file->write_report("Failover to next destination.",
+ "t_stun_transaction::process_icmp");
+
+ destinations.pop_front();
+ if (destinations.empty()) {
+ log_file->write_report("No next destination for failover.",
+ "t_stun_transaction::process_icmp");
+
+ log_file->write_header("t_stun_transaction::process_icmp",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("ICMP error received.\n\n");
+ log_file->write_raw("Send internal: 500 Server Error\n");
+ log_file->write_footer();
+
+ // No server could be reached, Notify the TU with 500 Server
+ // Error.
+ StunMessage *resp = stunBuildError(*request, 500, "Server Error");
+ evq_trans_layer->push_stun_response(resp, tuid, id);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ state = TS_TERMINATED;
+ return;
+ }
+
+ // Failover to next destination
+ evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
+ destinations.front().ipaddr, destinations.front().port);
+ num_transmissions = 1;
+ dur_req_timeout = DUR_STUN_START_INTVAL;
+ start_timer_req_timeout();
+}
+
+void t_stun_transaction::timeout(t_stun_timer t) {
+ // RFC 3489 9.3
+ if (num_transmissions < STUN_MAX_TRANSMISSIONS) {
+ retransmit();
+ start_timer_req_timeout();
+ return;
+ }
+
+ // Report timeout to TU
+ StunMessage *timeout_resp;
+ timeout_resp = stunBuildError(*request, 408, "Request Timeout");
+ log_file->write_report("STUN request timeout", "t_stun_transaction::timeout",
+ LOG_NORMAL);
+
+ evq_trans_layer->push_stun_response(timeout_resp, tuid, id);
+ MEMMAN_DELETE(timeout_resp);
+ delete timeout_resp;
+
+ state = TS_TERMINATED;
+}
+
+bool t_stun_transaction::match(StunMessage *resp) const {
+ return stunEqualId(*resp, *request);
+}
+
+// An ICMP error matches a transaction when the destination IP address/port
+// of the packet that caused the ICMP error equals the destination
+// IP address/port of the transaction. Other information of the packet causing
+// the ICMP error is not available.
+// In theory when multiple transactions are open for the same destination, the
+// wrong transaction may process the ICMP error. In practice this should rarely
+// happen as the destination will be unreachable for all those transactions.
+// If it happens a transaction gets aborted.
+bool t_stun_transaction::match(const t_icmp_msg &icmp) const {
+ return (destinations.front().ipaddr == icmp.ipaddr &&
+ destinations.front().port == icmp.port);
+}
+
+//////////////////////////////////////////////
+// SIP STUN transaction
+//////////////////////////////////////////////
+
+void t_sip_stun_trans::retransmit(void) {
+ // The SIP UDP sender will send out the STUN request.
+ evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
+ destinations.front().ipaddr, destinations.front().port);
+ num_transmissions++;
+}
+
+t_sip_stun_trans::t_sip_stun_trans(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst) :
+ t_stun_transaction(user, r, _tuid, dst)
+{
+ // The SIP UDP sender will send out the STUN request.
+ evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
+ destinations.front().ipaddr, destinations.front().port);
+ num_transmissions++;
+ start_timer_req_timeout();
+ state = TS_PROCEEDING;
+}
+
+//////////////////////////////////////////////
+// Media STUN transaction
+//////////////////////////////////////////////
+
+// TODO: this code is not used anymore. Remove?
+
+void t_media_stun_trans::retransmit(void) {
+ // Retransmit the STUN request
+ StunAtrString stun_pass;
+ stun_pass.sizeValue = 0;
+ char m[STUN_MAX_MESSAGE_SIZE];
+ int msg_size = stunEncodeMessage(*request, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false);
+
+ try {
+ sock->sendto(destinations.front().ipaddr, destinations.front().port,
+ m, msg_size);
+ } catch (int err) {
+ string msg("Failed to send STUN request for media.\n");
+ msg += get_error_str(err);
+ log_file->write_report(msg, "::t_media_stun_trans::retransmit",
+ LOG_NORMAL, LOG_CRITICAL);
+
+ StunMessage *resp;
+ resp = stunBuildError(*request, 500, "Could not send request");
+
+ evq_trans_layer->push_stun_response(resp, tuid, id);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ num_transmissions++;
+}
+
+t_media_stun_trans::t_media_stun_trans(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst,
+ unsigned short src_port) :
+ t_stun_transaction(user, r, _tuid, dst)
+{
+ thr_listen = NULL;
+
+ try {
+ sock = new t_socket_udp(src_port);
+ MEMMAN_NEW(sock);
+ sock->connect(destinations.front().ipaddr, destinations.front().port);
+ } catch (int err) {
+ string msg("Failed to create a UDP socket (STUN) on port ");
+ msg += int2str(src_port);
+ msg += "\n";
+ msg += get_error_str(err);
+ log_file->write_report(msg, "t_media_stun_trans::t_media_stun_trans", LOG_NORMAL,
+ LOG_CRITICAL);
+ delete sock;
+ sock = NULL;
+
+ StunMessage *resp;
+ resp = stunBuildError(*request, 500, "Could not create socket");
+
+ evq_trans_layer->push_stun_response(resp, tuid, id);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ // Send STUN request
+ StunAtrString stun_pass;
+ stun_pass.sizeValue = 0;
+ char m[STUN_MAX_MESSAGE_SIZE];
+ int msg_size = stunEncodeMessage(*r, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false);
+
+ try {
+ sock->send(m, msg_size);
+ } catch (int err) {
+ string msg("Failed to send STUN request for media.\n");
+ msg += get_error_str(err);
+ log_file->write_report(msg, "::t_media_stun_trans::t_media_stun_trans",
+ LOG_NORMAL, LOG_CRITICAL);
+
+ StunMessage *resp;
+ resp = stunBuildError(*request, 500, "Failed to send request");
+
+ evq_trans_layer->push_stun_response(resp, tuid, id);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ num_transmissions++;
+
+ try {
+ thr_listen = new t_thread(stun_listen_main, sock);
+ MEMMAN_NEW(thr_listen);
+ } catch (int) {
+ log_file->write_report("Failed to create STUN listener thread.",
+ "::t_media_stun_trans::t_media_stun_trans",
+ LOG_NORMAL, LOG_CRITICAL);
+ delete thr_listen;
+ thr_listen = NULL;
+
+ StunMessage *resp;
+ resp = stunBuildError(*request, 500, "Failed to create STUN listen thread");
+
+ evq_trans_layer->push_stun_response(resp, tuid, id);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ start_timer_req_timeout();
+ state = TS_PROCEEDING;
+}
+
+t_media_stun_trans::~t_media_stun_trans() {
+ if (sock) {
+ MEMMAN_DELETE(sock);
+ delete sock;
+ }
+
+ if (thr_listen) {
+ thr_listen->cancel();
+ thr_listen->join();
+ MEMMAN_DELETE(thr_listen);
+ delete thr_listen;
+ }
+}
+
+
diff --git a/src/stun/stun_transaction.h b/src/stun/stun_transaction.h
new file mode 100644
index 0000000..c06e1c5
--- /dev/null
+++ b/src/stun/stun_transaction.h
@@ -0,0 +1,159 @@
+/*
+ Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com>
+
+ 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
+*/
+
+#ifndef _STUN_TRANSACTION_H
+#define _STUN_TRANSACTION_H
+
+#include "stun.h"
+#include "phone_user.h"
+#include "protocol.h"
+#include "user.h"
+#include "transaction.h"
+#include "threads/mutex.h"
+#include "threads/thread.h"
+#include "sockets/socket.h"
+#include "sockets/url.h"
+
+// Create a binding in a NAT.
+// Returns true on success. Returns false when the STUN server returned
+// an error. Throws an int exception (containing errno) when some
+// socket operation fails.
+bool get_stun_binding(t_user *user_config, unsigned short src_port, unsigned long &mapped_ip,
+ unsigned short &mapped_port, int &err_code, string &err_reason);
+
+
+// Discover the type of NAT and determine is STUN should be used or
+// wether STUN is useless.
+// It sets the use_stun attribute of phone if STUN should be used.
+// Return false if STUN cannot be used. err_msg will contain an
+// error message that can be displayed to the user.
+bool stun_discover_nat(t_phone_user *pu, string &err_msg);
+
+
+//////////////////////////////////////////////
+// Base STUN transaction
+//////////////////////////////////////////////
+
+class t_stun_transaction {
+private:
+ static t_mutex mtx_class; // protect static members
+ static t_tid next_id; // next id to be issued
+
+protected:
+ t_tid id; // transaction id
+ unsigned short tuid; // TU id
+ t_trans_state state;
+
+ // Timer for retransmissions
+ unsigned short timer_req_timeout;
+
+ // Duration for the next timer start in msec
+ unsigned long dur_req_timeout;
+
+ // Number of transmissions of the request
+ unsigned short num_transmissions;
+
+ // Destinations for the request
+ list<t_ip_port> destinations;
+
+ // User profile of user that created this transaction
+ t_user *user_config;
+
+ void start_timer_req_timeout(void);
+ void stop_timer_req_timeout(void);
+
+ // Retransmit STUN request
+ virtual void retransmit(void) = 0;
+
+public:
+ StunMessage *request;
+
+ t_tid get_id(void) const;
+
+ // Get state of the transaction
+ t_trans_state get_state(void) const;
+
+ // The transaction will keep a copy of the request
+ t_stun_transaction(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst);
+
+ // All request and response pointers contained by the
+ // transaction will be deleted.
+ virtual ~t_stun_transaction();
+
+ // Process STUN response
+ virtual void process_response(StunMessage *r);
+
+ // Process ICMP error
+ virtual void process_icmp(const t_icmp_msg &icmp);
+
+ // Process timeout
+ virtual void timeout(t_stun_timer t);
+
+ // Match response with transaction
+ bool match(StunMessage *resp) const;
+
+ // Match ICMP error with transaction
+ bool match(const t_icmp_msg &icmp) const;
+};
+
+//////////////////////////////////////////////
+// SIP STUN transaction
+//////////////////////////////////////////////
+
+// A SIP STUN transaction is a STUN request to get a binding
+// for the SIP port. Such a request must be sent from the SIP port.
+// So it must be sent out via the SIP sender thread.
+
+class t_sip_stun_trans : public t_stun_transaction {
+protected:
+ virtual void retransmit(void);
+
+public:
+ // Create transaction and send out STUN request
+ t_sip_stun_trans(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst);
+};
+
+//////////////////////////////////////////////
+// Media STUN transaction
+//////////////////////////////////////////////
+
+// TODO: this code is not used anymore. Remove?
+
+// A media STUN transaction is a STUN request to get a binding
+// for a media port. Such a request must be sent from the media
+// port.
+
+class t_media_stun_trans : public t_stun_transaction {
+private:
+ t_socket_udp *sock; // UDP socket for STUN
+ t_thread *thr_listen; // Listener thread
+
+protected:
+ virtual void retransmit(void);
+
+public:
+ // Create transaction and send out STUN request
+ t_media_stun_trans(t_user *user, StunMessage *r,
+ unsigned short _tuid, const list<t_ip_port> &dst,
+ unsigned short src_port);
+ ~t_media_stun_trans();
+};
+
+#endif
diff --git a/src/stun/udp.cxx b/src/stun/udp.cxx
new file mode 100644
index 0000000..7a75134
--- /dev/null
+++ b/src/stun/udp.cxx
@@ -0,0 +1,349 @@
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <errno.h>
+#include <iostream>
+#include <cstdlib>
+#include <time.h>
+
+#ifdef WIN32
+
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+
+#else
+
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+
+#endif
+
+#include <string.h>
+
+#include "udp.h"
+
+using namespace std;
+
+
+StunSocket
+openPort( unsigned short port, unsigned int interfaceIp, bool verbose )
+{
+ StunSocket fd;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if ( fd == INVALID_SOCKET )
+ {
+ int err = getErrno();
+ cerr << "Could not create a UDP socket:" << err << endl;
+ return INVALID_SOCKET;
+ }
+
+ struct sockaddr_in addr;
+ memset((char*) &(addr),0, sizeof((addr)));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+
+ if ( (interfaceIp != 0) &&
+ ( interfaceIp != 0x100007f ) )
+ {
+ addr.sin_addr.s_addr = htonl(interfaceIp);
+ if (verbose )
+ {
+ clog << "Binding to interface "
+ << hex << "0x" << htonl(interfaceIp) << dec << endl;
+ }
+ }
+
+ if ( bind( fd,(struct sockaddr*)&addr, sizeof(addr)) != 0 )
+ {
+ int e = getErrno();
+
+ switch (e)
+ {
+ case 0:
+ {
+ cerr << "Could not bind socket" << endl;
+ return INVALID_SOCKET;
+ }
+ case EADDRINUSE:
+ {
+ cerr << "Port " << port << " for receiving UDP is in use" << endl;
+ return INVALID_SOCKET;
+ }
+ break;
+ case EADDRNOTAVAIL:
+ {
+ if ( verbose )
+ {
+ cerr << "Cannot assign requested address" << endl;
+ }
+ return INVALID_SOCKET;
+ }
+ break;
+ default:
+ {
+ cerr << "Could not bind UDP receive port"
+ << "Error=" << e << " " << strerror(e) << endl;
+ return INVALID_SOCKET;
+ }
+ break;
+ }
+ }
+ if ( verbose )
+ {
+ clog << "Opened port " << port << " with fd " << fd << endl;
+ }
+
+ assert( fd != INVALID_SOCKET );
+
+ return fd;
+}
+
+
+bool
+getMessage( StunSocket fd, char* buf, int* len,
+ unsigned int* srcIp, unsigned short* srcPort,
+ bool verbose)
+{
+ assert( fd != INVALID_SOCKET );
+
+ int originalSize = *len;
+ assert( originalSize > 0 );
+
+ struct sockaddr_in from;
+ int fromLen = sizeof(from);
+
+ *len = recvfrom(fd,
+ buf,
+ originalSize,
+ 0,
+ (struct sockaddr *)&from,
+ (socklen_t*)&fromLen);
+
+ if ( *len == SOCKET_ERROR )
+ {
+ int err = getErrno();
+
+ switch (err)
+ {
+ case ENOTSOCK:
+ cerr << "Error fd not a socket" << endl;
+ break;
+ case ECONNRESET:
+ cerr << "Error connection reset - host not reachable" << endl;
+ break;
+
+ default:
+ cerr << "StunSocket Error=" << err << endl;
+ }
+
+ return false;
+ }
+
+ if ( *len < 0 )
+ {
+ clog << "socket closed? negative len" << endl;
+ return false;
+ }
+
+ if ( *len == 0 )
+ {
+ clog << "socket closed? zero len" << endl;
+ return false;
+ }
+
+ *srcPort = ntohs(from.sin_port);
+ *srcIp = ntohl(from.sin_addr.s_addr);
+
+ if ( (*len)+1 >= originalSize )
+ {
+ if (verbose)
+ {
+ clog << "Received a message that was too large" << endl;
+ }
+ return false;
+ }
+ buf[*len]=0;
+
+ return true;
+}
+
+
+bool
+sendMessage( StunSocket fd, char* buf, int l,
+ unsigned int dstIp, unsigned short dstPort,
+ bool verbose)
+{
+ assert( fd != INVALID_SOCKET );
+
+ int s;
+ if ( dstPort == 0 )
+ {
+ // sending on a connected port
+ assert( dstIp == 0 );
+
+ s = send(fd,buf,l,0);
+ }
+ else
+ {
+ assert( dstIp != 0 );
+ assert( dstPort != 0 );
+
+ struct sockaddr_in to;
+ int toLen = sizeof(to);
+ memset(&to,0,toLen);
+
+ to.sin_family = AF_INET;
+ to.sin_port = htons(dstPort);
+ to.sin_addr.s_addr = htonl(dstIp);
+
+ s = sendto(fd, buf, l, 0,(sockaddr*)&to, toLen);
+ }
+
+ if ( s == SOCKET_ERROR )
+ {
+ int e = getErrno();
+ switch (e)
+ {
+ case ECONNREFUSED:
+ case EHOSTDOWN:
+ case EHOSTUNREACH:
+ {
+ // quietly ignore this
+ }
+ break;
+ case EAFNOSUPPORT:
+ {
+ cerr << "err EAFNOSUPPORT in send" << endl;
+ }
+ break;
+ default:
+ {
+ cerr << "err " << e << " " << strerror(e) << " in send" << endl;
+ }
+ }
+ return false;
+ }
+
+ if ( s == 0 )
+ {
+ cerr << "no data sent in send" << endl;
+ return false;
+ }
+
+ if ( s != l )
+ {
+ if (verbose)
+ {
+ cerr << "only " << s << " out of " << l << " bytes sent" << endl;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+initNetwork()
+{
+#ifdef WIN32
+ WORD wVersionRequested = MAKEWORD( 2, 2 );
+ WSADATA wsaData;
+ int err;
+
+ err = WSAStartup( wVersionRequested, &wsaData );
+ if ( err != 0 )
+ {
+ // could not find a usable WinSock DLL
+ cerr << "Could not load winsock" << endl;
+ assert(0); // is this is failing, try a different version that 2.2, 1.0 or later will likely work
+ exit(1);
+ }
+
+ /* Confirm that the WinSock DLL supports 2.2.*/
+ /* Note that if the DLL supports versions greater */
+ /* than 2.2 in addition to 2.2, it will still return */
+ /* 2.2 in wVersion since that is the version we */
+ /* requested. */
+
+ if ( LOBYTE( wsaData.wVersion ) != 2 ||
+ HIBYTE( wsaData.wVersion ) != 2 )
+ {
+ /* Tell the user that we could not find a usable */
+ /* WinSock DLL. */
+ WSACleanup( );
+ cerr << "Bad winsock verion" << endl;
+ assert(0); // is this is failing, try a different version that 2.2, 1.0 or later will likely work
+ exit(1);
+ }
+#endif
+}
+
+
+/* ====================================================================
+ * The Vovida Software License, Version 1.0
+ *
+ * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The names "VOCAL", "Vovida Open Communication Application Library",
+ * and "Vovida Open Communication Application Library (VOCAL)" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact vocal@vovida.org.
+ *
+ * 4. Products derived from this software may not be called "VOCAL", nor
+ * may "VOCAL" appear in their name, without prior written
+ * permission of Vovida Networks, Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
+ * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
+ * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by Vovida
+ * Networks, Inc. and many individuals on behalf of Vovida Networks,
+ * Inc. For more information on Vovida Networks, Inc., please see
+ * <http://www.vovida.org/>.
+ *
+ */
+
+// Local Variables:
+// mode:c++
+// c-file-style:"ellemtel"
+// c-file-offsets:((case-label . +))
+// indent-tabs-mode:nil
+// End:
diff --git a/src/stun/udp.h b/src/stun/udp.h
new file mode 100644
index 0000000..13f8353
--- /dev/null
+++ b/src/stun/udp.h
@@ -0,0 +1,156 @@
+#ifndef udp_h
+#define udp_h
+
+
+#ifdef __MACH__
+typedef int socklen_t;
+#endif
+
+#include <errno.h>
+
+#ifdef WIN32
+
+#include <winsock2.h>
+#include <io.h>
+
+typedef int socklen_t;
+typedef SOCKET StunSocket;
+
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define EINPROGRESS WSAEINPROGRESS
+#define EALREADY WSAEALREADY
+#define ENOTSOCK WSAENOTSOCK
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#define EMSGSIZE WSAEMSGSIZE
+#define EPROTOTYPE WSAEPROTOTYPE
+#define ENOPROTOOPT WSAENOPROTOOPT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define EADDRINUSE WSAEADDRINUSE
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#define ENETDOWN WSAENETDOWN
+#define ENETUNREACH WSAENETUNREACH
+#define ENETRESET WSAENETRESET
+#define ECONNABORTED WSAECONNABORTED
+#define ECONNRESET WSAECONNRESET
+#define ENOBUFS WSAENOBUFS
+#define EISCONN WSAEISCONN
+#define ENOTCONN WSAENOTCONN
+#define ESHUTDOWN WSAESHUTDOWN
+#define ETOOMANYREFS WSAETOOMANYREFS
+#define ETIMEDOUT WSAETIMEDOUT
+#define ECONNREFUSED WSAECONNREFUSED
+#define ELOOP WSAELOOP
+#define EHOSTDOWN WSAEHOSTDOWN
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#define EPROCLIM WSAEPROCLIM
+#define EUSERS WSAEUSERS
+#define EDQUOT WSAEDQUOT
+#define ESTALE WSAESTALE
+#define EREMOTE WSAEREMOTE
+
+typedef LONGLONG Int64;
+inline int getErrno() { return WSAGetLastError(); }
+
+#else
+
+typedef int StunSocket;
+static const StunSocket INVALID_SOCKET = -1;
+static const int SOCKET_ERROR = -1;
+
+inline int closesocket( StunSocket fd ) { return close(fd); };
+
+inline int getErrno() { return errno; }
+
+#define WSANOTINITIALISED EPROTONOSUPPORT
+
+#endif
+
+/// Open a UDP socket to receive on the given port - if port is 0, pick a a
+/// port, if interfaceIp!=0 then use ONLY the interface specified instead of
+/// all of them
+StunSocket
+openPort( unsigned short port, unsigned int interfaceIp,
+ bool verbose);
+
+
+/// recive a UDP message
+bool
+getMessage( StunSocket fd, char* buf, int* len,
+ unsigned int* srcIp, unsigned short* srcPort,
+ bool verbose);
+
+
+/// send a UDP message
+bool
+sendMessage( StunSocket fd, char* msg, int len,
+ unsigned int dstIp, unsigned short dstPort,
+ bool verbose);
+
+
+/// set up network - does nothing in unix but needed for windows
+void
+initNetwork();
+
+
+/* ====================================================================
+ * The Vovida Software License, Version 1.0
+ *
+ * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The names "VOCAL", "Vovida Open Communication Application Library",
+ * and "Vovida Open Communication Application Library (VOCAL)" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact vocal@vovida.org.
+ *
+ * 4. Products derived from this software may not be called "VOCAL", nor
+ * may "VOCAL" appear in their name, without prior written
+ * permission of Vovida Networks, Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
+ * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
+ * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by Vovida
+ * Networks, Inc. and many individuals on behalf of Vovida Networks,
+ * Inc. For more information on Vovida Networks, Inc., please see
+ * <http://www.vovida.org/>.
+ *
+ */
+
+// Local Variables:
+// mode:c++
+// c-file-style:"ellemtel"
+// c-file-offsets:((case-label . +))
+// indent-tabs-mode:nil
+// End:
+
+#endif