|
|
/*==========================================================================
* * Copyright (C) 1995 Microsoft Corporation. All Rights Reserved. * * File: winsock.c * Content: windows socket support for dpsp * History: * Date By Reason * ==== == ====== * 3/15/96 andyco created it * 4/12/96 andyco got rid of dpmess.h! use DPlay_ instead of message macros * 4/18/96 andyco added multihomed support, started ipx * 4/25/96 andyco messages now have blobs (sockaddr's) instead of dwReserveds * 5/31/96 andyco all non-system players share a socket (gsStream and * gsDGramSocket). * 7/18/96 andyco added dphelp for server socket * 8/1/96 andyco no retry on connect failure * 8/15/96 andyco local + remote data - killthread * 8/30/96 andyco clean it up b4 you shut it down! added globaldata. * 9/4/96 andyco took out bye_bye message * 12/18/96 andyco de-threading - use a fixed # of prealloced threads. * cruised the enum socket / thread - use the system * socket / thread instead * 3/17/97 kipo rewrote server dialog code to not use global variable * to return the address and to return any errors getting * the address, especially DPERR_USERCANCEL * 5/12/97 kipo the server address string is now stored in the globals * at SPInit and resolved when you do EnumSessions so we * will return any errors at that time instead of popping * the dialog again. Fixes bug #5866 * 11/19/97 myronth Changed LB_SETCURSEL to CB_SETCURSEL (#12711) * 01/27/98 sohaim added firewall support. * 02/13/98 aarono added async support. * 2/18/98 a-peterz Comment byte order for address and port params (CreateSocket) * 6/19/98 aarono turned on keepalive on reliable sockets. If we * don't do this we can hang if the send target crashes * while in a low buffer (i.e. no buffer) state. * 7/9/99 aarono Cleaning up GetLastError misuse, must call right away, * before calling anything else, including DPF. * 1/12/99 aarono added rsip support * 2/21/00 aarono fix socket leaks ***************************************************************************/
#include "dpsp.h"
#if USE_RSIP
#include "rsip.h"
#elif USE_NATHELP
#include "nathelp.h"
#endif
// backlog for listen() api. no constant in winsock, so we ask for the moon
#define LISTEN_BACKLOG 60
// how long to wait, in ms, til we abort a blocking WinSock connect() call
#define CONNECT_WATCHER_TIMEOUT 15000
/*
** CreateSocket * * CALLED BY: all over * * PARAMETERS: * pgd - pointer to a global data * psock - new socket. return value. * type - stream or datagram * port - what port we bind to (host byte order) * address - what address to use (net byte order) * *perr - set to the last socket error if fn fails * bInRange - use reserved range of ports; we also use this to determine whether we need to try mapping it on the NAT or not * * DESCRIPTION: * creates a new socket. binds to port specified, at the address specified * * RETURNS: DP_OK or E_FAIL. if E_FAIL, *perr is set with socket error code (see winsock.h) * */
HRESULT FAR PASCAL CreateSocket(LPGLOBALDATA pgd,SOCKET * psock,INT type,WORD wApplicationPort,ULONG address, SOCKERR * perr,BOOL bInRange) { WORD wRebindPort = 0; SOCKET sNew; SOCKADDR sockAddr; int bTrue = TRUE; int protocol = 0; BOOL bBroadcast = FALSE; WORD wPort; BOOL bBound = FALSE; UINT err; #if USE_NATHELP
BOOL bPortMapped=FALSE; BOOL ftcp_udp; #endif
*psock = INVALID_SOCKET; // in case we bail
// Create the socket.
if (AF_IPX == pgd->AddressFamily) { // set up protocol for ipx
if (SOCK_STREAM == type) { protocol = NSPROTO_SPXII; } else protocol = NSPROTO_IPX; }
Rebind:
sNew = socket( pgd->AddressFamily, type, protocol); if (INVALID_SOCKET == sNew) { // no cleanup needed, just bail
*perr = WSAGetLastError(); return E_FAIL; }
if (wRebindPort == 0) { DPF(8,"Creating new socket %d (app port = %u, bInRange = %i)",sNew,wApplicationPort,bInRange); } else { DPF(8,"Rebinding to port %u, new socket %d",wRebindPort,sNew); }
// try to bind an address to the socket.
// set up the sockaddr
memset(&sockAddr,0,sizeof(sockAddr)); switch (pgd->AddressFamily) { case AF_INET: { if ((SOCK_STREAM == type)) { // turn ON keepalive
if (SOCKET_ERROR == setsockopt(sNew, SOL_SOCKET, SO_KEEPALIVE, (CHAR FAR *)&bTrue, sizeof(bTrue))) { err = WSAGetLastError(); DPF(0,"Failed to turn ON keepalive - continue : err = %d\n",err); }
ASSERT(bTrue); // turn off nagling
if(pgd->dwSessionFlags & DPSESSION_OPTIMIZELATENCY) {
DPF(5, "Turning nagling off on socket"); if (SOCKET_ERROR == setsockopt(sNew, IPPROTO_TCP, TCP_NODELAY, (CHAR FAR *)&bTrue, sizeof(bTrue))) { err = WSAGetLastError(); DPF(0,"Failed to turn off nagling - continue : err = %d\n",err); } }
// Start out preventing others from using this port (and keeping us
// from sharing a port with another copy of DPlay). This may fail
// if we're not on NT, or we're not admin. The default settings
// will still work (and are pretty much as secure with the .NET
// Server behavior changes).
// The next time through we need to allow sharing in order for
// the fastsock code to work properly.
if (wRebindPort == 0) { SetExclusivePortAccess(sNew); } else { SetSharedPortAccess(sNew); } }
((SOCKADDR_IN *)&sockAddr)->sin_family = PF_INET; ((SOCKADDR_IN *)&sockAddr)->sin_addr.s_addr = address; ((SOCKADDR_IN *)&sockAddr)->sin_port = (wRebindPort == 0) ? htons(wApplicationPort) : htons(wRebindPort); if (bInRange && !wApplicationPort && !wRebindPort) { #ifdef RANDOM_PORTS
USHORT rndoffset; #else // ! RANDOM_PORTS
USHORT wInitialPort; #endif // ! RANDOM_PORTS
DPF(5, "Application didn't specify a port - using dplay range");
#ifdef RANDOM_PORTS
rndoffset=(USHORT)(GetTickCount()%DPSP_NUM_PORTS); //(USHORT)(0);// make predictable!
if (type != SOCK_STREAM) { // workaround bug in winsock using the same socket.
rndoffset = ((rndoffset + DPSP_NUM_PORTS/2) % DPSP_NUM_PORTS); } wPort = DPSP_MIN_PORT+rndoffset; #else // ! RANDOM_PORTS
wInitialPort = DPSP_MIN_PORT; if (type != SOCK_STREAM) { // workaround bug in winsock using the same socket.
wInitialPort = wInitialPort + (DPSP_NUM_PORTS / 2); }
// minimize problem with ICS machine stealing client's NAT connection entries by
// picking a different starting point on the ICS machine
if (natIsICSMachine(pgd)) { wInitialPort += (DPSP_NUM_PORTS / 4); } wPort = wInitialPort; #endif // ! RANDOM_PORTS
do { #if USE_NATHELP
HRESULT hr;
if (pgd->pINatHelp) { if (type == SOCK_STREAM) { if (pgd->hNatHelpTCP) { DPF(1, "Already have registered TCP port 0x%x.", pgd->hNatHelpTCP); goto pass_nat; }
ftcp_udp=TRUE; } else { if (pgd->hNatHelpUDP) { DPF(1, "Already have registered UDP port 0x%x.", pgd->hNatHelpUDP); goto pass_nat; }
ftcp_udp=FALSE; } hr = natRegisterPort(pgd, ftcp_udp, wPort); if (hr == DPNHERR_PORTUNAVAILABLE) { DPF(1,"CreateSocket: NatHelp said port %u was already in use, trying another.", wPort); goto try_next_port; }
if (hr != DP_OK) { DPF(1,"CreateSocket: NatHelp returned error 0x%x, port %u will not have mapping.", hr, wPort); } else { bPortMapped=TRUE; DPF(1,"CreateSocket: NatHelp successfully mapped port %u.", wPort); } }
pass_nat: #endif
DPF(5, "Trying to bind to port %d",wPort); ((SOCKADDR_IN *)&sockAddr)->sin_port = htons(wPort); // do the bind
if( SOCKET_ERROR != bind( sNew, (LPSOCKADDR)&sockAddr, sizeof(sockAddr) ) ) { bBound = TRUE; DPF(5, "Successfully bound to port %d", wPort); } else { err = WSAGetLastError(); DPF(1, "Bind to specific port failed (err %u), continuing.", err); try_next_port: #if USE_NATHELP
if (bPortMapped) { natDeregisterPort(pgd,ftcp_udp); bPortMapped=FALSE; } #endif
if(++wPort > DPSP_MAX_PORT){ wPort=DPSP_MIN_PORT; } } } #ifdef RANDOM_PORTS
while (!bBound && (wPort != DPSP_MIN_PORT+rndoffset)); #else // ! RANDOM_PORTS
while (!bBound && (wPort != wInitialPort)); #endif // ! RANDOM_PORTS
} else { DPF(5, "Application specified a port (%u), it doesn't need to be in dplay range (%i), or rebinding (port %u).", wApplicationPort, bInRange, wRebindPort); } } break; case AF_IPX: { ((SOCKADDR_IPX *)&sockAddr)->sa_family = (SHORT)pgd->AddressFamily; ((SOCKADDR_IPX *)&sockAddr)->sa_socket = wApplicationPort; // nodenum?
memset(&(((SOCKADDR_IPX *)&sockAddr)->sa_nodenum),0,6); } break; default: ASSERT(FALSE); break;
} // switch
// do the bind
if( !bBound && (SOCKET_ERROR == bind( sNew, (LPSOCKADDR)&sockAddr, sizeof(sockAddr))) ) { // Winsock sometimes complains if you attempt to rebind too quickly. Wait a bit, then
// try again a few times.
if (wRebindPort != 0) { DWORD dwSleepTime;
dwSleepTime = 50; do { dwSleepTime *= 2; // 100, 200, 400, 800
if (dwSleepTime >= 1000) { goto ERROR_EXIT; }
err = WSAGetLastError(); DPF(1, "Port %u reused too quickly, waiting %u ms then trying again (err = %u).", wRebindPort, dwSleepTime, err); Sleep(dwSleepTime); } while (bind( sNew, (LPSOCKADDR)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR); } else { goto ERROR_EXIT; } }
// if this is the first time through the loop for a AF_INET stream socket, we need to shutdown
// and rebind using shared access.
if ((wRebindPort == 0) && (pgd->AddressFamily == AF_INET) && (type==SOCK_STREAM)) { wRebindPort = ntohs(((SOCKADDR_IN *)&sockAddr)->sin_port);
// Make sure we know the port to rebind.
if (wRebindPort == 0) { int iAddrLen;
iAddrLen = sizeof(sockAddr); if (getsockname(sNew, &sockAddr, &iAddrLen) == SOCKET_ERROR) { goto ERROR_EXIT; } wRebindPort = ntohs(((SOCKADDR_IN *)&sockAddr)->sin_port); ASSERT(wRebindPort != 0); } DPF(8, "Closing socket 0x%p (port %u).", sNew, wRebindPort); closesocket(sNew); sNew = INVALID_SOCKET; bBound = FALSE; goto Rebind; }
// success!
*psock = sNew;
if(type==SOCK_STREAM){ DEBUGPRINTSOCK(8,"created a new stream socket (bound) - ",psock); } else { DEBUGPRINTSOCK(8,"created a new datagram socket (bound) - ",psock); }
return DP_OK;
ERROR_EXIT: // clean up and bail
*perr = WSAGetLastError(); DPF(0,"create socket failed- err = %d\n",*perr); closesocket(sNew); #if USE_NATHELP
if (bPortMapped) { natDeregisterPort(pgd,ftcp_udp); bPortMapped=FALSE; } #endif
return E_FAIL;
} // CreateSocket
#undef DPF_MODNAME
#define DPF_MODNAME "KillSocket"
HRESULT KillSocket(SOCKET sSocket,BOOL fStream,BOOL fHard) { UINT err;
if (INVALID_SOCKET == sSocket) { return E_FAIL; }
if (!fStream) { DPF(8,"killsocket - closing datagram socket %d\n", sSocket); if (SOCKET_ERROR == closesocket(sSocket)) { err = WSAGetLastError(); DPF(0,"killsocket - dgram close err = %d\n",err); return E_FAIL; } } else { LINGER Linger;
if (fHard) { // LINGER T/O=0 => Shutdown hard.
Linger.l_onoff=TRUE; // turn linger on
Linger.l_linger=0; // nice small time out
} else { // NOLINGER => shutdown clean, but not hard.
Linger.l_onoff=FALSE; // turn linger off -- SO_NOLINGER
Linger.l_linger=0; // nice small time out
}
if( SOCKET_ERROR == setsockopt( sSocket,SOL_SOCKET,SO_LINGER,(char FAR *)&Linger, sizeof(Linger) ) ) { err = WSAGetLastError(); DPF(0,"killsocket - stream setopt err = %d\n",err); }
#if 0
DWORD lNonBlock=0; err = ioctlsocket(sSocket,FIONBIO,&lNonBlock); if (SOCKET_ERROR == err) { err = WSAGetLastError(); DPF(0,"could not set blocking mode on socket err = %d!",err); } #endif
#if 0
if (SOCKET_ERROR == shutdown(sSocket,2)) { // this may well fail, if e.g. no one is using this socket right now...
// the error would be wsaenotconn
err = WSAGetLastError(); DPF(5,"killsocket - stream shutdown err = %d\n",err); } #endif
DPF(8,"killsocket - %s closing stream socket %d:", ((fHard) ? "hard" : "soft"), sSocket); DEBUGPRINTSOCK(8,"Addr :",&sSocket); if (SOCKET_ERROR == closesocket(sSocket)) { err = WSAGetLastError(); DPF(0,"killsocket - stream close err = %d\n",err); return E_FAIL; } else { DPF(8,"killsocket - closed socket %d\n",sSocket); } }
return DP_OK; }// KillSocket
#undef DPF_MODNAME
#define DPF_MODNAME "CreateAndInitStreamSocket"
// set up a stream socket to receive connections
// used w/ the gGlobalData.sStreamAcceptSocket
HRESULT CreateAndInitStreamSocket(LPGLOBALDATA pgd) { HRESULT hr; UINT err; LINGER Linger; BOOL bTrue=TRUE; SOCKADDR_IN saddr; INT dwSize;
hr = CreateSocket(pgd,&(pgd->sSystemStreamSocket),SOCK_STREAM,pgd->wApplicationPort,INADDR_ANY,&err,TRUE); if (FAILED(hr)) { DPF(0,"init listen socket failed - err = %d\n",err); return hr ; }
bTrue = SetSharedPortAccess(pgd->sSystemStreamSocket); if (! bTrue) { DPF(0,"Failed to to set shared mode on socket - continue\n"); }
// get the socket address, and keep it around for future reference.
dwSize = sizeof(saddr); err=getsockname(pgd->sSystemStreamSocket, (SOCKADDR *)&saddr, &dwSize);
if(err){ DPF(0,"Couldn't get socket name?\n"); DEBUG_BREAK(); }
pgd->SystemStreamPort = saddr.sin_port;
// set up socket w/ max listening connections
err = listen(pgd->sSystemStreamSocket,LISTEN_BACKLOG); if (SOCKET_ERROR == err) { err = WSAGetLastError(); DPF(0,"init listen socket / listen error - err = %d\n",err); return E_FAIL ; }
// set for hard disconnect
Linger.l_onoff=1; Linger.l_linger=0; if( SOCKET_ERROR == setsockopt( pgd->sSystemStreamSocket,SOL_SOCKET,SO_LINGER, (char FAR *)&Linger,sizeof(Linger) ) ) { err = WSAGetLastError(); DPF(0,"Delete service socket - stream setopt err = %d\n",err); } DEBUGPRINTSOCK(1,"enum - listening on",&(pgd->sSystemStreamSocket)); return DP_OK; } // CreateAndInitStreamSocket
#undef DPF_MODNAME
#define DPF_MODNAME "SPConnect"
// connect socket to sockaddr
HRESULT SPConnect(SOCKET* psSocket, LPSOCKADDR psockaddr,UINT addrlen, BOOL bOutBoundOnly) { UINT err; HRESULT hr = DP_OK; DWORD dwLastError; u_long lNonBlock = 1; // passed to ioctlsocket to make socket non-blocking
u_long lBlock = 0; // passed to ioctlsocket to make socket blocking again
fd_set fd_setConnect; fd_set fd_setExcept; TIMEVAL timevalConnect;
DPF(6, "SPConnect: Parameters (0x%x, 0x%x, %u, %i)", psSocket, psockaddr, addrlen, bOutBoundOnly);
err=ioctlsocket(*psSocket, FIONBIO, &lNonBlock); // make socket non-blocking
if(SOCKET_ERROR == err){ dwLastError=WSAGetLastError(); DPF(0,"sp - failed to set socket %d to non-blocking mode err= %d\n", *psSocket, dwLastError); return DPERR_CONNECTIONLOST; }
DEBUGPRINTADDR(4, "Connecting socket:", psockaddr);
// Start the socket connecting.
err = connect(*psSocket,psockaddr,addrlen); if(SOCKET_ERROR == err) { dwLastError=WSAGetLastError(); if(dwLastError != WSAEWOULDBLOCK){ DPF(0,"sp - connect failed err= %d\n", dwLastError); hr = DPERR_CONNECTIONLOST; goto err_exit; } // we are going to wait for either the connect to succeed (socket to be writeable)
// or the connect to fail (except fdset bit to be set). So we init an FDSET with
// the socket that is connecting and wait.
FD_ZERO(&fd_setConnect); FD_SET(*psSocket, &fd_setConnect);
FD_ZERO(&fd_setExcept); FD_SET(*psSocket, &fd_setExcept);
timevalConnect.tv_sec=0; timevalConnect.tv_usec=CONNECT_WATCHER_TIMEOUT*1000; //msec -> usec
err = select(0, NULL, &fd_setConnect, &fd_setExcept, &timevalConnect);
// err is the number of sockets with activity or 0 for timeout
// or SOCKET_ERROR for error
if(SOCKET_ERROR == err) { dwLastError=WSAGetLastError(); DPF(0,"sp - connect failed err= %d\n", dwLastError); hr = DPERR_CONNECTIONLOST; goto err_exit; } else if (0==err){ // timed out
DPF(0,"Connect timed out on socket %d\n",*psSocket); hr = DPERR_CONNECTIONLOST; goto err_exit; }
// Now see if the connect succeeded or the connect got an exception
if(!(FD_ISSET(*psSocket, &fd_setConnect))){ #ifdef DEBUG
DWORD optval=0; DWORD optlen=sizeof(optval); DPF(0,"Connect did not succeed on socket %d\n",*psSocket); if(FD_ISSET(*psSocket,&fd_setExcept)){ DPF(0,"FD Except Set IS Set (expected)\n"); } else { DPF(0,"FD Except Set IS NOT SET (unexpected)\n"); } err=getsockopt(*psSocket, SOL_SOCKET, SO_ERROR, (char *)&optval, &optlen); DPF(0,"Socket error %x\n",optval); #endif
return DPERR_CONNECTIONLOST; }
if(FD_ISSET(*psSocket,&fd_setExcept)){ DPF(0,"Got exception on socket %d during connect\n",*psSocket); hr = DPERR_CONNECTIONLOST; goto err_exit; } }
err=ioctlsocket(*psSocket, FIONBIO, &lBlock); // make socket blocking again
DEBUGPRINTSOCK(8,"successfully connected socket - ", psSocket);
if (bOutBoundOnly) { DEBUGPRINTADDR(5, "Sending reuse connection message to - ",psockaddr); // tell receiver to reuse connection
hr = SendReuseConnectionMessage(*psSocket); } DPF(6, "SPConnect: Return: [0x%lx]", hr);
return hr;
err_exit: err=ioctlsocket(*psSocket, FIONBIO, &lBlock); // make socket blocking again
DPF(6, "SPConnect: Return (err exit): [0x%lx]", hr); return hr;
} //SPConnect
#undef DPF_MODNAME
#define DPF_MODNAME "SetPlayerAddress"
// we've created a socket for a player. store its address in the players
// spplayerdata struct.
HRESULT SetPlayerAddress(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,SOCKET sSocket,BOOL fStream) { SOCKADDR sockaddr; UINT err; int iAddrLen = sizeof(SOCKADDR);
err = getsockname(sSocket,&sockaddr,&iAddrLen); if (SOCKET_ERROR == err) { err = WSAGetLastError(); DPF(0,"setplayeraddress - getsockname - err = %d\n",err); DPF(0,"closing socket %d\n",sSocket); closesocket(sSocket); return E_FAIL; }
if (fStream) { switch (pgd->AddressFamily) { case AF_INET: DEBUGPRINTADDR(7, "Setting player AF_INET stream socket address:", &sockaddr); STREAM_PSOCKADDR(ppd)->sa_family = AF_INET; IP_STREAM_PORT(ppd) = ((SOCKADDR_IN * )&sockaddr)->sin_port; // we don't know the address of the local player (multihomed!)
IP_STREAM_ADDR(ppd) = 0; break;
case AF_IPX: { SOCKADDR_IPX * pipx = (SOCKADDR_IPX * )STREAM_PSOCKADDR(ppd); pipx->sa_family = AF_IPX; pipx->sa_socket = ((SOCKADDR_IPX*)&sockaddr)->sa_socket; memset(pipx->sa_nodenum,0,6); break;
}
default: ASSERT(FALSE); } } // stream
else { switch (pgd->AddressFamily) { case AF_INET: DEBUGPRINTADDR(7, "Setting player AF_INET datagram socket address:", &sockaddr); DGRAM_PSOCKADDR(ppd)->sa_family = AF_INET; IP_DGRAM_PORT(ppd) = ((SOCKADDR_IN *)&sockaddr)->sin_port; // we don't know the address of the local player (multihomed!)
IP_DGRAM_ADDR(ppd) = 0; break;
case AF_IPX: { SOCKADDR_IPX * pipx = (SOCKADDR_IPX * )DGRAM_PSOCKADDR(ppd); pipx->sa_family = AF_IPX; pipx->sa_socket = ((SOCKADDR_IPX*)&sockaddr)->sa_socket; memset(pipx->sa_nodenum,0,6); break;
}
default: ASSERT(FALSE); }
} // dgram
return DP_OK; } // SetPlayerAddress
#undef DPF_MODNAME
#define DPF_MODNAME "GetIPXNameServerSocket"
// called by CreatePlayerDgramSocket
// bind to our well known port for ipx
HRESULT GetIPXNameServerSocket(LPGLOBALDATA pgd) { BOOL bTrue = TRUE; SOCKET sSocket; HRESULT hr; UINT err; // if there already was a receive thread, we need to kill
// the socket, and remember the thread, so at shutdown we
// can make sure it's gone. note - we can't wait for it to
// leave now, since dplay hasn't dropped its locks, and
// the thread may be blocked on dplay
if (pgd->hDGramReceiveThread) { // it's ipx, and we're deleting the system player
// we need to get rid of the system sockets, so that if we recreate as
// nameserver we can bind to a specific port...
// ipx only uses datagram, so we only stop those...kill the socket
ASSERT(INVALID_SOCKET != pgd->sSystemDGramSocket); KillSocket(pgd->sSystemDGramSocket,FALSE,TRUE); pgd->sSystemDGramSocket = INVALID_SOCKET; // remember the old thread - we'll need to make sure it's gone when we
// shut down
pgd->hIPXSpareThread = pgd->hDGramReceiveThread; pgd->hDGramReceiveThread = NULL; } DPF(2,"ipx - creating name server dgram socket\n"); // use name server port
hr = CreateSocket(pgd,&sSocket,SOCK_DGRAM,SERVER_DGRAM_PORT,INADDR_ANY,&err,FALSE); if (FAILED(hr)) { DPF(0,"IPX - DPLAY SERVER SOCKET IS ALREADY IN USE. PLEASE SHUTDOWN ANY"); DPF(0,"OTHER NETWORK APPLICATIONS AND TRY AGAIN"); // boned!
return DPERR_CANNOTCREATESERVER; }
if( SOCKET_ERROR == setsockopt( sSocket,SOL_SOCKET,SO_BROADCAST,(char FAR *)&bTrue, sizeof(bTrue) ) ) { err = WSAGetLastError(); DPF(0,"create - could not set broadcast err = %d\n",err); // keep trying
}
DEBUGPRINTSOCK(2,"name server dgram socket (bound) - ",&sSocket); pgd->sSystemDGramSocket = sSocket; return DP_OK;
} // GetIPXNameServerSocket
HRESULT CreatePlayerDgramSocket(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,DWORD dwFlags) { HRESULT hr=DP_OK; UINT err; SOCKET sSocket; if ( (AF_IPX == pgd->AddressFamily) && (dwFlags & DPLAYI_PLAYER_NAMESRVR)) { //
// AF_INET uses ddhelp to bind the nameserver to a specific port
// (SERVER_DGRAM_PORT). AF_IPX binds to that port here.
hr = GetIPXNameServerSocket(pgd); if (FAILED(hr)) { return hr; } // store this for setting player address below
sSocket = pgd->sSystemDGramSocket; } else if (dwFlags & DPLAYI_PLAYER_SYSPLAYER) { if (INVALID_SOCKET == pgd->sSystemDGramSocket) {
hr = CreateSocket(pgd,&sSocket,SOCK_DGRAM,pgd->wApplicationPort,INADDR_ANY,&err,TRUE); if (FAILED(hr)) { DPF(0,"create sysplayer dgram socket failed - err = %d\n",err); return hr; } #ifdef DEBUG
if (dwFlags & DPLAYI_PLAYER_NAMESRVR) { DEBUGPRINTSOCK(2,"name server dgram socket - ",&sSocket); } #endif // DEBUG
pgd->sSystemDGramSocket = sSocket; } else { // store this for setting player address below
sSocket = pgd->sSystemDGramSocket; } } else { ASSERT(INVALID_SOCKET != pgd->sSystemDGramSocket); sSocket = pgd->sSystemDGramSocket; }
// store the ip + port w/ the player...
hr = SetPlayerAddress(pgd,ppd,sSocket,FALSE);
return hr; } // CreatePlayerDgramSocket
HRESULT CreatePlayerStreamSocket(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,DWORD dwFlags) { SOCKET sSocket; HRESULT hr=DP_OK; UINT err; BOOL bListen = TRUE; // set if we created socket, + need to set it's listen
BOOL bTrue = TRUE; DWORD dwSize; SOCKADDR_IN saddr; if (dwFlags & DPLAYI_PLAYER_SYSPLAYER) { if (INVALID_SOCKET == pgd->sSystemStreamSocket) { hr = CreateSocket(pgd,&sSocket,SOCK_STREAM,pgd->wApplicationPort,INADDR_ANY,&err,TRUE); if (FAILED(hr)) { DPF(0,"create player stream socket failed - err = %d\n",err); return hr; } #ifdef DEBUG
if (dwFlags & DPLAYI_PLAYER_NAMESRVR) { DEBUGPRINTSOCK(2,"name server stream socket - ",&sSocket); } #endif // DEBUG
bTrue = SetSharedPortAccess(sSocket); if (! bTrue) { DPF(0,"Failed to to set shared mode on socket - continue\n"); } pgd->sSystemStreamSocket = sSocket;
// get the socket address, and keep it around for future reference.
dwSize = sizeof(saddr); err=getsockname(pgd->sSystemStreamSocket, (SOCKADDR *)&saddr, &dwSize);
if(err){ DPF(0,"Couldn't get socket name?\n"); DEBUG_BREAK(); } pgd->SystemStreamPort = saddr.sin_port; } else { sSocket = pgd->sSystemStreamSocket; bListen = FALSE; } } else { ASSERT (INVALID_SOCKET != pgd->sSystemStreamSocket); sSocket = pgd->sSystemStreamSocket; bListen = FALSE; } if (bListen) { // set up socket to receive connections
err = listen(sSocket,LISTEN_BACKLOG); if (SOCKET_ERROR == err) { err = WSAGetLastError(); ASSERT(FALSE); DPF(0,"ACK! stream socket listen failed - err = %d\n",err); // keep trying
} } hr = SetPlayerAddress(pgd,ppd,sSocket,TRUE); return hr;
} // CreatePlayerStreamSocket
#undef DPF_MODNAME
#define DPF_MODNAME "PokeAddr"
// poke an ip addr into a message blob
void IP_SetAddr(LPVOID pmsg,SOCKADDR_IN * paddrSrc) { LPSOCKADDR_IN paddrDest; // tempo variable, makes casting less ugly
LPMESSAGEHEADER phead;
phead = (LPMESSAGEHEADER)pmsg; // todo - validate header
// leave the port intact, copy over the ip addr
paddrDest = (SOCKADDR_IN *)&(phead->sockaddr); // poke the new ip addr into the message header
// only rehome addresses that aren't already homed.
if(paddrDest->sin_addr.s_addr == 0){ paddrDest->sin_addr.s_addr = paddrSrc->sin_addr.s_addr; }
return; } // IP_SetAddr
// get an ip addr from a message blob
void IP_GetAddr(SOCKADDR_IN * paddrDest,SOCKADDR_IN * paddrSrc) { // leave the port intact, copy over the nodenum
if (0 == paddrDest->sin_addr.s_addr) { DPF(2,"remote player - setting address!! = %s\n",inet_ntoa(paddrSrc->sin_addr)); paddrDest->sin_addr.s_addr = paddrSrc->sin_addr.s_addr; }
return; } // IP_GetAddr
// poke the ipx nodenumber / a message
void IPX_SetNodenum(LPVOID pmsg,SOCKADDR_IPX * paddrSrc) { LPSOCKADDR_IPX paddrDest; // tempo variable, makes casting less ugly
LPMESSAGEHEADER phead;
phead = (LPMESSAGEHEADER)pmsg; // todo - validate header
// leave the port intact, copy over the nodenum
paddrDest = (SOCKADDR_IPX *)&(phead->sockaddr); memcpy(paddrDest->sa_nodenum,paddrSrc->sa_nodenum,6); memcpy(paddrDest->sa_netnum,paddrSrc->sa_netnum,4);
return;
} // IPX_SetNodenum
// reconstruct the nodenum from the msg
void IPX_GetNodenum(SOCKADDR_IPX * paddrDest,SOCKADDR_IPX * paddrSrc) { char sa_nodenum_zero[6];
memset(sa_nodenum_zero,0,6);
// if the nodenum is zero, set it
if (0 == memcmp(paddrDest->sa_nodenum,sa_nodenum_zero,6)) { DEBUGPRINTADDR(4,"IPX - setting remote player address",(SOCKADDR *)paddrSrc); // leave the port intact, copy over the nodenum
memcpy(paddrDest->sa_nodenum,paddrSrc->sa_nodenum,6); memcpy(paddrDest->sa_netnum,paddrSrc->sa_netnum,4); } return; } // IPX_GetNodenum
// store the port of the socket w/ the message, so the receiving end
// can reconstruct the address to reply to
// psaddr will override the address if present
HRESULT SetReturnAddress(LPVOID pmsg,SOCKET sSocket, LPSOCKADDR psaddrPublic) { #define psaddr_inPublic ((LPSOCKADDR_IN)psaddrPublic)
SOCKADDR sockaddr; INT addrlen=sizeof(SOCKADDR); LPMESSAGEHEADER phead; UINT err;
// Note: extra test for 0 IP address may be extraneous (but certainly won't hurt).
if(psaddrPublic && psaddr_inPublic->sin_addr.s_addr ){
// We are behind an RSIP gateway, so put the public address in the header
phead = (LPMESSAGEHEADER)pmsg; phead->sockaddr = *psaddrPublic;
DEBUGPRINTADDR(8,"setting return address (using rsip public address) = ",&phead->sockaddr); } else { // find out what port gGlobalData.sEnumSocket is on
DPF(8,"==>GetSockName\n"); err = getsockname(sSocket,(LPSOCKADDR)&sockaddr,&addrlen); DPF(8,"<==GetSockName\n"); if (SOCKET_ERROR == err) { err = WSAGetLastError(); DPF(0,"could not get socket name - err = %d\n",err); return DP_OK; }
DEBUGPRINTADDR(8,"setting return address = ",&sockaddr);
phead = (LPMESSAGEHEADER)pmsg; // todo - validate header
phead->sockaddr = sockaddr; } return DP_OK;
#undef psaddr_inPublic
} // SetReturnAddress
// code below all called by GetServerAddress. For IP, prompts user for ip address
// for name server.
#undef DPF_MODNAME
#define DPF_MODNAME "GetAddress"
// get the ip address from the pBuffer passed in by a user
// can either be a real ip, or a hostname
// called after the user fills out our dialog box
HRESULT GetAddress(ULONG * puAddress,char *pBuffer,int cch) { UINT uiAddr; UINT err; PHOSTENT phostent; IN_ADDR hostaddr;
if ( (0 == cch) || (!pBuffer) || (0 == strlen(pBuffer)) ) { *puAddress = INADDR_BROADCAST; return (DP_OK); } // try inet_addr first
uiAddr = inet_addr(pBuffer);
if(0 == uiAddr) // fix bug where "" buffer passed in.
{ *puAddress = INADDR_BROADCAST; return (DP_OK); } if (INADDR_NONE != uiAddr) { // found it
*puAddress = uiAddr; return (DP_OK); } // try hostbyname
phostent = gethostbyname(pBuffer); if (NULL == phostent ) { err = WSAGetLastError(); DPF(0,"could not get host address - err = %d\n",err); return (DPERR_INVALIDPARAM); } memcpy(&hostaddr,phostent->h_addr,sizeof(hostaddr)); DPF(1,"name server address = %s \n",inet_ntoa(hostaddr)); *puAddress = hostaddr.s_addr;
return (DP_OK); } // GetAddress
// put up a dialog asking for a network address
// call get address to convert user specified address to network usable address
// called by GetServerAddress
INT_PTR CALLBACK DlgServer(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { HWND hWndCtl; char pBuffer[ADDR_BUFFER_SIZE]; UINT cch; ULONG *lpuEnumAddress; HRESULT hr;
switch (msg) { case WM_INITDIALOG: // set focus on edit box
hWndCtl = GetDlgItem(hDlg, IDC_EDIT1); if (hWndCtl == NULL) { EndDialog(hDlg, FALSE); return(TRUE); } SetFocus(hWndCtl); SendMessage(hWndCtl, CB_SETCURSEL, 0, 0);
// save pointer to enum address with the window
SetWindowLongPtr(hDlg, DWLP_USER, (LONG) lParam); return(FALSE);
case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: // get text entered in control
cch = GetDlgItemText(hDlg, IDC_EDIT1, pBuffer, ADDR_BUFFER_SIZE);
// get pointer to return address in
lpuEnumAddress = (ULONG *) GetWindowLongPtr(hDlg, DWLP_USER);
// convert string to enum address
hr = GetAddress(lpuEnumAddress,pBuffer,cch); if (FAILED(hr)) EndDialog(hDlg, hr); else EndDialog(hDlg, TRUE); return(TRUE);
case IDCANCEL: EndDialog(hDlg, FALSE); return(TRUE); } break; } return (FALSE); } // DlgServer
/*
** GetServerAddress * * CALLED BY: EnumSessions * * DESCRIPTION: launches the select network address dialog * * RETURNS: ip address (sockaddr.sin_addr.s_addr) * */ HRESULT ServerDialog(ULONG *lpuEnumAddress) { HWND hwnd; INT_PTR iResult; HRESULT hr; // we have a valid enum address
if (*lpuEnumAddress) return (DP_OK);
// use the fg window as our parent, since a ddraw app may be full screen
// exclusive
hwnd = GetForegroundWindow();
iResult = DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_SELECTSERVER), hwnd, DlgServer, (LPARAM) lpuEnumAddress); if (iResult == -1) { DPF_ERR("GetServerAddress - dialog failed"); hr = DPERR_GENERIC; } else if (iResult < 0) { DPF(0, "GetServerAddress - dialog failed: %08X", iResult); hr = (HRESULT) iResult; } else if (iResult == 0) { hr = DPERR_USERCANCEL; } else { hr = DP_OK; } return (hr); } //ServerDialog
// called by enumsessions - find out where server is...
HRESULT GetServerAddress(LPGLOBALDATA pgd,LPSOCKADDR psockaddr) { HRESULT hr;
if (AF_IPX == pgd->AddressFamily) { ((LPSOCKADDR_IPX)psockaddr)->sa_family = AF_IPX; ((LPSOCKADDR_IPX)psockaddr)->sa_socket = SERVER_DGRAM_PORT; memset(&(((LPSOCKADDR_IPX)psockaddr)->sa_nodenum),0xff,sizeof(((LPSOCKADDR_IPX)psockaddr)->sa_nodenum)); hr = DP_OK; } else { if (pgd->bHaveServerAddress) { // use enum address passed to SPInit
hr = GetAddress(&pgd->uEnumAddress,pgd->szServerAddress,strlen(pgd->szServerAddress)); } else { // ask user for enum address
hr = ServerDialog(&pgd->uEnumAddress); }
if (SUCCEEDED(hr)) { // setup winsock to enum this address
((LPSOCKADDR_IN)psockaddr)->sin_family = AF_INET; ((LPSOCKADDR_IN)psockaddr)->sin_addr.s_addr = pgd->uEnumAddress; // see byte-order comment in dpsp.h for this constant
((LPSOCKADDR_IN)psockaddr)->sin_port = SERVER_DGRAM_PORT;
#if USE_RSIP
#define IP_SOCKADDR(a) (((SOCKADDR_IN *)(&a))->sin_addr.s_addr)
// Make sure the address we are enumming doesn't have a local alias.
// If it does, use the local alias instead of the public address.
if(pgd->sRsip!=INVALID_SOCKET && pgd->uEnumAddress != INADDR_BROADCAST && pgd->uEnumAddress != INADDR_LOOPBACK){ SOCKADDR saddr;
hr=rsipQueryLocalAddress(pgd, FALSE, psockaddr, &saddr); if(hr==DP_OK){ // If there is an alias, go broadcast. This works just as well
// and avoids problems where more than 1 mapped shared
// UDP port is allocated.
DEBUGPRINTADDR(7, "Enum Socket is ",psockaddr); DEBUGPRINTADDR(7, "Got Local Alias for Enum socket ",&saddr);
IP_SOCKADDR(*psockaddr)=0xFFFFFFFF; #if 0
if(IP_SOCKADDR(saddr)==IP_SOCKADDR(*psockaddr)) { DPF(7, "Alias had same IP as queried, assuming local ICS, so using broadcast enum\n"); IP_SOCKADDR(*psockaddr)=0xFFFFFFFF; } else { memcpy(psockaddr, &saddr, sizeof(SOCKADDR)); } #endif
}else{ DEBUGPRINTADDR(7,"No local alias for Enum socket ",psockaddr); hr = DP_OK; } } #undef IP_SOCKADDR
#elif USE_NATHELP
#define IP_SOCKADDR(a) (((SOCKADDR_IN *)(&a))->sin_addr.s_addr)
// Make sure the address we are enumming doesn't have a local alias.
// If it does, use the local alias instead of the public address.
if(pgd->pINatHelp && pgd->uEnumAddress != INADDR_BROADCAST && pgd->uEnumAddress != INADDR_LOOPBACK){ SOCKADDR saddr; hr=IDirectPlayNATHelp_QueryAddress( pgd->pINatHelp, &pgd->INADDRANY, psockaddr, &saddr, sizeof(SOCKADDR_IN), DPNHQUERYADDRESS_CACHENOTFOUND );
if(hr==DP_OK){ // If there is an alias, go broadcast. This works just as well
// and avoids problems where more than 1 mapped shared
// UDP port is allocated.
DEBUGPRINTADDR(7, "Enum Socket is ",psockaddr); DEBUGPRINTADDR(7, "Got Local Alias for Enum socket ",&saddr); IP_SOCKADDR(*psockaddr)=0xFFFFFFFF; }else{ DEBUGPRINTADDR(7,"No local alias for Enum socket ",psockaddr); hr = DP_OK; } } #undef IP_SOCKADDR
#endif
} else { DPF(0, "Invalid server address: 0x%08lx", hr); } }
return (hr); } // GetServerAddress
|