HRESULT CTestMessageHandler::ValidateMappedAddress(CStunMessageReader& reader, const CSocketAddress& addrClient) { HRESULT hr = S_OK; StunTransactionId transid; CSocketAddress mappedaddr; CRefCountedBuffer spBuffer; Chk(reader.GetStream().GetBuffer(&spBuffer)); reader.GetTransactionId(&transid); //ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_XORMAPPEDADDRESS, &attrib)); //ChkA(GetXorMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, transid, &mappedaddr)); reader.GetXorMappedAddress(&mappedaddr); ChkIfA(false == addrClient.IsSameIP_and_Port(mappedaddr), E_FAIL); //ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_MAPPEDADDRESS, &attrib)); //ChkA(GetMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, &mappedaddr)); reader.GetMappedAddress(&mappedaddr); ChkIfA(false == addrClient.IsSameIP_and_Port(mappedaddr), E_FAIL); Cleanup: return hr; }
HRESULT CTestMessageHandler::SendHelper(CStunMessageBuilder& builderRequest, CStunMessageReader* pReaderResponse, IStunAuth* pAuth) { CRefCountedBuffer spBufferRequest; CRefCountedBuffer spBufferResponse(new CBuffer(MAX_STUN_MESSAGE_SIZE)); StunMessageIn msgIn; StunMessageOut msgOut; CStunMessageReader reader; CSocketAddress addrDest; TransportAddressSet tas; HRESULT hr = S_OK; InitTransportAddressSet(tas, true, true, true, true); builderRequest.GetResult(&spBufferRequest); ChkIf(CStunMessageReader::BodyValidated != reader.AddBytes(spBufferRequest->GetData(), spBufferRequest->GetSize()), E_FAIL); msgIn.fConnectionOriented = false; msgIn.addrLocal = _addrServerPP; msgIn.pReader = &reader; msgIn.socketrole = RolePP; msgIn.addrRemote = _addrMapped; msgOut.spBufferOut = spBufferResponse; ChkA(CStunRequestHandler::ProcessRequest(msgIn, msgOut, &tas, pAuth)); ChkIf(CStunMessageReader::BodyValidated != pReaderResponse->AddBytes(spBufferResponse->GetData(), spBufferResponse->GetSize()), E_FAIL); Cleanup: return hr; }
// this test validates that IPV6 addresses work fine with CStunMessageBuilder and CStunMessageReader HRESULT CTestBuilder::Test2() { HRESULT hr = S_OK; CSocketAddress addr(0,0); CSocketAddress addrValidate(0,0); const char* ip6addr = "ABCDEFGHIJKLMNOP"; sockaddr_in6 addr6 = {}; CStunMessageReader reader; StunTransactionId transid; CStunMessageBuilder builder; CRefCountedBuffer spBuffer; addr6.sin6_family = AF_INET6; addr6.sin6_port = htons(9999); memcpy(addr6.sin6_addr.s6_addr, ip6addr, 16); addr = CSocketAddress(addr6); ChkA(builder.AddHeader(StunMsgTypeBinding, StunMsgClassRequest)); ChkA(builder.AddRandomTransactionId(&transid)); ChkA(builder.AddMappedAddress(addr)); ChkA(builder.AddXorMappedAddress(addr)); ChkA(builder.GetResult(&spBuffer)); ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL); ChkA(reader.GetXorMappedAddress(&addrValidate)); ChkIf(addrValidate.IsSameIP_and_Port(addr) == false, E_FAIL); Cleanup: return hr; }
HRESULT CTestMessageHandler::ValidateMappedAddress(CStunMessageReader& reader, const CSocketAddress& addrExpected, bool fLegacyOnly) { HRESULT hr = S_OK; CSocketAddress addrMapped; CSocketAddress addrXorMapped; HRESULT hrResult; hrResult = reader.GetXorMappedAddress(&addrXorMapped); if (SUCCEEDED(hrResult)) { ChkIfA(false == addrExpected.IsSameIP_and_Port(addrXorMapped), E_FAIL); ChkIfA(fLegacyOnly, E_FAIL); // legacy responses should not include XOR mapped } else { ChkIfA(fLegacyOnly==false, E_FAIL); // non-legacy responses should include XOR Mapped address } ChkA(reader.GetMappedAddress(&addrMapped)); ChkIfA(false == addrExpected.IsSameIP_and_Port(addrMapped), E_FAIL); Cleanup: return hr; }
// This test validates that the construction and parsing of the message integrity attribute in a stun message works as expected // The test also validates both short term and long term credential modes with or without the presence of a fingerprint attribute HRESULT CTestIntegrity::TestMessageIntegrity(bool fWithFingerprint, bool fLongCredentials) { HRESULT hr = S_OK; const char* pszUserName = "******"; const char* pszRealm = "stunrealm"; const char* pszPassword = "******"; CStunMessageBuilder builder; CStunMessageReader reader; uint8_t *pMsg = NULL; size_t sizeMsg = 0; CStunMessageReader::ReaderParseState state; CRefCountedBuffer spBuffer; builder.AddBindingRequestHeader(); builder.AddRandomTransactionId(NULL); builder.AddUserName(pszUserName); builder.AddRealm(pszRealm); if (fLongCredentials == false) { Chk(builder.AddMessageIntegrityShortTerm(pszPassword)); } else { Chk(builder.AddMessageIntegrityLongTerm(pszUserName, pszRealm, pszPassword)); } if (fWithFingerprint) { builder.AddFingerprintAttribute(); } Chk(builder.GetResult(&spBuffer)); pMsg = spBuffer->GetData(); sizeMsg = spBuffer->GetSize(); state = reader.AddBytes(pMsg, sizeMsg); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); ChkIfA(reader.HasMessageIntegrityAttribute()==false, E_FAIL); if (fLongCredentials == false) { ChkA(reader.ValidateMessageIntegrityShort(pszPassword)); } else { ChkA(reader.ValidateMessageIntegrityLong(pszUserName, pszRealm, pszPassword)); } Cleanup: return hr; }
HRESULT CTestReader::TestFixedReadSizes(size_t chunksize) { HRESULT hr = S_OK; CStunMessageReader reader; CStunMessageReader::ReaderParseState prevState, state; size_t bytesread = 0; bool fRandomChunkSizing = (chunksize==0); prevState = CStunMessageReader::HeaderNotRead; state = prevState; size_t msgSize = sizeof(c_requestbytes)-1; // c_requestbytes is a string, hence the -1 while (bytesread < msgSize) { size_t remaining, toread; if (fRandomChunkSizing) { chunksize = (rand() % 17) + 1; } remaining = msgSize - bytesread; toread = (remaining > chunksize) ? chunksize : remaining; state = reader.AddBytes(&c_requestbytes[bytesread], toread); bytesread += toread; ChkIfA(state == CStunMessageReader::ParseError, E_UNEXPECTED); if ((state == CStunMessageReader::HeaderValidated) && (prevState != CStunMessageReader::HeaderValidated)) { ChkIfA(bytesread < STUN_HEADER_SIZE, E_UNEXPECTED); } if ((state == CStunMessageReader::BodyValidated) && (prevState != CStunMessageReader::BodyValidated)) { ChkIfA(prevState != CStunMessageReader::HeaderValidated, E_UNEXPECTED); ChkIfA(bytesread != msgSize, E_UNEXPECTED); } prevState = state; } ChkIfA(reader.GetState() != CStunMessageReader::BodyValidated, E_UNEXPECTED); // just validate the integrity and fingerprint, that should cover all the attributes ChkA(reader.ValidateMessageIntegrityShort(c_password)); ChkIfA(reader.IsFingerprintAttributeValid() == false, E_FAIL); Cleanup: return hr; }
HRESULT CBasicBindingTest::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) { HRESULT hr = S_OK; CStunMessageReader reader; CSocketAddress addrMapped; CSocketAddress addrOther; bool fHasOtherAddress = false; // todo - figure out a way to make buffering TCP fragments work Chk(BasicReaderValidation(spMsg, reader)); hr = reader.GetXorMappedAddress(&addrMapped); if (FAILED(hr)) { hr = reader.GetMappedAddress(&addrMapped); } Chk(hr); // again drop the message if we can't parse the binding response fHasOtherAddress = SUCCEEDED(reader.GetOtherAddress(&addrOther)); // ok, we got a response. So we are done _fCompleted = true; _pResults->fBindingTestSuccess = true; _pResults->fIsDirect = addrLocal.IsSameIP_and_Port(addrMapped); _pResults->addrLocal = addrLocal; _pResults->addrMapped = addrMapped; _pResults->fHasOtherAddress = fHasOtherAddress; if (fHasOtherAddress) { _pResults->addrAA = addrOther; _pResults->addrPA = _pConfig->addrServer; _pResults->addrPA.SetPort(addrOther.GetPort()); _pResults->addrAP = addrOther; _pResults->addrAP.SetPort(_pConfig->addrServer.GetPort()); if (Logging::GetLogLevel() >= LL_DEBUG) { char sz[100]; addrOther.ToStringBuffer(sz, 100); Logging::LogMsg(LL_DEBUG, "Other address is %s\n",sz); } } Cleanup: return hr; }
// Test1 - just do a basic binding request HRESULT CTestMessageHandler::Test1() { HRESULT hr=S_OK; CStunMessageBuilder builder; CSocketAddress clientaddr(0x12345678, 9876); CRefCountedBuffer spBuffer; CStunThreadMessageHandler handler; CStunMessageReader reader; CStunMessageReader::ReaderParseState state; StunMessageEnvelope message; _spTransport->Reset(); _spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234)); InitBindingRequest(builder); builder.GetStream().GetBuffer(&spBuffer); handler.SetResponder(_spTransport); message.localSocket = RolePP; message.remoteAddr = clientaddr; message.spBuffer = spBuffer; _spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr)); handler.ProcessRequest(message); spBuffer.reset(); _spTransport->GetOutputStream().GetBuffer(&spBuffer); state = reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); // validate that the binding response matches our expectations ChkA(ValidateMappedAddress(reader, clientaddr)); // validate that it came from the server port we expected ChkA(ValidateOriginAddress(reader, RolePP)); // did we get back the binding request we expected ChkA(ValidateResponseAddress(clientaddr)); Cleanup: return hr; }
HRESULT CBehaviorTest::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) { HRESULT hr = S_OK; CStunMessageReader reader; CSocketAddress addrMapped; Chk(BasicReaderValidation(spMsg, reader)); hr = reader.GetXorMappedAddress(&addrMapped); if (FAILED(hr)) { hr = reader.GetMappedAddress(&addrMapped); } Chk(hr); // again drop the message if we can't parse the binding response _fCompleted = true; if (_fIsTest3) { _pResults->addrMappingAA = addrMapped; _pResults->fBehaviorTestSuccess = true; if (addrMapped.IsSameIP_and_Port(_pResults->addrMappingAP)) { _pResults->behavior = ::AddressDependentMapping; } else { _pResults->behavior = ::AddressAndPortDependentMapping; } } else { _pResults->addrMappingAP = addrMapped; if (addrMapped.IsSameIP_and_Port(_pResults->addrMapped)) { _pResults->fBehaviorTestSuccess = true; _pResults->behavior = ::EndpointIndependentMapping; } } Cleanup: return hr; }
HRESULT CTestClientLogic::ValidateBindingRequest(CRefCountedBuffer& spMsg, StunTransactionId* pTransId) { HRESULT hr = S_OK; CStunMessageReader reader; CStunMessageReader::ReaderParseState state; state = reader.AddBytes(spMsg->GetData(), spMsg->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_UNEXPECTED); ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_UNEXPECTED); reader.GetTransactionId(pTransId); ChkIfA(false == IsTransactionIdValid(*pTransId), E_FAIL); Cleanup: return hr; }
HRESULT CStunClientTestBase::BasicReaderValidation(CRefCountedBuffer& spMsg, CStunMessageReader& reader) { HRESULT hr = S_OK; CStunMessageReader::ReaderParseState readerstate; StunTransactionId transid; int cmp = 0; readerstate = reader.AddBytes(spMsg->GetData(), spMsg->GetSize()); ChkIf(readerstate != CStunMessageReader::BodyValidated, E_FAIL); reader.GetTransactionId(&transid); cmp = memcmp(transid.id, _transid.id, sizeof(_transid)); ChkIf(cmp!=0, E_FAIL); Cleanup: return hr; }
HRESULT CTestMessageHandler::ValidateOtherAddress(CStunMessageReader& reader, const CSocketAddress& addrExpected) { HRESULT hr = S_OK; CSocketAddress addr; ChkA(reader.GetOtherAddress(&addr)); ChkIfA(false == addrExpected.IsSameIP_and_Port(addr), E_FAIL); Cleanup: return hr; }
// test long-credential authentication HRESULT CTestMessageHandler::Test4() { HRESULT hr=S_OK; CStunMessageBuilder builder1, builder2; CStunMessageReader readerResponse; CSocketAddress addrMapped; uint16_t errorcode = 0; char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]; char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]; // ----------------------------------------------------------------------- // simulate a user making a request with no message integrity attribute (or username, or realm) InitBindingRequest(builder1); builder1.FixLengthField(); ChkA(SendHelper(builder1, &readerResponse, _spAuthLong)); Chk(readerResponse.GetErrorCode(&errorcode)); ChkIfA(readerResponse.GetMessageClass() != ::StunMsgClassFailureResponse, E_UNEXPECTED); ChkIf(errorcode != ::STUN_ERROR_UNAUTHORIZED, E_UNEXPECTED); readerResponse.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, szRealm, ARRAYSIZE(szRealm)); readerResponse.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, szNonce, ARRAYSIZE(szNonce)); // -------------------------------------------------------------------------------- // now simulate the follow-up request readerResponse.Reset(); InitBindingRequest(builder2); builder2.AddNonce(szNonce); builder2.AddRealm(szRealm); builder2.AddUserName("AuthorizedUser"); builder2.AddMessageIntegrityLongTerm("AuthorizedUser", szRealm, "password"); builder2.FixLengthField(); ChkA(SendHelper(builder2, &readerResponse, _spAuthLong)); ChkIfA(readerResponse.GetMessageClass() != ::StunMsgClassSuccessResponse, E_UNEXPECTED); // should have a mapped address ChkA(readerResponse.GetMappedAddress(&addrMapped)); // and the message integrity field should be valid ChkA(readerResponse.ValidateMessageIntegrityLong("AuthorizedUser", szRealm, "password")); Cleanup: return hr; }
HRESULT CTestMessageHandler::ValidateOriginAddress(CStunMessageReader& reader, SocketRole socketExpected) { HRESULT hr = S_OK; StunAttribute attrib; CSocketAddress addrExpected, mappedaddr; CRefCountedBuffer spBuffer; Chk(reader.GetStream().GetBuffer(&spBuffer)); Chk(_spTransport->GetSocketAddressForRole(socketExpected, &addrExpected)); ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_RESPONSE_ORIGIN, &attrib)); ChkA(GetMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, &mappedaddr)); ChkIfA(false == addrExpected.IsSameIP_and_Port(mappedaddr), E_FAIL); ChkIfA(socketExpected != _spTransport->m_outputRole, E_FAIL); Cleanup: return hr; }
// Test1 - just do a basic binding request HRESULT CTestMessageHandler::Test1() { HRESULT hr = S_OK; CStunMessageBuilder builder; CRefCountedBuffer spBuffer, spBufferOut(new CBuffer(MAX_STUN_MESSAGE_SIZE)); CStunMessageReader reader; StunMessageIn msgIn; StunMessageOut msgOut; TransportAddressSet tas = {}; InitTransportAddressSet(tas, true, true, true, true); ChkA(InitBindingRequest(builder)); Chk(builder.GetResult(&spBuffer)); ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL); // a message send to the PP socket on the server from the msgIn.socketrole = RolePP; msgIn.addrRemote = _addrMapped; msgIn.pReader = &reader; msgIn.addrLocal = _addrServerPP; msgIn.fConnectionOriented = false; spBuffer.reset(); msgOut.spBufferOut = spBufferOut; msgOut.socketrole = RoleAA; // deliberately wrong - so we can validate if it got changed to RolePP ChkA(CStunRequestHandler::ProcessRequest(msgIn, msgOut, &tas, NULL)); reader.Reset(); ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBufferOut->GetData(), spBufferOut->GetSize()), E_FAIL); // validate that the message returned is a success response for a binding request ChkIfA(reader.GetMessageClass() != StunMsgClassSuccessResponse, E_FAIL); ChkIfA(reader.GetMessageType() != (uint16_t)StunMsgTypeBinding, E_FAIL); // Validate that the message came from the server port we expected // and that it's the same address the server set for the origin address ChkIfA(msgOut.socketrole != RolePP, E_FAIL); ChkA(ValidateResponseOriginAddress(reader, _addrServerPP)); ChkIfA(msgOut.addrDest.IsSameIP_and_Port(_addrMapped)==false, E_FAIL); // validate that the mapping was done correctly ChkA(ValidateMappedAddress(reader, _addrMapped, false)); ChkA(ValidateOtherAddress(reader, _addrServerAA)); Cleanup: return hr; }
// test simple authentication HRESULT CTestMessageHandler::Test3() { CStunMessageBuilder builder1, builder2, builder3; CStunMessageReader readerResponse; uint16_t errorcode = 0; HRESULT hr = S_OK; // ----------------------------------------------------------------------- // simulate an authorized user making a request with a valid password ChkA(InitBindingRequest(builder1)); builder1.AddStringAttribute(STUN_ATTRIBUTE_USERNAME, "AuthorizedUser"); builder1.AddMessageIntegrityShortTerm("password"); builder1.FixLengthField(); ChkA(SendHelper(builder1, &readerResponse, _spAuthShort)); ChkA(readerResponse.ValidateMessageIntegrityShort("password")); // ----------------------------------------------------------------------- // simulate a user with a bad password readerResponse.Reset(); InitBindingRequest(builder2); builder2.AddStringAttribute(STUN_ATTRIBUTE_USERNAME, "WrongUser"); builder2.AddMessageIntegrityShortTerm("wrongpassword"); builder2.FixLengthField(); ChkA(SendHelper(builder2, &readerResponse, _spAuthShort)) errorcode = 0; ChkA(readerResponse.GetErrorCode(&errorcode)); ChkIfA(errorcode != ::STUN_ERROR_UNAUTHORIZED, E_FAIL); // ----------------------------------------------------------------------- // simulate a client sending no credentials - we expect it to fire back with a 400/bad-request readerResponse.Reset(); ChkA(InitBindingRequest(builder3)); ChkA(SendHelper(builder3, &readerResponse, _spAuthShort)); errorcode = 0; ChkA(readerResponse.GetErrorCode(&errorcode)); ChkIfA(errorcode != ::STUN_ERROR_BADREQUEST, E_FAIL); Cleanup: return hr; }
void CStunThreadMessageHandler::ProcessRequest(StunMessageEnvelope& message) { CStunMessageReader reader; CStunMessageReader::ReaderParseState state; uint16_t responsePort = 0; HRESULT hr = S_OK; ChkIfA(_spStunResponder == NULL, E_FAIL); _spReaderBuffer->SetSize(0); _spResponseBuffer->SetSize(0); _message = message; _addrResponse = message.remoteAddr; _socketOutput = message.localSocket; _fRequestHasResponsePort = false; // zero out _error without the overhead of zero'ing out every byte in the strings _error.errorcode = 0; _error.szNonce[0] = 0; _error.szRealm[0] = 0; _error.attribUnknown = 0; _integrity.fSendWithIntegrity = false; _integrity.szUser[0] = '\0'; _integrity.szRealm[0] = '\0'; _integrity.szPassword[0] = '\0'; // attach the temp buffer to reader reader.GetStream().Attach(_spReaderBuffer, true); reader.SetAllowLegacyFormat(true); // parse the request state = reader.AddBytes(message.spBuffer->GetData(), message.spBuffer->GetSize()); // If we get something that can't be validated as a stun message, don't send back a response // STUN RFC may suggest sending back a "500", but I think that's the wrong approach. ChkIf (state != CStunMessageReader::BodyValidated, E_FAIL); // Regardless of what we send back, let's always attempt to honor a response port request // Fix the destination port if the client asked for us to send back to another port if (SUCCEEDED(reader.GetResponsePort(&responsePort))) { _addrResponse.SetPort(responsePort); _fRequestHasResponsePort = true; } reader.GetTransactionId(&_transid); // ignore anything that is not a request (with no response) ChkIf(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL); // pre-prep the error message in case we wind up needing to send it _error.msgtype = reader.GetMessageType(); _error.msgclass = StunMsgClassFailureResponse; if (reader.GetMessageType() != StunMsgTypeBinding) { // we're going to send back an error response _error.errorcode = STUN_ERROR_BADREQUEST; // invalid request } else { // handle authentication - but only if an auth provider has been set hr = ValidateAuth(reader); // if auth succeeded, then carry on to handling the request if (SUCCEEDED(hr) && (_error.errorcode==0)) { // handle the binding request hr = ProcessBindingRequest(reader); } // catch all for any case where an error occurred if (FAILED(hr) && (_error.errorcode==0)) { _error.errorcode = STUN_ERROR_BADREQUEST; } } if (_error.errorcode != 0) { // if either ValidateAuth or ProcessBindingRequest set an errorcode, or a fatal error occurred SendErrorResponse(); } else { SendResponse(); } Cleanup: return; }
HRESULT CStunThreadMessageHandler::ValidateAuth(CStunMessageReader& reader) { AuthAttributes authattributes; AuthResponse authresponse; HRESULT hr = S_OK; HRESULT hrRet = S_OK; if (_spAuth == NULL) { return S_OK; // nothing to do if there is no auth mechanism in place } memset(&authattributes, '\0', sizeof(authattributes)); memset(&authresponse, '\0', sizeof(authresponse)); reader.GetStringAttributeByType(STUN_ATTRIBUTE_USERNAME, authattributes.szUser, ARRAYSIZE(authattributes.szUser)); reader.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, authattributes.szRealm, ARRAYSIZE(authattributes.szRealm)); reader.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, authattributes.szNonce, ARRAYSIZE(authattributes.szNonce)); reader.GetStringAttributeByType(::STUN_ATTRIBUTE_LEGACY_PASSWORD, authattributes.szLegacyPassword, ARRAYSIZE(authattributes.szLegacyPassword)); authattributes.fMessageIntegrityPresent = reader.HasMessageIntegrityAttribute(); Chk(_spAuth->DoAuthCheck(&authattributes, &authresponse)); // enforce that everything is null terminated authresponse.szNonce[ARRAYSIZE(authresponse.szNonce)-1] = 0; authresponse.szRealm[ARRAYSIZE(authresponse.szRealm)-1] = 0; authresponse.szPassword[ARRAYSIZE(authresponse.szPassword)-1] = 0; // now decide how to handle the auth if (authresponse.responseType == StaleNonce) { _error.errorcode = STUN_ERROR_STALENONCE; } else if (authresponse.responseType == Unauthorized) { _error.errorcode = STUN_ERROR_UNAUTHORIZED; } else if (authresponse.responseType == Reject) { _error.errorcode = STUN_ERROR_BADREQUEST; } else if (authresponse.responseType == Allow) { // nothing to do! } else if (authresponse.responseType == AllowConditional) { // validate the message in // if either ValidateAuth or ProcessBindingRequest set an errorcode.... if (authresponse.authCredMech == AuthCredLongTerm) { hrRet = reader.ValidateMessageIntegrityLong(authattributes.szUser, authattributes.szRealm, authresponse.szPassword); } else { hrRet = reader.ValidateMessageIntegrityShort(authresponse.szPassword); } if (SUCCEEDED(hrRet)) { _integrity.fSendWithIntegrity = true; _integrity.fUseLongTerm = (authresponse.authCredMech == AuthCredLongTerm); COMPILE_TIME_ASSERT(sizeof(_integrity.szPassword)==sizeof(authresponse.szPassword)); strcpy(_integrity.szPassword, authresponse.szPassword); strcpy(_integrity.szUser, authattributes.szUser); strcpy(_integrity.szRealm, authattributes.szRealm); } else { // bad password - so now turn this thing into a 401 _error.errorcode = STUN_ERROR_UNAUTHORIZED; } } if ((_error.errorcode == STUN_ERROR_UNAUTHORIZED) || (_error.errorcode == STUN_ERROR_STALENONCE)) { strcpy(_error.szRealm, authresponse.szRealm); strcpy(_error.szNonce, authresponse.szNonce); } Cleanup: return hr; }
HRESULT CStunThreadMessageHandler::ProcessBindingRequest(CStunMessageReader& reader) { HRESULT hrTmp; bool fRequestHasPaddingAttribute = false; SocketRole socketOutput = _message.localSocket; StunChangeRequestAttribute changerequest = {}; bool fSendOtherAddress = false; bool fSendOriginAddress = false; SocketRole socketOther; CSocketAddress addrOrigin; CSocketAddress addrOther; CStunMessageBuilder builder; uint16_t paddingSize = 0; bool fLegacyFormat = false; // set to true if the client appears to be rfc3489 based instead of based on rfc 5789 _spResponseBuffer->SetSize(0); builder.GetStream().Attach(_spResponseBuffer, true); fLegacyFormat = reader.IsMessageLegacyFormat(); // check for an alternate response port // check for padding attribute (todo - figure out how to inject padding into the response) // check for a change request and validate we can do it. If so, set _socketOutput. If not, fill out _error and return. // determine if we have an "other" address to notify the caller about // did the request come with a padding request if (SUCCEEDED(reader.GetPaddingAttributeSize(&paddingSize))) { // todo - figure out how we're going to get the MTU size of the outgoing interface fRequestHasPaddingAttribute = true; } // as per 5780, section 6.1, If the Request contained a PADDING attribute... // "If the Request also contains the RESPONSE-PORT attribute the server MUST return an error response of type 400." if (_fRequestHasResponsePort && fRequestHasPaddingAttribute) { _error.errorcode = STUN_ERROR_BADREQUEST; return E_FAIL; } // handle change request logic and figure out what "other-address" attribute is going to be if (SUCCEEDED(reader.GetChangeRequest(&changerequest))) { if (changerequest.fChangeIP) { socketOutput = SocketRoleSwapIP(socketOutput); } if(changerequest.fChangePort) { socketOutput = SocketRoleSwapPort(socketOutput); } // IsValidSocketRole just validates the enum, not whether or not we can send on it ASSERT(IsValidSocketRole(socketOutput)); // now, make sure we have the ability to send from another socket if (_spStunResponder->HasAddress(socketOutput) == false) { // send back an error. We're being asked to respond using another address that we don't have a socket for _error.errorcode = STUN_ERROR_BADREQUEST; return E_FAIL; } } // If we're only working one socket, then that's ok, we just don't send back an "other address" unless we have all four sockets confgiured // now here's a problem. If we binded to "INADDR_ANY", all of the sockets will have "0.0.0.0" for an address (same for IPV6) // So we effectively can't send back "other address" if don't really know our own IP address // Fortunately, recvfromex and the ioctls on the socket allow address discovery a bit better fSendOtherAddress = (_spStunResponder->HasAddress(RolePP) && _spStunResponder->HasAddress(RolePA) && _spStunResponder->HasAddress(RoleAP) && _spStunResponder->HasAddress(RoleAA)); if (fSendOtherAddress) { socketOther = SocketRoleSwapIP(SocketRoleSwapPort(_message.localSocket)); hrTmp = _spStunResponder->GetSocketAddressForRole(socketOther, &addrOther); ASSERT(SUCCEEDED(hrTmp)); // so if our ip address is "0.0.0.0", disable this attribute fSendOtherAddress = (SUCCEEDED(hrTmp) && (addrOther.IsIPAddressZero()==false)); } // What's our address origin? VERIFY(SUCCEEDED(_spStunResponder->GetSocketAddressForRole(socketOutput, &addrOrigin))); if (addrOrigin.IsIPAddressZero()) { // Since we're sending back from the IP address we received on, we can just use the address the message came in on // Otherwise, we don't actually know it if (socketOutput == _message.localSocket) { addrOrigin = _message.localAddr; } } fSendOriginAddress = (false == addrOrigin.IsIPAddressZero()); // Success - we're all clear to build the response _socketOutput = socketOutput; _spResponseBuffer->SetSize(0); builder.GetStream().Attach(_spResponseBuffer, true); builder.AddHeader(StunMsgTypeBinding, StunMsgClassSuccessResponse); builder.AddTransactionId(_transid); builder.AddMappedAddress(_message.remoteAddr); if (fLegacyFormat == false) { builder.AddXorMappedAddress(_message.remoteAddr); } if (fSendOriginAddress) { builder.AddResponseOriginAddress(addrOrigin); } if (fSendOtherAddress) { builder.AddOtherAddress(addrOther, fLegacyFormat); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS } // finally - if we're supposed to have a message integrity attribute as a result of authorization, add it at the very end if (_integrity.fSendWithIntegrity) { if (_integrity.fUseLongTerm == false) { builder.AddMessageIntegrityShortTerm(_integrity.szPassword); } else { builder.AddMessageIntegrityLongTerm(_integrity.szUser, _integrity.szRealm, _integrity.szPassword); } } builder.FixLengthField(); return S_OK; }
// The goal of this test is to just validate that we can create a message from CStunMessageBuilder and have it's output parsed correctly by CStunMessageReader // Also helps validate CSocketAddress HRESULT CTestBuilder::Test1() { HRESULT hr = S_OK; CStunMessageBuilder builder; CStunMessageReader reader; StunAttribute attrib; CRefCountedBuffer spBuffer; CRefCountedBuffer spBufferReader; CSocketAddress addrValidate(0,0); StunTransactionId transid = {}; uint32_t ipvalidate = 0; CSocketAddress addr(0x7f000001, 9999); CSocketAddress addrOrigin(0xAABBCCDD, 8888); CSocketAddress addrOther(0x11223344, 7777); ChkA(builder.AddBindingRequestHeader()); ChkA(builder.AddRandomTransactionId(&transid)); ChkA(builder.AddStringAttribute(STUN_ATTRIBUTE_SOFTWARE, "FOOBAR")); ChkA(builder.AddMappedAddress(addr)); ChkA(builder.AddXorMappedAddress(addr)); ChkA(builder.AddOtherAddress(addrOther)); ChkA(builder.AddResponseOriginAddress(addrOrigin)); ChkA(builder.AddFingerprintAttribute()); ChkA(builder.GetResult(&spBuffer)); ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL); ChkIfA(reader.HasFingerprintAttribute() == false, E_FAIL); ChkIfA(reader.IsFingerprintAttributeValid() == false, E_FAIL); ChkIfA(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL); ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_FAIL); ChkA(reader.GetBuffer(&spBufferReader)); ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_SOFTWARE, &attrib)); ChkIfA(attrib.attributeType != STUN_ATTRIBUTE_SOFTWARE, E_FAIL); ChkIfA(0 != ::strncmp("FOOBAR", (const char*)(spBufferReader->GetData() + attrib.offset), attrib.size), E_FAIL); ChkA(reader.GetXorMappedAddress(&addrValidate)); ChkIf(addrValidate.IsSameIP_and_Port(addr) == false, E_FAIL); ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL); addrValidate = CSocketAddress(0,0); ChkA(reader.GetMappedAddress(&addrValidate)); ChkIfA(addrValidate.GetPort() != 9999, E_FAIL); ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL); ChkIfA(4 != addrValidate.GetIP(&ipvalidate, 4), E_FAIL); ChkIfA(ipvalidate != 0x7f000001, E_FAIL); addrValidate = CSocketAddress(0,0); ipvalidate = 0; reader.GetOtherAddress(&addrValidate); ChkIfA(addrValidate.GetPort() != 7777, E_FAIL); ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL); ChkIfA(4 != addrValidate.GetIP(&ipvalidate, 4), E_FAIL); ChkIf(ipvalidate != 0x11223344, E_FAIL); Cleanup: return hr; }
// Test2 - send a binding request to a duplex server instructing it to send back on it's alternate port and alternate IP to an alternate client port HRESULT CTestMessageHandler::Test2() { HRESULT hr = S_OK; CStunMessageBuilder builder; CRefCountedBuffer spBuffer, spBufferOut(new CBuffer(MAX_STUN_MESSAGE_SIZE)); CStunMessageReader reader; StunMessageIn msgIn; StunMessageOut msgOut; TransportAddressSet tas = {}; uint16_t responsePort = 2222; StunChangeRequestAttribute changereq; CStunMessageReader::ReaderParseState state; CSocketAddress addrDestExpected; InitTransportAddressSet(tas, true, true, true, true); InitBindingRequest(builder); builder.AddResponsePort(responsePort); changereq.fChangeIP = true; changereq.fChangePort = true; builder.AddChangeRequest(changereq); builder.AddResponsePort(responsePort); builder.GetResult(&spBuffer); ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL); msgIn.fConnectionOriented = false; msgIn.addrLocal = _addrServerPP; msgIn.pReader = &reader; msgIn.socketrole = RolePP; msgIn.addrRemote = _addrMapped; msgOut.socketrole = RolePP; // deliberate initialized wrong msgOut.spBufferOut = spBufferOut; ChkA(CStunRequestHandler::ProcessRequest(msgIn, msgOut, &tas, NULL)); // parse the response reader.Reset(); state = reader.AddBytes(spBufferOut->GetData(), spBufferOut->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); // validate that the message was sent back from the AA ChkIfA(msgOut.socketrole != RoleAA, E_FAIL); // validate that the server though it was sending back from the AA ChkA(ValidateResponseOriginAddress(reader, _addrServerAA)); // validate that the message was sent to the response port requested addrDestExpected = _addrMapped; addrDestExpected.SetPort(responsePort); ChkIfA(addrDestExpected.IsSameIP_and_Port(msgOut.addrDest)==false, E_FAIL); // validate that the binding response came back ChkA(ValidateMappedAddress(reader, _addrMapped, false)); // the "other" address is still AA (See RFC 3489 - section 8.1) ChkA(ValidateOtherAddress(reader, _addrServerAA)); Cleanup: return hr; }
HRESULT CTestReader::Test1() { HRESULT hr = S_OK; StunAttribute attrib; const char* pszExpectedSoftwareAttribute = "STUN test client"; const char* pszExpectedUserName = "******"; CRefCountedBuffer spBuffer; char szStringValue[100]; const unsigned char *req = c_requestbytes; size_t requestsize = sizeof(c_requestbytes)-1; // -1 to get rid of the trailing null CStunMessageReader reader; CStunMessageReader::ReaderParseState state; // reader is expecting at least enough bytes to fill the header ChkIfA(reader.AddBytes(NULL, 0) != CStunMessageReader::HeaderNotRead, E_FAIL); ChkIfA(reader.HowManyBytesNeeded() != STUN_HEADER_SIZE, E_FAIL); state = reader.AddBytes(req, requestsize); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); ChkIfA(reader.HowManyBytesNeeded() != 0, E_FAIL); ChkA(reader.GetBuffer(&spBuffer)); ChkIfA(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL); ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_FAIL); ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_SOFTWARE, &attrib)); ChkIfA(attrib.attributeType != STUN_ATTRIBUTE_SOFTWARE, E_FAIL); ChkIfA(0 != ::strncmp(pszExpectedSoftwareAttribute, (const char*)(spBuffer->GetData() + attrib.offset), attrib.size), E_FAIL); ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_USERNAME, &attrib)); ChkIfA(attrib.attributeType != STUN_ATTRIBUTE_USERNAME, E_FAIL); ChkIfA(0 != ::strncmp(pszExpectedUserName, (const char*)(spBuffer->GetData() + attrib.offset), attrib.size), E_FAIL); ChkA(reader.GetStringAttributeByType(STUN_ATTRIBUTE_SOFTWARE, szStringValue, ARRAYSIZE(szStringValue))); ChkIfA(0 != ::strcmp(pszExpectedSoftwareAttribute, szStringValue), E_FAIL); ChkIfA(reader.HasFingerprintAttribute() == false, E_FAIL); ChkIfA(reader.IsFingerprintAttributeValid() == false, E_FAIL); ChkIfA(reader.HasMessageIntegrityAttribute() == false, E_FAIL); ChkA(reader.ValidateMessageIntegrityShort(c_password)); Cleanup: return hr; }
void TcpClientLoop(StunClientLogicConfig& config, ClientSocketConfig& socketconfig) { HRESULT hr = S_OK; CStunSocket stunsocket; CStunClientLogic clientlogic; int sock; CRefCountedBuffer spMsg(new CBuffer(1500)); CRefCountedBuffer spMsgReader(new CBuffer(1500)); CSocketAddress addrDest, addrLocal; HRESULT hrRet, hrResult; int ret; size_t bytes_sent, bytes_recv; size_t bytes_to_send, max_bytes_recv, remaining; uint8_t* pData=NULL; size_t readsize; CStunMessageReader reader; StunClientResults results; hr= clientlogic.Initialize(config); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "clientlogic.Initialize failed (hr == %x)", hr); Chk(hr); } while (true) { stunsocket.Close(); hr = stunsocket.TCPInit(socketconfig.addrLocal, RolePP, true); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to create local socket for TCP connection (hr == %x)", hr); Chk(hr); } hrRet = clientlogic.GetNextMessage(spMsg, &addrDest, ::GetMillisecondCounter()); if (hrRet == E_STUNCLIENT_RESULTS_READY) { // clean exit break; } // we should never get a "still waiting" return with TCP, because config.timeout is 0 ASSERT(hrRet != E_STUNCLIENT_STILL_WAITING); if (FAILED(hrRet)) { Chk(hrRet); } // connect to server sock = stunsocket.GetSocketHandle(); ret = ::connect(sock, addrDest.GetSockAddr(), addrDest.GetSockAddrLength()); if (ret == -1) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Can't connect to server (hr == %x)", hrResult); Chk(hrResult); } Logging::LogMsg(LL_DEBUG, "Connected to server"); bytes_to_send = (int)(spMsg->GetSize()); bytes_sent = 0; pData = spMsg->GetData(); while (bytes_sent < bytes_to_send) { ret = ::send(sock, pData+bytes_sent, bytes_to_send-bytes_sent, 0); if (ret < 0) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Send failed (hr == %x)", hrResult); Chk(hrResult); } bytes_sent += ret; } Logging::LogMsg(LL_DEBUG, "Request sent - waiting for response"); // consume the response reader.Reset(); reader.GetStream().Attach(spMsgReader, true); pData = spMsg->GetData(); bytes_recv = 0; max_bytes_recv = spMsg->GetAllocatedSize(); remaining = max_bytes_recv; while (remaining > 0) { readsize = reader.HowManyBytesNeeded(); if (readsize == 0) { break; } if (readsize > remaining) { // technically an error, but the client logic will figure it out ASSERT(false); break; } ret = ::recv(sock, pData+bytes_recv, readsize, 0); if (ret == 0) { // server cut us off before we got all the bytes we thought we were supposed to get? ASSERT(false); break; } if (ret < 0) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Recv failed (hr == %x)", hrResult); Chk(hrResult); } reader.AddBytes(pData+bytes_recv, ret); bytes_recv += ret; remaining = max_bytes_recv - bytes_recv; spMsg->SetSize(bytes_recv); } // now feed the response into the client logic stunsocket.UpdateAddresses(); addrLocal = stunsocket.GetLocalAddress(); clientlogic.ProcessResponse(spMsg, addrDest, addrLocal); } stunsocket.Close(); results.Init(); clientlogic.GetResults(&results); ::DumpResults(config, results); Cleanup: return; }
HRESULT CTestClientLogic::TestBehaviorAndFiltering(bool fBehaviorTest, NatBehavior behavior, bool fFilteringTest, NatFiltering filtering) { HRESULT hr = S_OK; StunClientLogicConfig config; HRESULT hrRet; uint32_t time = 0; CRefCountedBuffer spMsgOut(new CBuffer(MAX_STUN_MESSAGE_SIZE)); CRefCountedBuffer spMsgResponse(new CBuffer(MAX_STUN_MESSAGE_SIZE)); SocketRole outputRole; CSocketAddress addrDummy; StunMessageIn stunmsgIn; StunMessageOut stunmsgOut; CSocketAddress addrDest; CSocketAddress addrMapped; CSocketAddress addrServerResponse; // what address the fake server responded back on StunClientResults results; StunTransactionId transid= {}; //std::string strAddr; ChkA(CommonInit(behavior, filtering)); config.addrServer = _addrServerPP; config.fBehaviorTest = fBehaviorTest; config.fFilteringTest = fFilteringTest; config.timeoutSeconds = 5; config.uMaxAttempts = 10; ChkA(_spClientLogic->Initialize(config)); while (true) { CStunMessageReader reader; bool fDropMessage = false; time += 1000; hrRet = _spClientLogic->GetNextMessage(spMsgOut, &addrDest, time); if (hrRet == E_STUNCLIENT_STILL_WAITING) { //printf("GetNextMessage returned 'still waiting'\n"); continue; } if (hrRet == E_STUNCLIENT_RESULTS_READY) { //printf("GetNextMessage returned 'results ready'\n"); break; } //addrDest.ToString(&strAddr); //printf("Client is sending stun packet to %s\n", strAddr.c_str()); ChkA(GetMappedAddressForDestinationAddress(addrDest, &addrMapped)); //addrMapped.ToString(&strAddr); //printf("Server is receiving stun packet from %s\n", strAddr.c_str()); ChkA(ValidateBindingRequest(spMsgOut, &transid)); // -------------------------------------------------- reader.AddBytes(spMsgOut->GetData(), spMsgOut->GetSize()); ChkIfA(reader.GetState() != CStunMessageReader::BodyValidated, E_UNEXPECTED); // Simulate sending the binding request and getting a response back stunmsgIn.socketrole = GetSocketRoleForDestinationAddress(addrDest); stunmsgIn.addrLocal = addrDest; stunmsgIn.addrRemote = addrMapped; stunmsgIn.fConnectionOriented = false; stunmsgIn.pReader = &reader; stunmsgOut.socketrole = (SocketRole)-1; // intentionally setting it wrong stunmsgOut.addrDest = addrDummy; // we don't care what address the server sent back to stunmsgOut.spBufferOut = spMsgResponse; spMsgResponse->SetSize(0); ChkA(::CStunRequestHandler::ProcessRequest(stunmsgIn, stunmsgOut, &_tsa, NULL)); // simulate the message coming back // make sure we got something! outputRole = stunmsgOut.socketrole; ChkIfA(::IsValidSocketRole(outputRole)==false, E_FAIL); ChkIfA(spMsgResponse->GetSize() == 0, E_FAIL); addrServerResponse = _tsa.set[stunmsgOut.socketrole].addr; // -------------------------------------------------- //addrServerResponse.ToString(&strAddr); //printf("Server is sending back from %s\n", strAddr.c_str()); // if the request went to PP, but came back from AA or AP, then it's likely a filtering test // decide if we need to drop the response fDropMessage = ( addrDest.IsSameIP_and_Port(_addrServerPP) && ( ((outputRole == RoleAA) && (_fAllowChangeRequestAA==false)) || ((outputRole == RolePA) && (_fAllowChangeRequestPA==false)) ) ); //{ // CStunMessageReader::ReaderParseState state; // CStunMessageReader readerDebug; // state = readerDebug.AddBytes(spMsgResponse->GetData(), spMsgResponse->GetSize()); // if (state != CStunMessageReader::BodyValidated) // { // printf("Error - response from server doesn't look valid"); // } // else // { // CSocketAddress addr; // readerDebug.GetMappedAddress(&addr); // addr.ToString(&strAddr); // printf("Response from server indicates our mapped address is %s\n", strAddr.c_str()); // } //} if (fDropMessage == false) { ChkA(_spClientLogic->ProcessResponse(spMsgResponse, addrServerResponse, _addrLocal)); } } // now validate the results results.Init(); // zero it out _spClientLogic->GetResults(&results); ChkIfA(results.behavior != behavior, E_UNEXPECTED); Cleanup: return hr; }
// send a binding request to a duplex server instructing it to send back on it's alternate port and alternate IP to an alternate client port HRESULT CTestMessageHandler::Test2() { HRESULT hr=S_OK; CStunMessageBuilder builder; CSocketAddress clientaddr(0x12345678, 9876); CSocketAddress recvaddr; uint16_t responsePort = 2222; CRefCountedBuffer spBuffer; CStunThreadMessageHandler handler; CStunMessageReader reader; CStunMessageReader::ReaderParseState state; ::StunChangeRequestAttribute changereq; StunMessageEnvelope message; _spTransport->Reset(); _spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234)); _spTransport->AddPA(CSocketAddress(0xaaaaaaaa, 1235)); _spTransport->AddAP(CSocketAddress(0xbbbbbbbb, 1234)); _spTransport->AddAA(CSocketAddress(0xbbbbbbbb, 1235)); InitBindingRequest(builder); builder.AddResponsePort(responsePort); changereq.fChangeIP = true; changereq.fChangePort = true; builder.AddChangeRequest(changereq); builder.AddResponsePort(responsePort); builder.GetResult(&spBuffer); message.localSocket = RolePP; message.remoteAddr = clientaddr; message.spBuffer = spBuffer; _spTransport->GetSocketAddressForRole(RolePP, &(message.localAddr)); handler.SetResponder(_spTransport); handler.ProcessRequest(message); spBuffer->Reset(); _spTransport->GetOutputStream().GetBuffer(&spBuffer); // parse the response state = reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); // validate that the binding response matches our expectations ChkA(ValidateMappedAddress(reader, clientaddr)); ChkA(ValidateOriginAddress(reader, RoleAA)); // did it get sent back to where we thought it was recvaddr = clientaddr; recvaddr.SetPort(responsePort); ChkA(ValidateResponseAddress(recvaddr)); Cleanup: return hr; }