2020-09-30 17:17:25 +02:00

2495 lines
72 KiB
C++

// ---------------------------------------------------------------------------------------
// sock.cpp
//
// Copyright (C) Microsoft Corporation
// ---------------------------------------------------------------------------------------
#include "xnp.h"
#include "xnver.h"
#pragma warning(disable:4102)
// ---------------------------------------------------------------------------------------
// Trace tags
// ---------------------------------------------------------------------------------------
DefineTag(sock, 0);
DefineTag(sockWarn, TAG_ENABLE);
// ---------------------------------------------------------------------------------------
// socket
// ---------------------------------------------------------------------------------------
SOCKET CXnSock::socket(IN int af, IN int type, IN int protocol)
{
WinsockApiProlog_(socket, INVALID_SOCKET);
ICHECK(SOCK, USER);
CSocket * pSocket;
if (af != 0 && af != AF_INET)
err = WSAEAFNOSUPPORT;
else if (type != 0 && type != SOCK_STREAM && type != SOCK_DGRAM)
err = WSAESOCKTNOSUPPORT;
else if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
err = WSAEPROTONOSUPPORT;
else
{
if (type == 0)
type = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
if (protocol == 0)
protocol = (type == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP;
if ((type == SOCK_STREAM) != (protocol == IPPROTO_TCP))
err = WSAEPROTONOSUPPORT;
}
WinsockApiCheckError_(INVALID_SOCKET);
pSocket = SockAlloc(type == SOCK_STREAM, FALSE);
if (pSocket == NULL)
{
WinsockApiReturnError_(WSAENOBUFS, INVALID_SOCKET);
}
RaiseToDpc();
pSocket->SetFlags(SOCKF_OWNED);
pSocket->Enqueue(&_leSockets);
return((SOCKET)pSocket);
}
CSocket * CXnSock::SockAlloc(BOOL fTcp, BOOL fPoolAlloc)
{
ICHECK(SOCK, USER|UDPC|SDPC);
CSocket * pSocket;
UINT cb = fTcp ? sizeof(CTcpSocket) : sizeof(CSocket);
ULONG tag = fTcp ? PTAG_CTcpSocket : PTAG_CSocket;
if (_cSockets >= cfgSockMaxSockets)
{
TraceSz(Warning, "Too many sockets");
return NULL;
}
if (fPoolAlloc)
pSocket = (CSocket *)PoolAllocZ(cb, tag);
else
pSocket = (CSocket *)SysAllocZ(cb, tag);
if (pSocket)
{
pSocket->Init(this, fTcp, fPoolAlloc);
if (fTcp)
{
((CTcpSocket *)pSocket)->TcpInit(this);
}
InterlockedIncrement((LONG *)&_cSockets);
}
return(pSocket);
}
// ---------------------------------------------------------------------------------------
// closesocket
// ---------------------------------------------------------------------------------------
int CXnSock::closesocket(IN SOCKET s)
{
WinsockApiPrologSockLock_(closesocket, SOCKET_ERROR);
RaiseToDpc();
if (pSocket == NULL)
err = NETERR_PARAM;
else if (pSocket->TestFlags(SOCKF_CLOSED))
{
SockFree(pSocket);
err = 0;
}
else
{
pSocket->ClearFlags(SOCKF_OWNED);
err = SockClose(pSocket, FALSE);
}
MapNtStatusToWinsockError_(err);
// Don't call SockUnlock here because the socket is most likely deallocated by now
WinsockApiCheckError_(SOCKET_ERROR);
return NO_ERROR;
}
NTSTATUS CXnSock::SockClose(CSocket * pSocket, BOOL fForce)
{
RaiseToDpc();
// If a TCP is being gracefully closed, then we'll
// leave the the CSocket structure in the global list
// until the connection is really gone.
if (pSocket->IsTcp() && !TcpClose((CTcpSocket *)pSocket, fForce))
{
pSocket->SetFlags(SOCKF_NOMORE_XMIT|SOCKF_NOMORE_RECV|SOCKF_LINGERING);
pSocket->SetClosed();
return(NETERR_OK);
}
pSocket->Dequeue();
// Clean up the information associated with the CSocket
SockCleanup(pSocket);
Assert(!pSocket->IsTcp() || !((CTcpSocket *)pSocket)->GetTimer()->IsActive());
EvtTerm(pSocket->GetEvent());
InterlockedDecrement((LONG *)&_cSockets);
if (pSocket->TestFlags(SOCKF_OWNED))
pSocket->SetFlags(SOCKF_CLOSED);
else
SockFree(pSocket);
return(NETERR_OK);
}
void CXnSock::SockFree(CSocket * pSocket)
{
// Mark socket as closed even though we are freeing the memory just in case the
// user tries to access it anyways.
pSocket->SetClosed();
if (pSocket->TestFlags(SOCKF_POOLALLOC))
PoolFree(pSocket);
else
SysFree(pSocket);
}
void CXnSock::SockCleanup(CSocket * pSocket)
{
ICHECK(SOCK, UDPC|SDPC);
NTSTATUS status;
status = NT_SUCCESS(pSocket->GetStatus()) ? NETERR_CANCELLED : pSocket->GetStatus();
SockReqComplete(pSocket, pSocket->GetRecvReq(), status);
SockReqComplete(pSocket, pSocket->GetSendReq(), status);
// Flush receive buffers
SockFlushRecvBuffers(pSocket);
// Flush send buffers
while (pSocket->HasSendBuf())
{
CSendBuf * pSendBuf = pSocket->DequeueSendBuf();
SockReleaseSendBuf(pSendBuf);
}
pSocket->SetCbSendBuf(0);
if (pSocket->_prte)
{
RouteRelease(pSocket->_prte);
pSocket->_prte = NULL;
}
}
void CXnSock::SockReleaseSendBuf(CSendBuf * pSendBuf)
{
if (pSendBuf->Release() == 0)
PacketFree(pSendBuf);
else
pSendBuf->SetPfn((PFNPKTFREE)PacketFree);
}
//
// Flush a socket's receive buffers
//
void CXnSock::SockFlushRecvBuffers(CSocket * pSocket)
{
while (pSocket->HasRecvBuf())
{
PoolFree(pSocket->DequeueRecvBuf());
}
pSocket->SetCbRecvBuf(0);
}
// ---------------------------------------------------------------------------------------
// shutdown
// ---------------------------------------------------------------------------------------
int CXnSock::shutdown(IN SOCKET s, IN int how)
{
DWORD dwFlags;
WinsockApiPrologSockLock_(shutdown, SOCKET_ERROR);
WinsockApiParamCheck_(
how == SD_SEND ||
how == SD_RECEIVE ||
how == SD_BOTH);
switch (how) {
case SD_SEND:
dwFlags = SOCKF_NOMORE_XMIT;
break;
case SD_RECEIVE:
dwFlags = SOCKF_NOMORE_RECV;
break;
default:
dwFlags = SOCKF_NOMORE_XMIT|SOCKF_NOMORE_RECV;
break;
}
if (pSocket->IsUdp())
{
err = UdpShutdown(pSocket, dwFlags);
}
else
{
if (!pSocket->TestFlags(SOCKF_CONNECTED))
{
WinsockApiGotoExit_(WSAENOTCONN);
}
err = TcpShutdown((CTcpSocket*) pSocket, dwFlags, TRUE);
}
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// ioctlsocket
// ---------------------------------------------------------------------------------------
int CXnSock::ioctlsocket(SOCKET s, long cmd, u_long* argp)
{
KIRQL irql;
UINT bytesReady;
WinsockApiPrologSockLock_(ioctlsocket, SOCKET_ERROR);
WinsockApiParamCheck_(argp != NULL);
switch (cmd) {
case FIONBIO:
pSocket->SetFlags((*argp) ? SOCKF_OPT_NONBLOCKING : 0, SOCKF_OPT_NONBLOCKING);
break;
case FIONREAD:
{
RaiseToDpc();
if (pSocket->IsUdp())
{
// For UDP sockets, return the size of the first buffered datagram
// NOTE: For compatibility with win2k behavior, if the first datagram
// is 0-sized, we'll return 1.
if (pSocket->IsUdpRecvBufEmpty())
{
bytesReady = 0;
}
else
{
bytesReady = ((CUdpRecvBuf *)pSocket->GetRecvBufFirst())->GetCbBuf();
if (bytesReady == 0)
bytesReady = 1;
}
}
else
{
// For TCP sockets, return the total number of bytes
// available for reading.
bytesReady = pSocket->GetCbRecvBuf();
}
*argp = bytesReady;
break;
}
// case SIOCATMARK:
default:
WinsockApiGotoExit_(WSAENOPROTOOPT);
break;
}
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// setsockopt
// ---------------------------------------------------------------------------------------
int CXnSock::setsockopt(SOCKET s, int level, int optname, const char* optval, int optlen)
{
INT val;
WinsockApiPrologSockLock_(setsockopt, SOCKET_ERROR);
WinsockApiParamCheck_(optval != NULL && optlen > 0);
if (optlen < (INT) sizeof(INT))
val = (UCHAR) *optval;
else
val = *((INT*) optval);
switch (level)
{
case SOL_SOCKET:
{
RaiseToDpc();
// NOTE: Some options may not be applicable to datagram sockets,
// while others may not be applicable to stream sockets. But
// we'll let apps set those options here anyway (to avoid extra
// checking code). Downstream code just won't use those
switch (optname)
{
case SO_BROADCAST:
if (pSocket->IsTcp()) goto noopt;
pSocket->SetFlags(val ? SOCKF_OPT_BROADCAST : 0, SOCKF_OPT_BROADCAST);
break;
case SO_DONTLINGER:
if (pSocket->IsUdp()) goto noopt;
((CTcpSocket *)pSocket)->SetLingerOnOff(!val);
break;
case SO_LINGER:
if (pSocket->IsUdp()) goto noopt;
WinsockApiParamCheck_(optlen >= sizeof(LINGER));
((CTcpSocket *)pSocket)->SetLinger((LINGER *)optval);
break;
case SO_REUSEADDR:
if (pSocket->TestFlags(SOCKF_OPT_REUSEADDR) && val != 0) goto inval;
pSocket->SetFlags(val? SOCKF_OPT_REUSEADDR : 0, SOCKF_OPT_REUSEADDR);
break;
case SO_EXCLUSIVEADDRUSE:
if (pSocket->TestFlags(SOCKF_OPT_EXCLUSIVEADDR) && val != 0) goto inval;
pSocket->SetFlags(val ? SOCKF_OPT_EXCLUSIVEADDR : 0, SOCKF_OPT_EXCLUSIVEADDR);
break;
case SO_RCVTIMEO:
// Timeout value is in milliseconds
WinsockApiParamCheck_(optlen >= sizeof(INT));
pSocket->_uiRecvTimeout = val;
break;
case SO_SNDTIMEO:
// Timeout value is in milliseconds
WinsockApiParamCheck_(optlen >= sizeof(INT));
pSocket->_uiSendTimeout = val;
break;
case SO_RCVBUF:
WinsockApiParamCheck_(optlen >= sizeof(INT));
err = SockUpdateBufferSize(pSocket, pSocket->_cbMaxSendBuf, val);
if (!NT_SUCCESS(err)) goto inval;
break;
case SO_SNDBUF:
WinsockApiParamCheck_(optlen >= sizeof(INT));
err = SockUpdateBufferSize(pSocket, val, pSocket->_cbMaxRecvBuf);
if (!NT_SUCCESS(err)) goto inval;
break;
#ifdef XNET_FEATURE_ONLINE
case SO_INSECURE:
if (optlen == sizeof(CXoBase *) && optval == (char *)IpGetXoBase())
pSocket->SetFlags(SOCKF_INSECURE);
break;
#endif
default:
goto noopt;
}
break;
}
case IPPROTO_TCP:
{
if (pSocket->IsUdp()) goto noopt;
switch (optname) {
case TCP_NODELAY:
pSocket->SetFlags(val ? SOCKF_OPT_NONAGLE : 0, SOCKF_OPT_NONAGLE);
break;
default:
goto noopt;
}
break;
}
default:
goto inval;
}
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
inval:
err = WSAEINVAL;
goto exit;
noopt:
err = WSAENOPROTOOPT;
goto exit;
}
NTSTATUS CXnSock::SockUpdateBufferSize(CSocket* pSocket, INT sendBufsize, INT recvBufsize)
/*++
Routine Description:
Update the send and receive buffer sizes
Arguments:
pSocket - Points to the CSocket structure
sendBufsize, recvBufsize -
Specifies the new send and receive buffer sizes
Return Value:
Status code
--*/
{
ICHECK(SOCK, UDPC);
NTSTATUS status;
if (sendBufsize > (INT) cfgSockMaxSendBufsizeInK * 1024)
sendBufsize = (INT) cfgSockMaxSendBufsizeInK * 1024;
else if (sendBufsize <= 0) {
// NOTE: we never set actual send buffer size to 0
// because we don't support the no-buffering option.
sendBufsize = 1;
}
if (recvBufsize > (INT) cfgSockMaxRecvBufsizeInK * 1024)
recvBufsize = (INT) cfgSockMaxRecvBufsizeInK * 1024;
else if (recvBufsize <= 0)
recvBufsize = 0;
status = NETERR_OK;
if (pSocket->IsUdp())
{
// For datagram sockets, we'll just update the send and
// receive buffers sizes. If the current buffers are bigger
// than the specified limits, we'll leave the current data alone.
pSocket->_cbMaxSendBuf = sendBufsize;
pSocket->_cbMaxRecvBuf = recvBufsize;
}
else
{
CTcpSocket* pTcpSocket = (CTcpSocket*) pSocket;
// Set the send buffer size. If the current send buffer size
// is larger than the specified limit, leave the current data untouched.
pTcpSocket->_cbMaxSendBuf = sendBufsize;
// If the TCP socket is already connected,
// don't allow the app to reduce the receive buffer size.
if (!pTcpSocket->IsIdleState() && recvBufsize < (INT) pTcpSocket->_cbMaxRecvBuf)
{
status = NETERR_PARAM;
}
else
{
// NOTE: we don't update receive window to the connection peer
// right away. The new window information will be sent
// in the next outgoing ACK segment.
pTcpSocket->_cbMaxRecvBuf = recvBufsize;
}
}
// NOTE: If the send buffer has just opened up,
// we don't check to see if there is any pending
// overlapped send request that can be started.
// Rather the overlapped send request will be started
// by the normal process.
return(status);
}
// ---------------------------------------------------------------------------------------
// getsockopt
// ---------------------------------------------------------------------------------------
int CXnSock::getsockopt(SOCKET s, int level, int optname, char * optval, int * optlen)
{
INT val;
WinsockApiPrologSockLock_(getsockopt, SOCKET_ERROR);
WinsockApiParamCheck_(
optval != NULL &&
optlen != NULL &&
*optlen > 0);
memset(optval, 0, *optlen);
switch (level) {
case SOL_SOCKET:
switch (optname) {
case SO_BROADCAST:
if (pSocket->IsTcp()) goto noopt;
val = pSocket->TestFlags(SOCKF_OPT_BROADCAST);
break;
case SO_DONTLINGER:
if (pSocket->IsUdp()) goto noopt;
val = !((CTcpSocket *)pSocket)->GetLingerOnOff();
break;
case SO_LINGER:
if (pSocket->IsUdp()) goto noopt;
WinsockApiParamCheck_(*optlen >= sizeof(LINGER));
((CTcpSocket *)pSocket)->GetLinger((LINGER *)optval);
*optlen = sizeof(LINGER);
goto exit;
case SO_REUSEADDR:
val = pSocket->TestFlags(SOCKF_OPT_REUSEADDR);
break;
case SO_EXCLUSIVEADDRUSE:
val = pSocket->TestFlags(SOCKF_OPT_EXCLUSIVEADDR);
break;
case SO_RCVTIMEO:
WinsockApiParamCheck_(*optlen >= sizeof(INT));
val = pSocket->_uiRecvTimeout;
break;
case SO_SNDTIMEO:
WinsockApiParamCheck_(*optlen >= sizeof(INT));
val = pSocket->_uiSendTimeout;
break;
case SO_RCVBUF:
WinsockApiParamCheck_(*optlen >= sizeof(INT));
val = pSocket->_cbMaxRecvBuf;
break;
case SO_SNDBUF:
WinsockApiParamCheck_(*optlen >= sizeof(INT));
val = pSocket->_cbMaxSendBuf;
break;
case SO_TYPE:
val = pSocket->IsUdp() ? SOCK_DGRAM : SOCK_STREAM;
break;
case SO_ACCEPTCONN:
if (pSocket->IsUdp()) goto noopt;
val = ((CTcpSocket *)pSocket)->IsListenState();
break;
default:
goto noopt;
}
break;
case IPPROTO_TCP:
if (pSocket->IsUdp())
goto noopt;
switch (optname) {
case TCP_NODELAY:
val = pSocket->TestFlags(SOCKF_OPT_NONAGLE);
break;
default:
goto noopt;
}
break;
default:
err = WSAEINVAL;
goto exit;
}
if (*optlen < sizeof(INT)) {
*optval = (CHAR) val;
*optlen = 1;
} else {
*((INT*) optval) = val;
*optlen = sizeof(INT);
}
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
noopt:
err = WSAENOPROTOOPT;
goto exit;
}
// ---------------------------------------------------------------------------------------
// getsockname
// ---------------------------------------------------------------------------------------
int CXnSock::getsockname(SOCKET s, struct sockaddr* name, int* namelen)
{
struct sockaddr_in* sin;
WinsockApiPrologSockLock_(getsockname, SOCKET_ERROR);
WinsockApiParamCheck_(
name != NULL &&
namelen != NULL &&
*namelen >= SOCKADDRLEN);
if (!pSocket->TestFlags(SOCKF_BOUND))
{
WinsockApiGotoExit_(WSAEINVAL);
}
sin = (struct sockaddr_in*) name;
memset(sin, 0, SOCKADDRLEN);
sin->sin_family = AF_INET;
sin->sin_port = pSocket->_ipportSrc;
sin->sin_addr.s_addr = 0;
*namelen = SOCKADDRLEN;
err = NO_ERROR;
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// getpeername
// ---------------------------------------------------------------------------------------
int CXnSock::getpeername(SOCKET s, struct sockaddr * name, int * namelen)
{
struct sockaddr_in* sin;
WinsockApiPrologSockLock_(getpeername, SOCKET_ERROR);
WinsockApiParamCheck_(
name != NULL &&
namelen != NULL &&
*namelen >= SOCKADDRLEN);
if (!pSocket->TestFlags(SOCKF_CONNECTED)) {
WinsockApiGotoExit_(WSAENOTCONN);
}
sin = (struct sockaddr_in*) name;
sin->sin_family = AF_INET;
sin->sin_port = pSocket->_ipportDst;
sin->sin_addr.s_addr = pSocket->_ipaDst;
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
*namelen = SOCKADDRLEN;
err = NO_ERROR;
SecRegSetOwned(sin->sin_addr.s_addr);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// bind
// ---------------------------------------------------------------------------------------
int CXnSock::bind(SOCKET s, const struct sockaddr * name, int namelen)
{
const struct sockaddr_in* sin = (const struct sockaddr_in*) name;
WinsockApiPrologSockLock_(bind, SOCKET_ERROR);
WinsockApiParamCheck_(
name != NULL &&
namelen >= SOCKADDRLEN &&
sin->sin_family == AF_INET &&
sin->sin_addr.s_addr == 0);
if (pSocket->TestFlags(SOCKF_BOUND))
{
WinsockApiGotoExit_(WSAEINVAL);
}
if (sin->sin_addr.s_addr != 0)
{
WinsockApiGotoExit_(WSAEADDRNOTAVAIL);
}
err = SockBind(pSocket, sin->sin_port);
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
NTSTATUS CXnSock::SockBind(CSocket * pSocket, CIpPort ipportBind)
/*++
Routine Description:
Bind a socket to the specified local address
Arguments:
pSocket - Points to the protocol control block
srcaddr, srcport - Specifies the local socket address
Return Value:
Status code
--*/
{
int cRetry = ipportBind ? 0 : min(cfgSockMaxSockets, TEMP_PORT_COUNT);
CSocket * pSocketCur;
RaiseToDpc();
Assert(!pSocket->TestFlags(SOCKF_BOUND));
while (1)
{
if (cRetry-- > 0)
{
while (1)
{
_ipportTempNext += 1;
if (_ipportTempNext > MAX_TEMP_PORT)
_ipportTempNext = MIN_TEMP_PORT;
if (_ipportTempNext != NTOHS(ESPUDP_CLIENT_PORT))
break;
}
ipportBind = HTONS((WORD)_ipportTempNext);
}
for (pSocketCur = GetFirstSocket(); pSocketCur; pSocketCur = GetNextSocket(pSocketCur))
{
if (pSocketCur->TestFlags(SOCKF_BOUND))
{
Assert(pSocketCur != pSocket);
if ( (pSocketCur->IsUdp() == pSocket->IsUdp())
&& (pSocketCur->_ipportSrc == ipportBind)
&& ( pSocketCur->TestFlags(SOCKF_OPT_EXCLUSIVEADDR)
|| pSocket->TestFlags(SOCKF_OPT_EXCLUSIVEADDR)
|| !pSocket->TestFlags(SOCKF_OPT_REUSEADDR)))
{
break;
}
}
}
if (pSocketCur == NULL)
{
pSocket->_ipportSrc = ipportBind;
pSocket->SetFlags(SOCKF_BOUND);
return(NETERR_OK);
}
if (cRetry <= 0)
{
return(NETERR_ADDRINUSE);
}
}
}
// ---------------------------------------------------------------------------------------
// connect
// ---------------------------------------------------------------------------------------
int CXnSock::connect(SOCKET s, const struct sockaddr * name, int namelen)
{
const struct sockaddr_in* sin = (const struct sockaddr_in*) name;
CIpAddr dstaddr;
CIpPort dstport;
WinsockApiPrologSockLock_(connect, SOCKET_ERROR);
WinsockApiParamCheck_(
name != NULL &&
namelen >= SOCKADDRLEN &&
sin->sin_family == AF_INET);
if (pSocket->TestFlags(SOCKF_CONNECTED) && pSocket->IsTcp()) {
WinsockApiGotoExit_(WSAEISCONN);
}
dstaddr = sin->sin_addr.s_addr;
dstport = sin->sin_port;
if (pSocket->IsUdp())
{
err = UdpConnect(pSocket, dstaddr, dstport);
}
else if (pSocket->TestFlags(SOCKF_OPT_NONBLOCKING))
{
err = TcpConnect((CTcpSocket*) pSocket, dstaddr, dstport, FALSE);
// For nonblocking socket, we'll return WSAEWOULDBLOCK
// error code but the operation proceeds.
if (NT_SUCCESS(err)) err = NETERR_WOULDBLOCK;
}
else
{
err = TcpConnect((CTcpSocket*) pSocket, dstaddr, dstport, FALSE);
if (NT_SUCCESS(err))
{
// For blocking socket, we need to wait here for
// the operation to complete.
err = SockWaitForEvent(pSocket, SOCKF_EVENT_CONNECT, 0);
pSocket->SetFlags(SOCKF_CONNECT_SELECTED);
}
}
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// listen
// ---------------------------------------------------------------------------------------
int CXnSock::listen(SOCKET s, int backlog)
{
WinsockApiPrologSockLock_(listen, SOCKET_ERROR);
WinsockApiParamCheck_(pSocket->IsTcp());
if (pSocket->TestFlags(SOCKF_CONNECTED))
{
WinsockApiGotoExit_(WSAEISCONN);
}
if (!pSocket->TestFlags(SOCKF_BOUND))
{
WinsockApiGotoExit_(WSAEINVAL);
}
err = TcpListen((CTcpSocket*) pSocket, backlog);
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// accept
// ---------------------------------------------------------------------------------------
SOCKET CXnSock::accept(SOCKET s, struct sockaddr * addr, int * addrlen)
{
CTcpSocket * pTcpSocketChild = NULL;
WinsockApiPrologSockLock_(accept, INVALID_SOCKET);
WinsockApiParamCheck_(
pSocket->IsTcp() &&
(addr == NULL ||
addrlen != NULL && *addrlen >= SOCKADDRLEN));
CTcpSocket * pTcpSocket = (CTcpSocket *)pSocket;
if (!pTcpSocket->IsTcp() || !pTcpSocket->IsListenState())
{
WinsockApiGotoExit_(WSAEINVAL);
}
while (1)
{
{
RaiseToDpc();
pTcpSocketChild = pTcpSocket->DequeueConnectedChild();
}
if (pTcpSocketChild)
break;
// For nonblocking socket, return an error code
// if we don't have any pending connection requests.
if (pTcpSocket->TestFlags(SOCKF_OPT_NONBLOCKING))
{
WinsockApiGotoExit_(WSAEWOULDBLOCK);
}
// For blocking sockets, wait until there
// is a connection request.
err = SockWaitForEvent(pSocket, SOCKF_EVENT_ACCEPT, 0);
if (err != 0)
break;
}
MapNtStatusToWinsockError_(err);
if (pTcpSocketChild)
{
pTcpSocketChild->SetFlags(SOCKF_OWNED);
if (addr && addrlen)
{
struct sockaddr_in* sin;
sin = (struct sockaddr_in*) addr;
sin->sin_family = AF_INET;
sin->sin_port = pTcpSocketChild->_ipportDst;
sin->sin_addr.s_addr = pTcpSocketChild->_ipaDst;
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
*addrlen = SOCKADDRLEN;
SecRegSetOwned(pTcpSocketChild->_ipaDst);
}
}
WinsockApiExitSockUnlock_((SOCKET) pTcpSocketChild, INVALID_SOCKET);
}
// ---------------------------------------------------------------------------------------
// select
// ---------------------------------------------------------------------------------------
//
// Count the total number of socket handles
//
#define SOCKETS_IN_SET(_set) ((_set) ? ((_set)->fd_count & 0xffff) : 0)
//
// Select event masks
//
#define SELECT_READ_EVENTS (SOCKF_EVENT_READ|SOCKF_EVENT_ACCEPT|SOCKF_EVENT_CLOSE|SOCKF_EVENT_RESET)
#define SELECT_WRITE_EVENTS (SOCKF_EVENT_WRITE|SOCKF_EVENT_CONNECT)
#define SELECT_EXCEPT_EVENTS (SOCKF_EVENT_RESET)
//
// Number of KWAIT_BLOCKs allocated on the stack for select() calls.
//
#define SELECT_STACK_KWAIT_BLOCKS 3
int CXnSock::select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout)
{
SELECTINFO tempinfo;
PRKEVENT tempevent;
SELECTINFO* selinfo;
PRKEVENT* events;
INT index, rdcnt, rwcnt, selcnt = 0;
CSocket* pSocket;
LARGE_INTEGER waittime;
LARGE_INTEGER* pwait;
#ifdef XNET_FEATURE_XBOX
KWAIT_BLOCK tempWaitBlocks[SELECT_STACK_KWAIT_BLOCKS];
PKWAIT_BLOCK waitBlockArray = tempWaitBlocks;
#endif
WinsockApiProlog_(select, SOCKET_ERROR);
// Count the total number of sockets
// (ignore the input nfds parameter)
rdcnt = SOCKETS_IN_SET(readfds);
rwcnt = rdcnt + SOCKETS_IN_SET(writefds);
nfds = rwcnt + SOCKETS_IN_SET(exceptfds);
if (nfds == 0) {
WinsockApiReturnError_(WSAEINVAL, SOCKET_ERROR);
}
if (nfds == 1) {
// Use temporary stack buffers for the special case
// where there is only one socket. This saves us from
// two extra memory allocations.
events = &tempevent;
selinfo = &tempinfo;
memset(selinfo, 0, sizeof(SELECTINFO));
} else {
selinfo = (SELECTINFO*) SysAllocZ(nfds*sizeof(SELECTINFO), PTAG_select);
events = (PRKEVENT*) SysAlloc(nfds*sizeof(PRKEVENT), PTAG_select);
if (!selinfo || !events) {
nfds = 0;
WinsockApiGotoExit_(WSAENOBUFS);
}
}
// Lock all the socket handles
if ((err = SockLockSelectSockets(readfds, selinfo, 0, SELECT_READ_EVENTS)) != 0 ||
(err = SockLockSelectSockets(writefds, selinfo, rdcnt, SELECT_WRITE_EVENTS)) != 0 ||
(err = SockLockSelectSockets(exceptfds, selinfo, rwcnt, SELECT_EXCEPT_EVENTS)) != 0) {
goto exit;
}
// Compute the wait time in 100ns unit
if (timeout) {
pwait = &waittime;
#ifdef XNET_FEATURE_XBOX
waittime.QuadPart = Int32x32To64(timeout->tv_sec, -10000000) +
Int32x32To64(timeout->tv_usec, -10);
#else
waittime.QuadPart = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
#endif
} else {
pwait = NULL;
}
// Check if we to wait:
// if we do, set up the socket event flags
if (!pwait || pwait->QuadPart) {
INT waitCount = 0;
for (index=0; index < nfds; index++) {
pSocket = selinfo[index].pSocket;
if (selinfo[index].pSocketMasks) {
if (SockCheckSelectEvents(pSocket, selinfo[index].pSocketMasks, -1)) break;
events[waitCount++] = pSocket->GetEvent();
}
}
if (index == nfds) {
#ifdef XNET_FEATURE_XBOX
if (waitCount > SELECT_STACK_KWAIT_BLOCKS) {
waitBlockArray = (PKWAIT_BLOCK) SysAllocZ(waitCount * sizeof(KWAIT_BLOCK), PTAG_select);
if (!waitBlockArray) {
WinsockApiGotoExit_(WSAENOBUFS);
}
}
#ifdef XNET_FEATURE_XBDM_SERVER
do {
err = KeWaitForMultipleObjects(
waitCount,
(void **)events,
WaitAny,
UserRequest,
KernelMode,
TRUE,
pwait,
waitBlockArray);
} while(err == STATUS_KERNEL_APC);
if(err == STATUS_ALERTED)
err = STATUS_TIMEOUT;
#else
err = KeWaitForMultipleObjects(
waitCount,
(void **)events,
WaitAny,
UserRequest,
UserMode,
FALSE,
pwait,
waitBlockArray);
#endif // XBDM
if ((err < 0 || err >= waitCount) && err != STATUS_TIMEOUT) {
WinsockApiGotoExit_(WSAEFAULT);
}
#else
err = WaitForMultipleObjects(waitCount, (HANDLE *)events, FALSE, (DWORD)waittime.QuadPart);
if (err == WAIT_FAILED)
WinsockApiGotoExit_(WSAEFAULT);
#endif
}
}
// Determine which socket events are ready
// and return appropriate information
if (readfds) { FD_ZERO(readfds); }
if (writefds) { FD_ZERO(writefds); }
if (exceptfds) { FD_ZERO(exceptfds); }
for (index=selcnt=0; index < nfds; index++)
{
if (SockCheckSelectEvents(selinfo[index].pSocket, selinfo[index].eventMasks, 0))
{
FD_SET(selinfo[index].s, selinfo[index].fdset);
selcnt++;
}
}
err = NO_ERROR;
exit:
for (index=0; index < nfds; index++)
{
pSocket = selinfo[index].pSocket;
if (pSocket && selinfo[index].pSocketMasks)
{
pSocket->ClearFlags(SOCKF_EVENT_MASK);
pSocket->Unlock();
}
}
#ifdef XNET_FEATURE_XBOX
if (waitBlockArray != tempWaitBlocks) { SysFree(waitBlockArray); }
#endif
if (selinfo != &tempinfo) { SysFree(selinfo); }
if (events != &tempevent) { SysFree(events); }
WinsockApiCheckError_(SOCKET_ERROR);
return selcnt;
}
INT CXnSock::SockLockSelectSockets(fd_set* fdset, SELECTINFO* selinfo, INT offset, INT eventMasks)
/*++
Routine Description:
Lock the socket handles that was passed to the select API
Arguments:
fdset - Points to the socket set
selinfo - Points to an array of SELECTINFO structures
for storing the locked socket information
eventMasks - Specifies the interested events
Return Value:
Winsock error code
--*/
{
INT i, count;
// Nothing to do if the set is empty
count = SOCKETS_IN_SET(fdset);
for (i=0; i < count; i++) {
SOCKET s = fdset->fd_array[i];
INT j, k = offset + i;
selinfo[k].s = s;
selinfo[k].fdset = fdset;
selinfo[k].eventMasks = eventMasks;
// Check to see if the socket is already used
// in the same select call
for (j=0; j < k && selinfo[j].s != s; j++)
;
if (j == k) {
//
// The socket isn't seen already
//
selinfo[k].pSocketMasks = eventMasks;
selinfo[k].pSocket = CSocket::Lock(s);
if (!selinfo[k].pSocket)
return GetLastError();
} else {
//
// The socket is already seen
//
selinfo[j].pSocketMasks |= eventMasks;
selinfo[k].pSocketMasks = 0;
selinfo[k].pSocket = selinfo[j].pSocket;
}
}
return NO_ERROR;
}
INT CXnSock::SockCheckSelectEvents(CSocket * pSocket, INT eventMasks, INT setwait)
/*++
Routine Description:
Check if the specified socket events are available
and optionally set up the socket to wait for them
Arguments:
pSocket - Points to the protocol control block
eventMasks - Specifies the socket events the caller is interested in
setwait - Whether to set up the sockets to wait if
none of the specified events are avaiable
Return Value:
Set of event flags that are already available
--*/
{
NTSTATUS status;
INT readyMasks;
RaiseToDpc();
// Check to see if the specified event is already available
// Since our checks are trivial, it's faster to check
// everything instead of trying to check selectively
// based on the flags specified by the caller.
if (pSocket->IsTcp())
{
CTcpSocket * pTcpSocket = (CTcpSocket *)pSocket;
// If the connection was reset, return reset status
status = pSocket->GetStatus();
if (!NT_SUCCESS(status))
{
// Note: If we already told the app that the socket was connected
// and then the socket got resetted, we don't need to set the socket
// in the exceptfds again to tell the app the connection has failed.
if (eventMasks != SOCKF_EVENT_RESET || !pSocket->TestFlags(SOCKF_CONNECT_SELECTED))
{
return(SOCKF_EVENT_RESET);
}
}
readyMasks = (pTcpSocket->IsTcpRecvBufEmpty() ? 0 : SOCKF_EVENT_READ) |
(pTcpSocket->HasConnectedChild() ? SOCKF_EVENT_ACCEPT : 0) |
(pTcpSocket->IsFinReceived() ? SOCKF_EVENT_READ|SOCKF_EVENT_CLOSE : 0);
if (pSocket->TestFlags(SOCKF_CONNECTED))
{
if (!pSocket->IsSendBufFull())
{
readyMasks |= SOCKF_EVENT_WRITE;
}
// NOTE: we only signal the connect event exactly once
if ((eventMasks & SOCKF_EVENT_CONNECT) && !pSocket->TestFlags(SOCKF_CONNECT_SELECTED))
{
readyMasks |= SOCKF_EVENT_CONNECT;
if (setwait >= 0)
{
pSocket->SetFlags(SOCKF_CONNECT_SELECTED);
}
}
}
} else {
readyMasks = (pSocket->IsUdpRecvBufEmpty() ? 0 : SOCKF_EVENT_READ) |
(pSocket->IsSendBufFull() ? 0 : SOCKF_EVENT_WRITE);
}
if ((readyMasks &= eventMasks) == 0 && setwait)
{
// Indicate that we're interested in the specified event
// and prepare to wait
pSocket->SetFlags(eventMasks, SOCKF_EVENT_MASK);
EvtClear(pSocket->GetEvent());
}
return(readyMasks);
}
// ---------------------------------------------------------------------------------------
// WSAGetOverlappedResult
// ---------------------------------------------------------------------------------------
BOOL CXnSock::WSAGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED overlapped, LPDWORD byteCount, BOOL fWait, LPDWORD flags)
{
WinsockApiPrologSockLock_(WSAGetOverlappedResult, FALSE);
WinsockApiParamCheck_(
overlapped != NULL &&
overlapped->hEvent != NULL &&
byteCount != NULL &&
flags != NULL);
//
// Check if we need to wait for the I/O request to complete
//
if (overlapped->_iostatus == NETERR_PENDING && fWait)
{
WaitForSingleObject(overlapped->hEvent, INFINITE);
RaiseToDpc();
if (overlapped->_iostatus == NETERR_PENDING)
{
err = overlapped->_ioxfercnt ? NETERR_OK : NETERR_CANCELLED;
SockReqComplete(pSocket, (CSockReq *)overlapped->_ioreq, err);
}
}
//
// If the I/O request was completed,
// return the completion status information
//
if ((err = overlapped->_iostatus) != NETERR_PENDING && NT_SUCCESS(err))
{
*byteCount = overlapped->_ioxfercnt;
*flags = overlapped->_ioflags;
}
if (err == NETERR_PENDING)
{
WinsockApiGotoExit_(WSA_IO_INCOMPLETE);
}
else
{
MapNtStatusToWinsockError_(err);
}
WinsockApiExitSockUnlock_(TRUE, FALSE);
}
// ---------------------------------------------------------------------------------------
// WSACancelOverlappedIO
// ---------------------------------------------------------------------------------------
INT CXnSock::WSACancelOverlappedIO(SOCKET s)
{
WinsockApiPrologSockLock_(WSACancelOverlappedIO, SOCKET_ERROR);
{
RaiseToDpc();
SockReqComplete(pSocket, pSocket->GetRecvReq(), NETERR_CANCELLED);
SockReqComplete(pSocket, pSocket->GetSendReq(), NETERR_CANCELLED);
}
WinsockApiGotoExit_(NO_ERROR);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// recv
// ---------------------------------------------------------------------------------------
int CXnSock::recv(SOCKET s, char* buf, int len, int flags)
{
CRecvReq pRecvReq;
DWORD count;
WinsockApiPrologSockLock_(recv, SOCKET_ERROR);
WinsockApiParamCheck_(
(len > 0 && buf != NULL || len == 0) &&
flags == 0);
pRecvReq.buf = (BYTE*) buf;
pRecvReq.buflen = len;
pRecvReq.flags = flags;
pRecvReq.bytesRecv = &count;
pRecvReq.fromaddr = NULL;
pRecvReq._pWsaOverlapped = NULL;
err = SockRead(pSocket, &pRecvReq);
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(count, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// WSARecv
// ---------------------------------------------------------------------------------------
//
// Verify buffers passed to WSARecv API
// NOTE: we do not support more than 1 receive buffers.
//
INLINE
BOOL CheckRecvWsaBuf(WSABUF* bufs, UINT bufcnt)
{
return (bufcnt == 1 && bufs != NULL && (bufs->len > 0 && bufs->buf != NULL || bufs->len == 0));
}
int CXnSock::WSARecv(SOCKET s, LPWSABUF bufs, DWORD bufcnt, LPDWORD bytesRecv, LPDWORD flags,
LPWSAOVERLAPPED overlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc)
{
CRecvReq pRecvReq;
WinsockApiPrologSockLock_(WSARecv, SOCKET_ERROR);
WinsockApiParamCheck_(
CheckRecvWsaBuf(bufs, bufcnt) &&
bytesRecv != NULL &&
flags != NULL && *flags == 0 &&
completionproc == NULL);
pRecvReq.buf = (BYTE*) bufs->buf;
pRecvReq.buflen = bufs->len;
pRecvReq.flags = *flags;
pRecvReq.bytesRecv = bytesRecv;
pRecvReq.fromaddr = NULL;
pRecvReq._pWsaOverlapped = overlapped;
err = SockRead(pSocket, &pRecvReq);
*flags = pRecvReq.flags;
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// recvfrom
// ---------------------------------------------------------------------------------------
int CXnSock::recvfrom(SOCKET s, char FAR * buf, int len, int flags, struct sockaddr * from, int * fromlen)
{
DWORD count;
INT err;
WSABUF wsabuf;
WinsockApiPrologLight_(recvfrom);
WinsockApiParamCheck_(
(len > 0 && buf != NULL || len == 0) &&
(from == NULL ||
fromlen != NULL && *fromlen >= SOCKADDRLEN));
wsabuf.len = len;
wsabuf.buf = buf;
err = WSARecvFrom(s, &wsabuf, 1, &count, (DWORD*) &flags, from, fromlen, NULL, NULL);
return (err == NO_ERROR) ? count : SOCKET_ERROR;
}
// ---------------------------------------------------------------------------------------
// WSARecvFrom
// ---------------------------------------------------------------------------------------
int CXnSock::WSARecvFrom(SOCKET s, LPWSABUF bufs, DWORD bufcnt, LPDWORD bytesRecv, LPDWORD flags,
struct sockaddr * fromaddr, LPINT fromlen, LPWSAOVERLAPPED overlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc)
{
CRecvReq pRecvReq;
WinsockApiPrologSockLock_(WSARecvFrom, SOCKET_ERROR);
WinsockApiParamCheck_(
CheckRecvWsaBuf(bufs, bufcnt) &&
bytesRecv != NULL &&
(fromaddr == NULL ||
fromlen != NULL && *fromlen >= SOCKADDRLEN) &&
flags != NULL && *flags == 0 &&
completionproc == NULL);
// Winsock documentation on this call is extremely confusing
// regarding the correct behavior for connection-oriented sockets
// Here I've taken the liberty to treat WSARecvFrom the same way
// as WSARecv for such cases.
if (pSocket->IsTcp()) {
TraceSz(Verbose, "WSARecvFrom called on stream socket!");
}
pRecvReq.buf = (BYTE*) bufs->buf;
pRecvReq.buflen = bufs->len;
pRecvReq.flags = *flags;
pRecvReq.bytesRecv = bytesRecv;
pRecvReq._pWsaOverlapped = overlapped;
pRecvReq.fromaddr = (struct sockaddr_in*) fromaddr;
if (fromaddr)
{
memset(fromaddr, 0, SOCKADDRLEN);
*fromlen = SOCKADDRLEN;
pRecvReq.fromaddr->sin_family = AF_INET;
}
err = SockRead(pSocket, &pRecvReq);
*flags = pRecvReq.flags;
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// SockRead
// ---------------------------------------------------------------------------------------
NTSTATUS CXnSock::SockRead(CSocket * pSocket, CRecvReq * pRecvReq)
{
WSAOVERLAPPED * pWsaOverlapped;
WSAOVERLAPPED WsaOverlappedTemp;
NTSTATUS status;
if (pSocket->IsTcp())
{
if (!pSocket->TestFlags(SOCKF_CONNECTED))
{
return NETERR(WSAENOTCONN);
}
}
else
{
if (!pSocket->TestFlags(SOCKF_BOUND))
{
return NETERR(WSAEINVAL);
}
}
if (pSocket->TestFlags(SOCKF_NOMORE_RECV))
{
return(!NT_SUCCESS(pSocket->GetStatus()) ? pSocket->GetStatus() : NETERR(WSAESHUTDOWN));
}
// Since all our sockets are overlapped, we don't
// enforce the Win32 behavior that the input socket
// must be a non-overlapped socket.
pWsaOverlapped = pRecvReq->_pWsaOverlapped;
if (pWsaOverlapped != NULL)
{
pRecvReq->_pEvent = EvtFromHandle(pWsaOverlapped->hEvent);
if (pRecvReq->_pEvent == NULL)
{
return(NETERR(WSASYSCALLFAILURE));
}
}
else
{
pRecvReq->_pEvent = NULL;
}
// Check if we have any buffered data waiting to be read
if (pSocket->IsRecvBufEmpty())
{
if (pWsaOverlapped)
{
// Overlapped call. This used to clear the event here, but that breaks if the caller
// is using the same event for multiple sockets and/or online tasks. So now we
// let the caller reset the event themselves before making the overlapped call.
// Normally they would use an auto-reset event anyways, so there is nothing for them to do.
}
else if (pSocket->TestFlags(SOCKF_OPT_NONBLOCKING))
{
// Nonoverlapped call and socket is nonblocking:
// just return WOULDBLOCK error code.
//
// Note: For TCP socket, if FIN has been received
// we should return success with bytesRecv set to 0.
if (pSocket->IsUdp() || !((CTcpSocket *)pSocket)->IsFinReceived())
{
return(NETERR(WSAEWOULDBLOCK));
}
}
else
{
// Blocking call - prepare to wait
pRecvReq->_pWsaOverlapped = &WsaOverlappedTemp;
pRecvReq->_pEvent = pSocket->GetEvent();
EvtClear(pRecvReq->_pEvent);
}
}
status = pSocket->IsUdp() ?
UdpRead(pSocket, pRecvReq) :
TcpRead((CTcpSocket *) pSocket, pRecvReq);
if (status == NETERR_PENDING)
{
if (pWsaOverlapped == NULL)
{
// A blocking call is still in progress
EvtWait(pRecvReq->_pEvent, pSocket->_uiRecvTimeout);
if (WsaOverlappedTemp._iostatus == NETERR_PENDING)
{
RaiseToDpc();
if (WsaOverlappedTemp._iostatus == NETERR_PENDING)
{
// We can use pRecvReq directly here because
// blocking recv call is treated as a special case
// in SockQueueOverlappedRecv
SockReqComplete(pSocket, pRecvReq, NETERR_TIMEOUT);
}
}
*pRecvReq->bytesRecv = WsaOverlappedTemp._ioxfercnt;
pRecvReq->flags = WsaOverlappedTemp._ioflags;
status = WsaOverlappedTemp._iostatus;
}
}
else
{
if (pWsaOverlapped)
{
// An overlapped call was completed immediately
pWsaOverlapped->_ioflags = pRecvReq->flags;
pWsaOverlapped->_ioxfercnt = *pRecvReq->bytesRecv;
pWsaOverlapped->_iostatus = status;
// It would seem to be a waste to signal the event here.
// But that's win2k behavior.
EvtSet(pRecvReq->_pEvent, 0);
EvtDereference(pRecvReq->_pEvent);
}
}
return(status);
}
NTSTATUS CXnSock::RecvReqEnqueue(CSocket * pSocket, CRecvReq * pRecvReq)
{
ICHECK(SOCK, UDPC|SDPC);
CRecvReq * pRecvReqNew;
if (pSocket->HasRecvReq())
{
TraceSz2(sockWarn, "[%08lX:%s] Can only have one overlapped read at a time",
pSocket, pSocket->TypeStr());
return(NETERR_WOULDBLOCK);
}
else if (!pRecvReq->_pEvent)
{
TraceSz2(sockWarn, "[%08lX:%s] Overlapped read on nonblocking socket must supply an event",
pSocket, pSocket->TypeStr());
return(NETERR_WOULDBLOCK);
}
if (pRecvReq->_pEvent == pSocket->GetEvent())
{
// If the wait event is our internal per-CSocket event,
// then this is a special case for implementing the
// blocking recv call.
//
// In this case, we avoid an allocation by directly
// queuing up the RECVREQ structure that was passed
// in from the caller (winsock layer). This works because
// the caller will wait for the recv to complete after
// this function returns.
pRecvReqNew = pRecvReq;
}
else
{
pRecvReqNew = (CRecvReq *)SysAlloc(sizeof(CRecvReq), PTAG_CRecvReq);
if (pRecvReqNew == NULL)
{
TraceSz2(sockWarn, "[%08lX:%s] Out of memory allocating overlapped read control block",
pSocket, pSocket->TypeStr());
return(NETERR_MEMORY);
}
*pRecvReqNew = *pRecvReq;
}
pSocket->SetRecvReq(pRecvReqNew);
pRecvReqNew->_pWsaOverlapped->_ioxfercnt = 0;
pRecvReqNew->_pWsaOverlapped->_ioflags = 0;
pRecvReqNew->_pWsaOverlapped->_ioreq = (UINT_PTR)pRecvReqNew;
pRecvReqNew->_pWsaOverlapped->_iostatus = (DWORD)NETERR_PENDING;
TraceSz3(sock, "[%08lX:%s] Enqueued overlapped read of %ld bytes",
pSocket, pSocket->TypeStr(), pRecvReq->GetCbBuf());
return(NETERR_PENDING);
}
// ---------------------------------------------------------------------------------------
// Forward Declarations
// ---------------------------------------------------------------------------------------
UINT SockCountSendTotal(WSABUF * bufs, UINT bufcnt);
BOOL SockCheckSendWsaBuf(WSABUF * bufs, UINT bufcnt);
// ---------------------------------------------------------------------------------------
// send
// ---------------------------------------------------------------------------------------
int CXnSock::send(SOCKET s, const char* buf, int len, int flags)
{
WSABUF wsabuf;
CSendReq SendReq;
WinsockApiPrologSockLock_(send, SOCKET_ERROR);
WinsockApiParamCheck_(
(len > 0 && buf != NULL || len == 0) &&
(flags == 0));
wsabuf.len = len;
wsabuf.buf = (char*) buf;
SendReq._pWsaOverlapped = NULL;
SendReq.bufs = &wsabuf;
SendReq.bufcnt = 1;
SendReq.sendtotal = len;
SendReq.toaddr = NULL;
err = SockSend(pSocket, &SendReq);
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(SendReq.sendtotal, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// WSASend
// ---------------------------------------------------------------------------------------
int CXnSock::WSASend(SOCKET s, LPWSABUF bufs, DWORD bufcnt, LPDWORD bytesSent, DWORD flags,
LPWSAOVERLAPPED overlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc)
{
CSendReq SendReq;
WinsockApiPrologSockLock_(WSASend, SOCKET_ERROR);
WinsockApiParamCheck_(
SockCheckSendWsaBuf(bufs, bufcnt) &&
bytesSent != NULL &&
flags == 0 &&
completionproc == NULL);
SendReq._pWsaOverlapped = overlapped;
SendReq.bufs = bufs;
SendReq.bufcnt = (WORD)bufcnt;
SendReq.sendtotal = SockCountSendTotal(bufs, bufcnt);
SendReq.toaddr = NULL;
err = SockSend(pSocket, &SendReq);
if (NT_SUCCESS(err)) *bytesSent = SendReq.sendtotal;
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
BOOL SockCheckSendWsaBuf(WSABUF* bufs, UINT bufcnt)
{
if (bufs == NULL || bufcnt == 0 || bufcnt > 0xFFFF)
return FALSE;
while (bufcnt--) {
if (bufs->len && bufs->buf == NULL)
return FALSE;
bufs++;
}
return TRUE;
}
UINT SockCountSendTotal(WSABUF* bufs, UINT bufcnt)
{
UINT total = 0;
while (bufcnt--)
{
total += bufs->len;
bufs++;
}
return(total);
}
// ---------------------------------------------------------------------------------------
// sendto
// ---------------------------------------------------------------------------------------
int CXnSock::sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen)
{
DWORD count;
INT err;
WSABUF wsabuf;
WinsockApiPrologLight_(sendto);
WinsockApiParamCheck_(len > 0 && buf != NULL || len == 0);
wsabuf.len = len;
wsabuf.buf = (char*) buf;
err = WSASendTo(s, &wsabuf, 1, &count, flags, to, tolen, NULL, NULL);
return (err == NO_ERROR) ? count : SOCKET_ERROR;
}
// ---------------------------------------------------------------------------------------
// WSASendTo
// ---------------------------------------------------------------------------------------
int CXnSock::WSASendTo(SOCKET s, LPWSABUF bufs, DWORD bufcnt, LPDWORD bytesSent, DWORD flags,
const struct sockaddr* toaddr, int tolen, LPWSAOVERLAPPED overlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc)
{
struct sockaddr_in* sin = (struct sockaddr_in*) toaddr;
CSendReq SendReq;
WinsockApiPrologSockLock_(WSASendTo, SOCKET_ERROR);
WinsockApiParamCheck_(
SockCheckSendWsaBuf(bufs, bufcnt) &&
bytesSent != NULL &&
flags == 0 &&
(toaddr == NULL ||
tolen >= SOCKADDRLEN && sin->sin_family == AF_INET) &&
completionproc == NULL);
SendReq._pWsaOverlapped = overlapped;
SendReq.bufs = bufs;
SendReq.bufcnt = (WORD)bufcnt;
SendReq.sendtotal = SockCountSendTotal(bufs, bufcnt);
if (pSocket->IsTcp() || toaddr == NULL)
{
// For TCP sockets, WSASendTo is equivalent to WSASend.
// We simply ignore lpTo and iToLen parameters.
//
// Also, if toaddr parameter is NULL, we treat WSASendTo
// the same way as WSASend.
SendReq.toaddr = NULL;
}
else
{
CIpAddr ipaDst = sin->sin_addr.s_addr;
// Must do this check because downstream code
// doesn't expect the destination address to be 0.
// Also, we consider sending to UDP port 0 an error.
if ( ipaDst == 0
|| sin->sin_port == 0
|| ipaDst.IsMulticast()
|| (ipaDst.IsLoopback() && ipaDst != IPADDR_LOOPBACK))
{
WinsockApiGotoExit_(WSAEADDRNOTAVAIL);
}
// Is this socket allowed to send broadcast
// datagrams on this socket?
if (ipaDst.IsBroadcast() && !pSocket->TestFlags(SOCKF_OPT_BROADCAST))
{
WinsockApiGotoExit_(WSAEACCES);
}
// If the socket is not bound, bind it here
if (!pSocket->TestFlags(SOCKF_BOUND))
{
err = SockBind(pSocket, 0);
if (!NT_SUCCESS(err))
{
MapNtStatusToWinsockError_(err);
goto exit;
}
}
// Send to the new destination
SendReq.toaddr = sin;
}
err = SockSend(pSocket, &SendReq);
if (NT_SUCCESS(err))
{
*bytesSent = SendReq.sendtotal;
}
MapNtStatusToWinsockError_(err);
WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}
// ---------------------------------------------------------------------------------------
// SockSend
// ---------------------------------------------------------------------------------------
NTSTATUS CXnSock::SockSend(CSocket* pSocket, CSendReq* pSendReq)
/*++
Routine Description:
Internal function for sending out data from a socket
Arguments:
pSocket - Points to the protocol control block
pSendReq - Points to send user request information
Return Value:
Status code
--*/
{
WSAOVERLAPPED * pWsaOverlapped;
NTSTATUS status;
if (!pSocket->TestFlags(SOCKF_CONNECTED) && !pSendReq->toaddr)
return NETERR(WSAENOTCONN);
if (pSocket->TestFlags(SOCKF_NOMORE_XMIT))
{
return(!NT_SUCCESS(pSocket->GetStatus()) ? pSocket->GetStatus() : NETERR(WSAESHUTDOWN));
}
pWsaOverlapped = pSendReq->_pWsaOverlapped;
if (pWsaOverlapped != NULL)
{
pSendReq->_pEvent = EvtFromHandle(pWsaOverlapped->hEvent);
if (pSendReq->_pEvent == NULL)
{
return(NETERR(WSASYSCALLFAILURE));
}
}
// Check if we have room in the send buffer
if (pSocket->IsSendBufFull())
{
if (pWsaOverlapped)
{
// Overlapped call. This used to clear the event here, but that breaks if the caller
// is using the same event for multiple sockets and/or online tasks. So now we
// let the caller reset the event themselves before making the overlapped call.
// Normally they would use an auto-reset event anyways, so there is nothing for them to do.
status = SockQueueOverlappedSend(pSocket, pSendReq);
// The overlapped request was successfully queued up
if (status == NETERR_PENDING)
return(status);
// The overlapped send request wasn't queued
// because of an error.
if (!NT_SUCCESS(status))
goto exit;
// The send buffer has opened up and
// the overlapped send request wasn't queued.
}
else if (pSocket->TestFlags(SOCKF_OPT_NONBLOCKING) || pSocket->HasSendReq())
{
return(NETERR_WOULDBLOCK);
}
else
{
status = SockWaitForEvent(pSocket, SOCKF_EVENT_WRITE, pSocket->_uiSendTimeout);
if (!NT_SUCCESS(status))
return(status);
}
}
status = pSocket->IsUdp() ?
UdpSend(pSocket, pSendReq, 0) :
TcpSend((CTcpSocket *) pSocket, pSendReq, 0);
exit:
if (pWsaOverlapped)
{
// An overlapped call was completed immediately
pWsaOverlapped->_ioflags = 0;
pWsaOverlapped->_ioxfercnt = pSendReq->sendtotal;
pWsaOverlapped->_iostatus = status;
// It would seem to be a waste to signal the event here.
// But that's win2k behavior.
EvtSet(pSendReq->_pEvent, 0);
EvtDereference(pSendReq->_pEvent);
}
return(status);
}
NTSTATUS CXnSock::SockQueueOverlappedSend(CSocket * pSocket, CSendReq * pSendReq)
/*++
Routine Description:
Queue up an overlapped send request
Arguments:
pSocket - Points to the protocol control block
pSendReq - Points to the overlapped send request
Return Value:
Status code:
NETERR_PENDING - the request was successfully queued up
NETERR_OK - if the send buffer has opened up and
there is no need to queue up the request
otherwise - the request was not queued up due to an error
--*/
{
NTSTATUS status;
RaiseToDpc();
if (pSocket->HasSendReq())
{
// We only support 1 outstanding overlapped send request.
status = NETERR_WOULDBLOCK;
}
else if (!pSocket->IsSendBufFull())
{
// The send buffer opened up just as
// we were raising to DPC level
status = NETERR_OK;
}
else
{
CSendReq* newreq;
UINT size = sizeof(CSendReq) +
(pSendReq->toaddr ? sizeof(*pSendReq->toaddr) : 0) +
sizeof(WSABUF) * pSendReq->bufcnt;
newreq = (CSendReq*) SysAlloc(size, PTAG_CXmitReq);
if (!newreq)
{
// Out of memory
status = NETERR_MEMORY;
}
else
{
VOID* bufs;
pSocket->SetSendReq(newreq);
*newreq = *pSendReq;
bufs = newreq+1;
if (pSendReq->toaddr) {
newreq->toaddr = (struct sockaddr_in*) bufs;
*newreq->toaddr = *pSendReq->toaddr;
bufs = newreq->toaddr + 1;
}
newreq->bufs = (WSABUF*) bufs;
memcpy(bufs, pSendReq->bufs, sizeof(WSABUF) * pSendReq->bufcnt);
newreq->_pWsaOverlapped->_ioxfercnt = 0;
newreq->_pWsaOverlapped->_ioflags = 0;
newreq->_pWsaOverlapped->_ioreq = (UINT_PTR) newreq;
newreq->_pWsaOverlapped->_iostatus = status = NETERR_PENDING;
}
}
return(status);
}
// ---------------------------------------------------------------------------------------
// inet_addr
// ---------------------------------------------------------------------------------------
ULONG _WSAAPI_ inet_addr(const char * pch)
{
WinsockApiPrologTrivial_(inet_addr);
WinsockApiParamCheck_(pch != NULL);
// Convert the string representation of IP address to its binary form.
// The following formats are recognized:
// a.b.c.d 8-8-8-8
// a.b.c 8-8-16
// a.b 8-24
// a 32
// Each field can be in decimal, octal, or hex format.
ULONG fields[4], addr;
UINT fieldcnt = 0;
const UCHAR* p = (const UCHAR *) pch;
// NOTE: We don't handle overflow conditions.
while (TRUE) {
// skip leading spaces
while (*p == ' ') p++;
if (fieldcnt >= 4 || *p == 0) break;
addr = 0;
if (*p == '0' && (p[1] == 'x' || p[1] == 'X')) {
// hex number
const UCHAR* q = (p += 2);
while (TRUE) {
if (*p >= '0' && *p <= '9')
addr = (addr << 4) + (*p - '0');
else if (*p >= 'a' && *p <= 'f')
addr = (addr << 4) + 10 + (*p - 'a');
else if (*p >= 'A' && *p <= 'F')
addr = (addr << 4) + 10 + (*p - 'A');
else
break;
p++;
}
if (q == p)
{
return(INADDR_NONE);
}
} else if (*p == '0') {
// octal number
do {
addr = (addr << 3) + (*p - '0');
p++;
} while (*p >= '0' && *p <= '7');
} else if (*p >= '1' && *p <= '9') {
// decimal number
do {
addr = addr*10 + (*p - '0');
p++;
} while (*p >= '0' && *p <= '9');
} else {
// invalid character
break;
}
// skip trailing spaces and . separator
while (*p == ' ') p++;
if (*p == '.') p++;
fields[fieldcnt++] = addr;
}
if (*p)
{
return(INADDR_NONE);
}
switch (fieldcnt) {
case 1:
addr = fields[0];
break;
case 2:
addr = ((fields[0] & 0xff) << 24) |
(fields[1] & 0xffffff);
break;
case 3:
addr = ((fields[0] & 0xff) << 24) |
((fields[1] & 0xff) << 16) |
(fields[2] & 0xffff);
break;
case 4:
addr = ((fields[0] & 0xff) << 24) |
((fields[1] & 0xff) << 16) |
((fields[2] & 0xff) << 8) |
(fields[3] & 0xff);
break;
default:
addr = 0;
break;
}
return(HTONL(addr));
}
// ---------------------------------------------------------------------------------------
// Miscellanous
// ---------------------------------------------------------------------------------------
u_long _WSAAPI_ htonl(IN u_long hostlong) { return HTONL(hostlong); }
u_short _WSAAPI_ htons(IN u_short hostshort) { return HTONS(hostshort); }
u_long _WSAAPI_ ntohl(IN u_long netlong) { return NTOHL(netlong); }
u_short _WSAAPI_ ntohs(IN u_short netshort) { return NTOHS(netshort); }
int _WSAAPI_ WSAGetLastError() { return(GetLastError()); }
void _WSAAPI_ WSASetLastError(IN int error) { SetLastError(error); }
WSAEVENT _WSAAPI_ WSACreateEvent() { return(CreateEvent(NULL, TRUE, FALSE, NULL)); }
BOOL _WSAAPI_ WSACloseEvent(IN WSAEVENT hEvent) { return(CloseHandle(hEvent)); }
BOOL _WSAAPI_ WSASetEvent(IN WSAEVENT hEvent) { return(SetEvent(hEvent)); }
BOOL _WSAAPI_ WSAResetEvent(IN WSAEVENT hEvent) { return(ResetEvent(hEvent)); }
DWORD _WSAAPI_ WSAWaitForMultipleEvents(IN DWORD cEvents, IN const WSAEVENT* lphEvents,
IN BOOL fWaitAll, IN DWORD dwTimeout, IN BOOL fAlertable)
{
return(WaitForMultipleObjectsEx(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable));
}
int _WSAAPI_ __WSAFDIsSet(SOCKET fd, fd_set * set)
{
int i = (set->fd_count & 0xffff);
while (i--)
{
if (set->fd_array[i] == fd)
return 1;
}
return 0;
}
// ---------------------------------------------------------------------------------------
// SockLock
// ---------------------------------------------------------------------------------------
CSocket * CSocket::Lock(SOCKET s)
{
CSocket * pSocket;
LONG lLock;
int err;
// NOTE: We don't allow multiple threads to access the same
// socket simultaneously. If the app tries to do that, the first
// thread will succeed and other threads will get an error return.
if (s == 0 || s == INVALID_SOCKET)
{
err = WSAENOTSOCK;
}
else
{
pSocket = (CSocket *)s;
lLock = InterlockedCompareExchange(&pSocket->_lLock, SOCK_LOCK_BUSY, SOCK_LOCK_ACTIVE);
if (lLock == SOCK_LOCK_ACTIVE)
return(pSocket);
err = (lLock == SOCK_LOCK_BUSY) ? WSAEINPROGRESS : WSAENOTSOCK;
}
#if defined(XNET_FEATURE_TRACE) && !defined(XNET_FEATURE_XBDM_SERVER)
if (err == WSAEINPROGRESS)
{
TraceSz1(sockWarn, "[%X] Socket thread contention", s);
}
#endif
SetLastError(err);
return(NULL);
}
// ---------------------------------------------------------------------------------------
// CXnSock::SockInit
// ---------------------------------------------------------------------------------------
NTSTATUS CXnSock::SockInit(XNetInitParams * pxnip)
{
TCHECK(USER);
LARGE_INTEGER dueTime;
UINT period;
NTSTATUS status = IpInit(pxnip);
if (!NT_SUCCESS(status))
return(status);
SetInitFlag(INITF_SOCK);
InitializeListHead(&_leSockets);
InitializeListHead(&_leDeadSockets);
_ipportTempNext = MAX_TEMP_PORT;
return(NETERR_OK);
}
void CXnSock::SockTerm()
{
TCHECK(USER);
RaiseToDpc();
if (TestInitFlag(INITF_SOCK))
{
SockStop();
SockShutdown();
}
SetInitFlag(INITF_SOCK_TERM);
IpTerm();
}
void CXnSock::SockShutdown()
{
if (TestInitFlag(INITF_SOCK))
{
RaiseToDpc();
while (!IsListEmpty(&_leSockets))
{
CSocket * pSocket = (CSocket*)_leSockets.Flink;
if (pSocket->TestFlags(SOCKF_OWNED))
{
TraceSz1(LeakWarn, "Socket %08lX not closed before WSACleanup", pSocket);
}
SockClose(pSocket, TRUE);
}
while (!IsListEmpty(&_leDeadSockets))
{
CSocket * pSocket = (CSocket*) _leDeadSockets.Flink;
SockClose(pSocket, TRUE);
}
}
}
// ---------------------------------------------------------------------------------------
// SockWaitForEvent
// ---------------------------------------------------------------------------------------
NTSTATUS CXnSock::SockWaitForEvent(CSocket* pSocket, INT eventMask, UINT timeout)
/*++
Routine Description:
Block the current thread until the specified CSocket event is signalled
Arguments:
pSocket - Points to the protocol control block
eventMask - Flag bit to indicate which event to block on
timeout - Specifies the wait timeout (in milliseconds, 0 means forever)
Return Value:
Status code
--*/
{
INT readyMask;
NTSTATUS status = NETERR_OK;
//
// Check if the specified event is already available
// or if the socket connection has been reset.
//
readyMask = SockCheckSelectEvents(pSocket, eventMask, 1);
if (readyMask & SOCKF_EVENT_RESET) goto exit;
if (readyMask != 0) return NETERR_OK;
status = EvtWait(pSocket->GetEvent(), timeout) ? NETERR_OK : NETERR_TIMEOUT;
pSocket->ClearFlags(SOCKF_EVENT_MASK);
exit:
return NT_SUCCESS(status) ? pSocket->GetStatus() : status;
}
void CXnSock::SockReqComplete(CSocket * pSocket, CSockReq * pSockReq, NTSTATUS status)
{
if (pSockReq)
{
if (pSocket->GetSendReq() == pSockReq)
pSocket->SetSendReq(NULL);
else if (pSocket->GetRecvReq() == pSockReq)
pSocket->SetRecvReq(NULL);
pSockReq->_pWsaOverlapped->_iostatus = status;
EvtSet(pSockReq->_pEvent, EVENT_INCREMENT);
if (pSockReq->_pEvent != pSocket->GetEvent())
{
EvtDereference(pSockReq->_pEvent);
SysFree(pSockReq);
}
}
}
CSocket * CXnSock::SockFindMatch(CIpPort toport, CIpAddr fromaddr, CIpPort fromport, BYTE type)
{
TCHECK(SDPC);
CSocket * pSocket;
CSocket * pSocketBest = NULL;
BOOL fIsUdp = (type == SOCK_DGRAM);
UINT cWildMin = 3;
UINT cWild;
for (pSocket = GetFirstSocket(); pSocket; pSocket = GetNextSocket(pSocket))
{
if (!!pSocket->IsUdp() != fIsUdp)
continue;
if (pSocket->_ipportSrc != toport)
continue;
cWild = 0;
if (pSocket->_ipaDst != fromaddr)
{
if (pSocket->_ipaDst)
continue;
cWild++;
}
if (pSocket->_ipportDst != fromport)
{
if (pSocket->_ipportDst)
continue;
cWild++;
}
if (cWild == 0)
{
pSocketBest = pSocket;
break;
}
if (cWild < cWildMin)
{
pSocketBest = pSocket;
cWildMin = cWild;
}
}
return(pSocketBest);
}
void CSocket::SetFlagsAndOr(DWORD dwAnd, DWORD dwOr)
{
ICHECK_(GetXnBase(), SOCK, USER|UDPC|SDPC);
DWORD dwFlagsOld, dwFlagsNew;
while (1)
{
dwFlagsOld = _dwFlags;
dwFlagsNew = (dwFlagsOld & dwAnd) | dwOr;
if (InterlockedCompareExchange((LONG *)&_dwFlags, (LONG)dwFlagsNew, (LONG)dwFlagsOld) == (LONG)dwFlagsOld)
break;
TraceSz(Warning, "CSocket::SetFlagsAndOr: Thread/DPC contention detected. Retrying.");
}
}
// ---------------------------------------------------------------------------------------
// SockReset
// ---------------------------------------------------------------------------------------
void CXnSock::SockReset(CIpAddr ipa)
{
TCHECK(UDPC|SDPC);
if (ipa != 0 && TestInitFlag(INITF_SOCK) && !TestInitFlag(INITF_SOCK_TERM))
{
CSocket * pSocket;
CSocket * pSocketNext;
CTcpSocket * pTcpSocket;
for (pSocket = GetFirstSocket(); pSocket; pSocket = pSocketNext)
{
pSocketNext = GetNextSocket(pSocket);
if (pSocket->IsTcp() && pSocket->_ipaDst == ipa)
{
pTcpSocket = (CTcpSocket *)pSocket;
if (!pTcpSocket->IsIdleState())
{
TcpReset(pTcpSocket, NETERR_CONNRESET);
}
}
}
}
}