/*========================================================================== * * 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; iiAddressCount; 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