diff options
author | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
---|---|---|
committer | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
commit | e2bc6f4153813cc570ae814c8ddb74628009b488 (patch) | |
tree | a40b171be1d859c2232ccc94f758010f9ae54d3c /src/stun | |
download | twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.gz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.lz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.xz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.zip |
initial checkin
Check in contents of upstream 1.4.2 tarball, exclude generated files.
Diffstat (limited to 'src/stun')
-rw-r--r-- | src/stun/Makefile.am | 15 | ||||
-rw-r--r-- | src/stun/stun.cxx | 2587 | ||||
-rw-r--r-- | src/stun/stun.h | 405 | ||||
-rw-r--r-- | src/stun/stun_transaction.cpp | 680 | ||||
-rw-r--r-- | src/stun/stun_transaction.h | 159 | ||||
-rw-r--r-- | src/stun/udp.cxx | 349 | ||||
-rw-r--r-- | src/stun/udp.h | 156 |
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 |