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;
    
}
Exemple #3
0
// 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;
}
Exemple #6
0
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;
}
Exemple #10
0
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;
}
Exemple #20
0
// 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;
}
Exemple #22
0
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;
 }
Exemple #23
0
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;
    
}
Exemple #24
0
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;
}