/*++ Copyright (c) 1991 Microsoft Corporation Module Name: socket.c Abstract: Contains functions to create, delete and manipulate IPX sockets and SPX connections Contents: CreateSocket AllocateTemporarySocket QueueSocket DequeueSocket FindSocket FindActiveSocket ReopenSocket KillSocket KillShortLivedSockets AllocateConnection DeallocateConnection FindConnection QueueConnection DequeueConnection KillConnection AbortOrTerminateConnection CheckPendingSpxRequests (CheckSocketState) (CheckSelectRead) (CheckSelectWrite) (AsyncReadAction) (AsyncWriteAction) (CompleteAccept) (CompleteReceive) (CompleteConnect) (CompleteSend) Author: Richard L Firth (rfirth) 25-Oct-1993 Environment: User-mode Win32 Revision History: 25-Oct-1993 rfirth Created --*/ #include "vw.h" #pragma hdrstop // // miscellaneous manifests // #define ARBITRARY_CONNECTION_INCREMENT 2 // // macros // #define ALLOCATE_CONNECTION_NUMBER() (ConnectionNumber += ARBITRARY_CONNECTION_INCREMENT) // // private data // PRIVATE LPSOCKET_INFO SocketList = NULL; PRIVATE LPCONNECTION_INFO ConnectionList = NULL; PRIVATE WORD ConnectionNumber = ARBITRARY_CONNECTION_NUMBER; // // private functions // PRIVATE BOOL CheckSocketState( IN SOCKET Socket, OUT LPBOOL Readable, OUT LPBOOL Writeable, OUT LPBOOL Error ); PRIVATE VOID CheckSelectRead( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *CheckRead ); PRIVATE VOID CheckSelectWrite( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *CheckWrite ); PRIVATE VOID AsyncReadAction( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *ReadPerformed ); PRIVATE VOID AsyncWriteAction( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *WritePerformed ); PRIVATE VOID CompleteAccept( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ); PRIVATE VOID CompleteReceive( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ); PRIVATE VOID CompleteConnect( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ); PRIVATE VOID CompleteSend( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ); #if SPX_HACK PRIVATE VOID ModifyFirstReceive(LPBYTE, LPDWORD, WORD, SOCKET); #endif // // public functions // int CreateSocket( IN SOCKET_TYPE SocketType, IN OUT ULPWORD pSocketNumber, OUT SOCKET* pSocket ) /*++ Routine Description: Creates a socket for IPX or SPX (a connection). Once the socket is created we have to bind it to the IPX/SPX 'socket' - i.e. port. We also need to change a few things about the standard socket: * if this is an SPX request then we must set the REUSEADDR socket option since there may typically be several connect requests over the same WinSock socket: we need to be able to bind multiple connections to the same socket number * all sockets opened by this function are put into non-blocking mode * all sockets opened by this function will return the packet header in any received data (IPX_RECVHDR) The requested socket number can be 0 in which case we bind to a dynamic socket number. We always return the number of the socket bound to: if not 0 on input, this should always be the same value as that requested in pSocketNumber If any WinSock call fails (and the socket was created) then we close the socket before returning Arguments: SocketType - SOCKET_TYPE_IPX or SOCKET_TYPE_SPX pSocketNumber - input: socket number to bind (can be 0) output: socket number bound pSocket - pointer to address of socket identifier to return Return Value: int Success - IPX_SUCCESS/SPX_SUCCESS (0) Failure - IPX_SOCKET_TABLE_FULL WinSock cannot create the socket IPX_SOCKET_ALREADY_OPEN Assume the request was for an IPX socket: we do not allow multiple IPX sockets to be bound to the same socket number, only SPX --*/ { SOCKET s; SOCKADDR_IPX socketAddress; BOOL true = TRUE; int rc; int status = IPX_SOCKET_TABLE_FULL; // default error s = socket(AF_IPX, (SocketType == SOCKET_TYPE_SPX) ? SOCK_SEQPACKET : SOCK_DGRAM, (SocketType == SOCKET_TYPE_SPX) ? NSPROTO_SPX : NSPROTO_IPX ); if (s != INVALID_SOCKET) { // // for stream (SPX) sockets, we need multiple sockets bound to the // same socket number if we are to have multiple connections on the // same SPX socket // if (SocketType == SOCKET_TYPE_SPX) { rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char FAR*)&true, sizeof(true) ); if (rc == SOCKET_ERROR) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: setsockopt(SO_REUSEADDR) returns %d\n", WSAGetLastError() )); } else { rc = setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char FAR*)&true, sizeof(true) ); if (rc == SOCKET_ERROR) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: setsockopt(SO_OOBINLINE) returns %d\n", WSAGetLastError() )); } } } else { // // allow broadcasts to be transmitted on IPX sockets // rc = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char FAR*)&true, sizeof(true) ); } if (!rc) { // // bind the socket to the local socket number (port) // ZeroMemory(&socketAddress, sizeof(socketAddress)); socketAddress.sa_family = AF_IPX; socketAddress.sa_socket = *pSocketNumber; rc = bind(s, (LPSOCKADDR)&socketAddress, sizeof(socketAddress)); if (rc != SOCKET_ERROR) { int length = sizeof(socketAddress); ZeroMemory(&socketAddress, sizeof(socketAddress)); socketAddress.sa_family = AF_IPX; // // use getsockname() to find the (big-endian) socket value that // was actually assigned: should only be different from // *pSocketNumber if the latter was 0 on input // rc = getsockname(s, (LPSOCKADDR)&socketAddress, &length); if (rc != SOCKET_ERROR) { u_long arg = !0; // // put the socket into non-blocking mode. Neither IPX nor // SPX sockets are blocking: the app starts an I/O request // and if it doesn't complete immediately, will be completed // by AES which periodically polls the outstanding I/O // requests // rc = ioctlsocket(s, FIONBIO, &arg); if (rc != SOCKET_ERROR) { // // return protocol header on receive frames // rc = setsockopt(s, NSPROTO_IPX, IPX_RECVHDR, (char FAR*)&true, sizeof(true) ); if (rc != SOCKET_ERROR) { *pSocketNumber = socketAddress.sa_socket; *pSocket = s; status = IPX_SUCCESS; } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: setsockopt(RECVHDR) returns %d\n", WSAGetLastError() )); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: ioctlsocket(FIONBIO) returns %d\n", WSAGetLastError() )); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: getsockname() returns %d\n", WSAGetLastError() )); } } else { // // bind() failed - either an expected error (the requested socket // is already in use), or (horror) an unexpected error, in which // case report table full (?) // switch (WSAGetLastError()) { case WSAEADDRINUSE: ASSERT(*pSocketNumber != 0); ASSERT(SocketType == SOCKET_TYPE_IPX); status = IPX_SOCKET_ALREADY_OPEN; break; default: IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: bind() on socket %#x returns %d\n", s, WSAGetLastError() )); } } } } else { // // the socket() call failed - treat as table full // IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CreateSocket: socket() returns %d\n", WSAGetLastError() )); } if (status != IPX_SUCCESS) { if (s != INVALID_SOCKET) { closesocket(s); } } return status; } LPSOCKET_INFO AllocateTemporarySocket( VOID ) /*++ Routine Description: Allocates a temporary socket. Creates an IPX socket having a dynamically allocated socket number Arguments: None. Return Value: LPSOCKET_INFO Success - pointer to SOCKET_INFO structure Failure - NULL --*/ { LPSOCKET_INFO pSocketInfo; int rc; pSocketInfo = AllocateSocket(); if (pSocketInfo) { // // assumption: the SOCKET_INFO structure was zeroed by LocalAlloc(LPTR,.. // hence the SocketNumber fields is 0. This causes CreateSocket to // generate a dynamic socket number // rc = CreateSocket(SOCKET_TYPE_IPX, &pSocketInfo->SocketNumber, &pSocketInfo->Socket ); if (rc == IPX_SUCCESS) { pSocketInfo->Flags |= SOCKET_FLAG_TEMPORARY; } else { DeallocateSocket(pSocketInfo); pSocketInfo = NULL; } } return pSocketInfo; } VOID QueueSocket( IN LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: Add a SOCKET_INFO structure to the list (LIFO) of (opened) sockets Arguments: pSocketInfo - pointer to filled-in SOCKET_INFO structure Return Value: None. --*/ { RequestMutex(); pSocketInfo->Next = SocketList; SocketList = pSocketInfo; ReleaseMutex(); } LPSOCKET_INFO DequeueSocket( IN LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: Remove a SOCKET_INFO structure from the list Arguments: pSocketInfo - pointer to SOCKET_INFO structure to remove Return Value: LPSOCKET_INFO pSocketInfo - should be this value NULL - couldn't find pSocketInfo (should not get this!) --*/ { LPSOCKET_INFO prev, p; ASSERT(SocketList); RequestMutex(); prev = (LPSOCKET_INFO)&SocketList; p = SocketList; while (p) { if (p == pSocketInfo) { prev->Next = p->Next; p->Next = NULL; break; } else { prev = p; p = p->Next; } } if (!p) { // // should never reach here // IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_FATAL, "DequeueSocket: can't find socket structure %08x on queue\n", pSocketInfo )); } ReleaseMutex(); return p; } LPSOCKET_INFO FindSocket( IN WORD SocketNumber ) /*++ Routine Description: Locate a SOCKET_INFO structure in the list, by (big-endian) socket number Assumes: 1. There is 1 and only 1 SOCKET_INFO structure that contains SocketNumber Arguments: SocketNumber - big-endian socket number to find Return Value: LPSOCKET_INFO NULL - couldn't find requested socket !NULL - pointer to discovered SOCKET_INFO structure --*/ { LPSOCKET_INFO p; RequestMutex(); p = SocketList; while (p) { if (p->SocketNumber == SocketNumber) { break; } else { p = p->Next; } } ReleaseMutex(); return p; } LPSOCKET_INFO FindActiveSocket( IN LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: Find a SOCKET_INFO structure with pending send or receive. Called as FindFirst, FindNext - first call made with pSocketInfo == NULL: enters critical section if an active socket is found, returns pointer Subsequent calls are made with pSocketInfo pointing to last returned SOCKET_INFO. This continues the search. When search exhausted, critical section is released Arguments: pSocketInfo - pointer to SOCKET_INFO structure: first time must be NULL Return Value: LPSOCKET_INFO - next active SOCKET_INFO structure or NULL --*/ { if (!pSocketInfo) { RequestMutex(); pSocketInfo = SocketList; } else { pSocketInfo = pSocketInfo->Next; } for (; pSocketInfo; pSocketInfo = pSocketInfo->Next) { if (pSocketInfo->Flags & (SOCKET_FLAG_SENDING | SOCKET_FLAG_LISTENING)) { return pSocketInfo; } } ReleaseMutex(); return NULL; } int ReopenSocket( LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: Called expressly to close an IPX socket and reassign the descriptor to SPX. Note that after this function completes, IPXSendPacket and IPXListenForPacket cannot be made agains the IPX socket Arguments: pSocketInfo - pointer to SOCKET_INFO which currently describes an IPX socket Return Value: int - return code from CreateSocket --*/ { int rc; rc = closesocket(pSocketInfo->Socket); if (rc == SOCKET_ERROR) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "ReopenSocket: closesocket() returns %d\n", WSAGetLastError() )); } // // mark this socket as connection-based (SPX) socket // pSocketInfo->SpxSocket = TRUE; // // BUGBUG: need the DOS/Windows task ID: owner may have changed! // // // re-open the socket for SPX use // return CreateSocket(SOCKET_TYPE_SPX, &pSocketInfo->SocketNumber, &pSocketInfo->Socket ); } VOID KillSocket( IN LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: closes a socket, removes the SOCKET_INFO structure from the list and cancels any pending send, listen or timed events associated with the socket Arguments: pSocketInfo - identifying socket to kill Return Value: None. --*/ { int rc; // // remove the SOCKET_INFO structure from the list of sockets. Cancel // any pending ECB requests and any IPX timed events that have the // same socket number // DequeueSocket(pSocketInfo); rc = closesocket(pSocketInfo->Socket); if (rc == SOCKET_ERROR) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "KillSocket: closesocket() returns %d\n", WSAGetLastError() )); } // // the socket has been removed from SocketList: no need to grab mutex to // perform the following // CancelTimedEvents(pSocketInfo->SocketNumber, 0, 0); CancelSocketQueue(&pSocketInfo->ListenQueue); CancelSocketQueue(&pSocketInfo->HeaderQueue); CancelSocketQueue(&pSocketInfo->SendQueue); if (pSocketInfo->SpxSocket) { LPCONNECTION_INFO pConnectionInfo; while (pConnectionInfo = pSocketInfo->Connections) { DequeueConnection(pSocketInfo, pConnectionInfo); KillConnection(pConnectionInfo); } } DeallocateSocket(pSocketInfo); } VOID KillShortLivedSockets( IN WORD Owner ) /*++ Routine Description: For all those sockets created by a DOS process as SHORT_LIVED, terminate the sockets, cancelling any outstanding ECBs Arguments: Owner - DOS PDB which opened sockets Return Value: None. --*/ { LPSOCKET_INFO pSocketInfo; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "KillShortLivedSockets(%04x)\n", Owner )); RequestMutex(); // // kill any non-socket (AES) timed events owned by this DOS process // CancelTimedEvents(0, Owner, 0); // // kill all sockets owned by this PDB // pSocketInfo = SocketList; while (pSocketInfo) { LPSOCKET_INFO next; next = pSocketInfo->Next; if (!pSocketInfo->LongLived && (pSocketInfo->Owner == Owner)) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "KillShortLivedSockets: Socket %04x owned by %04x\n", B2LW(pSocketInfo->SocketNumber), pSocketInfo->Owner )); KillSocket(pSocketInfo); } pSocketInfo = next; } ReleaseMutex(); } LPCONNECTION_INFO AllocateConnection( LPSOCKET_INFO pSocketInfo ) /*++ Routine Description: Allocates a CONNECTION_INFO structure. If successful, links it at the head of ConnectionList Arguments: pSocketInfo - pointer to owner SOCKET_INFO Return Value: LPCONNECTION_INFO Success - !NULL Failure - NULL --*/ { LPCONNECTION_INFO pConnectionInfo; pConnectionInfo = (LPCONNECTION_INFO)LocalAlloc(LPTR, sizeof(*pConnectionInfo)); if (pConnectionInfo) { RequestMutex(); pConnectionInfo->ConnectionId = ALLOCATE_CONNECTION_NUMBER(); pConnectionInfo->List = ConnectionList; ConnectionList = pConnectionInfo; ReleaseMutex(); } #if SPX_HACK pConnectionInfo->Flags = CF_1ST_RECEIVE; #endif return pConnectionInfo; } VOID DeallocateConnection( IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Undoes the work of AllocateConnection - removes pConnectionInfo from ConnectionList and deallocates the structure Arguments: pConnectionInfo - pointer to CONNECTION_INFO to deallocate Return Value: None. --*/ { LPCONNECTION_INFO p; LPCONNECTION_INFO prev = (LPCONNECTION_INFO)&ConnectionList; RequestMutex(); for (p = ConnectionList; p != pConnectionInfo; ) { prev = p; p = p->List; } // // if p is NULL or differs from pConnectionInfo then there's a problem // ASSERT(p); // // special case if pConnectionInfo is first on list: can't say // &ConnectionList->List - accesses one pointer beyond ConnectionList // which is WRONG // if (prev == (LPCONNECTION_INFO)&ConnectionList) { ConnectionList = p->List; } else { prev->List = p->List; } FREE_OBJECT(pConnectionInfo); ReleaseMutex(); } LPCONNECTION_INFO FindConnection( IN WORD ConnectionId ) /*++ Routine Description: Returns a pointer to CONNECTION_INFO given a unique connection ID Arguments: ConnectionId - value to find Return Value: LPCONNECTION_INFO Success - !NULL Failure - NULL --*/ { LPCONNECTION_INFO pConnectionInfo; RequestMutex(); for (pConnectionInfo = ConnectionList; pConnectionInfo; ) { if (pConnectionInfo->ConnectionId == ConnectionId) { break; } else { pConnectionInfo = pConnectionInfo->List; } } ReleaseMutex(); return pConnectionInfo; } VOID QueueConnection( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Adds a CONNECTION_INFO to the list of connections owned by a SOCKET_INFO. Points the CONNECTION_INFO back to the SOCKET_INFO Arguments: pSocketInfo - owning SOCKET_INFO pConnectionInfo - CONNECTION_INFO to add Return Value: None. --*/ { pConnectionInfo->Next = pSocketInfo->Connections; pSocketInfo->Connections = pConnectionInfo; pConnectionInfo->OwningSocket = pSocketInfo; } LPCONNECTION_INFO DequeueConnection( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Removes a CONNECTION_INFO from the list of connections owned by a SOCKET_INFO Arguments: pSocketInfo - owning SOCKET_INFO pConnectionInfo - CONNECTION_INFO to remove Return Value: LPCONNECTION_INFO Success - pointer to removed CONNECTION_INFO (should be same as pConnectionInfo) Failure - NULL (not expected) --*/ { LPCONNECTION_INFO prev = (LPCONNECTION_INFO)&pSocketInfo->Connections; LPCONNECTION_INFO p = prev->Next; while (p && p != pConnectionInfo) { prev = p; p = p->Next; } ASSERT(p == pConnectionInfo); prev->Next = p->Next; p->OwningSocket = NULL; return p; } VOID KillConnection( IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Closes a socket belonging to a connection and cancels all outstanding requests. The CONNECTION_INFO is deallocated Arguments: pConnectionInfo - pointer to CONNECTION_INFO to kill Return Value: None. --*/ { if (pConnectionInfo->Socket) { closesocket(pConnectionInfo->Socket); } CancelConnectionQueue(&pConnectionInfo->ConnectQueue); CancelConnectionQueue(&pConnectionInfo->AcceptQueue); CancelConnectionQueue(&pConnectionInfo->ListenQueue); CancelConnectionQueue(&pConnectionInfo->SendQueue); DeallocateConnection(pConnectionInfo); } VOID AbortOrTerminateConnection( IN LPCONNECTION_INFO pConnectionInfo, IN BYTE CompletionCode ) /*++ Routine Description: Aborts or terminates a connection: closes the socket, dequeues and completes all outstanding ECBs with relevant code and deallocates the CONNECTION_INFO structure The CONNECTION_INFO must NOT be queued on a SOCKET_INFO when this routine is called Arguments: pConnectionInfo - pointer to CONNECTION_INFO to kill CompletionCode - completion code to put in pending ECBs Return Value: None. --*/ { if (pConnectionInfo->Socket) { closesocket(pConnectionInfo->Socket); } AbortQueue(&pConnectionInfo->ConnectQueue, CompletionCode); AbortQueue(&pConnectionInfo->AcceptQueue, CompletionCode); AbortQueue(&pConnectionInfo->ListenQueue, CompletionCode); AbortQueue(&pConnectionInfo->SendQueue, CompletionCode); DeallocateConnection(pConnectionInfo); } VOID CheckPendingSpxRequests( BOOL *pfOperationPerformed ) /*++ Routine Description: Checks the open non-blocking SPX sockets for: errors outgoing established connections (connect) incoming established connections (listen/accept) data to receive (recv) send completions (send) Arguments: None. Return Value: None. --*/ { LPSOCKET_INFO pSocketInfo; *pfOperationPerformed = FALSE ; RequestMutex(); pSocketInfo = SocketList; while (pSocketInfo) { if (pSocketInfo->SpxSocket) { LPCONNECTION_INFO pConnectionInfo; pConnectionInfo = pSocketInfo->Connections; while (pConnectionInfo) { LPCONNECTION_INFO next; // // pluck out the Next field now, in case this CONNECTION_INFO // is destroyed as the result of an error // next = pConnectionInfo->Next; // // if this connection has an active socket or we have issued // SPXListenForConnection against the socket then check the // state // if (pConnectionInfo->Socket || (pConnectionInfo->State == CI_WAITING)) { SOCKET sock; BOOL readable; BOOL writeable; BOOL sockError; CheckSelectRead(pSocketInfo, pConnectionInfo, &readable); CheckSelectWrite(pSocketInfo, pConnectionInfo, &writeable); sock = pConnectionInfo->Socket ? pConnectionInfo->Socket : pSocketInfo->Socket ; if (CheckSocketState(sock, &readable, &writeable, &sockError)) { if (!sockError) { if (readable) { AsyncReadAction(pSocketInfo, pConnectionInfo, pfOperationPerformed); } if (writeable) { AsyncWriteAction(pSocketInfo, pConnectionInfo, pfOperationPerformed); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CheckPendingSpxRequests: socket %x has error. Connection %08x state %d\n", sock, pConnectionInfo, pConnectionInfo->State )); // // irrespective of the error, we just abort any // connection that gets an error // DequeueConnection(pConnectionInfo->OwningSocket, pConnectionInfo ); AbortOrTerminateConnection(pConnectionInfo, ECB_CC_CONNECTION_ABORTED ); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CheckPendingSpxRequests: CheckSocketState returns %d\n", WSAGetLastError() )); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CheckPendingSpxRequests: connection %04x (%08x) in weird state?\n", pConnectionInfo->ConnectionId, pConnectionInfo )); } pConnectionInfo = next; } } pSocketInfo = pSocketInfo->Next; } ReleaseMutex(); } PRIVATE BOOL CheckSocketState( IN SOCKET Socket, OUT LPBOOL Readable, OUT LPBOOL Writeable, OUT LPBOOL Error ) /*++ Routine Description: Given a socket descriptor, checks to see if it is in one of the following states: readable - if waiting for a connection, connection has been made else if established, data is ready to be received writeable - if waiting to make a connection, connection has been made, else if established, we can send data on this socket error - some error has occurred on the socket Arguments: Socket - socket descriptor to check Readable - returned TRUE if readable Writeable - returned TRUE if writeable Error - returned TRUE if error on socket Return Value: BOOL TRUE - contents of Readable, Writeable and Error are valid FALSE - an error occurred performing the select --*/ { fd_set errors; fd_set reads; fd_set writes; int n; static struct timeval timeout = {0, 0}; FD_ZERO(&errors); FD_ZERO(&reads); FD_ZERO(&writes); if (*Readable) FD_SET(Socket, &reads); if (*Writeable) FD_SET(Socket, &writes); FD_SET(Socket, &errors); n = select(0, &reads, &writes, &errors, &timeout); if (n != SOCKET_ERROR) { *Readable = (BOOL)(reads.fd_count == 1); *Writeable = (BOOL)(writes.fd_count == 1); *Error = (BOOL)(errors.fd_count == 1); return TRUE; } else if (n) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CheckSocketState: select returns %d\n", WSAGetLastError() )); } return FALSE; } PRIVATE VOID AsyncReadAction( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *ReadPerformed ) /*++ Routine Description: A connection has some read action to complete - complete a pending SPXListenForConnection or SPXListenForSequencedPacket Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { *ReadPerformed = FALSE ; switch (pConnectionInfo->State) { case CI_STARTING: IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "AsyncReadAction: STARTING connection %04x (%08x) readable\n", pConnectionInfo->ConnectionId, pConnectionInfo )); break; case CI_WAITING: if (pConnectionInfo->AcceptQueue.Head) { CompleteAccept(pSocketInfo, pConnectionInfo); *ReadPerformed = TRUE ; } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "AsyncReadAction: connection %04x (%08x): no AcceptQueue\n", pConnectionInfo->ConnectionId, pConnectionInfo )); } break; case CI_ESTABLISHED: if (pSocketInfo->ListenQueue.Head) { CompleteReceive(pSocketInfo, pConnectionInfo); *ReadPerformed = TRUE ; } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_WARNING, "AsyncReadAction: connection %04x (%08x): no ListenQueue\n", pConnectionInfo->ConnectionId, pConnectionInfo )); } break; case CI_TERMINATING: IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "AsyncReadAction: TERMINATING connection %04x (%08x) readable\n", pConnectionInfo->ConnectionId, pConnectionInfo )); break; } } PRIVATE VOID AsyncWriteAction( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *WritePerformed ) /*++ Routine Description: A connection has some write action to complete - complete a pending SPXEstablishConnection or SPXSendSequencedPacket Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { *WritePerformed = FALSE ; switch (pConnectionInfo->State) { case CI_STARTING: if (pConnectionInfo->ConnectQueue.Head) { CompleteConnect(pSocketInfo, pConnectionInfo); *WritePerformed = TRUE ; } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "AsyncWriteAction: connection %04x (%08x): no ConnectQueue\n", pConnectionInfo->ConnectionId, pConnectionInfo )); } break; case CI_WAITING: IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "AsyncWriteAction: WAITING connection %04x (%08x) is writeable\n", pConnectionInfo->ConnectionId, pConnectionInfo )); break; case CI_ESTABLISHED: if (pConnectionInfo->SendQueue.Head) { CompleteSend(pSocketInfo, pConnectionInfo); *WritePerformed = TRUE ; } else { /* IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_WARNING, "AsyncWriteAction: connection %04x (%08x): no SendQueue\n", pConnectionInfo->ConnectionId, pConnectionInfo )); */ } break; case CI_TERMINATING: IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "AsyncWriteAction: TERMINATING connection %04x (%08x) writeable\n", pConnectionInfo->ConnectionId, pConnectionInfo )); break; } } PRIVATE VOID CheckSelectRead( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *CheckRead ) /*++ Routine Description: See if want to check for Read readiness in select statement. Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { *CheckRead = FALSE ; switch (pConnectionInfo->State) { case CI_WAITING: if (pConnectionInfo->AcceptQueue.Head) *CheckRead = TRUE ; break; case CI_ESTABLISHED: if (pSocketInfo->ListenQueue.Head) *CheckRead = TRUE ; break; default: break; } } PRIVATE VOID CheckSelectWrite( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo, OUT BOOL *CheckWrite ) /*++ Routine Description: See if want to check for Write readiness in select statement. Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { *CheckWrite = FALSE ; switch (pConnectionInfo->State) { case CI_STARTING: if (pConnectionInfo->ConnectQueue.Head) *CheckWrite = TRUE ; break; case CI_ESTABLISHED: if (pConnectionInfo->SendQueue.Head) *CheckWrite = TRUE ; break; default: break; } } PRIVATE VOID CompleteAccept( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Complete a SPXListenForConnection Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { SOCKET conn; SOCKADDR_IPX remoteAddress; int addressLength = sizeof(remoteAddress); LPXECB pXecb = pConnectionInfo->AcceptQueue.Head; BOOL true = TRUE; int rc; conn = accept(pSocketInfo->Socket, (LPSOCKADDR)&remoteAddress, &addressLength); if (conn != SOCKET_ERROR) { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteAccept: connection %04x (%08x) socket=%x\n", pConnectionInfo->ConnectionId, pConnectionInfo, conn )); // // we want to receive the frame headers from this socket // rc = setsockopt(conn, NSPROTO_IPX, IPX_RECVHDR, (char FAR*)&true, sizeof(true) ); rc = !SOCKET_ERROR; if (rc != SOCKET_ERROR) { // // update the CONNECTION_INFO structure with the actual socket // identifier and set the connection state to established // pConnectionInfo->Socket = conn; pConnectionInfo->State = CI_ESTABLISHED; // // update the app's ECB with the connection ID // SPX_ECB_CONNECTION_ID(pXecb->Ecb) = pConnectionInfo->ConnectionId; // // and with the partner address info // CopyMemory(&pXecb->Ecb->DriverWorkspace, &remoteAddress.sa_netnum, sizeof(pXecb->Ecb->DriverWorkspace) ); // // fill in the immediate address field // CopyMemory(&pXecb->Ecb->ImmediateAddress, &remoteAddress.sa_nodenum, sizeof(pXecb->Ecb->ImmediateAddress) ); // // remove the XECB from AcceptQueue and complete the SPXListenForConnection ECB // DequeueEcb(pXecb, &pConnectionInfo->AcceptQueue); IPXDUMPECB((pXecb->Ecb, HIWORD(pXecb->EcbAddress), LOWORD(pXecb->EcbAddress), ECB_TYPE_SPX, FALSE, FALSE, IS_PROT_MODE(pXecb) )); CompleteOrQueueEcb(pXecb, ECB_CC_SUCCESS); } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CompleteAccept: setsockopt(IPX_RECVHDR) returns %d\n", WSAGetLastError() )); // // BUGBUG: value? // closesocket(conn); DequeueEcb(pXecb, &pConnectionInfo->AcceptQueue); DequeueConnection(pSocketInfo, pConnectionInfo); DeallocateConnection(pConnectionInfo); CompleteOrQueueEcb(pXecb, ECB_CC_CONNECTION_ABORTED); } } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CompleteAccept: accept() returns %d\n", WSAGetLastError() )); } } PRIVATE VOID CompleteReceive( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Complete a SPXListenForSequencedPacket Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { LPXECB pXecb; int rc; BOOL conn_q; LPXECB_QUEUE pQueue; int len; BOOL completeRequest; BYTE status; // // receive packets while there are listen ECBs and data waiting // while (1) { if (pConnectionInfo->ListenQueue.Head) { pQueue = &pConnectionInfo->ListenQueue; pXecb = pConnectionInfo->ListenQueue.Head; conn_q = TRUE; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteReceive: XECB %08x from CONNECTION_INFO %08x\n", pXecb, pConnectionInfo )); } else if (pSocketInfo->ListenQueue.Head) { pQueue = &pSocketInfo->ListenQueue; pXecb = pSocketInfo->ListenQueue.Head; conn_q = FALSE; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteReceive: XECB %08x from SOCKET_INFO %08x\n", pXecb, pSocketInfo )); } else { break; } rc = recv(pConnectionInfo->Socket, pXecb->Data, pXecb->Length, 0); if (rc != SOCKET_ERROR) { len = rc; status = ECB_CC_SUCCESS; completeRequest = TRUE; } else { rc = WSAGetLastError(); if (rc == WSAEMSGSIZE) { len = pXecb->Length; status = ECB_CC_PACKET_OVERFLOW; completeRequest = TRUE; } else { completeRequest = FALSE; // // if no data to receive, quit the loop (don't go down error path) // if (rc == WSAEWOULDBLOCK) { break; } } IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CompleteReceive: error %d on socket %08x (CID %04x)\n", rc, pConnectionInfo->Socket, pConnectionInfo->ConnectionId )); DUMPXECB(pXecb); } if( rc == WSAEDISCON ) { // // handle the disconnect case - we still need to complete the // ECB. // LPSPX_PACKET pPacket = (LPSPX_PACKET)pXecb->Buffer; status = ECB_CC_SUCCESS; pPacket->DestinationConnectId = pConnectionInfo->ConnectionId; pPacket->SourceConnectId = pConnectionInfo->RemoteConnectionId; pPacket->DataStreamType = SPX_DS_TERMINATE ; pPacket->Checksum = 0xffff; pPacket->Length = L2BW(SPX_HEADER_LENGTH); pPacket->TransportControl = 0; pPacket->PacketType = 5; pXecb->Length = SPX_HEADER_LENGTH ; ScatterData(pXecb); DequeueEcb(pXecb, pQueue); // // Put the remote node address in the ECB's immediate address // field // CopyMemory(pXecb->Ecb->ImmediateAddress, pConnectionInfo->RemoteNode, sizeof(pXecb->Ecb->ImmediateAddress) ); CompleteOrQueueIo(pXecb, status); DequeueConnection(pConnectionInfo->OwningSocket, pConnectionInfo); AbortOrTerminateConnection(pConnectionInfo, ECB_CC_CONNECTION_ABORTED); break ; } else if (completeRequest) { #if SPX_HACK if (pConnectionInfo->Flags & CF_1ST_RECEIVE) { pConnectionInfo->Flags &= ~CF_1ST_RECEIVE; ModifyFirstReceive(pXecb->Data, &len, pSocketInfo->SocketNumber, pConnectionInfo->Socket); } #endif IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteReceive: recv() on socket %#x returns %d bytes (Addr=%08x)\n", pConnectionInfo->Socket, len, pXecb->Data )); IPXDUMPDATA((pXecb->Data, 0, 0, FALSE, (WORD)len)); pXecb->Length -= len; pXecb->ActualLength += len; pXecb->Data += len; if (pXecb->ActualLength >= SPX_HEADER_LENGTH) { if (pXecb->Flags & XECB_FLAG_FIRST_RECEIVE) { LPSPX_PACKET pPacket = (LPSPX_PACKET)pXecb->Buffer; // // record in the SPX header the local connection id we invented // pPacket->DestinationConnectId = pConnectionInfo->ConnectionId; // // record the actual frame length from the header // pXecb->FrameLength = B2LW(((LPSPX_PACKET)pXecb->Buffer)->Length); pXecb->Flags &= ~XECB_FLAG_FIRST_RECEIVE; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteReceive: FrameLength=%x (%d)\n", pXecb->FrameLength, pXecb->FrameLength )); } // // if we received all the data in the packet (according to length // field in the SPX header) OR we ran out of buffer space, remove // the ECB from its queue and complete it // if (!pXecb->Length || (pXecb->ActualLength == pXecb->FrameLength)) { if (pXecb->Flags & XECB_FLAG_BUFFER_ALLOCATED) { // // update the XECB.Length field to reflect the amount of // data received and copy it to the fragmented buffers // in VDM. do not overflow buffer if FrameLength turns // out to be larger than we expect. // pXecb->Length = min(pXecb->FrameLength, pXecb->ActualLength); ScatterData(pXecb); } DequeueEcb(pXecb, pQueue); // DUMPXECB(pXecb); IPXDUMPECB((pXecb->Ecb, HIWORD(pXecb->EcbAddress), LOWORD(pXecb->EcbAddress), ECB_TYPE_SPX, TRUE, TRUE, IS_PROT_MODE(pXecb) )); // // Put the remote node address in the ECB's immediate address // field // CopyMemory(pXecb->Ecb->ImmediateAddress, pConnectionInfo->RemoteNode, sizeof(pXecb->Ecb->ImmediateAddress) ); CompleteOrQueueIo(pXecb, status); } else { // // partial receive. If the listen ECB came off the socket // queue then put it on the connection queue: this is the // ECB that will be used for this connection until all data // received or we get an error // if (!conn_q) { DequeueEcb(pXecb, &pSocketInfo->ListenQueue); QueueEcb(pXecb, &pConnectionInfo->ListenQueue, CONNECTION_LISTEN_QUEUE ); } // // not enough data to satisfy read: don't continue yet // break; } } } else { // // error occurred - abort the connection // if (!conn_q) { DequeueEcb(pXecb, &pSocketInfo->ListenQueue); QueueEcb(pXecb, &pConnectionInfo->ListenQueue, CONNECTION_LISTEN_QUEUE ); } DequeueConnection(pConnectionInfo->OwningSocket, pConnectionInfo); AbortOrTerminateConnection(pConnectionInfo, ECB_CC_CONNECTION_ABORTED); // // don't continue in this case // break; } } } PRIVATE VOID CompleteConnect( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Complete a SPXEstablishConnection Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { LPXECB pXecb = pConnectionInfo->ConnectQueue.Head; /* LPSPX_PACKET pPacket; // // the connection ID also appears in the first segment of the establish // ECB // pPacket = (LPSPX_PACKET)GET_FAR_POINTER(&ECB_FRAGMENT(pXecb->Ecb, 0)->Address, IS_PROT_MODE(pXecb) ); pPacket->Checksum = 0xffff; pPacket->Length = L2BW(SPX_HEADER_LENGTH); pPacket->TransportControl = 0; pPacket->PacketType = 5; pPacket->Source.Socket = pSocketInfo->SocketNumber; pPacket->ConnectionControl = 0xc0; pPacket->DataStreamType = 0; pPacket->SourceConnectId = pConnectionInfo->ConnectionId; pPacket->DestinationConnectId = 0xffff; pPacket->SequenceNumber = 0; pPacket->AckNumber = 0; pPacket->AllocationNumber = 0; */ pConnectionInfo->State = CI_ESTABLISHED; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteConnect: connection %04x (%08x) completed\n", pConnectionInfo->ConnectionId, pConnectionInfo )); DUMPCONN(pConnectionInfo); DequeueEcb(pXecb, &pConnectionInfo->ConnectQueue); IPXDUMPECB((pXecb->Ecb, HIWORD(pXecb->EcbAddress), LOWORD(pXecb->EcbAddress), ECB_TYPE_SPX, TRUE, TRUE, IS_PROT_MODE(pXecb) )); CompleteOrQueueEcb(pXecb, ECB_CC_SUCCESS); } PRIVATE VOID CompleteSend( IN LPSOCKET_INFO pSocketInfo, IN LPCONNECTION_INFO pConnectionInfo ) /*++ Routine Description: Complete a SPXSendSequencedPacket Arguments: pSocketInfo - pointer to SOCKET_INFO pConnectionInfo - pointer to CONNECTION_INFO Return Value: None. --*/ { LPXECB pXecb = pConnectionInfo->SendQueue.Head; int rc; BYTE status; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "CompleteSend: sending %d (0x%x) bytes from %08x\n", pXecb->Length, pXecb->Length, pXecb->Data )); IPXDUMPECB((pXecb->Ecb, HIWORD(pXecb->EcbAddress), LOWORD(pXecb->EcbAddress), ECB_TYPE_SPX, TRUE, TRUE, IS_PROT_MODE(pXecb) )); rc = send(pConnectionInfo->Socket, pXecb->Data, pXecb->Length, 0); if (rc == pXecb->Length) { // // all data sent // status = ECB_CC_SUCCESS; } else if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); if (rc == WSAEWOULDBLOCK) { // // huh??? // IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CompleteSend: send() returns WSAEWOODBLOCK??\n" )); // // leave ECB on queue // return; } else { IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_ERROR, "CompleteSend: send() returns %d\n", rc )); // // BUGBUG: abort connection? // status = ECB_CC_CONNECTION_ABORTED; } } else { // // partial data sent. Update the buffer pointer and length fields // and leave this ECB at the head of the send queue // pXecb->Data += rc; pXecb->Length -= (WORD)rc; return; } DequeueEcb(pXecb, &pConnectionInfo->SendQueue); CompleteOrQueueIo(pXecb, status); } #if SPX_HACK PRIVATE VOID ModifyFirstReceive( LPBYTE Buffer, LPDWORD pLength, WORD SocketNumber, SOCKET Socket ) { WORD len = *(LPWORD)pLength; if ((*(ULPWORD)Buffer != 0xffff) && (*(ULPWORD)(Buffer+2) != L2BW(len))) { LPSPX_PACKET packet; SOCKADDR_IPX remote; int rc; int remlen; IPXDBGPRINT((__FILE__, __LINE__, FUNCTION_ANY, IPXDBG_LEVEL_INFO, "ModifyFirstReceive: Modifying: Buffer=%08x Length=%04x SocketNumber=%04x Socket=%08x\n", Buffer, len, B2LW(SocketNumber), Socket )); MoveMemory(Buffer+42, Buffer, len); packet = (LPSPX_PACKET)Buffer; packet->Checksum = 0xffff; packet->Length = L2BW(42+len); packet->TransportControl = 0; packet->PacketType = 5; CopyMemory((LPVOID)&packet->Destination, (LPVOID)&MyInternetAddress.sa_netnum, sizeof(INTERNET_ADDRESS) ); packet->Destination.Socket = SocketNumber; rc = getpeername(Socket, (LPSOCKADDR)&remote, &remlen); if (rc != SOCKET_ERROR) { CopyMemory((LPVOID)&packet->Source, (LPVOID)&remote.sa_netnum, sizeof(NETWARE_ADDRESS) ); } else { ZeroMemory((LPVOID)&packet->Source, sizeof(NETWARE_ADDRESS)); } packet->ConnectionControl = 0x40; packet->DataStreamType = 0; packet->SourceConnectId = 0; packet->DestinationConnectId = 0; packet->SequenceNumber = 0; packet->AckNumber = 0; packet->AllocationNumber = 0; *pLength += 42; } } #endif