/** * Suggests a default adapter for a given stun server socket * @param fPrimary - true if the returned adapter is to be used for the primary socket in a stun server. Typically passing "true" means "return the first adapter enumerated", otherwise return the second adapter enumerated" * @param family - Either AF_INET or AF_INET6 * @param pSocketAddr - OUT param. On success, contains the address to bind to * @return S_OK on success. Error code otherwise. */ HRESULT GetBestAddressForSocketBind(bool fPrimary, int family, uint16_t port, CSocketAddress* pSocketAddr) { HRESULT hr = S_OK; ifaddrs* pList = NULL; ifaddrs* pAdapter = NULL; ifaddrs* pAdapter1 = NULL; ifaddrs* pAdapter2 = NULL; ChkIfA(pSocketAddr == NULL, E_INVALIDARG); ChkIf(getifaddrs(&pList) < 0, ERRNOHR); GetDefaultAdapters(family, pList, &pAdapter1, &pAdapter2); pAdapter = fPrimary ? pAdapter1 : pAdapter2; ChkIf(pAdapter==NULL, E_FAIL); ChkIfA(pAdapter->ifa_addr==NULL, E_UNEXPECTED); *pSocketAddr = CSocketAddress(*pAdapter->ifa_addr); pSocketAddr->SetPort(port); Cleanup: freeifaddrs(pList); return hr; }
HRESULT CTestAtomicHelpers::Run() { HRESULT hr = S_OK; int value = -2000; int nextexpected = -2000; int result = 0; while (value < 2000) { nextexpected++; result = AtomicIncrement(&value); ChkIf(result != nextexpected, E_UNEXPECTED); ChkIf(result != value, E_UNEXPECTED); } value = 2000; nextexpected = 2000; while (value > -2000) { nextexpected--; result = AtomicDecrement(&value); ChkIf(result != nextexpected, E_UNEXPECTED); ChkIf(result != value, E_UNEXPECTED); } 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; }
HRESULT CStunMessageBuilder::AddErrorCode(uint16_t errorNumber, const char* pszReason) { HRESULT hr = S_OK; uint8_t padBytes[4] = {0}; size_t strsize = (pszReason==NULL) ? 0 : strlen(pszReason); size_t size = strsize + 4; size_t sizeheader = size; size_t padding = 0; uint8_t cl = 0; uint8_t ernum = 0; ChkIf(strsize >= 128, E_INVALIDARG); ChkIf(errorNumber < 300, E_INVALIDARG); ChkIf(errorNumber > 600, E_INVALIDARG); padding = (size%4) ? (4-size%4) : 0; // fix for RFC 3489 clients - explicitly do the 4-byte padding alignment on the string with spaces instead of // padding the message with zeros. Adjust the length field to always be a multiple of 4. if ((size % 4) && _fLegacyMode) { padding = 4 - (size % 4); } if (_fLegacyMode) { sizeheader += padding; } Chk(AddAttributeHeader(STUN_ATTRIBUTE_ERRORCODE, sizeheader)); Chk(_stream.WriteInt16(0)); cl = (uint8_t)(errorNumber / 100); ernum = (uint8_t)(errorNumber % 100); Chk(_stream.WriteUint8(cl)); Chk(_stream.WriteUint8(ernum)); if (strsize > 0) { _stream.Write(pszReason, strsize); } if (padding > 0) { Chk(_stream.Write(padBytes, padding)); } 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; }
//static HRESULT CStunSocket::Create(const CSocketAddress& addrlocal, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared) { int sock = -1; int ret; CStunSocket* pSocket = NULL; sockaddr_storage addrBind = {}; socklen_t sizeaddrBind; HRESULT hr = S_OK; ChkIfA(pStunSocketShared == NULL, E_INVALIDARG); sock = socket(addrlocal.GetFamily(), SOCK_DGRAM, 0); ChkIf(sock < 0, ERRNOHR); ret = bind(sock, addrlocal.GetSockAddr(), addrlocal.GetSockAddrLength()); ChkIf(ret < 0, ERRNOHR); // call get sockname to find out what port we just binded to. (Useful for when addrLocal.port is 0) sizeaddrBind = sizeof(addrBind); ret = ::getsockname(sock, (sockaddr*)&addrBind, &sizeaddrBind); ChkIf(ret < 0, ERRNOHR); pSocket = new CStunSocket(); pSocket->_sock = sock; pSocket->_addrlocal = CSocketAddress(*(sockaddr*)&addrBind); pSocket->_role = role; sock = -1; { boost::shared_ptr<CStunSocket> spTmp(pSocket); pStunSocketShared->swap(spTmp); } Cleanup: if (sock != -1) { close(sock); sock = -1; } 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 CSocketAddress::ToStringBuffer(char* pszAddrBytes, size_t length) const { HRESULT hr = S_OK; int family = GetFamily(); const void *pAddrBytes = NULL; const char* pszResult = NULL; const size_t portLength = 6; // colon plus 5 digit string e.g. ":55555" char szPort[portLength+1]; ChkIfA(pszAddrBytes == NULL, E_INVALIDARG); ChkIf(length <= 0, E_INVALIDARG); pszAddrBytes[0] = 0; if (family == AF_INET) { pAddrBytes = &(_address.addr4.sin_addr); ChkIf(length < (INET_ADDRSTRLEN+portLength), E_INVALIDARG); } else if (family == AF_INET6) { pAddrBytes = &(_address.addr6.sin6_addr); ChkIf(length < (INET6_ADDRSTRLEN+portLength), E_INVALIDARG); } else { ChkA(E_FAIL); } pszResult = ::inet_ntop(family, pAddrBytes, pszAddrBytes, length); ChkIf(pszResult == NULL, ERRNOHR); sprintf(szPort, ":%d", GetPort()); #if DEBUG ChkIfA(strlen(szPort) > portLength, E_FAIL); #endif strcat(pszAddrBytes, szPort); 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 CTCPStunThread::CreatePipes() { HRESULT hr = S_OK; int ret; ASSERT(_pipe[0] == -1); ASSERT(_pipe[1] == -1); ret = ::pipe(_pipe); ChkIf(ret == -1, ERRNOHR); Cleanup: return hr; }
HRESULT CTCPStunThread::Start() { int ret; HRESULT hr = S_OK; ChkIfA(_fThreadIsValid, E_FAIL); ChkIf(_pipe[0] == -1, E_UNEXPECTED); // Init hasn't been called _fNeedToExit = false; ret = ::pthread_create(&_pthread, NULL, ThreadFunction, this); ChkIfA(ret != 0, ERRNO_TO_HRESULT(ret)); _fThreadIsValid = true; Cleanup: return hr; }
/** * Returns true if there are two or more host interfaces(adapters) for the specified family of IP addresses that are both "up" and not loopback adapters * @param family either AF_INET or AF_INET6 */ bool HasAtLeastTwoAdapters(int family) { HRESULT hr = S_OK; ifaddrs* pList = NULL; ifaddrs* pAdapter1 = NULL; ifaddrs* pAdapter2 = NULL; bool fRet = false; ChkIf(getifaddrs(&pList) < 0, ERRNOHR); GetDefaultAdapters(family, pList, &pAdapter1, &pAdapter2); fRet = (pAdapter1 && pAdapter2); Cleanup: freeifaddrs(pList); return fRet; }
HRESULT CStunClientLogic::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) { HRESULT hr = S_OK; IStunClientTest* pCurrentTest = NULL; ChkIfA(_fInitialized == false, E_FAIL); ChkIfA(spMsg->GetSize() == 0, E_INVALIDARG); ChkIf (_nTestIndex >= _testlist.size(), E_UNEXPECTED); pCurrentTest = _testlist[_nTestIndex]; // passing a response to a test that is already completed ?? ChkIfA(pCurrentTest->IsCompleted(), E_UNEXPECTED); hr = pCurrentTest->ProcessResponse(spMsg, addrRemote, addrLocal); // this likely puts the test into the completed state // A subsequent call to GetNextMessage will invoke the next tset Cleanup: return hr; }
// test long-credential authentication HRESULT CTestMessageHandler::Test4() { HRESULT hr=S_OK; CStunMessageBuilder builder1, builder2; CStunMessageReader reader1, reader2; CSocketAddress clientaddr(0x12345678, 9876); CSocketAddress addrMapped; CRefCountedBuffer spBuffer; CStunThreadMessageHandler handler; uint16_t errorcode = 0; char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]; char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]; CStunMessageReader::ReaderParseState state; StunMessageEnvelope message; _spTransport->Reset(); _spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234)); handler.SetAuth(_spAuthLong); handler.SetResponder(_spTransport); // ----------------------------------------------------------------------- // simulate a user making a request with no message integrity attribute (or username, or realm) InitBindingRequest(builder1); builder1.GetResult(&spBuffer); message.localSocket = RolePP; message.remoteAddr = clientaddr; message.spBuffer = spBuffer; _spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr)); handler.ProcessRequest(message); spBuffer.reset(); _spTransport->m_outputstream.GetBuffer(&spBuffer); state = reader1.AddBytes(spBuffer->GetData(), spBuffer->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); // we expect the response back will be a 401 with a provided nonce and realm Chk(reader1.GetErrorCode(&errorcode)); ChkIfA(reader1.GetMessageClass() != ::StunMsgClassFailureResponse, E_UNEXPECTED); ChkIf(errorcode != ::STUN_ERROR_UNAUTHORIZED, E_UNEXPECTED); reader1.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, szRealm, ARRAYSIZE(szRealm)); reader1.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, szNonce, ARRAYSIZE(szNonce)); // -------------------------------------------------------------------------------- // now simulate the follow-up request _spTransport->ClearStream(); spBuffer.reset(); InitBindingRequest(builder2); builder2.AddNonce(szNonce); builder2.AddRealm(szRealm); builder2.AddUserName("AuthorizedUser"); builder2.AddMessageIntegrityLongTerm("AuthorizedUser", szRealm, "password"); builder2.GetResult(&spBuffer); message.localSocket = RolePP; message.remoteAddr = clientaddr; message.spBuffer = spBuffer; _spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr)); handler.ProcessRequest(message); spBuffer.reset(); _spTransport->m_outputstream.GetBuffer(&spBuffer); state = reader2.AddBytes(spBuffer->GetData(), spBuffer->GetSize()); ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL); ChkIfA(reader2.GetMessageClass() != ::StunMsgClassSuccessResponse, E_UNEXPECTED); // should have a mapped address ChkA(reader2.GetMappedAddress(&addrMapped)); // and the message integrity field should be valid ChkA(reader2.ValidateMessageIntegrityLong("AuthorizedUser", szRealm, "password")); Cleanup: return hr; }
HRESULT CTCPStunThread::WriteBytesForConnection(StunConnection* pConn) { HRESULT hr = S_OK; int sock = pConn->_stunsocket.GetSocketHandle(); int sent = -1; uint8_t* pData = NULL; size_t bytestotal, bytesremaining; bool fForceClose = false; int err; ASSERT(pConn != NULL); pData = pConn->_spOutputBuffer->GetData(); bytestotal = pConn->_spOutputBuffer->GetSize(); while (true) { ASSERT(pConn->_state == ConnectionState_Transmitting); ASSERT(bytestotal > pConn->_txCount); bytesremaining = bytestotal - pConn->_txCount; sent = ::send(sock, pData + pConn->_txCount, bytesremaining, 0); err = errno; // Can't send any more bytes, come back again later ChkIf( ((sent == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))), S_OK); Logging::LogMsg(LL_VERBOSE, "send on socket %d returns %d (errno=%d)", sock, sent, (sent<0)?err:0); // general connection error ChkIf(sent == -1, E_FAIL); // can "send" ever return 0? ChkIfA(sent == 0, E_UNEXPECTED); pConn->_txCount += sent; // txCount should never exceed the total output message size, right? ASSERT(pConn->_txCount <= bytestotal); if (pConn->_txCount >= bytestotal) { pConn->_state = ConnectionState_Closing; shutdown(sock, SHUT_WR); // go back to listening for read events ChkA(_spPolling->ChangeEventSet(sock, EPOLL_CLIENT_READ_EVENT_SET)); ConsumeRemoteClose(pConn); // so we can't assume the connection is still alive. And if it's not alive, pConn likely got deleted // either refetch from the hash tables, or invent an out parameter on WriteBytesForConnection and ConsumeRemoteClose to better propagate the close state of the connection pConn = NULL; break; } // loop back and try to send the remaining bytes } Cleanup: if ((FAILED(hr) || fForceClose)) { CloseConnection(pConn); } return hr; }
HRESULT CTCPStunThread::ReceiveBytesForConnection(StunConnection* pConn) { uint8_t buffer[MAX_STUN_MESSAGE_SIZE]; size_t bytesneeded; int bytesread; HRESULT hr = S_OK; CStunMessageReader::ReaderParseState readerstate; int err; int sock = pConn->_stunsocket.GetSocketHandle(); while (true) { ASSERT(pConn->_state == ConnectionState_Receiving); ASSERT(pConn->_reader.GetState() != CStunMessageReader::ParseError); ASSERT(pConn->_reader.GetState() != CStunMessageReader::BodyValidated); bytesneeded = pConn->_reader.HowManyBytesNeeded(); ChkIfA(bytesneeded == 0, E_UNEXPECTED); bytesread = recv(sock, buffer, bytesneeded, 0); err = errno; Logging::LogMsg(LL_VERBOSE, "recv on socket %d returns %d (errno=%d)", sock, bytesread, (bytesread<0)?err:0); if ((bytesread < 0) && ((err == EWOULDBLOCK) || (err==EAGAIN)) ) { // no more bytes to be consumed - bail out of here and return success break; } // any other error (or an EOF/shutdown notification) means the connection is dead ChkIf(bytesread <= 0, E_FAIL); // we got data, now let's feed it into the reader readerstate = pConn->_reader.AddBytes(buffer, bytesread); ChkIf(readerstate == CStunMessageReader::ParseError, E_FAIL); if (readerstate == CStunMessageReader::BodyValidated) { StunMessageIn msgIn; StunMessageOut msgOut; msgIn.addrLocal = pConn->_stunsocket.GetLocalAddress(); msgIn.addrRemote = pConn->_stunsocket.GetRemoteAddress(); msgIn.fConnectionOriented = true; msgIn.pReader = &pConn->_reader; msgIn.socketrole = pConn->_stunsocket.GetRole(); msgOut.spBufferOut = pConn->_spOutputBuffer; Chk(CStunRequestHandler::ProcessRequest(msgIn, msgOut, &_tsa, _spAuth)); // success - transition to the response state pConn->_state = ConnectionState_Transmitting; // change the socket such that we only listen for "write events" Chk(_spPolling->ChangeEventSet(sock, EPOLL_CLIENT_WRITE_EVENT_SET)); // optimization - go ahead and try to send the response WriteBytesForConnection(pConn); // WriteBytesForConnection will close the connection on error // And it might call ConsumeRemoteClose, which will also null it out // so we can't assume the connection is still alive. And if it's not alive, pConn likely got deleted // either refetch from the hash tables, or invent an out parameter on WriteBytesForConnection and ConsumeRemoteClose to better propagate the close state of the connection pConn = NULL; break; } // keep trying to read more bytes } Cleanup: if (FAILED(hr)) { CloseConnection(pConn); } return hr; }
StunConnection* CTCPStunThread::AcceptConnection(CStunSocket* pListenSocket) { int listensock = pListenSocket->GetSocketHandle(); SocketRole role = pListenSocket->GetRole(); int clientsock = -1; int socktmp = -1; sockaddr_storage addrClient; socklen_t socklen = sizeof(addrClient); StunConnection* pConn = NULL; HRESULT hr = S_OK; int insertresult; int err; ASSERT(listensock != -1); ASSERT(::IsValidSocketRole(role)); socktmp = ::accept(listensock, (sockaddr*)&addrClient, &socklen); err = errno; Logging::LogMsg(LL_VERBOSE, "accept returns %d (errno == %d)", socktmp, (socktmp<0)?err:0); ChkIfA(socktmp == -1, E_FAIL); // --- rate limit check------- if (_spLimiter.get()) { CSocketAddress addr = CSocketAddress(addrClient); bool allowed_to_pass = _spLimiter->RateCheck(addr); if (allowed_to_pass == false) { if (Logging::GetLogLevel() >= LL_VERBOSE) { char szIP[100]; addr.ToStringBuffer(szIP, 100); Logging::LogMsg(LL_VERBOSE, "Rate Limiter has blocked incoming connection from IP %s", szIP); } ChkIf(false, E_FAIL); // this will trigger the socket to be immediately closed } } // -------------------------- clientsock = socktmp; pConn = _connectionpool.GetConnection(clientsock, role); ChkIfA(pConn == NULL, E_FAIL); // Our connection pool has nothing left to give, only thing to do is abort this connection and close the socket socktmp = -1; ChkA(pConn->_stunsocket.SetNonBlocking(true)); ChkA(_spPolling->Add(clientsock, EPOLL_CLIENT_READ_EVENT_SET)); // add connection to our tracking tables pConn->_idHashTable = (_pNewConnList == &_hashConnections1) ? 1 : 2; insertresult = _pNewConnList->Insert(clientsock, pConn); // out of space in the lookup tables? ChkIfA(insertresult == -1, E_FAIL); if (Logging::GetLogLevel() >= LL_VERBOSE) { char szIPRemote[100]; char szIPLocal[100]; pConn->_stunsocket.GetLocalAddress().ToStringBuffer(szIPLocal, ARRAYSIZE(szIPLocal)); pConn->_stunsocket.GetRemoteAddress().ToStringBuffer(szIPRemote, ARRAYSIZE(szIPRemote)); Logging::LogMsg(LL_VERBOSE, "accepting new connection on socket %d from %s on interface %s", pConn->_stunsocket.GetSocketHandle(), szIPRemote, szIPLocal); } Cleanup: if (FAILED(hr)) { CloseConnection(pConn); pConn = NULL; if (socktmp != -1) { close(socktmp); } } return pConn; }
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; }
// 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; }
HRESULT GetSocketAddressForAdapter(int family, const char* pszAdapterName, uint16_t port, CSocketAddress* pSocketAddr) { HRESULT hr = S_OK; ifaddrs* pList = NULL; ifaddrs* pAdapter = NULL; ifaddrs* pAdapterFound = NULL; ChkIfA(pszAdapterName == NULL, E_INVALIDARG); ChkIfA(pszAdapterName[0] == '\0', E_INVALIDARG); ChkIfA(pSocketAddr == NULL, E_INVALIDARG); // what if the socket address is available, but not "up". Well, just let this call succeed. If the server errors out, it will get cleaned up then ChkIf(getifaddrs(&pList) < 0, ERRNOHR); pAdapter = pList; while (pAdapter) { if ((pAdapter->ifa_addr != NULL) && (pAdapter->ifa_name != NULL) && (family == pAdapter->ifa_addr->sa_family)) { if (strcmp(pAdapter->ifa_name, pszAdapterName) == 0) { pAdapterFound = pAdapter; break; } } pAdapter = pAdapter->ifa_next; } // If pszAdapterName is an IP address, convert it into a sockaddr and compare the address field with that of the adapter // Note: an alternative approach would be to convert pAdapter->ifa_addr to a string and then do a string compare. // But then it would be difficult to match "::1" with "0:0:0:0:0:0:0:1" and other formats of IPV6 strings if ((pAdapterFound == NULL) && ((family == AF_INET) || (family == AF_INET6)) ) { uint8_t addrbytes[sizeof(in6_addr)] = {}; int comparesize = (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr); void* pCmp = NULL; if (inet_pton(family, pszAdapterName, addrbytes) == 1) { pAdapter = pList; while (pAdapter) { if ((pAdapter->ifa_addr != NULL) && (family == pAdapter->ifa_addr->sa_family)) { // offsetof(sockaddr_in, sin_addr) != offsetof(sockaddr_in6, sin6_addr) // so you really can't do too many casting tricks like you can with sockaddr and sockaddr_in if (family == AF_INET) { sockaddr_in *pAddr4 = (sockaddr_in*)(pAdapter->ifa_addr); pCmp = &(pAddr4->sin_addr); } else { sockaddr_in6 *pAddr6 = (sockaddr_in6*)(pAdapter->ifa_addr); pCmp = &(pAddr6->sin6_addr); } if (memcmp(pCmp, addrbytes, comparesize) == 0) { // match on ip address string found pAdapterFound = pAdapter; break; } } pAdapter = pAdapter->ifa_next; } } } ChkIf(pAdapterFound == NULL, E_FAIL); { *pSocketAddr = CSocketAddress(*(pAdapterFound->ifa_addr)); pSocketAddr->SetPort(port); } Cleanup: freeifaddrs(pList); return hr; }