mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
3181 lines
110 KiB
C
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);
|
|
}
|