Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

3181 lines
110 KiB
C

/*H*************************************************************************************/
/*!
\File dirtynetwin.c
\Description
Provide a wrapper that translates the Winsock network interface
into DirtySock calls. In the case of Winsock, little translation
is needed since it is based off BSD sockets (as is DirtySock).
\Copyright
Copyright (c) Electronic Arts 1999-2018.
\Version 1.0 01/02/2002 (gschaefer) First Version
*/
/*************************************************************************************H*/
/*** Include files *********************************************************************/
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1 // avoid windows.h including extra stuff, including winsock.h which we don't want
#endif
#include "DirtySDK/platform.h"
#if defined(DIRTYCODE_XBOXONE)
#pragma warning(push,0)
#include <windows.h>
#pragma warning(pop)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mstcpip.h> // for tcp keep alive definitions
#else
#pragma warning(push,0)
#include <windows.h>
#include <ws2tcpip.h>
#include <mstcpip.h> // for tcp keep alive definitions
#pragma warning(pop)
#endif
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/dirtynet.h"
#include "DirtySDK/dirtysock/dirtyerr.h"
#include "DirtySDK/dirtysock/dirtythread.h"
#include "DirtySDK/dirtysock/netconn.h"
#include "DirtySDK/dirtyvers.h"
#include "dirtynetpriv.h" // private include for dirtynet common functions
/*** Defines ***************************************************************************/
#define SOCKET_MAXEVENTS (64)
/*** Macros ****************************************************************************/
/*** Type Definitions ******************************************************************/
//! dirtysock connection socket structure
struct SocketT
{
SocketT *pNext; //!< link to next active
SocketT *pKill; //!< link to next killed socket
SOCKET uSocket; //!< winsock socket ref
int32_t iFamily; //!< protocol family
int32_t iType; //!< protocol type
int32_t iProto; //!< protocol ident
int8_t iOpened; //!< negative=error, zero=not open (connecting), positive=open
uint8_t uImported; //!< whether socket was imported or not
uint8_t bVirtual; //!< if true, socket is virtual
uint8_t uShutdown; //!< shutdown flag
int32_t iLastError; //!< last socket error
struct sockaddr LocalAddr; //!< local address
struct sockaddr RemoteAddr; //!< remote address
uint16_t uVirtualPort; //!< virtual port, if set
uint8_t bAsyncRecv; //!< TRUE if async recv is enabled
int8_t iVerbose; //!< debug level
uint8_t bSendCbs; //!< TRUE if send cbs are enabled, false otherwise
uint8_t _pad0[3];
SocketRateT SendRate; //!< send rate estimation data
SocketRateT RecvRate; //!< recv rate estimation data
uint32_t uCallMask; //!< valid callback events
uint32_t uCallLast; //!< last callback tick
uint32_t uCallIdle; //!< ticks between idle calls
void *pCallRef; //!< reference calback value
int32_t (*pCallback)(SocketT *pSocket, int32_t iFlags, void *pRef);
WSAOVERLAPPED Overlapped; //!< overlapped i/o structure
NetCritT RecvCrit; //!< receive critical section
int32_t iAddrLen; //!< storage for async address length write by WSARecv
uint32_t uRecvFlag; //!< flags from recv operation
uint8_t bRecvInp; //!< if true, a receive operation is in progress
uint8_t bInCallback; //!< in a socket callback
uint8_t _pad1[2];
struct sockaddr RecvAddr; //!< receive address
struct sockaddr_in6 RecvAddr6; //!< receive address (ipv6)
int32_t iPacketQueueResizePending; // -1 if no resize pending, new size otherwise
SocketPacketQueueT *pRecvQueue;
SocketPacketQueueEntryT *pRecvPacket;
};
//! local state
typedef struct SocketStateT
{
SocketT *pSockList; //!< master socket list
SocketT *pSockKill; //!< list of killed sockets
HostentT *pHostList; //!< list of ongoing name resolution operations
uint16_t aVirtualPorts[SOCKET_MAXVIRTUALPORTS]; //!< virtual port list
int32_t iMaxPacket; //!< maximum packet size
int32_t iFamily; //!< family to use for socket operations
// module memory group
int32_t iMemGroup; //!< module mem group id
void *pMemGroupUserData; //!< user data associated with mem group
int32_t iVersion; //!< winsock version
int32_t iVerbose; //!< debug level
#if defined(DIRTYCODE_XBOXONE)
uint32_t uLocalAddr; //!< local ipv4 address
uint32_t uRandBindPort;
#else
uint32_t uAdapterAddress; //!< local interface used for SocketBind() operations if non-zero
#endif
volatile int32_t iRecvLife; //!< receive thread alive indicator
WSAEVENT hEvent; //!< event used to wake up receive thread
NetCritT EventCrit; //!< event critical section (used when killing events)
SocketAddrMapT AddrMap; //!< address map for translating ipv6 addresses to ipv4 virtual addresses and back
SocketHostnameCacheT *pHostnameCache; //!< hostname cache
SocketSendCallbackEntryT aSendCbEntries[SOCKET_MAXSENDCALLBACKS]; //!< collection of send callbacks
} SocketStateT;
/*** Function Prototypes ***************************************************************/
/*** Variables *************************************************************************/
// Private variables
//! module state ref
static SocketStateT *_Socket_pState = NULL;
// Public variables
/*** Private Functions *****************************************************************/
/*F*************************************************************************************/
/*!
\Function _XlatError0
\Description
Translate a winsock error to dirtysock
\Input iErr - return value from winsock call
\Input iWsaErr - winsock error (from WSAGetLastError())
\Output
int32_t - dirtysock error
\Version 09/09/2004 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _XlatError0(int32_t iErr, int32_t iWsaErr)
{
if (iErr < 0)
{
iErr = iWsaErr;
if ((iErr == WSAEWOULDBLOCK) || (iErr == WSA_IO_PENDING))
iErr = SOCKERR_NONE;
else if ((iErr == WSAENETUNREACH) || (iErr == WSAEHOSTUNREACH))
iErr = SOCKERR_UNREACH;
else if (iErr == WSAENOTCONN)
iErr = SOCKERR_NOTCONN;
else if (iErr == WSAECONNREFUSED)
iErr = SOCKERR_REFUSED;
else if (iErr == WSAEINVAL)
iErr = SOCKERR_INVALID;
else if (iErr == WSAECONNRESET)
iErr = SOCKERR_CONNRESET;
else
{
NetPrintf(("dirtynetwin: error %s\n", DirtyErrGetName(iErr)));
iErr = SOCKERR_OTHER;
}
}
return(iErr);
}
/*F*************************************************************************************/
/*!
\Function _XlatError
\Description
Translate the most recent winsock error to dirtysock
\Input iErr - return value from winsock call
\Output
int32_t - dirtysock error
\Version 01/02/2002 (gschaefer)
*/
/************************************************************************************F*/
static int32_t _XlatError(int32_t iErr)
{
return(_XlatError0(iErr, WSAGetLastError()));
}
/*F*************************************************************************************/
/*!
\Function _SocketOpen
\Description
Allocates a SocketT. If uSocket is INVALID_SOCKET, a WinSock socket ref is
created, otherwise uSocket is used.
\Input uSocket - socket to use, or INVALID_SOCKET
\Input iFamily - address family
\Input iType - type (SOCK_DGRAM, SOCK_STREAM, ...)
\Input iProto - protocol
\Output
SocketT * - pointer to new socket, or NULL
\Version 03/03/2005 (jbrookes)
*/
/************************************************************************************F*/
static SocketT *_SocketOpen(SOCKET uSocket, int32_t iFamily, int32_t iType, int32_t iProto)
{
SocketStateT *pState = _Socket_pState;
const uint32_t uTrue = 1, uFalse = 0;
const int32_t iQueueSize = (iType != SOCK_STREAM) ? 1 : 8;
SocketT *pSocket;
// allocate memory
if ((pSocket = (SocketT *)DirtyMemAlloc(sizeof(*pSocket), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)
{
NetPrintf(("dirtynetwin: unable to allocate memory for socket\n"));
return(NULL);
}
ds_memclr(pSocket, sizeof(*pSocket));
// open a winsock socket
if ((uSocket == INVALID_SOCKET) && ((uSocket = WSASocketW(iFamily, iType, iProto, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET))
{
NetPrintf(("dirtynetwin: error %d creating socket\n", WSAGetLastError()));
DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
return(NULL);
}
// create packet queue
if ((pSocket->pRecvQueue = SocketPacketQueueCreate(iQueueSize, pState->iMemGroup, pState->pMemGroupUserData)) == NULL)
{
NetPrintf(("dirtynetwin: failed to create socket queue for socket\n"));
closesocket(uSocket);
DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
return(NULL);
}
pSocket->iPacketQueueResizePending = -1;
// save the socket
pSocket->uSocket = uSocket;
pSocket->iLastError = SOCKERR_NONE;
pSocket->iVerbose = 1;
// set to non blocking
ioctlsocket(uSocket, FIONBIO, (u_long *)&uTrue);
// if udp, allow broadcast
if (iType == SOCK_DGRAM)
{
setsockopt(uSocket, SOL_SOCKET, SO_BROADCAST, (char *)&uTrue, sizeof(uTrue));
}
// if raw, set hdrincl
if (iType == SOCK_RAW)
{
setsockopt(uSocket, IPPROTO_IP, IP_HDRINCL, (char *)&uTrue, sizeof(uTrue));
}
// disable IPv6 only (allow IPv4 use)
if (iFamily == AF_INET6)
{
setsockopt(uSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&uFalse, sizeof(uFalse));
}
// set family/proto info
pSocket->iFamily = iFamily;
pSocket->iType = iType;
pSocket->iProto = iProto;
pSocket->bAsyncRecv = ((iType == SOCK_DGRAM) || (iType == SOCK_RAW)) ? TRUE : FALSE;
pSocket->bSendCbs = TRUE;
// create overlapped i/o event object
ds_memclr(&pSocket->Overlapped, sizeof(pSocket->Overlapped));
pSocket->Overlapped.hEvent = WSACreateEvent();
// initialize receive critical section
NetCritInit(&pSocket->RecvCrit, "recvthread");
// install into list
NetCritEnter(NULL);
pSocket->pNext = pState->pSockList;
pState->pSockList = pSocket;
NetCritLeave(NULL);
// return the socket
return(pSocket);
}
/*F*************************************************************************************/
/*!
\Function _SocketClose
\Description
Disposes of a SocketT, including removal from the global socket list and
disposal of the SocketT allocated memory. Does NOT dispose of the winsock
socket ref.
\Input *pSocket - socket to close
\Input bShutdown - if TRUE, shutdown and close socket ref
\Output
int32_t - negative=failure, zero=success
\Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
static int32_t _SocketClose(SocketT *pSocket, uint32_t bShutdown)
{
SocketStateT *pState = _Socket_pState;
uint8_t bSockInList = FALSE;
SocketT **ppSocket;
// for access to socket list
NetCritEnter(NULL);
// remove sock from linked list
for (ppSocket = &pState->pSockList; *ppSocket != NULL; ppSocket = &(*ppSocket)->pNext)
{
if (*ppSocket == pSocket)
{
*ppSocket = pSocket->pNext;
bSockInList = TRUE;
break;
}
}
// release before NetIdleDone
NetCritLeave(NULL);
// make sure the socket is in the socket list (and therefore valid)
if (!bSockInList)
{
NetPrintf(("dirtynetwin: warning, trying to close socket 0x%08x that is not in the socket list\n", (uintptr_t)pSocket));
return(-1);
}
// finish any idle call
NetIdleDone();
// acquire global critical section
NetCritEnter(NULL);
// wake up out of WaitForMultipleEvents()
WSASetEvent(pState->hEvent);
// acquire event critical section
NetCritEnter(&pState->EventCrit);
// close event
WSACloseEvent(pSocket->Overlapped.hEvent);
pSocket->Overlapped.hEvent = WSA_INVALID_EVENT;
// release event critical section
NetCritLeave(&pState->EventCrit);
// release global critical section
NetCritLeave(NULL);
// destroy packet queue
if (pSocket->pRecvQueue != NULL)
{
SocketPacketQueueDestroy(pSocket->pRecvQueue);
}
// mark as closed
if (bShutdown && (pSocket->uSocket != INVALID_SOCKET))
{
// close winsock socket
shutdown(pSocket->uSocket, 2);
closesocket(pSocket->uSocket);
}
pSocket->uSocket = INVALID_SOCKET;
pSocket->iOpened = 0;
/* Put into killed list:
Usage of a kill list allows for postponing two things
* destruction of RecvCrit
* release of socket data structure memory
This ensures that RecvCrit is not freed while in-use by a running thread.
Such a scenario can occur when the receive callback invoked by _SocketRecvThread()
(while RecvCrit is entered) calls _SocketClose() */
NetCritEnter(NULL);
pSocket->pKill = pState->pSockKill;
pState->pSockKill = pSocket;
NetCritLeave(NULL);
return(0);
}
/*F*************************************************************************************/
/*!
\Function _SocketIdle
\Description
Call idle processing code to give connections time.
\Input *_pState - module state
\Version 10/15/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketIdle(void *_pState)
{
SocketStateT *pState = (SocketStateT *)_pState;
SocketT *pSocket;
uint32_t uTick = NetTick();
// for access to socket list and kill list
NetCritEnter(NULL);
// walk socket list and perform any callbacks
for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
{
// see if we should do callback
if ((pSocket->uCallIdle != 0) && (pSocket->pCallback != NULL) && (!pSocket->bInCallback) && (NetTickDiff(uTick, pSocket->uCallLast) > pSocket->uCallIdle))
{
pSocket->bInCallback = TRUE;
pSocket->pCallback(pSocket, 0, pSocket->pCallRef);
pSocket->bInCallback = FALSE;
pSocket->uCallLast = uTick = NetTick();
}
}
// delete any killed sockets
while ((pSocket = pState->pSockKill) != NULL)
{
pState->pSockKill = pSocket->pKill;
// release the socket's receive critical section
NetCritKill(&pSocket->RecvCrit);
// free the socket memory
DirtyMemFree(pSocket, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
}
// process dns cache list, delete expired entries
SocketHostnameCacheProcess(pState->pHostnameCache, pState->iVerbose);
// process hostname list, delete completed lookup requests
SocketHostnameListProcess(&pState->pHostList, pState->iMemGroup, pState->pMemGroupUserData);
// release access to socket list and kill list
NetCritLeave(NULL);
}
/*F*************************************************************************************/
/*!
\Function _SocketRecvfromPacketQueue
\Description
Check if there is a pending inbound packet in the socket packet queue.
\Input *pSocket - pointer to socket
\Input *pBuf - [out] buffer to receive data
\Input iLen - length of recv buffer
\Input *pFrom - [out] address data was received from (NULL=ignore)
\Input *pFromLen - [out] length of address
\Output
int32_t - size of packet extracted from queue, 0 if no packet
\Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static int32_t _SocketRecvfromPacketQueue(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen)
{
int32_t iResult = 0;
// make sure destination buffer is valid
if ((iLen > 0) && (pBuf != NULL))
{
// get a packet
if (pSocket->iType != SOCK_STREAM)
{
iResult = SocketPacketQueueRem(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen, &pSocket->RecvAddr);
}
else
{
iResult = SocketPacketQueueRemStream(pSocket->pRecvQueue, (uint8_t *)pBuf, iLen);
}
if (iResult > 0)
{
if (pFrom != NULL)
{
ds_memcpy_s(pFrom, sizeof(*pFrom), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr));
*pFromLen = sizeof(*pFrom);
}
}
}
return(iResult);
}
/*F*************************************************************************************/
/*!
\Function _SocketRecvfrom
\Description
Check if there is a pending inbound packet in the receive buffer of the
system socket
\Input *pSocket - pointer to socket
\Input *pBuf - [out] buffer to receive data
\Input iLen - length of recv buffer
\Input *pFrom - [out] address data was received from (NULL=ignore)
\Input *pFromLen - [out] length of address
\Input *pRecvErr - [out] pointer to variable to be filled with recv err code
\Output
int32_t - positive=data bytes received, else standard error code
\Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static int32_t _SocketRecvfrom(SocketT *pSocket, const char *pBuf, int32_t iLen, struct sockaddr *pFrom, int32_t *pFromLen, int32_t *pRecvErr)
{
int32_t iResult = 0;
// make sure socket ref is valid
if (pSocket->uSocket == INVALID_SOCKET)
{
pSocket->iLastError = SOCKERR_INVALID;
return(pSocket->iLastError);
}
if (pFrom != NULL)
{
if (pSocket->iFamily == AF_INET)
{
iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, pFrom, pFromLen);
}
if (pSocket->iFamily == AF_INET6)
{
struct sockaddr_in6 SockAddr6;
int32_t iFromLen6 = sizeof(SockAddr6);
SockAddr6.sin6_family = AF_INET;
if ((iResult = recvfrom(pSocket->uSocket, (char *)pBuf, iLen, 0, (struct sockaddr *)&SockAddr6, &iFromLen6)) > 0)
{
SocketAddrMapTranslate(&_Socket_pState->AddrMap, pFrom, (struct sockaddr *)&SockAddr6, pFromLen);
}
}
SockaddrInSetMisc(pFrom, NetTick());
}
else
{
iResult = recv(pSocket->uSocket, (char *)pBuf, iLen, 0);
pFrom = &pSocket->RemoteAddr;
}
// get most recent socket error
if ((*pRecvErr = WSAGetLastError()) == WSAEMSGSIZE)
{
/* if there was a message truncation, simply return the truncated size. this matches what we
do with the async recv thread version and also the linux behavior of recvfrom() */
iResult = iLen;
}
return(iResult);
}
/*F*************************************************************************************/
/*!
\Function _SocketProcessQueueResize
\Description
Resize socket packet queue if needed.
\Input *pSocket - pointer to socket
\Notes
This function is meant to be called from the _SocketRecvThread() only.
Do not use this function in code paths exercised by other threads.
\Version 04/20/2016 (mclouatre)
*/
/************************************************************************************F*/
static void _SocketProcessQueueResize(SocketT *pSocket)
{
SocketStateT *pState = _Socket_pState;
if ((pSocket->iPacketQueueResizePending != -1))
{
pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, pSocket->iPacketQueueResizePending, pState->iMemGroup, pState->pMemGroupUserData);
pSocket->iPacketQueueResizePending = -1;
}
}
/*F*************************************************************************************/
/*!
\Function _SocketRecvfromAsyncComplete
\Description
Called when data is received by _SocketRecvThread(). If there is a callback
registered, then the socket is passed to that callback so the data may be
consumed.
\Input *pSocket - pointer to socket that has new data
\Input iBytesReceived - number of bytes received
\Notes
This function is meant to be called from the _SocketRecvThread() only.
Do not use this function in code paths exercised by other threads.
\Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvfromAsyncComplete(SocketT *pSocket, int32_t iBytesReceived)
{
// translate IPv6->IPv4, save receive timestamp
if (pSocket->iType != SOCK_STREAM)
{
if (pSocket->iFamily == AF_INET6)
{
int32_t iNameLen = sizeof(pSocket->RecvAddr);
SockaddrInit(&pSocket->RecvAddr, AF_INET);
SocketAddrMapTranslate(&_Socket_pState->AddrMap, &pSocket->RecvAddr, (struct sockaddr *)&pSocket->RecvAddr6, &iNameLen);
}
SockaddrInSetMisc(&pSocket->RecvAddr, NetTick());
}
// complete setup of packet queue entry (already has data) by updating the size and reception time
pSocket->pRecvPacket->iPacketSize = iBytesReceived;
pSocket->pRecvPacket->uPacketTick = NetTick();
ds_memcpy_s(&pSocket->pRecvPacket->PacketAddr, sizeof(pSocket->pRecvPacket->PacketAddr), &pSocket->RecvAddr, sizeof(pSocket->RecvAddr));
// we are done with populating that specific packet queue entry, kill reference into packet queue
pSocket->pRecvPacket = NULL;
/* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked
and there is no async recv operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT
pointing to a buffer in the queue.
It is important to execute this after pSocket->pRecvPacket (which points to an entry in the queue) is fully initialized
and befor the recv callback is invoked (because we want to resize to happen before SocketRecvfrom being potentially
invoked from the callback. */
_SocketProcessQueueResize(pSocket);
// see if we should issue callback
if ((!pSocket->bInCallback) && (pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV))
{
pSocket->bInCallback = TRUE;
(pSocket->pCallback)(pSocket, 0, pSocket->pCallRef);
pSocket->bInCallback = FALSE;
pSocket->uCallLast = NetTick();
}
}
/*F*************************************************************************************/
/*!
\Function _SocketRecvfromAsync
\Description
Issue an overlapped recv call on the given socket.
\Input *pSocket - pointer to socket to read from
\Notes
This function is meant to be called from the _SocketRecvThread() only.
Do not use this function in code paths exercised by other threads.
\Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvfromAsync(SocketT *pSocket)
{
int32_t iResult = 0;
int32_t iRecvErr;
WSABUF RecvBuf;
/* Mark the operation as in progress.
For scenarios where the recv call returned 0 (meaning immediate completion), we enforce an execution
path similar to recv call not completing immediately. We asssume two things:
* _SocketRecvThread() will not wait on the next WSAWaitForMultipleEvents() because the event for this socket is signaled.
* The following call to WSAGetOverlappedResult() will then detect completion of the recv operation. */
pSocket->bRecvInp = TRUE;
// get a packet queue entry to receive into
pSocket->pRecvPacket = SocketPacketQueueAlloc(pSocket->pRecvQueue);
// set up for recv call
RecvBuf.buf = (CHAR *)pSocket->pRecvPacket->aPacketData;
RecvBuf.len = sizeof(pSocket->pRecvPacket->aPacketData);
pSocket->uRecvFlag = 0;
// try and receive some data
if (pSocket->iType == SOCK_DGRAM)
{
if (pSocket->iFamily == AF_INET)
{
pSocket->iAddrLen = sizeof(pSocket->RecvAddr);
iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL);
}
if (pSocket->iFamily == AF_INET6)
{
pSocket->iAddrLen = sizeof(pSocket->RecvAddr6);
iResult = WSARecvFrom(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, (struct sockaddr *)&pSocket->RecvAddr6, (LPINT)&pSocket->iAddrLen, &pSocket->Overlapped, NULL);
}
}
else // pSocket->iType == SOCK_RAW
{
iResult = WSARecv(pSocket->uSocket, &RecvBuf, 1, NULL, (LPDWORD)&pSocket->uRecvFlag, &pSocket->Overlapped, NULL);
}
// error?
if ((iResult == SOCKET_ERROR) && ((iRecvErr = WSAGetLastError()) != WSA_IO_PENDING))
{
if (pSocket->iType != SOCK_STREAM)
{
NetPrintf(("dirtynetwin: [%p] error %s when trying to initiate async receive on socket\n", pSocket, DirtyErrGetName(iRecvErr)));
}
else
{
// in the testing done, winsock2 returns WSAECONNABORTED when the FIN comes in for the socket. in reviewing the error codes there was no other more suitable check to make
NetPrintf(("dirtynetwin: [%p] connection %s\n", pSocket, (iRecvErr == WSAECONNABORTED) ? "closed" : "failed"));
pSocket->iOpened = -1;
}
// clean up resources that were reserved when the async recv was initiated
SocketPacketQueueAllocUndo(pSocket->pRecvQueue);
// mark that receive operation is no longer in progress
pSocket->bRecvInp = FALSE;
}
}
/*F*************************************************************************************/
/*!
\Function _SocketRecvThread
\Description
Wait for incoming data and deliver it immediately to the registered socket callback,
if any.
\Input pUnused - unused
\Version 08/30/2004 (jbrookes)
*/
/************************************************************************************F*/
static void _SocketRecvThread(void *pUnused)
{
SocketStateT *pState = _Socket_pState;
WSAEVENT aEventList[SOCKET_MAXEVENTS];
int32_t iNumEvents, iResult;
SocketT *pSocket;
char strThreadId[32];
// get the thread id
DirtyThreadGetThreadId(strThreadId, sizeof(strThreadId));
// show we are alive
NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread started (thid=%s)\n", strThreadId));
// clear event list
ds_memclr(aEventList, sizeof(aEventList));
// loop until done
while (pState->iRecvLife == 1)
{
// acquire global critical section for access to g_socklist
NetCritEnter(NULL);
// add global event to list
iNumEvents = 0;
aEventList[iNumEvents++] = pState->hEvent;
/* Walk the socket list and for each eligible socket do:
1- If an async recv has recently completed, then finalize the recv operation and invoke the recv callback if registered.
Then mark the async recv operatoin has no longer in progress.
2- If an async recv is not in progress, initiate one if n-deep packet queue is not full. Then mark the async recv operation
as in progress.
3- If an async revc is in progress (old or newly initiated), then add the socket's event to the event list that the
thread later blocks on when calling WSAWaitForMultipleEvents(). */
for (pSocket = pState->pSockList; (pSocket != NULL) && (iNumEvents < SOCKET_MAXEVENTS); pSocket = pSocket->pNext)
{
// only handle non-virtual sockets with asyncrecv true
if ((!pSocket->bVirtual) && (pSocket->uSocket != INVALID_SOCKET) && pSocket->bAsyncRecv)
{
// acquire socket critical section
NetCritEnter(&pSocket->RecvCrit);
// is an asynchronous recv in progress on this socket?
if (pSocket->bRecvInp)
{
int32_t iRecvResult;
// check for overlapped read completion
if (WSAGetOverlappedResult(pSocket->uSocket, &pSocket->Overlapped, (LPDWORD)&iRecvResult, FALSE, (LPDWORD)&pSocket->uRecvFlag) == TRUE)
{
// mark that receive operation is no longer in progress
pSocket->bRecvInp = FALSE;
_SocketRecvfromAsyncComplete(pSocket, iRecvResult);
}
else if ((iResult = WSAGetLastError()) != WSA_IO_INCOMPLETE)
{
// clean up resources that were reserved when the async recv was initiated
SocketPacketQueueAllocUndo(pSocket->pRecvQueue);
#if DIRTYCODE_LOGGING
if (iResult != WSAECONNRESET)
{
NetPrintf(("dirtynetwin: [%p] WSAGetOverlappedResult error %d\n", pSocket, iResult));
}
#endif
// mark that receive operation is no longer in progress
pSocket->bRecvInp = FALSE;
/* This is a safe place to deal with pending packet queue resize because the recvcrit is guaranteed to be locked
and there is no async read operation in progress on the queue (which implicitly means that pSocket->pRecvPacket is NOT
pointing to a buffer in the queue. */
_SocketProcessQueueResize(pSocket);
}
}
/* Before proceeding, make sure that the user callback invoked in _SocketRecvThreadFinishAsyncRead() did not close the socket
with SocketClose(). We know that SocketClose() function resets the value of pSocket->socket to INVALID_SOCKET. Also, we
know that it does not destroy pSocket but it queues it in the global kill list. Since this list cannot be processed before
the code below, as it also runs in the context of the global critical section, the following code is thread-safe. */
if ((pSocket->uSocket != INVALID_SOCKET) && ((pSocket->iType != SOCK_STREAM) || (pSocket->iOpened > 0)))
{
// should we initiate a new asynchronous recv on this socket?
if (!pSocket->bRecvInp && !SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful'))
{
_SocketRecvfromAsync(pSocket);
}
// if an asynchronous recv (old or newly initiated) is in progress, then add the associated event to event list
if (pSocket->bRecvInp)
{
aEventList[iNumEvents++] = pSocket->Overlapped.hEvent;
}
}
// release socket critical section
NetCritLeave(&pSocket->RecvCrit);
}
}
// protect against events being deleted
NetCritEnter(&pState->EventCrit);
// release global critical section
NetCritLeave(NULL);
// wait for an event to trigger
iResult = WSAWaitForMultipleEvents(iNumEvents, aEventList, FALSE, WSA_INFINITE, FALSE) - WSA_WAIT_EVENT_0;
// reset the signaled event
if ((iResult >= 0) && (iResult < iNumEvents))
{
WSAResetEvent(aEventList[iResult]);
}
// leave event protected section
NetCritLeave(&pState->EventCrit);
}
// indicate we are done
NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: receive thread exit\n"));
pState->iRecvLife = 0;
}
/*F*************************************************************************************/
/*!
\Function _SocketPoll
\Description
Perform a blocking poll in nanoseconds
\Input *pState - pointer to module state
\Input uPollUsec - time to perform the poll (select) for
\Input *ppSocketList - socket list or NULL
\Output
int32_t - result of the select call
\Version 04/17/2019 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketPoll(SocketStateT *pState, uint32_t uPollUsec, SocketT **ppSocketList)
{
fd_set FdRead, FdExcept;
struct timeval TimeVal;
SocketT *pTempSocket, **ppSocket;
int32_t iSocket, iResult;
int32_t iMaxSocket = 0;
FD_ZERO(&FdRead);
FD_ZERO(&FdExcept);
TimeVal.tv_sec = uPollUsec/1000000;
TimeVal.tv_usec = uPollUsec%1000000;
// if socket list specified, use it
if (ppSocketList != NULL)
{
// add sockets to select list (FD_SETSIZE is 64)
for (ppSocket = ppSocketList, iSocket = 0; (*ppSocket != NULL) && (iSocket < FD_SETSIZE); ppSocket++, iSocket++)
{
FD_SET((*ppSocket)->uSocket, &FdRead);
FD_SET((*ppSocket)->uSocket, &FdExcept);
iMaxSocket = DS_MAX((int32_t)((*ppSocket)->uSocket), iMaxSocket);
}
}
else
{
// get exclusive access to socket list
NetCritEnter(NULL);
// walk socket list and add all sockets
for (pTempSocket = pState->pSockList, iSocket = 0; (pTempSocket != NULL) && (iSocket < FD_SETSIZE); pTempSocket = pTempSocket->pNext, iSocket++)
{
if (pTempSocket->uSocket != INVALID_SOCKET)
{
FD_SET(pTempSocket->uSocket, &FdRead);
FD_SET(pTempSocket->uSocket, &FdExcept);
iMaxSocket = DS_MAX((int32_t)(pTempSocket->uSocket), iMaxSocket);
}
}
// for access to g_socklist and g_sockkill
NetCritLeave(NULL);
}
// wait for input on the socket list for up to iData1 milliseconds
iResult = select(iMaxSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal);
// if any sockets have pending data, figure out which ones
if (iResult > 0)
{
// re-acquire critical section
NetCritEnter(NULL);
// update sockets if there is data to be read or not
for (pTempSocket = pState->pSockList; pTempSocket != NULL; pTempSocket = pTempSocket->pNext)
{
if ((pTempSocket->uSocket != INVALID_SOCKET) &&
(FD_ISSET(pTempSocket->uSocket, &FdRead)) &&
(pTempSocket->pCallback != NULL) &&
(pTempSocket->uCallMask & CALLB_RECV))
{
pTempSocket->pCallback(pTempSocket, 0, pTempSocket->pCallRef);
pTempSocket->uCallLast = NetTick();
}
}
// release the critical section
NetCritLeave(NULL);
}
// return number of file descriptors with pending data
return(iResult);
}
/*F*************************************************************************************/
/*!
\Function _SocketInfoGlobal
\Description
Return information about global state
\Input iInfo - selector for desired information
\Input iData - selector specific
\Input *pBuf - [out] return buffer
\Input iLen - buffer length
\Output
int32_t - size of returned data or error code (negative value)
\Notes
These selectors need to be documented in SocketInfo() to allow our
documentation generation to pick them up.
\Version 03/31/2017 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketInfoGlobal(int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen)
{
SocketStateT *pState = _Socket_pState;
if (iInfo == 'addr')
{
#if defined(DIRTYCODE_XBOXONE)
// force new acquisition of address?
if (iData == 1)
{
pState->uLocalAddr = 0;
}
#endif
return(SocketGetLocalAddr());
}
// get socket bound to given port
if ( (iInfo == 'bind') || (iInfo == 'bndu') )
{
SocketT *pSocket;
struct sockaddr BindAddr;
int32_t iFound = -1;
// for access to socket list
NetCritEnter(NULL);
// walk socket list and find matching socket
for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
{
// if iInfo is 'bndu', only consider sockets of type SOCK_DGRAM
// note: 'bndu' stands for "bind udp"
if ( (iInfo == 'bind') || ((iInfo == 'bndu') && (pSocket->iType == SOCK_DGRAM)) )
{
// get socket info
SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr));
if (SockaddrInGetPort(&BindAddr) == iData)
{
*(SocketT **)pBuf = pSocket;
iFound = 0;
break;
}
}
}
// for access to g_socklist and g_sockkill
NetCritLeave(NULL);
return(iFound);
}
// check if specified ipv4 address is virtual and return associated ipv6 address if so
if (iInfo == '?ip6')
{
int32_t iResult = -1;
struct sockaddr_in6 SockAddr6, *pSockAddr6;
struct sockaddr SockAddr;
int32_t iNameLen;
SockaddrInit(&SockAddr, AF_INET);
SockaddrInSetAddr(&SockAddr, iData);
SockaddrInit6(&SockAddr6, AF_INET6);
pSockAddr6 = ((pBuf != NULL) && (iLen == sizeof(SockAddr6))) ? (struct sockaddr_in6 *)pBuf : &SockAddr6;
iNameLen = sizeof(SockAddr6);
iResult = SocketAddrMapGet(&pState->AddrMap, (struct sockaddr *)pSockAddr6, &SockAddr, &iNameLen) ? 1 : 0;
return(iResult);
}
#if !defined(DIRTYCODE_XBOXONE)
// get local address previously specified by user for subsequent SocketBind() operations
if (iInfo == 'ladr')
{
// If 'ladr' had not been set previously, address field of output sockaddr buffer
// will just be filled with 0.
SockaddrInSetAddr((struct sockaddr *)pBuf, pState->uAdapterAddress);
return(0);
}
#endif
// return max packet size
if (iInfo == 'maxp')
{
return(pState->iMaxPacket);
}
// get send callback function pointer (iData specifies index in array)
if (iInfo == 'sdcf')
{
if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallback)))
{
ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallback, sizeof(pState->aSendCbEntries[iData].pSendCallback));
return(0);
}
NetPrintf(("dirtynetwin: 'sdcf' selector used with invalid paramaters\n"));
return(-1);
}
// get send callback user data pointer (iData specifies index in array)
if (iInfo == 'sdcu')
{
if ((pBuf != NULL) && (iLen == sizeof(pState->aSendCbEntries[iData].pSendCallref)))
{
ds_memcpy(pBuf, &pState->aSendCbEntries[iData].pSendCallref, sizeof(pState->aSendCbEntries[iData].pSendCallref));
return(0);
}
NetPrintf(("dirtynetwin: 'sdcu' selector used with invalid paramaters\n"));
return(-1);
}
// return global debug output level
if (iInfo == 'spam')
{
return(pState->iVerbose);
}
// unhandled
NetPrintf(("dirtynetwin: unhandled global SocketInfo() selector '%C'\n", iInfo));
return(-1);
}
/*F*************************************************************************************/
/*!
\Function _SocketControlGlobal
\Description
Process a global control message (type specific operation)
\Input iOption - the option to pass
\Input iData1 - message specific parm
\Input *pData2 - message specific parm
\Input *pData3 - message specific parm
\Output
int32_t - message specific result (-1=unsupported message, -2=no such module)
\Notes
These selectors need to be documented in SocketControl() to allow our
documentation generation to pick them up.
\Version 03/31/2017 (eesponda)
*/
/************************************************************************************F*/
static int32_t _SocketControlGlobal(int32_t iOption, int32_t iData1, void *pData2, void *pData3)
{
SocketStateT *pState = _Socket_pState;
// set address family to use for socket operations
if (iOption == 'afam')
{
if ((iData1 != AF_INET) && (iData1 != AF_INET6))
{
return(-1);
}
pState->iFamily = iData1;
NetPrintf(("dirtynetwin: address family used for socket operations set to %s\n", pState->iFamily == AF_INET ? "AF_INET" : "AF_INET6"));
return(0);
}
// handle connect message
if (iOption == 'conn')
{
return(0);
}
// handle disconnect message
if (iOption == 'disc')
{
return(0);
}
// set an ipv6 address into the mapping table
if (iOption == '+ip6')
{
return(SocketAddrMapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1));
}
// del an ipv6 address from the mapping table
if (iOption == '-ip6')
{
return(SocketAddrUnmapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, iData1));
}
// remap an existing ipv6 address in the mapping table
if (iOption == '~ip6')
{
return(SocketAddrRemapAddress(&pState->AddrMap, (const struct sockaddr *)pData2, (const struct sockaddr *)pData3, iData1));
}
#if !defined(DIRTYCODE_XBOXONE)
// set local address? (used to select between multiple network interfaces)
if (iOption == 'ladr')
{
pState->uAdapterAddress = (unsigned)iData1;
return(0);
}
#endif
// set max udp packet size
if (iOption == 'maxp')
{
NetPrintf(("dirtynetwin: setting max udp packet size to %d\n", iData1));
pState->iMaxPacket = iData1;
return(0);
}
// block waiting on input from socket list in milliseconds
if (iOption == 'poll')
{
return(_SocketPoll(pState, (unsigned)iData1*1000, pData2));
}
if (iOption == 'poln')
{
return(_SocketPoll(pState, (unsigned)iData1, pData2));
}
// set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref)
if (iOption == 'sdcb')
{
SocketSendCallbackEntryT sendCbEntry;
sendCbEntry.pSendCallback = (SocketSendCallbackT *)pData2;
sendCbEntry.pSendCallref = pData3;
if (iData1)
{
return(SocketSendCallbackAdd(&pState->aSendCbEntries[0], &sendCbEntry));
}
else
{
return(SocketSendCallbackRem(&pState->aSendCbEntries[0], &sendCbEntry));
}
}
// set debug level
if (iOption == 'spam')
{
// set module level debug level
pState->iVerbose = iData1;
return(0);
}
// mark a port as virtual
if (iOption == 'vadd')
{
int32_t iPort;
// find a slot to add virtual port
for (iPort = 0; pState->aVirtualPorts[iPort] != 0; iPort++)
;
if (iPort < SOCKET_MAXVIRTUALPORTS)
{
NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: added port %d to virtual port list\n", iData1));
pState->aVirtualPorts[iPort] = (uint16_t)iData1;
return(0);
}
}
// remove port from virtual port list
if (iOption == 'vdel')
{
int32_t iPort;
// find virtual port in list
for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != (uint16_t)iData1); iPort++)
;
if (iPort < SOCKET_MAXVIRTUALPORTS)
{
NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: removed port %d from virtual port list\n", iData1));
pState->aVirtualPorts[iPort] = 0;
return(0);
}
}
// unhandled
NetPrintf(("dirtynetwin: unhandled global SocketControl() option '%C'\n", iOption));
return(-1);
}
/*** Public Functions ******************************************************************/
/*F*************************************************************************************/
/*!
\Function SocketCreate
\Description
Create new instance of socket interface module. Initializes all global
resources and makes module ready for use.
\Input iThreadPrio - priority to start threads with
\Input iThreadStackSize - stack size to start threads with (in bytes)
\Input iThreadCpuAffinity - cpu affinity to start threads with
\Output
int32_t - negative=error, zero=success
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketCreate(int32_t iThreadPrio, int32_t iThreadStackSize, int32_t iThreadCpuAffinity)
{
SocketStateT *pState = _Socket_pState;
WSADATA WSAData;
int32_t iResult;
int32_t iMemGroup;
void *pMemGroupUserData;
DirtyThreadConfigT ThreadConfig;
// Query current mem group data
DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// error if already started
if (pState != NULL)
{
NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: SocketCreate() called while module is already active\n"));
return(-1);
}
// print version info
NetPrintf(("dirtynetwin: DirtySDK v%d.%d.%d.%d.%d\n", DIRTYSDK_VERSION_YEAR, DIRTYSDK_VERSION_SEASON, DIRTYSDK_VERSION_MAJOR, DIRTYSDK_VERSION_MINOR, DIRTYSDK_VERSION_PATCH));
// alloc and init state ref
if ((pState = (SocketStateT *)DirtyMemAlloc(sizeof(*pState), SOCKET_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
{
NetPrintf(("dirtynetwin: unable to allocate module state\n"));
return(-2);
}
ds_memclr(pState, sizeof(*pState));
pState->iMemGroup = iMemGroup;
pState->pMemGroupUserData = pMemGroupUserData;
pState->iMaxPacket = SOCKET_MAXUDPRECV;
pState->iFamily = AF_INET6;
pState->iVerbose = 1;
// save global module ref
_Socket_pState = pState;
// startup network libs
NetLibCreate(iThreadPrio, iThreadStackSize, iThreadCpuAffinity);
// start winsock
ds_memclr(&WSAData, sizeof(WSAData));
iResult = WSAStartup(MAKEWORD(2,2), &WSAData);
if (iResult != 0)
{
NetPrintf(("dirtynetwin: error %d loading winsock library\n", iResult));
SocketDestroy((uint32_t)(-1));
return(-3);
}
// save the available version
pState->iVersion = (LOBYTE(WSAData.wVersion)<<8)|(HIBYTE(WSAData.wVersion)<<0);
// create hostname cache
if ((pState->pHostnameCache = SocketHostnameCacheCreate(iMemGroup, pMemGroupUserData)) == NULL)
{
NetPrintf(("dirtynetwin: unable to create hostname cache\n"));
SocketDestroy((uint32_t)(-1));
return(-4);
}
// add our idle handler
NetIdleAdd(&_SocketIdle, pState);
// create a global event to notify recv thread of newly available sockets
pState->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (pState->hEvent == NULL)
{
NetPrintf(("dirtynetwin: error %d creating state event\n", GetLastError()));
SocketDestroy(0);
return(-5);
}
// initialize event critical section
NetCritInit(&pState->EventCrit, "recv-event-crit");
/* if recvthread has been created but has no chance to run (upon failure or shutdown
immediately), there will be a problem. by setting iRecvLife to non-zero before
thread creation we ensure SocketDestroy work properly. */
pState->iRecvLife = 1; // see _SocketRecvThread for more details
// configure thread parameters
ds_memclr(&ThreadConfig, sizeof(ThreadConfig));
ThreadConfig.pName = "SocketRecv";
ThreadConfig.iPriority = iThreadPrio;
ThreadConfig.iAffinity = iThreadCpuAffinity;
ThreadConfig.iVerbosity = pState->iVerbose;
// start up socket receive thread
if ((iResult = DirtyThreadCreate(_SocketRecvThread, NULL, &ThreadConfig)) != 0)
{
pState->iRecvLife = 0; // no recvthread was created, reset to 0
NetPrintf(("dirtynetwin: error %d creating socket receive thread\n", iResult));
SocketDestroy(0);
return(-6);
}
// init socket address map
SocketAddrMapInit(&pState->AddrMap, pState->iMemGroup, pState->pMemGroupUserData);
// return success
return(0);
}
/*F*************************************************************************************/
/*!
\Function SocketDestroy
\Description
Release resources and destroy module.
\Input uShutdownFlags - shutdown flags
\Output
int32_t - negative=error, zero=success
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketDestroy(uint32_t uShutdownFlags)
{
SocketStateT *pState = _Socket_pState;
// error if not active
if (pState == NULL)
{
NetPrintf(("dirtynetwin: SocketDestroy() called while module is not active\n"));
return(-1);
}
NetPrintf(("dirtynetwin: shutting down\n"));
// $$TODO- why don't we do this on XONE too?
#if !defined(DIRTYCODE_XBOXONE)
// wait until all lookup threads are done
while (pState->pHostList != NULL)
{
volatile HostentT **ppHost;
int32_t iSocketLookups;
// check for lookup threads that are still active
for (ppHost = &pState->pHostList, iSocketLookups = 0; *ppHost != NULL; ppHost = (volatile HostentT **)&(*ppHost)->pNext)
{
iSocketLookups += (*ppHost)->thread ? 0 : 1;
}
// if no ongoing socket lookups, we're done
if (iSocketLookups == 0)
{
break;
}
Sleep(1);
}
#endif
// kill idle callbacks
NetIdleDel(&_SocketIdle, pState);
// let any idle event finish
NetIdleDone();
// tell receive thread to quit and wake it up (if running)
if (pState->iRecvLife == 1)
{
pState->iRecvLife = 2;
if (pState->hEvent != NULL)
{
WSASetEvent(pState->hEvent);
}
// wait for thread to terminate
while (pState->iRecvLife > 0)
{
Sleep(1);
}
}
// cleanup addr map, if allocated
SocketAddrMapShutdown(&pState->AddrMap);
// close all sockets
NetCritEnter(NULL);
while (pState->pSockList != NULL)
{
SocketClose(pState->pSockList);
}
NetCritLeave(NULL);
// clear the kill list
_SocketIdle(pState);
// destroy hostname cache
if (pState->pHostnameCache != NULL)
{
SocketHostnameCacheDestroy(pState->pHostnameCache);
}
// we rely on the Handle having been created to know if the critical section was created.
if (pState->hEvent != NULL)
{
// destroy event critical section
NetCritKill(&pState->EventCrit);
// delete global event
CloseHandle(pState->hEvent);
}
// free the memory and clear global module ref
_Socket_pState = NULL;
DirtyMemFree(pState, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
// shut down network libs
NetLibDestroy(0);
// shutdown winsock, unless told otherwise
if (uShutdownFlags == 0)
{
WSACleanup();
}
NetPrintf(("dirtynetwin: shutdown complete\n"));
return(0);
}
/*F*************************************************************************************/
/*!
\Function SocketOpen
\Description
Create a new transfer endpoint. A socket endpoint is required for any
data transfer operation.
\Input iFamily - address family (AF_INET) [IGNORED]
\Input iType - socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...)
\Input iProto - protocol type for SOCK_RAW (unused by others)
\Output
SocketT - socket reference
\Notes
The address family specified here is ignored; instead it is dictated based on
the internal configuration setting which defaults to AF_INET6, but can be
overridden using the 'fam' SocketControl() selector.
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
SocketT *SocketOpen(int32_t iFamily, int32_t iType, int32_t iProto)
{
return(_SocketOpen(INVALID_SOCKET, _Socket_pState->iFamily, iType, iProto));
}
/*F*************************************************************************************/
/*!
\Function SocketClose
\Description
Close a socket. Performs a graceful shutdown of connection oriented protocols.
\Input *pSocket - socket reference
\Output
int32_t - zero
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketClose(SocketT *pSocket)
{
return(_SocketClose(pSocket, TRUE));
}
/*F*************************************************************************************/
/*!
\Function SocketImport
\Description
Import a socket. The given socket ref may be a SocketT, in which case a
SocketT pointer to the ref is returned, or it can be an actual Sony socket ref,
in which case a SocketT is created for the Sony socket ref.
\Input uSockRef - socket reference
\Output
SocketT * - pointer to imported socket, or NULL
\Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
SocketT *SocketImport(intptr_t uSockRef)
{
SocketStateT *pState = _Socket_pState;
int32_t iProto, iProtoSize;
SocketT *pSocket;
// see if this socket is already in our socket list
NetCritEnter(NULL);
for (pSocket = pState->pSockList; pSocket != NULL; pSocket = pSocket->pNext)
{
if (pSocket == (SocketT *)uSockRef)
{
break;
}
}
NetCritLeave(NULL);
// if socket is in socket list, just return it
if (pSocket != NULL)
{
return(pSocket);
}
//$$ TODO - this assumes AF_INET
// get info from socket ref
iProtoSize = sizeof(iProto);
if (getsockopt((SOCKET)uSockRef, 0, SO_TYPE, (char *)&iProto, &iProtoSize) != SOCKET_ERROR)
{
// create the socket (note: winsock socket types directly map to dirtysock socket types)
pSocket = _SocketOpen(uSockRef, pSocket->iFamily, iProto, 0);
// update local and remote addresses
SocketInfo(pSocket, 'bind', 0, &pSocket->LocalAddr, sizeof(pSocket->LocalAddr));
SocketInfo(pSocket, 'peer', 0, &pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr));
// mark it as imported
pSocket->uImported = 1;
}
return(pSocket);
}
/*F*************************************************************************************/
/*!
\Function SocketRelease
\Description
Release an imported socket.
\Input *pSocket - pointer to socket
\Version 01/14/2005 (jbrookes)
*/
/************************************************************************************F*/
void SocketRelease(SocketT *pSocket)
{
// if it wasn't imported, nothing to do
if (!pSocket->uImported)
{
return;
}
// dispose of SocketT, but leave the sockref alone
_SocketClose(pSocket, FALSE);
}
/*F*************************************************************************************/
/*!
\Function SocketShutdown
\Description
Perform partial/complete shutdown of socket indicating that either sending
and/or receiving is complete.
\Input *pSocket - socket reference
\Input iHow - SOCK_NOSEND and/or SOCK_NORECV
\Output
int32_t - zero
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketShutdown(SocketT *pSocket, int32_t iHow)
{
// no shutdown for an invalid socket
if (pSocket->uSocket == INVALID_SOCKET)
{
return(0);
}
// translate how
if (iHow == SOCK_NOSEND)
{
iHow = SD_SEND;
}
else if (iHow == SOCK_NORECV)
{
iHow = SD_RECEIVE;
}
else if (iHow == (SOCK_NOSEND|SOCK_NORECV))
{
iHow = SD_BOTH;
}
pSocket->uShutdown |= iHow;
shutdown(pSocket->uSocket, iHow);
return(0);
}
/*F*************************************************************************************/
/*!
\Function SocketBind
\Description
Bind a local address/port to a socket.
\Input *pSocket - socket reference
\Input *pName - local address/port
\Input iNameLen - length of name
\Output
int32_t - standard network error code (SOCKERR_xxx)
\Notes
If either address or port is zero, then they are filled in automatically.
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketBind(SocketT *pSocket, const struct sockaddr *pName, int32_t iNameLen)
{
SocketStateT *pState = _Socket_pState;
struct sockaddr_in6 SockAddr6;
int32_t iResult;
#if !defined(DIRTYCODE_XBOXONE)
struct sockaddr BindAddr;
// bind to specific address?
if ((SockaddrInGetAddr(pName) == 0) && (pState->uAdapterAddress != 0))
{
ds_memcpy_s(&BindAddr, sizeof(BindAddr), pName, sizeof(*pName));
SockaddrInSetAddr(&BindAddr, pState->uAdapterAddress);
pName = &BindAddr;
}
#endif
// is the bind port a virtual port?
if (pSocket->iType == SOCK_DGRAM)
{
int32_t iPort;
uint16_t uPort;
if ((uPort = SockaddrInGetPort(pName)) != 0)
{
// find virtual port in list
for (iPort = 0; (iPort < SOCKET_MAXVIRTUALPORTS) && (pState->aVirtualPorts[iPort] != uPort); iPort++)
;
if (iPort < SOCKET_MAXVIRTUALPORTS)
{
// acquire socket critical section
NetCritEnter(&pSocket->RecvCrit);
// check to see if the socket is bound
if (pSocket->bVirtual && (pSocket->uVirtualPort != 0))
{
NetPrintf(("dirtynetwin: [%p] failed to bind socket to %u which was already bound to port %u virtual\n", pSocket, uPort, pSocket->uVirtualPort));
NetCritLeave(&pSocket->RecvCrit);
return(pSocket->iLastError = SOCKERR_INVALID);
}
// close winsock socket
NetPrintf(("dirtynetwin: [%p] making socket bound to port %d virtual\n", pSocket, uPort));
if (pSocket->uSocket != INVALID_SOCKET)
{
shutdown(pSocket->uSocket, SOCK_NOSEND);
closesocket(pSocket->uSocket);
pSocket->uSocket = INVALID_SOCKET;
}
/* increase socket queue size; this protects virtual sockets from having data pushed into
them and overwriting previous data that hasn't been read yet */
pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, 4, pState->iMemGroup, pState->pMemGroupUserData);
// mark socket as virtual
pSocket->uVirtualPort = uPort;
pSocket->bVirtual = TRUE;
// release socket critical section
NetCritLeave(&pSocket->RecvCrit);
return(0);
}
}
}
#if defined(DIRTYCODE_XBOXONE)
// set up IPv6 address (do not use an IPv4-mapped IPv6 here as it will disallow IPv6 address use)
ds_memclr(&SockAddr6, sizeof(SockAddr6));
SockAddr6.sin6_family = AF_INET6;
SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName));
pName = (const struct sockaddr *)&SockAddr6;
iNameLen = sizeof(SockAddr6);
#else
// translate IPv4 -> IPv6 address (if needed)
if ((pSocket->iFamily == AF_INET6) && (pName->sa_family != AF_INET6))
{
ds_memclr(&SockAddr6, sizeof(SockAddr6));
SockAddr6.sin6_family = AF_INET6;
SockAddr6.sin6_port = SocketNtohs(SockaddrInGetPort(pName));
pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, pName, &iNameLen);
}
#endif
// execute the bind
iResult = _XlatError(bind(pSocket->uSocket, pName, iNameLen));
// notify read thread that socket is ready to be read from
if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv)
{
WSASetEvent(pState->hEvent);
}
// return result to caller
pSocket->iLastError = iResult;
return(pSocket->iLastError);
}
/*F*************************************************************************************/
/*!
\Function SocketConnect
\Description
Initiate a connection attempt to a remote host.
\Input *pSocket - socket reference
\Input *pName - pointer to name of socket to connect to
\Input iNameLen - length of name
\Output
int32_t - standard network error code (SOCKERR_xxx)
\Notes
Only has real meaning for stream protocols. For a datagram protocol, this
just sets the default remote host.
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketConnect(SocketT *pSocket, struct sockaddr *pName, int32_t iNameLen)
{
struct sockaddr_in6 SockAddr6;
struct sockaddr SockAddr, *pSockAddr = NULL;
int32_t iResult, iSockAddrLen = 0;
// mark as not open
pSocket->iOpened = 0;
// save connect address
ds_memcpy_s(&pSocket->RemoteAddr, sizeof(pSocket->RemoteAddr), pName, sizeof(*pName));
// init sockaddr
if (pSocket->iFamily == AF_INET)
{
SockaddrInit(&SockAddr, AF_INET);
pSockAddr = &SockAddr;
iSockAddrLen = sizeof(SockAddr);
}
else if (pSocket->iFamily == AF_INET6)
{
// initialize family of SockAddr6
SockaddrInit6(&SockAddr6, AF_INET6);
pSockAddr = (struct sockaddr *)&SockAddr6;
iSockAddrLen = sizeof(SockAddr6);
}
#if !defined(DIRTYCODE_XBOXONE)
/* execute an explicit bind - this allows us to specify a non-zero local address
or port (see SocketBind()). a SOCKERR_INVALID result here means the socket has
already been bound, so we ignore that particular error */
if (((iResult = SocketBind(pSocket, pSockAddr, iSockAddrLen)) < 0) && (iResult != SOCKERR_INVALID))
{
pSocket->iLastError = iResult;
return(pSocket->iLastError);
}
#endif
// translate to IPv6 if required
if (pSocket->iFamily == AF_INET6)
{
pName = SocketAddrMapTranslate(&_Socket_pState->AddrMap, pSockAddr, pName, &iNameLen);
}
// execute the connect
NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connecting to %A\n", pName));
iResult = _XlatError(connect(pSocket->uSocket, pName, iNameLen));
// notify read thread that socket is ready to be read from
if ((iResult == SOCKERR_NONE) && pSocket->bAsyncRecv)
{
WSASetEvent(_Socket_pState->hEvent);
}
// return result to caller
pSocket->iLastError = iResult;
return(pSocket->iLastError);
}
/*F*************************************************************************************/
/*!
\Function SocketListen
\Description
Start listening for an incoming connection on the socket. The socket must already
be bound and a stream oriented connection must be in use.
\Input *pSocket - socket reference to bound socket (see SocketBind())
\Input iBackLog - number of pending connections allowed
\Output
int32_t - standard network error code (SOCKERR_xxx)
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketListen(SocketT *pSocket, int32_t iBackLog)
{
// do the listen
pSocket->iLastError = _XlatError(listen(pSocket->uSocket, iBackLog));
return(pSocket->iLastError);
}
/*F*************************************************************************************/
/*!
\Function SocketAccept
\Description
Accept an incoming connection attempt on a socket.
\Input *pSocket - socket reference to socket in listening state (see SocketListen())
\Input *pAddr - pointer to storage for address of the connecting entity, or NULL
\Input *pAddrLen - pointer to storage for length of address, or NULL
\Output
SocketT * - the accepted socket, or NULL if not available
\Notes
The integer pointed to by addrlen should on input contain the number of characters
in the buffer addr. On exit it will contain the number of characters in the
output address.
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
SocketT *SocketAccept(SocketT *pSocket, struct sockaddr *pAddr, int32_t *pAddrLen)
{
SocketT *pOpen = NULL;
SOCKET iIncoming;
pSocket->iLastError = SOCKERR_INVALID;
// see if already connected
if (pSocket->uSocket == INVALID_SOCKET)
{
return(NULL);
}
// make sure turn parm is valid
if ((pAddr != NULL) && (*pAddrLen < sizeof(struct sockaddr)))
{
return(NULL);
}
#if !defined(DIRTYCODE_XBOXONE)
// perform inet accept
if (pSocket->iFamily == AF_INET)
{
iIncoming = accept(pSocket->uSocket, pAddr, pAddrLen);
if (iIncoming != INVALID_SOCKET)
{
pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto);
pSocket->iLastError = SOCKERR_NONE;
}
else
{
// use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result
pSocket->iLastError = _XlatError(-99);
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError()));
}
}
}
#endif
// perform inet6 accept
if (pSocket->iFamily == AF_INET6)
{
struct sockaddr_in6 SockAddr6;
int32_t iAddrLen;
SockaddrInit6(&SockAddr6, AF_INET6);
iAddrLen = sizeof(SockAddr6);
iIncoming = accept(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iAddrLen);
if (iIncoming != INVALID_SOCKET)
{
pOpen = _SocketOpen(iIncoming, pSocket->iFamily, pSocket->iType, pSocket->iProto);
pSocket->iLastError = SOCKERR_NONE;
// translate ipv6 to ipv4 virtual address
SocketAddrMapAddress(&_Socket_pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6));
// save translated connecting info for caller
SockaddrInit(pAddr, AF_INET);
SocketAddrMapTranslate(&_Socket_pState->AddrMap, pAddr, (struct sockaddr *) &SockAddr6, pAddrLen);
}
else
{
// use a negative error code to force _XlatError to internally call WSAGetLastError() and translate the obtained result
pSocket->iLastError = _XlatError(-99);
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
NetPrintf(("dirtynetwin: accept() failed err=%d\n", WSAGetLastError()));
}
}
}
// return the socket
return(pOpen);
}
/*F*************************************************************************************/
/*!
\Function SocketSendto
\Description
Send data to a remote host. The destination address is supplied along with
the data. Should only be used with datagram sockets as stream sockets always
send to the connected peer.
\Input *pSocket - socket reference
\Input *pBuf - the data to be sent
\Input iLen - size of data
\Input iFlags - unused
\Input *pTo - the address to send to (NULL=use connection address)
\Input iToLen - length of address
\Output
int32_t - standard network error code (SOCKERR_xxx)
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketSendto(SocketT *pSocket, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen)
{
SocketStateT *pState = _Socket_pState;
int32_t iResult;
if (pSocket->bSendCbs)
{
// if installed, give socket callback right of first refusal
if ((iResult = SocketSendCallbackInvoke(&pState->aSendCbEntries[0], pSocket, pSocket->iType, pBuf, iLen, pTo)) > 0)
{
return(iResult);
}
}
// make sure socket ref is valid
if (pSocket->uSocket == INVALID_SOCKET)
{
#if DIRTYCODE_LOGGING
uint32_t uAddr = 0, uPort = 0;
if (pTo)
{
uAddr = SockaddrInGetAddr(pTo);
uPort = SockaddrInGetPort(pTo);
}
NetPrintf(("dirtynetwin: attempting to send to %a:%d on invalid socket\n", uAddr, uPort));
#endif
pSocket->iLastError = SOCKERR_INVALID;
return(pSocket->iLastError);
}
// handle optional data rate throttling
if ((iLen = SocketRateThrottle(&pSocket->SendRate, pSocket->iType, iLen, "send")) == 0)
{
return(0);
}
// use appropriate version
if (pTo == NULL)
{
iResult = send(pSocket->uSocket, pBuf, iLen, 0);
pTo = &pSocket->RemoteAddr;
}
else
{
struct sockaddr_in6 SockAddr6;
if (pSocket->iFamily == AF_INET6)
{
SockaddrInit6(&SockAddr6, AF_INET6);
iToLen = sizeof(SockAddr6);
SocketAddrMapTranslate(&pState->AddrMap, (struct sockaddr *)&SockAddr6, pTo, &iToLen);
pTo = (struct sockaddr *)&SockAddr6;
}
iResult = sendto(pSocket->uSocket, pBuf, iLen, 0, pTo, iToLen);
}
// update data rate estimation
SocketRateUpdate(&pSocket->SendRate, iResult, "send");
// return bytes sent
pSocket->iLastError = _XlatError(iResult);
return(pSocket->iLastError);
}
/*F*************************************************************************************/
/*!
\Function SocketRecvfrom
\Description
Receive data from a remote host. If socket is a connected stream, then data can
only come from that source. A datagram socket can receive from any remote host.
\Input *pSocket - socket reference
\Input *pBuf - buffer to receive data
\Input iLen - length of recv buffer
\Input iFlags - unused
\Input *pFrom - address data was received from (NULL=ignore)
\Input *pFromLen - length of address
\Output
int32_t - positive=data bytes received, else standard error code
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketRecvfrom(SocketT *pSocket, char *pBuf, int32_t iLen, int32_t iFlags, struct sockaddr *pFrom, int32_t *pFromLen)
{
int32_t iRecv = 0, iRecvErr = 0;
// handle rate throttling, if enabled
if ((iLen = SocketRateThrottle(&pSocket->RecvRate, pSocket->iType, iLen, "recv")) == 0)
{
return(0);
}
// handle if the socket was killed
if (pSocket->pKill != NULL)
{
pSocket->iLastError = SOCKERR_INVALID;
return(pSocket->iLastError);
}
// sockets marked for async recv had actual receive operation take place in the thread
if (pSocket->bAsyncRecv)
{
uint32_t bSocketPacketQueueFull;
// acquire socket receive critical section
NetCritEnter(&pSocket->RecvCrit);
// remember if packet queue was full prior to calling _SocketRecvfromPacketQueue()
bSocketPacketQueueFull = SocketPacketQueueStatus(pSocket->pRecvQueue, 'pful');
/* given the socket could be either a TCP or UDP socket we handle the no data condition the same.
this is due to the below, when we do error conversion we override the system error with EWOULDBLOCK because
with TCP zero would be mean closed. if we are doing direct recv calls then the translation will
convert based on the errno returned after the call */
if ((iRecv = _SocketRecvfromPacketQueue(pSocket, pBuf, iLen, pFrom, pFromLen)) == 0)
{
iRecv = -1;
iRecvErr = WSAEWOULDBLOCK;
}
// when data is obtained from the packet queue, we lose visibility on system socket errors
pSocket->iLastError = SOCKERR_NONE;
// release socket receive critical section
NetCritLeave(&pSocket->RecvCrit);
/* If packet queue had reached "full" state, then _SocketRecvThread had not been
adding that socket to the event list anymore. Consequently, if _SocketRecvThread
is currently blocked on WSAWaitForMultipleEvents(), it may not wake up if there is
inbound traffic on this socket. To work around this, we wake it up explicitly here. */
if (bSocketPacketQueueFull)
{
WSASetEvent(_Socket_pState->hEvent);
}
}
else
{
iRecv = _SocketRecvfrom(pSocket, pBuf, iLen, pFrom, pFromLen, &iRecvErr);
}
// do error conversion
iRecv = (iRecv == 0) ? SOCKERR_CLOSED : _XlatError0(iRecv, iRecvErr);
// update data rate estimation
SocketRateUpdate(&pSocket->RecvRate, pSocket->iLastError, "recv");
// return the error code
pSocket->iLastError = iRecv;
return(pSocket->iLastError);
}
/*F*************************************************************************************/
/*!
\Function SocketInfo
\Description
Return information about an existing socket.
\Input *pSocket - socket reference
\Input iInfo - selector for desired information
\Input iData - selector specific
\Input *pBuf - [out] return buffer
\Input iLen - buffer length
\Output
int32_t - size of returned data or error code (negative value)
\Notes
iInfo can be one of the following:
\verbatim
'addr' - return local address
'conn' - who we are connecting to
'bind' - return bind data (if pSocket == NULL, get socket bound to given port)
'bndu' - return bind data (only with pSocket=NULL, get SOCK_DGRAM socket bound to given port)
'?ip6' - return TRUE if ipv4 address specified in iData is virtual, and fill in pBuf with ipv6 address if not NULL
'ladr' - get local address previously specified by user for subsequent SocketBind() operations (pc only)
'maxp' - return configured max packet size
'maxr' - return configured max recv rate (bytes/sec; zero=uncapped)
'maxs' - return configured max send rate (bytes/sec; zero=uncapped)
'pdrp' - return socket packet queue number of packets dropped
'peer' - peer info (only valid if connected)
'pmax' - return socket packet queue max depth
'pnum' - return socket packet queue current depth
'ratr' - return current recv rate estimation (bytes/sec)
'rats' - return current send rate estimation (bytes/sec)
'sdcf' - get installed send callback function pointer (iData specifies index in array)
'sdcu' - get installed send callback userdata pointer (iData specifies index in array)
'serr' - last socket error
'psiz' - return socket packet queue max size
'sock' - return windows socket associated with the specified DirtySock socket
'spam' - return debug level for debug output
'stat' - TRUE if connected, else FALSE
'virt' - TRUE if socket is virtual, else FALSE
\endverbatim
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketInfo(SocketT *pSocket, int32_t iInfo, int32_t iData, void *pBuf, int32_t iLen)
{
SocketStateT *pState = _Socket_pState;
// always zero results by default
#if defined(DIRTYCODE_XBOXONE)
if (pBuf != NULL)
#else
if ((pBuf != NULL) && (iInfo != 'ladr'))
#endif
{
ds_memclr(pBuf, iLen);
}
// handle global socket options
if (pSocket == NULL)
{
return(_SocketInfoGlobal(iInfo, iData, pBuf, iLen));
}
// return local bind data
if (iInfo == 'bind')
{
int32_t iResult = -1;
if (pSocket->bVirtual)
{
SockaddrInit((struct sockaddr *)pBuf, AF_INET);
SockaddrInSetPort((struct sockaddr *)pBuf, pSocket->uVirtualPort);
iResult = 0;
}
else if (pSocket->uSocket != INVALID_SOCKET)
{
struct sockaddr_in6 SockAddr6;
iLen = sizeof(SockAddr6);
if ((iResult = getsockname(pSocket->uSocket, (struct sockaddr *)&SockAddr6, &iLen)) == 0)
{
SockaddrInit((struct sockaddr *)pBuf, AF_INET);
SockaddrInSetPort((struct sockaddr *)pBuf, SocketHtons(SockAddr6.sin6_port));
SockaddrInSetAddr((struct sockaddr *)pBuf, SocketAddrMapAddress(&pState->AddrMap, (struct sockaddr *)&SockAddr6, sizeof(SockAddr6)));
}
iResult = _XlatError(iResult);
}
return(iResult);
}
// return configured max recv rate
if (iInfo == 'maxr')
{
return(pSocket->RecvRate.uMaxRate);
}
// return configured max send rate
if (iInfo == 'maxs')
{
return(pSocket->SendRate.uMaxRate);
}
// return socket protocol
if (iInfo == 'prot')
{
return(pSocket->iProto);
}
// return whether the socket is virtual or not
if (iInfo == 'virt')
{
return(pSocket->bVirtual);
}
/*
make sure the socket is alive
** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID **
*/
if (pSocket->pKill != NULL)
{
pSocket->iLastError = SOCKERR_INVALID;
return(pSocket->iLastError);
}
// return peer info (only valid if connected)
if ((iInfo == 'conn') || (iInfo == 'peer'))
{
getpeername(pSocket->uSocket, (struct sockaddr *)pBuf, &iLen);
//$$TODO this needs to be IPv6, translate to virtual address for output
return(0);
}
// get packet queue info
if ((iInfo == 'pdrp') || (iInfo == 'pmax') || (iInfo == 'psiz'))
{
int32_t iResult;
// acquire socket receive critical section
NetCritEnter(&pSocket->RecvCrit);
// get packet queue status
iResult = SocketPacketQueueStatus(pSocket->pRecvQueue, iInfo);
// release socket receive critical section
NetCritLeave(&pSocket->RecvCrit);
return(iResult);
}
// return current recv rate estimation
if (iInfo == 'ratr')
{
return(pSocket->RecvRate.uCurRate);
}
// return current send rate estimation
if (iInfo == 'rats')
{
return(pSocket->SendRate.uCurRate);
}
// return last socket error
if (iInfo == 'serr')
{
return(pSocket->iLastError);
}
// return windows socket identifier
if (iInfo == 'sock')
{
if (pBuf != NULL)
{
if (iLen == (int32_t)sizeof(pSocket->uSocket))
{
ds_memcpy(pBuf, &pSocket->uSocket, sizeof(pSocket->uSocket));
}
}
return((int32_t)pSocket->uSocket);
}
// return socket status
if (iInfo == 'stat')
{
fd_set FdRead, FdWrite, FdExcept;
struct timeval TimeVal;
// if not a connected socket, return TRUE
if (pSocket->iType != SOCK_STREAM)
{
return(1);
}
// if not connected, use select to determine connect
if (pSocket->iOpened == 0)
{
// setup write/exception lists so we can select against the socket
FD_ZERO(&FdWrite);
FD_ZERO(&FdExcept);
FD_SET(pSocket->uSocket, &FdWrite);
FD_SET(pSocket->uSocket, &FdExcept);
TimeVal.tv_sec = TimeVal.tv_usec = 0;
if (select(pSocket->uSocket + 1, NULL, &FdWrite, &FdExcept, &TimeVal) > 0)
{
// if we got an exception, that means connect failed
if (FdExcept.fd_count > 0)
{
NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failed\n"));
pSocket->iOpened = -1;
}
// if socket is writable, that means connect succeeded
else if (FdWrite.fd_count > 0)
{
NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection open\n"));
pSocket->iOpened = 1;
}
}
}
/* if previously connected, make sure connect still valid. we only do this when not doing async receive for two
reasons
1. there is a race condition between the poll waking up and querying the bytes available. if the bytes are
read on the receive thread between the poll and ioctl then it would think the socket is closed because the
socket has already been drained
2. our reasoning behind using the async receive thread could be the cost of recv, plus other calls may be
expensive as well. the async receive thread will already set the correct state on the socket thus we can
skip the query and return iOpened back to the user */
if (!pSocket->bAsyncRecv && (pSocket->iOpened > 0))
{
FD_ZERO(&FdRead);
FD_ZERO(&FdExcept);
FD_SET(pSocket->uSocket, &FdRead);
FD_SET(pSocket->uSocket, &FdExcept);
TimeVal.tv_sec = TimeVal.tv_usec = 0;
if (select(pSocket->uSocket + 1, &FdRead, NULL, &FdExcept, &TimeVal) > 0)
{
// if we got an exception, that means connect failed (usually closed by remote peer)
if (FdExcept.fd_count > 0)
{
NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection failure\n"));
pSocket->iOpened = -1;
}
else if (FdRead.fd_count > 0)
{
u_long uAvailBytes = 1; // u_long is required by ioctlsocket
// if socket is readable but there's no data available, connect was closed
// uAvailBytes might be less than actual bytes, so it can only be used for zero-test
if ((ioctlsocket(pSocket->uSocket, FIONREAD, &uAvailBytes) != 0) || (uAvailBytes == 0))
{
pSocket->iLastError = SOCKERR_CLOSED;
NetPrintfVerbose((pSocket->iVerbose, 0, "dirtynetwin: connection closed (wsaerr=%d)\n", (uAvailBytes==0) ? WSAECONNRESET : WSAGetLastError()));
pSocket->iOpened = -1;
}
}
}
}
/* if we still have packets in the queue, tell the caller that we are still open. this makes sure they read the
complete stream of data */
else if (pSocket->bAsyncRecv && (pSocket->iOpened < 0) && (SocketInfo(pSocket, 'pnum', 0, NULL, 0) != 0))
{
return(1);
}
// return connect status
return(pSocket->iOpened);
}
return(-1);
}
/*F*************************************************************************************/
/*!
\Function SocketCallback
\Description
Register a callback routine for notification of socket events. Also includes
timeout support.
\Input *pSocket - socket reference
\Input iMask - valid callback events (CALLB_NONE, CALLB_SEND, CALLB_RECV)
\Input iIdle - if nonzero, specifies the number of ticks between idle calls
\Input *pRef - user data to be passed to proc
\Input *pProc - user callback
\Output
int32_t - zero
\Notes
A callback will reset the idle timer, so when specifying a callback and an
idle processing time, the idle processing time represents the maximum elapsed
time between calls.
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
#pragma optimize("", off) // make sure this block of code is not reordered
int32_t SocketCallback(SocketT *pSocket, int32_t iMask, int32_t iIdle, void *pRef, int32_t (*pProc)(SocketT *pSocket, int32_t iFlags, void *pRef))
{
pSocket->uCallIdle = iIdle;
pSocket->uCallMask = iMask;
pSocket->pCallRef = pRef;
pSocket->pCallback = pProc;
return(0);
}
#pragma optimize("", on)
/*F*************************************************************************************/
/*!
\Function SocketControl
\Description
Process a control message (type specific operation)
\Input *pSocket - socket to control, or NULL for module-level option
\Input iOption - the option to pass
\Input iData1 - message specific parm
\Input *pData2 - message specific parm
\Input *pData3 - message specific parm
\Output
int32_t - message specific result (-1=unsupported message, -2=no such module)
\Notes
iOption can be one of the following:
\verbatim
'afam' - set internet address family to use for socket operations (defaults to AF_INET6, AF_INET is also supported))
'arcv' - set async receive enable/disable (default enabled for DGRAM/RAW, disabled for TCP)
'conn' - handle connect message
'disc' - handle disconnect message
'+ip6' - add an IPv6 address into the mapping table and return a virtual IPv4 address to reference it
'-ip6' - del an IPv6 address from the mapping table
'~ip6' - remap an existing IPv6 address in the mapping table
'keep' - set TCP keep-alive settings on PC (iData1=enable/disable, iData2=keep-alive time, iData3=keep-alive interval)
'ladr' - set local address for subsequent SocketBind() operations (pc only)
'maxp' - set max udp packet size
'maxr' - set max recv rate (bytes/sec; zero=uncapped)
'maxs' - set max send rate (bytes/sec; zero=uncapped)
'nbio' - set nonblocking/blocking mode (TCP only, iData1=TRUE (nonblocking) or FALSE (blocking))
'ndly' - set TCP_NODELAY state for given stream socket (iData1=zero or one)
'pdev' - set simulated packet deviation
'plat' - set simulated packet latency
'plos' - set simulated packet loss
'poll' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in milliseconds
'poln' - execute blocking wait on given socket list (pData2) or all sockets (pData2=NULL), 64 max sockets in microseconds
'pque' - set socket packet queue depth
'push' - push data into given socket (iData1=size, pData2=data ptr, pData3=sockaddr ptr)
'rbuf' - set socket recv buffer size
'sbuf' - set socket send buffer size
'scbk' - enable/disable "send callbacks usage" on specified socket (defaults to enable)
'sdcb' - set/unset send callback (iData1=TRUE for set - FALSE for unset, pData2=callback, pData3=callref)
'soli' - set SO_LINGER On the specified socket, iData1 is timeout in seconds
'spam' - set debug level for debug output
'vadd' - add a port to virtual port list
'vdel' - del a port from virtual port list
\endverbatim
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketControl(SocketT *pSocket, int32_t iOption, int32_t iData1, void *pData2, void *pData3)
{
SocketStateT *pState = _Socket_pState;
int32_t iResult;
// handle global control
if (pSocket == NULL)
{
return(_SocketControlGlobal(iOption, iData1, pData2, pData3));
}
// set async recv enable
if (iOption == 'arcv')
{
// set socket async recv flag
pSocket->bAsyncRecv = iData1 ? TRUE : FALSE;
// wake up recvthread to update socket polling
WSASetEvent(pState->hEvent);
return(0);
}
// set max recv rate
if (iOption == 'maxr')
{
NetPrintf(("dirtynetwin: setting max recv rate to %d bytes/sec\n", iData1));
pSocket->RecvRate.uMaxRate = iData1;
return(0);
}
// set max send rate
if (iOption == 'maxs')
{
NetPrintf(("dirtynetwin: setting max send rate to %d bytes/sec\n", iData1));
pSocket->SendRate.uMaxRate = iData1;
return(0);
}
// enable/disable "send callbacks usage" on specified socket (defaults to enable)
if (iOption == 'scbk')
{
if (pSocket->bSendCbs != (iData1?TRUE:FALSE))
{
NetPrintf(("dirtynetwin: send callbacks usage changed from %s to %s for socket ref %p\n", (pSocket->bSendCbs?"ON":"OFF"), (iData1?"ON":"OFF"), pSocket));
pSocket->bSendCbs = (iData1?TRUE:FALSE);
}
return(0);
}
// set debug level
if (iOption == 'spam')
{
// per-socket debug level
pSocket->iVerbose = iData1;
return(0);
}
/*
make sure the socket is alive
** AFTER THIS POINT WE ENSURE THE SOCKET DESCRIPTOR AND PACKET QUEUE ARE VALID **
*/
if (pSocket->pKill != NULL)
{
pSocket->iLastError = SOCKERR_INVALID;
return(pSocket->iLastError);
}
#if !defined(DIRTYCODE_XBOXONE)
// configure TCP keep-alive
if (iOption == 'keep')
{
struct tcp_keepalive TcpKeepAlive;
DWORD dwBytesReturned;
if (pSocket->iType != SOCK_STREAM)
{
NetPrintf(("dirtynetwin: [%p] 'keep' selector can only be used on a SOCK_STREAM socket\n", pSocket));
return(-1);
}
if (pSocket->uSocket == INVALID_SOCKET)
{
NetPrintf(("dirtynetwin: [%p] 'keep' selector used with an invalid socket\n", pSocket));
return(-1);
}
// initialize tcpkeep alive structure
ds_memclr(&TcpKeepAlive, sizeof(TcpKeepAlive));
TcpKeepAlive.onoff = (uint8_t)iData1; // on/off
if (TcpKeepAlive.onoff)
{
TcpKeepAlive.keepalivetime = *(uint32_t*)pData2; // timeout in ms
TcpKeepAlive.keepaliveinterval = *(uint32_t*)pData3; // interval in ms
}
else
{
TcpKeepAlive.keepalivetime = 0; // timeout in ms
TcpKeepAlive.keepaliveinterval = 0; // interval in ms
}
if ((iResult = WSAIoctl(pSocket->uSocket, SIO_KEEPALIVE_VALS, (void *)&TcpKeepAlive, sizeof(TcpKeepAlive), NULL, 0, &dwBytesReturned, NULL, NULL)) == 0)
{
pSocket->iLastError = SOCKERR_NONE;
NetPrintfVerbose((pSocket->iVerbose, 1, "dirtynetwin: [%p] successfully %s the TCP keep-alive (timeout=%dms, interval=%dms)\n", pSocket,
iData1?"enabled":"disabled", TcpKeepAlive.keepalivetime, TcpKeepAlive.keepaliveinterval));
}
else
{
pSocket->iLastError = _XlatError(iResult);
NetPrintf(("dirtynetwin: [%p] failed to %s TCP keep-alive for low-level socket (err = %d)\n", pSocket,
iData1?"enable":"disable", WSAGetLastError()));
}
return(pSocket->iLastError);
}
#endif
// if a stream socket, set nonblocking/blocking mode
if ((iOption == 'nbio') && (pSocket->iType == SOCK_STREAM))
{
uint32_t uNbio = (uint32_t)iData1;
iResult = ioctlsocket(pSocket->uSocket, FIONBIO, (u_long *)&uNbio);
pSocket->iLastError = _XlatError(iResult);
NetPrintf(("dirtynetwin: setting socket 0x%p to %s mode %s (LastError=%d).\n", pSocket, iData1 ? "nonblocking" : "blocking", iResult ? "failed" : "succeeded", pSocket->iLastError));
return(pSocket->iLastError);
}
// if a stream socket, set TCP_NODELAY state
if ((iOption == 'ndly') && (pSocket->iType == SOCK_STREAM))
{
iResult = setsockopt(pSocket->uSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&iData1, sizeof(iData1));
pSocket->iLastError = _XlatError(iResult);
return(pSocket->iLastError);
}
// set simulated packet loss or packet latency
if ((iOption == 'pdev') || (iOption == 'plat') || (iOption == 'plos'))
{
// acquire socket receive critical section
NetCritEnter(&pSocket->RecvCrit);
// forward selector to packet queue
iResult = SocketPacketQueueControl(pSocket->pRecvQueue, iOption, iData1);
// release socket receive critical section
NetCritLeave(&pSocket->RecvCrit);
return(iResult);
}
// change packet queue size
if (iOption == 'pque')
{
if (pSocket->bAsyncRecv)
{
pSocket->iPacketQueueResizePending = iData1;
}
else
{
// acquire socket receive critical section
NetCritEnter(&pSocket->RecvCrit);
// resize the queue
pSocket->pRecvQueue = SocketPacketQueueResize(pSocket->pRecvQueue, iData1, pState->iMemGroup, pState->pMemGroupUserData);
// release socket receive critical section
NetCritLeave(&pSocket->RecvCrit);
}
// return success
return(0);
}
// push data into receive buffer
if (iOption == 'push')
{
// acquire socket critical section
NetCritEnter(&pSocket->RecvCrit);
// don't allow data that is too large (for the buffer) to be pushed
if (iData1 > SOCKET_MAXUDPRECV)
{
NetPrintf(("dirtynetwin: request to push %d bytes of data discarded (max=%d)\n", iData1, SOCKET_MAXUDPRECV));
NetCritLeave(&pSocket->RecvCrit);
return(-1);
}
// add packet to queue
SocketPacketQueueAdd(pSocket->pRecvQueue, (uint8_t *)pData2, iData1, (struct sockaddr *)pData3);
// release socket critical section
NetCritLeave(&pSocket->RecvCrit);
// see if we should issue callback
if ((pSocket->pCallback != NULL) && (pSocket->uCallMask & CALLB_RECV))
{
pSocket->pCallback(pSocket, 0, pSocket->pCallRef);
}
return(0);
}
// set REUSEADDR
if (iOption == 'radr')
{
iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&iData1, sizeof(iData1));
pSocket->iLastError = _XlatError(iResult);
return(pSocket->iLastError);
}
// set socket receive buffer size
if ((iOption == 'rbuf') || (iOption == 'sbuf'))
{
int32_t iOldSize, iNewSize, iOptLen=4;
int32_t iSockOpt = (iOption == 'rbuf') ? SO_RCVBUF : SO_SNDBUF;
// get current buffer size
getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iOldSize, &iOptLen);
// set new size
iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (const char *)&iData1, sizeof(iData1));
pSocket->iLastError = _XlatError(iResult);
// get new size
getsockopt(pSocket->uSocket, SOL_SOCKET, iSockOpt, (char *)&iNewSize, &iOptLen);
NetPrintf(("dirtynetwin: setsockopt(%s) changed buffer size from %d to %d\n", (iOption == 'rbuf') ? "SO_RCVBUF" : "SO_SNDBUF",
iOldSize, iNewSize));
return(pSocket->iLastError);
}
// set SO_LINGER
if (iOption == 'soli')
{
struct linger lingerOptions;
lingerOptions.l_onoff = TRUE;
lingerOptions.l_linger = iData1;
iResult = setsockopt(pSocket->uSocket, SOL_SOCKET, SO_LINGER, (const char *)&lingerOptions, sizeof(lingerOptions));
pSocket->iLastError = _XlatError(iResult);
return(pSocket->iLastError);
}
// unhandled
return(-1);
}
/*F*************************************************************************************/
/*!
\Function SocketGetLocalAddr
\Description
Returns the "external" local address (ie, the address as a machine "out on
the Internet" would see as the local machine's address).
\Output
uint32_t - local address
\Version 07/28/2003 (jbrookes)
*/
/************************************************************************************F*/
uint32_t SocketGetLocalAddr(void)
{
#if defined(DIRTYCODE_XBOXONE)
SocketStateT *pState = _Socket_pState;
struct sockaddr InetAddr, HostAddr;
if ((pState->uLocalAddr == 0) || (pState->uLocalAddr == 0x7f000001))
{
// create a remote internet address
ds_memclr(&InetAddr, sizeof(InetAddr));
InetAddr.sa_family = AF_INET;
SockaddrInSetPort(&InetAddr, 79);
SockaddrInSetAddr(&InetAddr, 0x9f990000);
// ask socket to give us local address that can connect to it
ds_memclr(&HostAddr, sizeof(HostAddr));
SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr));
pState->uLocalAddr = SockaddrInGetAddr(&HostAddr);
}
return(pState->uLocalAddr);
#else
struct sockaddr InetAddr, HostAddr;
// create a remote internet address
ds_memclr(&InetAddr, sizeof(InetAddr));
InetAddr.sa_family = AF_INET;
SockaddrInSetPort(&InetAddr, 79);
SockaddrInSetAddr(&InetAddr, 0x9f990000);
// ask socket to give us local address that can connect to it
ds_memclr(&HostAddr, sizeof(HostAddr));
SocketHost(&HostAddr, sizeof(HostAddr), &InetAddr, sizeof(InetAddr));
return(SockaddrInGetAddr(&HostAddr));
#endif
}
/*F*************************************************************************************/
/*!
\Function _SocketLookupDone
\Description
Callback to determine if gethostbyname is complete.
\Input *pHost - pointer to host lookup record
\Output
int32_t - zero=in progess, neg=done w/error, pos=done w/success
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static int32_t _SocketLookupDone(HostentT *pHost)
{
// return current status
return(pHost->done);
}
/*F*************************************************************************************/
/*!
\Function _SocketLookupFree
\Description
Release resources used by gethostbyname.
\Input *pHost - pointer to host lookup record
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketLookupFree(HostentT *pHost)
{
// release resource
pHost->refcount -= 1;
}
/*F*************************************************************************************/
/*!
\Function _SocketLookupThread
\Description
Socket lookup thread
\Input *pUserData - pointer to host lookup record
\Output
int32_t - zero
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
static void _SocketLookupThread(void *pUserData)
{
SocketStateT *pState = _Socket_pState;
HostentT *pHost = (HostentT *)pUserData;
struct addrinfo Hints, *pList = NULL;
int32_t iResult;
// setup lookup hints
ds_memclr(&Hints, sizeof(Hints));
Hints.ai_family = AF_UNSPEC;
Hints.ai_socktype = SOCK_STREAM; // set specific socktype to limit to one result per type
Hints.ai_protocol = IPPROTO_TCP; // set specific proto to limit to one result per type
Hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
// perform the blocking dns query
if ((iResult = getaddrinfo(pHost->name, NULL, &Hints, &pList)) == 0)
{
struct addrinfo *pAddrInfo;
// first loop we look for IPv4 addresses (prefer IPv4 to IPv6)
for (pAddrInfo = pList; pAddrInfo != NULL; pAddrInfo = pAddrInfo->ai_next)
{
// verbose logging of address info if spam settings warrant
NetPrintfVerbose((pState->iVerbose, 1, "dirtynetwin: addr=%A\n", pAddrInfo->ai_addr));
NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_flags=0x%08x\n", pAddrInfo->ai_flags));
NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_family=%d\n", pAddrInfo->ai_family));
NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_socktype=%d\n", pAddrInfo->ai_socktype));
NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: ai_protocol=%d\n", pAddrInfo->ai_protocol));
NetPrintfVerbose((pState->iVerbose, 2, "dirtynetwin: name=%s\n", pAddrInfo->ai_canonname));
// extract first IPv4 address we come across
if ((pHost->addr == 0) && (pAddrInfo->ai_family == AF_INET))
{
pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen);
}
}
// if we haven't found one yet, look for any address, and pick the first one we come across
for (pAddrInfo = pList; (pAddrInfo != NULL) && (pHost->addr == 0); pAddrInfo = pAddrInfo->ai_next)
{
pHost->addr = SocketAddrMapAddress(&pState->AddrMap, pAddrInfo->ai_addr, (int32_t)pAddrInfo->ai_addrlen);
}
// print selected address
NetPrintfVerbose((pState->iVerbose, 0, "dirtynetwin: %s=%a\n", pHost->name, pHost->addr));
// mark success
pHost->done = 1;
// add hostname to cache
SocketHostnameCacheAdd(pState->pHostnameCache, pHost->name, pHost->addr, pState->iVerbose);
// release memory
freeaddrinfo(pList);
}
else
{
// unsuccessful
NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", pHost->name, iResult));
pHost->done = -1;
}
#if !defined(DIRTYCODE_XBOXONE)
// note thread completion
pHost->thread = 1;
#endif
// release thread-allocated refcount on hostname resource
pHost->refcount -= 1;
}
/*F*************************************************************************************/
/*!
\Function SocketLookup
\Description
Lookup a host by name and return the corresponding Internet address. Uses
a callback/polling system since the socket library does not allow blocking.
\Input *pText - pointer to null terminated address string
\Input iTimeout - number of milliseconds to wait for completion
\Output
HostentT * - hostent struct that includes callback vectors
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
HostentT *SocketLookup(const char *pText, int32_t iTimeout)
{
SocketStateT *pState = _Socket_pState;
int32_t iAddr, iResult;
HostentT *pHost, *pHostRef;
DirtyThreadConfigT ThreadConfig;
NetPrintf(("dirtynetwin: looking up address for host '%s'\n", pText));
// dont allow negative timeouts
if (iTimeout < 0)
{
return(NULL);
}
// create new structure
pHost = DirtyMemAlloc(sizeof(*pHost), SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
ds_memclr(pHost, sizeof(*pHost));
// setup callbacks
pHost->Done = &_SocketLookupDone;
pHost->Free = &_SocketLookupFree;
// copy over the target address
ds_strnzcpy(pHost->name, pText, sizeof(pHost->name));
// check for refcounted lookup
if ((pHostRef = SocketHostnameAddRef(&pState->pHostList, pHost, TRUE)) != NULL)
{
DirtyMemFree(pHost, SOCKET_MEMID, pState->iMemGroup, pState->pMemGroupUserData);
return(pHostRef);
}
// check for dot notation, then check hostname cache
if (((iAddr = SocketInTextGetAddr(pText)) != 0) || ((iAddr = SocketHostnameCacheGet(pState->pHostnameCache, pText, pState->iVerbose)) != 0))
{
// save the data
pHost->addr = iAddr;
pHost->done = 1;
// mark as "thread complete" so _SocketLookupFree will release memory
pHost->thread = 1;
// return completed record
return(pHost);
}
/* add an extra refcount for the thread; this ensures the host structure survives until the thread
is done with it. this must be done before thread creation. */
pHost->refcount += 1;
// configure dns thread parameters
ds_memclr(&ThreadConfig, sizeof(ThreadConfig));
ThreadConfig.pName = "SocketLookup";
ThreadConfig.iAffinity = NetConnStatus('affn', 0, NULL, 0);
ThreadConfig.iVerbosity = pState->iVerbose-1;
// create dns lookup thread
if ((iResult = DirtyThreadCreate(_SocketLookupThread, pHost, &ThreadConfig)) != 0)
{
NetPrintf(("dirtynetwin: unable to create socket lookup thread (err=%d)\n", iResult));
pHost->done = -1;
// remove refcount we just added
pHost->refcount -= 1;
}
// return the host reference
return(pHost);
}
/*F*************************************************************************************/
/*!
\Function SocketHost
\Description
Return the host address that would be used in order to communicate with the given
destination address.
\Input *pHost - local sockaddr struct
\Input iHostLen - length of structure (sizeof(host))
\Input *pDest - remote sockaddr struct
\Input iDestLen - length of structure (sizeof(dest))
\Output
int32_t - zero=success, negative=error
\Version 10/04/1999 (gschaefer)
*/
/************************************************************************************F*/
int32_t SocketHost(struct sockaddr *pHost, int32_t iHostLen, const struct sockaddr *pDest, int32_t iDestLen)
{
SocketStateT *pState = _Socket_pState;
struct sockaddr SockAddr;
char strHostName[256];
int32_t iErr;
SOCKET iSock;
// must be same kind of addresses
if (iHostLen != iDestLen)
{
return(-1);
}
// do family specific lookup
if (pDest->sa_family == AF_INET)
{
// make them match initially
ds_memcpy(pHost, pDest, iHostLen);
#if !defined(DIRTYCODE_XBOXONE)
// respect adapter override
if (pState->uAdapterAddress != 0)
{
SockaddrInSetAddr(pHost, pState->uAdapterAddress);
return(0);
}
#endif
// zero the address portion
pHost->sa_data[2] = pHost->sa_data[3] = pHost->sa_data[4] = pHost->sa_data[5] = 0;
// create a temp socket (must be datagram)
iSock = socket(AF_INET, SOCK_DGRAM, 0);
if (iSock != INVALID_SOCKET)
{
// use routing check if winsock 2.0
if (pState->iVersion >= 0x200)
{
// get interface that would be used for this dest
ds_memclr(&SockAddr, sizeof(SockAddr));
if (WSAIoctl(iSock, SIO_ROUTING_INTERFACE_QUERY, (void *)pDest, iDestLen, &SockAddr, sizeof(SockAddr), (LPDWORD)&iHostLen, NULL, NULL) < 0)
{
iErr = WSAGetLastError();
NetPrintf(("dirtynetwin: WSAIoctl(SIO_ROUTING_INTERFACE_QUERY) returned %d\n", iErr));
}
else
{
// copy over the result
ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4);
}
// convert loopback to requested address
if (SockaddrInGetAddr(pHost) == INADDR_LOOPBACK)
{
ds_memcpy(pHost->sa_data+2, pDest->sa_data+2, 4);
}
}
// if no result, try connecting to socket then reading
// (this only works on some non-microsoft stacks)
if (SockaddrInGetAddr(pHost) == 0)
{
// connect to remote address
if (connect(iSock, pDest, iDestLen) == 0)
{
// query the host address
if (getsockname(iSock, &SockAddr, &iHostLen) == 0)
{
// just copy over the address portion
ds_memcpy(pHost->sa_data+2, SockAddr.sa_data+2, 4);
}
}
}
// $$TODO evaluate possibility of unifying - (keep PC version)
#if defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_GDK)
// if still no result, use classic gethosthame/gethostbyname
// (works for microsoft, not for non-microsoft stacks)
if (SockaddrInGetAddr(pHost) == 0)
{
struct hostent *pHostent;
// get machine name
gethostname(strHostName, sizeof(strHostName));
// lookup ip info
pHostent = gethostbyname(strHostName);
if (pHostent != NULL)
{
ds_memcpy(pHost->sa_data+2, pHostent->h_addr_list[0], 4);
}
}
#else
// if still no result, use classic gethosthame/getaddrinfo
// (works for microsoft, not for non-microsoft stacks)
if (SockaddrInGetAddr(pHost) == 0)
{
int32_t iResult;
struct addrinfo Hints, *pList = NULL;
// get machine name
ds_memclr(&Hints, sizeof(Hints));
Hints.ai_family = AF_INET;
Hints.ai_socktype = SOCK_STREAM;
Hints.ai_protocol = IPPROTO_TCP;
gethostname(strHostName, sizeof(strHostName));
// lookup ip info
if ((iResult = getaddrinfo(strHostName, NULL, &Hints, &pList)) == 0)
{
ds_memcpy(pHost->sa_data+2, pList->ai_addr->sa_data+2, 4);
}
else
{
NetPrintf(("dirtynetwin: getaddrinfo('%s', ...) failed err=%d\n", strHostName, iResult));
}
// release memory
freeaddrinfo(pList);
}
#endif
// close the socket
closesocket(iSock);
}
return(0);
}
// unsupported family
ds_memclr(pHost, iHostLen);
return(-3);
}