windows-nt/Source/XPSP1/NT/net/tcpip/tpipv6/dplsvr6/dphelp.c

802 lines
22 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*==========================================================================
*
* Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
*
* File: dphelp.c
* Content: allows the dplay winsock sp's to all share a single
* server socket
* History:
* Date By Reason
* ==== == ======
* 18-jul-96 andyco initial implementation
* 25-jul-96 andyco ddhelp now watches dplay procs so it can remove
* them from our list when they go away
* 3-sep-96 andyco don't get stale ip's - pick up a default ip whenever
* we add a servernode. bug 3716.
* 2-oct-96 andyco propagated from \orange\ddhelp.2 to \mustard\ddhelp
* 3-oct-96 andyco made the winmain crit section "cs" a global so we can take
* it in dphelps receive thread before forwarding requests
* 21-jan-97 kipo use LoadLibrary on "wsock32.dll" instead of statically
* linking to it so DDHELP will still run even when Winsock
* is not around. This lets DDRAW and DSOUND work. Fixes
* bug #68596.
* 15-feb-97 andyco moved from ddhelp to the project formerly known as
* ddhelp (playhelp? dplayhlp? dplay.exe? dphost?) Allowed
* one process to host mulitple sessions
* 29-jan-98 sohailm added support for stream enum sessions
*
***************************************************************************/
/*============================================================================
*
* Why this file exists :
*
* when you want to find a dplay game, you send a message to a well
* known port (an enumrequest).
*
* if a game is being hosted on that system, it will listen on that
* port, and respond to the message.
*
* BUT, only one process can listen on a given socket.
*
* So, we let ddhelp.exe listen on that socket, and forward enumrequests
* to all games registered as being hosted on this system.
*
* see also : \%MANROOT%\dplay\wsock\dpsp.h
*
*****************************************************************************/
// todo - should we return error codes on AddServer xproc to our caller?
#include "dphelp.h"
#undef DPF_MODNAME
#define DPF_MODNAME "DPHELP"
/*
* GLOBALS
*/
const IN6_ADDR in6addr_multicast = IN6ADDR_MULTICAST_INIT;
SOCKET gsDatagramListener = INVALID_SOCKET; // we listen for datagrams on this socket
SOCKET gsForwardSocket = INVALID_SOCKET;
SOCKET gsStreamListener; // we listen for tcp connections on this socket
LPSPNODE gNodeList;
BOOL gbInit;
HANDLE ghDatagramReceiveThread,ghStreamReceiveThread;
BOOL gbReceiveShutdown; // receive thread will exit when TRUE
// pointers to Winsock routines returned from GetProcAddress
cb_accept g_accept;
cb_bind g_bind;
cb_closesocket g_closesocket;
cb_gethostbyname g_gethostbyname;
cb_gethostname g_gethostname;
cb_getpeername g_getpeername;
cb_getsockname g_getsockname;
cb_recvfrom g_recvfrom;
cb_recv g_recv;
cb_select g_select;
cb_send g_send;
cb_sendto g_sendto;
cb_setsockopt g_setsockopt;
cb_shutdown g_shutdown;
cb_socket g_socket;
cb_WSAFDIsSet g_WSAFDIsSet;
cb_WSAGetLastError g_WSAGetLastError;
cb_WSAStartup g_WSAStartup;
cb_listen g_listen;
cb_htons g_htons;
#ifdef DEBUG
#undef DPF_MODNAME
#define DPF_MODNAME "DebugPrintAddr"
// helper function called from DEBUGPRINTADDR macro
void DebugPrintAddr(UINT nLevel,LPSTR pStr,SOCKADDR * psockaddr)
{
char buff[INET6_ADDRSTRLEN];
int ret;
LPSOCKADDR_IN6 pin6 = (LPSOCKADDR_IN6)psockaddr;
ULONG ulLength = INET6_ADDRSTRLEN;
ret = WSAAddressToString(psockaddr, sizeof(SOCKADDR_IN6), NULL,
buff, &ulLength);
if (!ret)
DPF(nLevel,"%s af = AF_INET6 : address = %s\n",pStr,buff);
} // DebugPrintAddr
#undef DPF_MODNAME
#define DPF_MODNAME "DebugPrintSocket"
void DebugPrintSocket(UINT level,LPSTR pStr,SOCKET * pSock)
{
SOCKADDR_IN6 sockaddr;
int addrlen=sizeof(sockaddr);
g_getsockname(*pSock,(LPSOCKADDR)&sockaddr,&addrlen);
DEBUGPRINTADDR(level,pStr,&sockaddr);
}
#endif // debug
// this is called every time we add a new server node to our list...
HRESULT GetDefaultHostAddr(SOCKADDR_IN6 * psockaddr)
{
// a-josbor: we used to get the first interface and use that, but WebTV taught
// us that that can be dangerous. So we just use the loopback address.
// It's guaranteed to be there. Or so they say...
ZeroMemory(psockaddr, sizeof(SOCKADDR_IN6));
psockaddr->sin6_family = AF_INET6;
psockaddr->sin6_addr = in6addr_loopback;
return DP_OK;
} // GetDefaultHostAddr
// the functions DPlayHelp_xxx are called from dphelp.c
//
// add a new node to our list of servers which want to have enum
// requests forwarded to them...
HRESULT DPlayHelp_AddServer(LPDPHELPDATA phd)
{
LPSPNODE pNode;
BOOL bFoundIt=FALSE;
HRESULT hr;
if (!gbInit)
{
hr = DPlayHelp_Init();
if (FAILED(hr))
{
DPF_ERR("dphelp : could not init wsock ! not adding server");
return (hr);
}
}
// see if we're already watching this process
// if we are, we won't start a watcher thread (below)
pNode = gNodeList;
// search the list
while (pNode && !bFoundIt)
{
if (pNode->pid == phd->pid) bFoundIt = TRUE;
pNode = pNode->pNextNode;
}
//
// now, build a new server node
pNode = MemAlloc(sizeof(SPNODE));
if (!pNode)
{
DPF_ERR("could not add new server node OUT OF MEMORY");
return (DPERR_OUTOFMEMORY);
}
pNode->pid = phd->pid;
// build the sockaddr
// dwReserved1 of the phd is the port that the server is listening on
pNode->sockaddr.sin6_family = AF_INET6;
// find the default ip to use w/ this host
hr = GetDefaultHostAddr(&(pNode->sockaddr));
if (FAILED(hr))
{
DPF_ERR("could not get host IP address");
MemFree(pNode);
return (DPERR_UNAVAILABLE);
}
pNode->sockaddr.sin6_port = phd->port;
DPF(5,"dphelp :: adding new server node : pid = %d, port = %d\n",phd->pid,g_htons(phd->port));
// link our new node onto the beginning of the list
pNode->pNextNode = gNodeList;
gNodeList = pNode;
// see if we need to start our watcher thread
if (!bFoundIt)
{
//
// set up a thread to keep on eye on this process.
// we'll let the thread notify us when the process goes away
WatchNewPid(phd);
}
return (DP_OK);
} // DPlayHelp_AddServer
//
// delete the server node from proc pid from our list
// called by "ThreadProc" from DPHELP.c when the process that
// goes away, or from the client side when a session goes away.
//
// if bFreeAll is TRUE, we delete all server nodes for process
// phd->pid. otherwise, we just delete the first server node whose
// port matches phd->port
//
BOOL FAR PASCAL DPlayHelp_DeleteServer(LPDPHELPDATA phd,BOOL bFreeAll)
{
BOOL bFoundIt = FALSE;
LPSPNODE pNode,pNodePrev,pNodeNext;
pNode = gNodeList;
pNodePrev = NULL;
pNodeNext = NULL;
// search the whole list
while (pNode && !bFoundIt)
{
// if we have the right pid, and it's either FreeAll or the right port - cruise it!
if ((pNode->pid == phd->pid) && (bFreeAll || (pNode->sockaddr.sin6_port == phd->port)) )
{
// remove it from the list
if (pNodePrev) pNodePrev->pNextNode = pNode->pNextNode;
else gNodeList = pNode->pNextNode;
if (bFreeAll)
{
// pick up the next one b4 we free pNode
pNodeNext = pNode->pNextNode;
}
else
{
// mark us as done
bFoundIt = TRUE;
pNodeNext = NULL;
}
DPF(5,"dphelp :: deleting server node : pid = %d\n",pNode->pid);
// free up the node
MemFree(pNode);
pNode = pNodeNext;
// pNodePrev doesn't change here...
}
else
{
// just get the next one
pNodePrev = pNode;
pNode = pNode->pNextNode;
}
}
return FALSE;
} // DPlayHelp_DeleteServer
//
// poke an ip addr into a message blob
// code stolen from \orange\dplay\wsock\winsock.c
void IP6_SetAddr(LPVOID pmsg,SOCKADDR_IN6 * paddrSrc)
{
LPSOCKADDR_IN6 paddrDest; // tempo variable, makes casting less ugly
LPMESSAGEHEADER phead;
phead = (LPMESSAGEHEADER)pmsg;
paddrDest = (SOCKADDR_IN6 *)&(phead->sockaddr);
// poke the new ip addr into the message header
paddrDest->sin6_addr = paddrSrc->sin6_addr;
return;
} // IP6_SetAddr
//
// we get a message. presumably its an enumrequest. forward it to all registered clients.
// we "home" the message (store the received ip addr w/ it) here, 'cause otherwise the clients
// would all think it came from us. we change the token to srvr_token so the clients know it
// came from us (so they don't home it again)
void HandleIncomingMessage(LPBYTE pBuffer,DWORD dwBufferSize,SOCKADDR_IN6 * psockaddr)
{
LPSPNODE pNode = gNodeList;
UINT addrlen = sizeof(SOCKADDR_IN6);
UINT err;
ASSERT(VALID_SP_MESSAGE(pBuffer));
// reset the old token
*( (DWORD *)pBuffer) &= ~TOKEN_MASK;
// set the new token
*( (DWORD *)pBuffer) |= HELPER_TOKEN;
// home it
IP6_SetAddr((LPVOID)pBuffer,psockaddr);
// now, forward the message to all registered servers
while (pNode)
{
DEBUGPRINTADDR(7,"dplay helper :: forwarding enum request to",(SOCKADDR *)&(pNode->sockaddr));
// send out the enum message
err = g_sendto(gsForwardSocket,pBuffer,dwBufferSize,0,(LPSOCKADDR)&(pNode->sockaddr),
addrlen);
if (SOCKET_ERROR == err)
{
err = g_WSAGetLastError();
DPF(0,"dphelp : send failed err = %d\n",err);
}
pNode = pNode->pNextNode;
}
return ;
} // HandleIncomingMessage
#if 1
void JoinEnumGroups(SOCKET s)
{
SOCKET_ADDRESS_LIST *pList;
int i;
LPSOCKADDR_IN6 paddr;
HRESULT hr;
//
// join link-local multicast group for enumeration on every link
//
// do a passive getaddrinfo
pList = GetHostAddr();
if (pList)
{
// for each linklocal address
for (i=0; i<pList->iAddressCount; i++)
{
paddr = (LPSOCKADDR_IN6)pList->Address[i].lpSockaddr;
// skip if not linklocal
if (!IN6_IS_ADDR_LINKLOCAL(&paddr->sin6_addr))
{
continue;
}
// join the multicast group on that ifindex
if (SOCKET_ERROR == JoinEnumGroup(s, paddr->sin6_scope_id))
{
DPF(0,"join enum group failed - err = %d\n",WSAGetLastError());
closesocket(s);
}
}
FreeHostAddr(pList);
}
}
#endif
//
// BUF_SIZE is our initial guess at a receive buffer size
// if we get an enum request bigger than this, we'll realloc our
// buffer, and receive successfully if they send again
// (the only way this could happen is if they have password > ~ 1000
// bytes).
#define BUF_SIZE 1024
//
// listen on our socket for enum requests
DWORD WINAPI ListenThreadProc(LPVOID pvUnused)
{
UINT err;
LPBYTE pBuffer=NULL;
SOCKADDR_IN6 sockaddr; // the from address
INT addrlen=sizeof(sockaddr);
DWORD dwBufSize = BUF_SIZE;
DPF(2,"dphelp :: starting udp listen thread ");
pBuffer = MemAlloc(BUF_SIZE);
if (!pBuffer)
{
DPF_ERR("could not alloc dgram receive buffer");
ExitThread(0);
return 0;
}
JoinEnumGroups(gsDatagramListener);
while (1)
{
err = g_recvfrom(gsDatagramListener,pBuffer,dwBufSize,0,(LPSOCKADDR)&sockaddr,&addrlen);
if (SOCKET_ERROR == err)
{
err = g_WSAGetLastError();
if (WSAEMSGSIZE == err)
{
LPBYTE pNewBuffer;
// buffer too small!
dwBufSize *= 2;
DPF(9,"\n udp recv thread - resizing buffer newsize = %d\n",dwBufSize);
pNewBuffer = MemReAlloc(pBuffer,dwBufSize);
if (!pNewBuffer)
{
DPF_ERR("could not realloc dgram receive buffer");
goto ERROR_EXIT;
}
pBuffer = pNewBuffer;
// we can't do anything with this message, since it was truncated...
} // WSAEMSGSIZE
else
{
#ifdef DEBUG
if (WSAEINTR != err)
{
// WSAEINTR is what winsock uses to break a blocking socket out of
// its wait. it means someone killed this socket.
// if it's not that, then it's a real error.
DPF(0,"\n udp recv error - err = %d socket = %d",err,(DWORD)gsDatagramListener);
}
else
{
DPF(9,"\n udp recv error - err = %d socket = %d",err,(DWORD)gsDatagramListener);
}
#endif // DEBUG
// we bail on errors other than WSAEMSGSIZE
goto ERROR_EXIT;
}
} // SOCKET_ERROR
else if ((err >= sizeof(DWORD)) && VALID_SP_MESSAGE(pBuffer))
{
// now, if we succeeded, err is the # of bytes read
DEBUGPRINTADDR(9,"dplay helper :: received enum request from ",(SOCKADDR *)&sockaddr);
// take the dplay lock so no one messes w/ our list of registered serves while we're
// trying to send to them...
ENTER_DPLAYSVR();
HandleIncomingMessage(pBuffer,err,(SOCKADDR_IN6 *)&sockaddr);
// give up the lock
LEAVE_DPLAYSVR();
}
else
{
ASSERT(FALSE);
// ?
}
} // 1
ERROR_EXIT:
DPF(2,"UDP Listen thread exiting");
if (pBuffer) MemFree(pBuffer);
// all done
ExitThread(0);
return 0;
} // UDPListenThreadProc
// startup winsock and find the default ip addr for this machine
HRESULT StartupIP()
{
UINT err;
WSADATA wsaData;
HINSTANCE hWinsock;
// load winsock library
hWinsock = LoadLibrary("wsock32.dll");
if (!hWinsock)
{
DPF(0,"Could not load wsock32.dll\n");
goto LOADLIBRARYFAILED;
}
// get pointers to the entry points we need
g_accept = (cb_accept) GetProcAddress(hWinsock, "accept");
if (!g_accept)
goto GETPROCADDRESSFAILED;
g_bind = (cb_bind) GetProcAddress(hWinsock, "bind");
if (!g_bind)
goto GETPROCADDRESSFAILED;
g_closesocket = (cb_closesocket) GetProcAddress(hWinsock, "closesocket");
if (!g_closesocket)
goto GETPROCADDRESSFAILED;
g_gethostbyname = (cb_gethostbyname) GetProcAddress(hWinsock, "gethostbyname");
if (!g_gethostbyname)
goto GETPROCADDRESSFAILED;
g_gethostname = (cb_gethostname) GetProcAddress(hWinsock, "gethostname");
if (!g_gethostname)
goto GETPROCADDRESSFAILED;
g_getpeername = (cb_getpeername) GetProcAddress(hWinsock, "getpeername");
if (!g_getpeername)
goto GETPROCADDRESSFAILED;
g_getsockname = (cb_getsockname) GetProcAddress(hWinsock, "getsockname");
if (!g_getsockname)
goto GETPROCADDRESSFAILED;
g_htons = (cb_htons) GetProcAddress(hWinsock, "htons");
if (!g_htons)
goto GETPROCADDRESSFAILED;
g_listen = (cb_listen) GetProcAddress(hWinsock, "listen");
if (!g_listen)
goto GETPROCADDRESSFAILED;
g_recv = (cb_recv) GetProcAddress(hWinsock, "recv");
if (!g_recv)
goto GETPROCADDRESSFAILED;
g_recvfrom = (cb_recvfrom) GetProcAddress(hWinsock, "recvfrom");
if (!g_recvfrom)
goto GETPROCADDRESSFAILED;
g_select = (cb_select) GetProcAddress(hWinsock, "select");
if (!g_select)
goto GETPROCADDRESSFAILED;
g_send = (cb_send) GetProcAddress(hWinsock, "send");
if (!g_send)
goto GETPROCADDRESSFAILED;
g_sendto = (cb_sendto) GetProcAddress(hWinsock, "sendto");
if (!g_sendto)
goto GETPROCADDRESSFAILED;
g_setsockopt = (cb_setsockopt) GetProcAddress(hWinsock, "setsockopt");
if (!g_setsockopt)
goto GETPROCADDRESSFAILED;
g_shutdown = (cb_shutdown) GetProcAddress(hWinsock, "shutdown");
if (!g_shutdown)
goto GETPROCADDRESSFAILED;
g_socket = (cb_socket) GetProcAddress(hWinsock, "socket");
if (!g_socket)
goto GETPROCADDRESSFAILED;
g_WSAFDIsSet = (cb_WSAFDIsSet) GetProcAddress(hWinsock, "__WSAFDIsSet");
if (!g_WSAFDIsSet)
goto GETPROCADDRESSFAILED;
g_WSAGetLastError = (cb_WSAGetLastError) GetProcAddress(hWinsock, "WSAGetLastError");
if (!g_WSAGetLastError)
goto GETPROCADDRESSFAILED;
g_WSAStartup = (cb_WSAStartup) GetProcAddress(hWinsock, "WSAStartup");
if (!g_WSAStartup)
goto GETPROCADDRESSFAILED;
// start up sockets, asking for version 1.1
err = g_WSAStartup(MAKEWORD(1,1), &wsaData);
if (err)
{
DPF(0,"dphelp :: could not start winsock err = %d\n",err);
goto WSASTARTUPFAILED;
}
DPF(3,"dphelp :: started up winsock succesfully");
return DP_OK;
GETPROCADDRESSFAILED:
DPF(0,"Could not find required Winsock entry point");
WSASTARTUPFAILED:
FreeLibrary(hWinsock);
LOADLIBRARYFAILED:
return DPERR_UNAVAILABLE;
} // StartupIP
// helper function to create the socket we listen on
HRESULT GetSocket(SOCKET * psock,DWORD type,PORT port,BOOL bBroadcast,BOOL bListen)
{
SOCKADDR_IN6 sockaddr;
UINT err;
SOCKET sNew;
sNew = g_socket( AF_INET6, type, 0);
if (INVALID_SOCKET == sNew)
{
goto ERROR_EXIT;
}
// set up the sockaddr to bind to
ZeroMemory(&sockaddr, sizeof(sockaddr));
sockaddr.sin6_family = PF_INET6;
sockaddr.sin6_port = port;
// do the bind
if( SOCKET_ERROR == g_bind( sNew, (LPSOCKADDR)&sockaddr, sizeof(sockaddr) ) )
{
goto ERROR_EXIT;
}
if (bListen)
{
LINGER Linger;
// set up socket w/ max listening connections
err = g_listen(sNew,LISTEN_BACKLOG);
if (SOCKET_ERROR == err)
{
err = g_WSAGetLastError();
DPF(0,"init listen socket / listen error - err = %d\n",err);
goto ERROR_EXIT;
}
// set for hard disconnect
Linger.l_onoff=1;
Linger.l_linger=0;
if( SOCKET_ERROR == g_setsockopt( sNew,SOL_SOCKET,SO_LINGER,
(char FAR *)&Linger,sizeof(Linger) ) )
{
err = g_WSAGetLastError();
DPF(0,"Failed to set linger option on the socket = %d\n",err);
}
}
// success!
*psock = sNew;
return DP_OK;
ERROR_EXIT:
// clean up and bail
err = g_WSAGetLastError();
DPF(0,"dphelp - could not get helper socket :: err = %d\n",err);
if (INVALID_SOCKET != sNew)
{
g_closesocket(sNew);
}
return E_FAIL;
} // GetSocket
void CloseSocket(SOCKET * psSocket)
{
UINT err;
if (INVALID_SOCKET != *psSocket)
{
if (SOCKET_ERROR == g_closesocket(*psSocket))
{
err = g_WSAGetLastError();
DPF(1,"dphelp : killsocket - socket close err = %d\n",err);
}
*psSocket = INVALID_SOCKET;
}
return ;
} // CloseSocket
extern int
InitIPv6Library(void);
HRESULT DPlayHelp_Init()
{
DWORD dwThreadID;
HRESULT hr;
// start winsock, and get the default ip addr for this system
hr = StartupIP();
if (FAILED(hr))
{
return hr; // StartupIP will have printed an error
}
InitIPv6Library();
// get the listen socket
hr = GetSocket(&gsDatagramListener,SOCK_DGRAM,SERVER_DGRAM_PORT,TRUE,FALSE);
if (FAILED(hr))
{
goto ERROR_EXIT; // GetSocket will have printed an error
}
// get the forward socket
hr = GetSocket(&gsForwardSocket,SOCK_DGRAM,0,FALSE,FALSE);
if (FAILED(hr))
{
goto ERROR_EXIT; // GetSocket will have printed an error
}
// get us a enum sessions stream listener
hr = GetSocket(&gsStreamListener,SOCK_STREAM,SERVER_STREAM_PORT,FALSE,TRUE);
if (FAILED(hr))
{
goto ERROR_EXIT; // GetSocket will have printed an error
}
ghDatagramReceiveThread = CreateThread(NULL,0,ListenThreadProc,NULL,0,&dwThreadID);
if (!ghDatagramReceiveThread)
{
DPF_ERR("could not create udp listen thread");
hr = E_FAIL;
goto ERROR_EXIT; // GetSocket will have printed an error
}
ghStreamReceiveThread = CreateThread(NULL,0,StreamReceiveThreadProc,NULL,0,&dwThreadID);
if (!ghStreamReceiveThread)
{
DPF_ERR("could not create tcp listen thread");
hr = E_FAIL;
goto ERROR_EXIT; // GetSocket will have printed an error
}
DPF(5,"DPLAYHELP : init succeeded");
gbInit = TRUE;
return DP_OK;
ERROR_EXIT:
CloseSocket(&gsDatagramListener);
CloseSocket(&gsForwardSocket);
CloseSocket(&gsStreamListener);
return hr;
} // DPlayHelp_Init
void DPlayHelp_FreeServerList()
{
LPSPNODE pNodeKill,pNodeNext;
pNodeNext = gNodeList;
// search the whole list
while (pNodeNext)
{
// kill this node
pNodeKill = pNodeNext;
// but first, remember what's next
pNodeNext = pNodeKill->pNextNode;
// free up the node
MemFree(pNodeKill);
}
CloseSocket(&gsDatagramListener);
CloseSocket(&gsForwardSocket);
// close stream receive
RemoveSocketFromList(gsStreamListener);
gbReceiveShutdown = TRUE;
// drop the lock so the threads can exit - they might be waiting on
// the lock for cleanup
LEAVE_DPLAYSVR();
// wait for the threads to go away
if (ghDatagramReceiveThread)
WaitForSingleObject(ghDatagramReceiveThread, INFINITE);
if (ghStreamReceiveThread)
WaitForSingleObject(ghStreamReceiveThread, INFINITE);
ENTER_DPLAYSVR();
if (ghDatagramReceiveThread)
{
DPF(5,"datagram receive thread exited!");
CloseHandle(ghDatagramReceiveThread);
ghDatagramReceiveThread = NULL;
}
if (ghStreamReceiveThread)
{
DPF(5,"stream receive thread exited!");
CloseHandle(ghStreamReceiveThread);
ghStreamReceiveThread = NULL;
}
return ;
} // DPlayHelp_FreeServerList