#include #include #include #include #include #ifdef WIN32 #include #include #include #include #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" <= 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= 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(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="<(&ndata), sizeof(UInt16)); return buf + sizeof(UInt16); } static char* encode32(char* buf, UInt32 data) { UInt32 ndata = htonl(data); memcpy(buf, reinterpret_cast(&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= sizeof(StunMsgHdr)); char* ptr = buf; ptr = encode16(ptr, msg.msgHdr.msgType); char* lengthp = ptr; ptr = encode16(ptr, 0); ptr = encode(ptr, reinterpret_cast(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 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(input), length, reinterpret_cast(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 Address = "; s += h_ip2str(m.mappedAddress.ipv4.addr); s += ':'; s += int2str(m.mappedAddress.ipv4.port); s += "\n"; } if (m.hasResponseAddress) { s += "Response Address = "; 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 Address = "; s += h_ip2str(m.sourceAddress.ipv4.addr); s += ':'; s += int2str(m.sourceAddress.ipv4.port); s += "\n"; } if (m.hasChangedAddress) { s += "Changed Address = "; 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(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; irelayPort = 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; ifd) { 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; ifd) { 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; ifd) { 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; idestination.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; ifd == 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" < 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 unknown\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 <. * */ // Local Variables: // mode:c++ // c-file-style:"ellemtel" // c-file-offsets:((case-label . +)) // indent-tabs-mode:nil // End: