/*==========================================================================
 *
 *  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
 *   01-jan-2000 aarono aaded support for rsip
 *
 ***************************************************************************/
/*============================================================================
*                                                                             
*  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
 */ 
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=FALSE,gbIPStarted=FALSE;
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_inet_ntoa		g_inet_ntoa;
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;
cb_ntohs			g_ntohs;
cb_inet_addr		g_inet_addr;
cb_htonl            g_htonl;

#ifdef DEBUG

#undef DPF_MODNAME
#define DPF_MODNAME	"DebugPrintAddr"

// helper function called from DEBUGPRINTADDR macro
void DebugPrintAddr(UINT nLevel,LPSTR pStr,SOCKADDR * psockaddr)
{
    SOCKADDR_IN * pin = (SOCKADDR_IN *)psockaddr;

     DPF(nLevel,"%s af = AF_INET : address =  %s : port = %d\n",pStr,
     	g_inet_ntoa(pin->sin_addr),g_htons(pin->sin_port));

} // DebugPrintAddr

#undef DPF_MODNAME
#define DPF_MODNAME	"DebugPrintSocket"

void DebugPrintSocket(UINT level,LPSTR pStr,SOCKET * pSock) 
{
	SOCKADDR sockaddr;
	int addrlen=sizeof(sockaddr);

	g_getsockname(*pSock,&sockaddr,&addrlen);
	DEBUGPRINTADDR(level,pStr,&sockaddr);
	
}

#endif // debug

// this is called every time we add a new server node to our list...
HRESULT GetDefaultHostAddr(DWORD * puHostAddr)
{

//	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...

    *puHostAddr = 0x0100007f;	// loopback	127.0.0.1 (<bleeping> little-endian)
	
    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.sin_family =  AF_INET;
    
    // find the default ip to use w/ this host
    hr = GetDefaultHostAddr(&(pNode->sockaddr.sin_addr.s_addr));
	if (FAILED(hr))
    {
        DPF_ERR("could not get host IP address");
		MemFree(pNode);
        return (DPERR_UNAVAILABLE);
    }
    
    pNode->sockaddr.sin_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.sin_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 IP_SetAddr(LPVOID pmsg,SOCKADDR_IN * paddrSrc)
{
    LPSOCKADDR_IN  paddrDest; // tempo variable, makes casting less ugly
    LPMESSAGEHEADER phead;

    phead = (LPMESSAGEHEADER)pmsg;

    paddrDest = (SOCKADDR_IN *)&(phead->sockaddr);
    // poke the new ip addr into the message header
    if(paddrDest->sin_addr.s_addr==0){ // don't rehome already homed messages
	    paddrDest->sin_addr.s_addr = paddrSrc->sin_addr.s_addr;
	}    

    return;
	
} // IP_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_IN * psockaddr)
{
    LPSPNODE pNode = gNodeList;
    UINT addrlen = sizeof(SOCKADDR_IN);
    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
    IP_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

//
// 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;
    INT addrlen=sizeof(SOCKADDR);
    SOCKADDR sockaddr; // the from address
    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;
    }
    while (1)
    {
        err = g_recvfrom(gsDatagramListener,pBuffer,dwBufSize,0,&sockaddr,&addrlen);
        if (SOCKET_ERROR == err) 
        {
            err = g_WSAGetLastError();
            if (WSAEMSGSIZE == err)
            {
                LPBYTE pNewBuffer;

                // buffer too small!
                if(dwBufSize < 16384)
                {
	                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...
                } else {
                	DPF(4,"SECURITY WARN: very large enum request received > 16K");
	            }
            } // 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_IN *)&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_inet_ntoa = (cb_inet_ntoa) GetProcAddress(hWinsock, "inet_ntoa");
	if (!g_inet_ntoa)
		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_ntohs = (cb_ntohs) GetProcAddress(hWinsock, "ntohs");
	if (!g_ntohs)
		goto GETPROCADDRESSFAILED;
		
    g_htonl = (cb_htonl) GetProcAddress(hWinsock, "htonl");
	if (!g_htonl)
		goto GETPROCADDRESSFAILED;
		
    g_inet_addr = (cb_inet_addr) GetProcAddress(hWinsock, "inet_addr");
	if (!g_inet_addr)
		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");
    
	gbIPStarted=TRUE;
	
    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_IN sockaddr;
    UINT err;
    SOCKET sNew;

    sNew = g_socket( AF_INET, type, 0);
    if (INVALID_SOCKET == sNew) 
    {
        goto ERROR_EXIT;
    }

    // set up the sockaddr to bind to
    sockaddr.sin_family         = PF_INET;
    sockaddr.sin_addr.s_addr    = INADDR_ANY;
    sockaddr.sin_port           = port;

    // do the bind
    if( SOCKET_ERROR == g_bind( sNew, (LPSOCKADDR)&sockaddr, sizeof(sockaddr) ) )
    {
        goto ERROR_EXIT;
    }

    if (bBroadcast)
    {
		BOOL bTrue = TRUE;

        if( SOCKET_ERROR == g_setsockopt( sNew,SOL_SOCKET,SO_BROADCAST,(char FAR *)&bTrue,
	    	sizeof(bTrue) ) )
		{
            err = g_WSAGetLastError();
		    DPF(0," dphelp - create - could not set broadcast err = %d\n",err);
		    // not really tragic, since for AF_INET it's not required to set broadcast 
		    // b4 receiving w/ MS winsock...
		}
    }

    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

HRESULT DPlayHelp_Init()
{
    DWORD dwThreadID;
    HRESULT hr;

    // start winsock, and get the default ip addr for this system
    if(!gbIPStarted){
	    hr = StartupIP();
	    if (FAILED(hr))
	    {
	        return hr; // StartupIP will have printed an error
	    }
	}

    // 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