/*========================================================================== * * Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved. * * File: reliable.c * Content: stream communication related routines * History: * Date By Reason * ==== == ====== * 01-29-98 sohailm initial implementation * 02-15-98 a-peterz Remove unused SetMessageHeader * 02-10-00 aarono only allow one enum per socket then turf it. * ***************************************************************************/ #include "dphelp.h" /* * Globals */ FDS gReadfds; // fd set to receive data RECEIVELIST gReceiveList; // list of connections + listener /* * Externs */ extern SOCKET gsStreamListener; // we listen for tcp connections on this socket extern gbReceiveShutdown; // receive thread will exit when TRUE extern LPSPNODE gNodeList; #undef DPF_MODNAME #define DPF_MODNAME "MakeBufferSpace" // make sure the buffer is big enough to fit the message size HRESULT MakeBufferSpace(LPBYTE * ppBuffer,LPDWORD pdwBufferSize,DWORD dwMessageSize) { HRESULT hr = DP_OK; ASSERT(ppBuffer); ASSERT(pdwBufferSize); ENTER_DPLAYSVR(); if (!*ppBuffer) { DPF(9, "Allocating space for message of size %d", dwMessageSize); // need to alloc receive buffer? *ppBuffer = MemAlloc(dwMessageSize); if (!*ppBuffer) { DPF_ERR("could not alloc stream receive buffer - out of memory"); hr = E_OUTOFMEMORY; goto CLEANUP_EXIT; } *pdwBufferSize = dwMessageSize; } // make sure receive buffer can hold data else if (dwMessageSize > *pdwBufferSize) { LPVOID pvTemp; DPF(9, "ReAllocating space for message of size %d", dwMessageSize); // realloc buffer to hold data pvTemp = MemReAlloc(*ppBuffer,dwMessageSize); if (!pvTemp) { DPF_ERR("could not realloc stream receive buffer - out of memory"); hr = E_OUTOFMEMORY; goto CLEANUP_EXIT; } *ppBuffer = pvTemp; *pdwBufferSize = dwMessageSize; } // fall through CLEANUP_EXIT: LEAVE_DPLAYSVR(); return hr; } // MakeBufferSpace #undef DPF_MODNAME #define DPF_MODNAME "AddSocketToReceiveList" HRESULT AddSocketToReceiveList(SOCKET sSocket) { UINT i = 0; UINT err, iNewSlot; BOOL bFoundSlot = FALSE; HRESULT hr = DP_OK; INT addrlen=sizeof(SOCKADDR); LPCONNECTION pNewConnection; ENTER_DPLAYSVR(); // look for an empty slot while ( (i < gReceiveList.nConnections) && !bFoundSlot) { if (INVALID_SOCKET == gReceiveList.pConnection[i].socket) { bFoundSlot = TRUE; iNewSlot = i; } else { i++; } } if (!bFoundSlot) { DWORD dwCurrentSize,dwNewSize; // allocate space for list of connections dwCurrentSize = gReceiveList.nConnections * sizeof(CONNECTION); dwNewSize = dwCurrentSize + INITIAL_RECEIVELIST_SIZE * sizeof(CONNECTION); hr = MakeBufferSpace((LPBYTE *)&(gReceiveList.pConnection),&dwCurrentSize,dwNewSize); if (FAILED(hr)) { ASSERT(FALSE); goto CLEANUP_EXIT; } ASSERT(dwCurrentSize == dwNewSize); // set all the new entries to INVALID for (i = gReceiveList.nConnections + 1; i < gReceiveList.nConnections + INITIAL_RECEIVELIST_SIZE; i++ ) { gReceiveList.pConnection[i].socket = INVALID_SOCKET; } // store the new socket in the 1st new spot iNewSlot = gReceiveList.nConnections; // allocate space for an fd set (fd_count + fd_array) if (gReceiveList.nConnections) { dwCurrentSize = sizeof(u_int) + gReceiveList.nConnections * sizeof(SOCKET); dwNewSize = dwCurrentSize + INITIAL_RECEIVELIST_SIZE * sizeof(SOCKET); } else { dwCurrentSize = 0; dwNewSize = sizeof(u_int) + INITIAL_RECEIVELIST_SIZE * sizeof(SOCKET); } hr = MakeBufferSpace((LPBYTE *)&(gReadfds.pfdbigset),&dwCurrentSize,dwNewSize); if (FAILED(hr)) { ASSERT(FALSE); goto CLEANUP_EXIT; } ASSERT(dwCurrentSize == dwNewSize); // update the # of connections gReceiveList.nConnections += INITIAL_RECEIVELIST_SIZE; // update the fd_array buffer size gReadfds.dwArraySize = gReceiveList.nConnections; } // !bFoundSlot // Initialize new connection pNewConnection = &(gReceiveList.pConnection[iNewSlot]); pNewConnection->socket = sSocket; // allocate a default receive buffer pNewConnection->pDefaultBuffer = MemAlloc(DEFAULT_RECEIVE_BUFFERSIZE); if (NULL == pNewConnection->pDefaultBuffer) { DPF_ERR("could not alloc default receive buffer - out of memory"); hr = E_OUTOFMEMORY; goto CLEANUP_EXIT; } // receive buffer initially points to our default buffer pNewConnection->pBuffer = pNewConnection->pDefaultBuffer; // remember the address we are connected to err = g_getpeername(pNewConnection->socket, &(pNewConnection->sockAddr), &addrlen); if (SOCKET_ERROR == err) { err = g_WSAGetLastError(); DPF(1,"could not getpeername err = %d\n",err); } DPF(9, "Added new socket at index %d", iNewSlot); CLEANUP_EXIT: LEAVE_DPLAYSVR(); return hr; } // AddSocketToReceiveList #undef DPF_MODNAME #define DPF_MODNAME "KillSocket" HRESULT KillSocket(SOCKET sSocket,BOOL fStream,BOOL fHard) { UINT err; if (INVALID_SOCKET == sSocket) { return E_FAIL; } if (!fStream) { if (SOCKET_ERROR == g_closesocket(sSocket)) { err = g_WSAGetLastError(); DPF(0,"killsocket - dgram close err = %d\n",err); return E_FAIL; } } else { LINGER Linger; if (fHard) { Linger.l_onoff=TRUE; // turn linger on Linger.l_linger=0; // nice small time out if( SOCKET_ERROR == g_setsockopt( sSocket,SOL_SOCKET,SO_LINGER,(char FAR *)&Linger, sizeof(Linger) ) ) { err = g_WSAGetLastError(); DPF(0,"killsocket - stream setopt err = %d\n",err); } } #if 0 // DON'T DO SHUTDOWN! It leads to TIME_WAIT on sockets. if (SOCKET_ERROR == g_shutdown(sSocket,2)) { // this may well fail, if e.g. no one is using this socket right now... // the error would be wsaenotconn err = g_WSAGetLastError(); DPF(5,"killsocket - stream shutdown err = %d\n",err); } #endif if (SOCKET_ERROR == g_closesocket(sSocket)) { err = g_WSAGetLastError(); DPF(0,"killsocket - stream close err = %d\n",err); return E_FAIL; } } return DP_OK; }// KillSocket void FreeConnection(LPCONNECTION pConnection) { DEBUGPRINTSOCK(5,"Freeing connection - ",&pConnection->socket); // Kill them all hard so they don't wind up in TIME_WAIT state. KillSocket(pConnection->socket,TRUE,TRUE); if (pConnection->pBuffer && (pConnection->pBuffer != pConnection->pDefaultBuffer)) { MemFree(pConnection->pBuffer); pConnection->pBuffer = NULL; } if (pConnection->pDefaultBuffer) { MemFree(pConnection->pDefaultBuffer); pConnection->pDefaultBuffer = NULL; } // initialize connection pConnection->socket = INVALID_SOCKET; // this tells us if connection is valid pConnection->dwCurMessageSize = 0; pConnection->dwTotalMessageSize = 0; } #undef DPF_MODNAME #define DPF_MODNAME "RemoveSocketFromList" void RemoveSocketFromList(SOCKET socket) { UINT i = 0; BOOL bFound = FALSE; ENTER_DPLAYSVR(); // look for the corresponding connection while ( (i < gReceiveList.nConnections) && !bFound) { if (gReceiveList.pConnection[i].socket == socket) { bFound = TRUE; FreeConnection(&gReceiveList.pConnection[i]); } else { i++; } } // while LEAVE_DPLAYSVR(); return ; } #undef DPF_MODNAME #define DPF_MODNAME "EmptyConnectionList" void EmptyConnectionList(void) { UINT i; DPF(5, "Emptying connection list"); ENTER_DPLAYSVR(); for (i=0;idwCurMessageSize == 0) { // receive the header first pConnection->dwTotalMessageSize = SPMESSAGEHEADERLEN; } // continue receiving message pReceiveBuffer = pConnection->pBuffer + pConnection->dwCurMessageSize; dwReceiveBufferSize = pConnection->dwTotalMessageSize - pConnection->dwCurMessageSize; DPF(9,"Attempting to receive %d bytes", dwReceiveBufferSize); DEBUGPRINTSOCK(9,">>> receiving data on socket - ",&pConnection->socket); // receive data from socket // note - make exactly one call to recv after select otherwise we'll hang dwBytesReceived = g_recv(pConnection->socket, (LPBYTE)pReceiveBuffer, dwReceiveBufferSize, 0); DEBUGPRINTSOCK(9,"<<< received data on socket - ",&pConnection->socket); DPF(5, "received %d bytes", dwBytesReceived); if (0 == dwBytesReceived) { // remote side has shutdown connection gracefully hr = DP_OK; DPF(5,"Remote side has shutdown connection gracefully"); goto CLEANUP_EXIT; } else if (SOCKET_ERROR == dwBytesReceived) { err = g_WSAGetLastError(); DPF(0,"STREAMRECEIVEE: receive error - err = %d",err); hr = E_UNEXPECTED; goto CLEANUP_EXIT; } // we have received this much message so far pConnection->dwCurMessageSize += dwBytesReceived; if (pConnection->dwCurMessageSize == SPMESSAGEHEADERLEN) { // we just completed receiving message header if (VALID_DPLAYSVR_MESSAGE(pConnection->pDefaultBuffer)) { dwMessageSize = SP_MESSAGE_SIZE(pConnection->pDefaultBuffer); // total message size // SECURITY: limit message size for enum. if(dwMessageSize > 8192) { hr=E_UNEXPECTED; goto CLEANUP_EXIT; } } else { DPF(2,"got invalid message"); ASSERT(FALSE); hr = E_UNEXPECTED; goto CLEANUP_EXIT; } // prepare to receive the rest of the message (after token) if (dwMessageSize) { pConnection->dwTotalMessageSize = dwMessageSize; // which buffer to receive message in ? if (dwMessageSize > DEFAULT_RECEIVE_BUFFERSIZE) { ASSERT(pConnection->pBuffer == pConnection->pDefaultBuffer); // get a new buffer to fit the message pConnection->pBuffer = MemAlloc(dwMessageSize); if (!pConnection->pBuffer) { DPF(0,"Failed to allocate receive buffer for message - out of memory"); goto CLEANUP_EXIT; } // copy header into new message buffer memcpy(pConnection->pBuffer, pConnection->pDefaultBuffer, SPMESSAGEHEADERLEN); } } } // did we receive a complete message ? if (pConnection->dwCurMessageSize == pConnection->dwTotalMessageSize) { // received a complete message - process it if (TOKEN == SP_MESSAGE_TOKEN(pConnection->pBuffer)) { DEBUGPRINTADDR(9,"dplay helper :: received reliable enum request from ",(SOCKADDR *)&pConnection->sockAddr); // take the dplay lock so no one messes w/ our list of registered serves while we're // trying to send to them... ENTER_DPLAYSVR(); HandleIncomingMessage(pConnection->pBuffer, pConnection->dwTotalMessageSize, (SOCKADDR_IN *)&pConnection->sockAddr); // give up the lock LEAVE_DPLAYSVR(); } // cleanup up new receive buffer if any if (pConnection->dwTotalMessageSize > DEFAULT_RECEIVE_BUFFERSIZE) { DPF(9, "Releasing receive buffer of size %d", pConnection->dwTotalMessageSize); if (pConnection->pBuffer) MemFree(pConnection->pBuffer); } // initialize message information pConnection->dwCurMessageSize = 0; pConnection->dwTotalMessageSize = 0; pConnection->pBuffer = pConnection->pDefaultBuffer; // new... only allow one enum per socket, then turf it.... AO 2/10/2000 //goto CLEANUP_EXIT; } // all done return DP_OK; CLEANUP_EXIT: RemoveSocketFromList(pConnection->socket); return hr; } // StreamReceive #undef DPF_MODNAME #define DPF_MODNAME "StreamReceiveThreadProc" // watch our list of sockets, waiting for one to have data to be received, or to be closed DWORD WINAPI StreamReceiveThreadProc(LPVOID pvCast) { HRESULT hr; INT_PTR rval; UINT i = 0; UINT err; DWORD dwBufferSize = 0; UINT nSelected; SOCKADDR sockaddr; // socket we receive from INT addrlen=sizeof(sockaddr); SOCKET sSocket; BOOL bTrue = TRUE; // add listener socket to receive list // listener socket should be the first socket in the receive list hr = AddSocketToReceiveList(gsStreamListener); if (FAILED(hr)) { DPF(0, "Failed to add TCP listener to receive list"); return hr; } while (1) { ENTER_DPLAYSVR(); ASSERT(gReadfds.pfdbigset); // add all sockets in our recv list to readfds FD_ZERO(gReadfds.pfdbigset); nSelected = 0; for (i=0;i < gReceiveList.nConnections ; i++) { if (INVALID_SOCKET != gReceiveList.pConnection[i].socket) { FD_BIG_SET(gReceiveList.pConnection[i].socket,&gReadfds); nSelected++; } } LEAVE_DPLAYSVR(); if (0 == nSelected) { if (gbReceiveShutdown) { DPF(2,"stream receive thread proc detected shutdown - bailing"); goto CLEANUP_EXIT; } // we should have at least one? DPF_ERR("No sockets in receive list - missing listener socket? bailing!"); ASSERT(FALSE); goto CLEANUP_EXIT; } // now, we wait for something to happen w/ our socket set rval = g_select(0,(fd_set *)(gReadfds.pfdbigset),NULL,NULL,NULL); if (SOCKET_ERROR == rval) { err = g_WSAGetLastError(); if (WSAEINTR != err) { // WSAEINTR is what winsock uses to break a blocking socket out of // its wait. it means someone killed this socket. // if it's not that, then it's a real error. DPF(0,"\n select error = %d socket - trying again",err); } else { DPF(9,"\n select error = %d socket - trying again",err); } rval = 0; } // shut 'em down? if (gbReceiveShutdown) { DPF(2,"receive thread proc detected bShutdown - bailing"); goto CLEANUP_EXIT; } DPF(5,"receive thread proc - events on %d sockets",rval); i = 0; ENTER_DPLAYSVR(); while (rval>0) { // walk the receive list, dealing w/ all new sockets if (i >= gReceiveList.nConnections) { rval = 0; // just to be safe, reset } if (gReceiveList.pConnection[i].socket != INVALID_SOCKET) { // see if it's in the set if (g_WSAFDIsSet(gReceiveList.pConnection[i].socket,(fd_set *)gReadfds.pfdbigset)) { if (0==i) // we got a new connection { // accept any incoming connection sSocket = g_accept(gReceiveList.pConnection[i].socket,&sockaddr,&addrlen); if (INVALID_SOCKET == sSocket) { err = g_WSAGetLastError(); DPF(0,"\n stream accept error - err = %d socket = %d BAILING",err,(DWORD)sSocket); DPF(0, "\n !!! stream accept thread is going away - won't get reliable enum sessions anymore !!!"); ASSERT(FALSE); LEAVE_DPLAYSVR(); goto CLEANUP_EXIT; } DEBUGPRINTADDR(5,"stream - accepted connection from",&sockaddr); // Turn on KEEPALIVE for the socket. if (SOCKET_ERROR == g_setsockopt(sSocket, SOL_SOCKET, SO_KEEPALIVE, (CHAR FAR *)&bTrue, sizeof(bTrue))) { err = g_WSAGetLastError(); DPF(0,"Failed to turn ON keepalive - continue : err = %d\n",err); } // add the new socket to our receive list hr = AddSocketToReceiveList(sSocket); if (FAILED(hr)) { ASSERT(FALSE); } } else // socket has new data { DPF(9, "Receiving on socket %d from ReceiveList", i); // got one! this socket has something going on... hr = StreamReceive(&(gReceiveList.pConnection[i])); if (FAILED(hr)) { DPF(1,"Stream Receive failed - hr = 0x%08lx\n",hr); } } rval--; // one less to hunt for } // IS_SET } // != INVALID_SOCKET i++; } // while rval LEAVE_DPLAYSVR(); } // while 1 CLEANUP_EXIT: EmptyConnectionList(); DPF(5, "Stream receive thread exiting"); return 0; } // ReceiveThreadProc