;Homer's IOCP Networking Engine ;This file contains a bunch of necessary data declarations (structs and so forth), ;as well as 'almost' all of the necessary 'includes' required. ;The following files must be included by YOU in your application: ;- ws2_32 and Mswsock (inc and lib) ;- Stream, Collection, and DwordCollection object classes OPERATION_ENDED equ 0 OPERATION_DOACCEPT equ 1 OPERATION_DOWRITE equ 2 OPERATION_READ equ 3 OPERATION_CONNECT equ 4 ERROR_BADPROTOCOL equ -1 ERROR_BADVERSION equ -2 ERROR_USERQUIT equ -666 CLIENT_DISCONNECTED equ 0FFFFFFFFh WSA_FLAG_OVERLAPPED equ 1 WSA_INVALID_EVENT equ 00h WSA_FLAG_OVERLAPPED equ 01h MSG_PARTIAL equ 8000h SO_UPDATE_ACCEPT_CONTEXT equ 700Bh WSAEVENT typedef HANDLE WSAOVERLAPPED struct Internal DWORD ? InternalHigh DWORD ? an_Offset DWORD ? OffsetHigh DWORD ? hEvent WSAEVENT ? WSAOVERLAPPED ends PWSAOVERLAPPED typedef ptr WSAOVERLAPPED WSABUF struct len DWORD ? buf PBYTE ? WSABUF ends PWSABUF typedef ptr WSABUF XOVL struct ovl WSAOVERLAPPED <> wsabuf WSABUF <> bytesused dd ? ;#bytes currently in buffer operation dd ? bytes dd ? ;#bytes due to this operation piojob dd ? ;ptr to wrapper object pbuforig dd ? ;original copy of wsabuf.buf pointer bufsizeorig dd ? ;original copy of wsabuf.len value XOVL ends IOC_WS2 equ 008000000h SIO_GET_EXTENSION_FUNCTION_POINTER equ IOC_INOUT or IOC_WS2 or 6 SO_UPDATE_CONNECT_CONTEXT equ 07010h .data ConnectExGuid GUID <25a207b9h,0ddf3h,4660h,{8eh,0e9h,76h,0e5h,8ch,74h,06h,3eh}> .code ;Networking framework ;MakeObjects NetApp ;Abstract class for the Application to talk to NetEngine MakeObjects Pool ;BaseClass for recycling of arbitrary derived objects MakeObjects IOJob ;this object represents one package of network data, owned by a Client MakeObjects IOJobPool ;Derived class for recycling of IOJob objects MakeObjects NetworkProtocol ;BaseClass for handling of Network Events MakeObjects Client ;this object represents one TCP/IP network session, or if you like, "a live connection with remote user" MakeObjects ClientPool ;Derived class for recycling of Client objects MakeObjects ClientGroup ;Support for arbitrary and hierarchical grouping of clients (eg chat channel) ;MakeObjects BaseProtocol ;Foundation of our protocol enforcement ;MakeObjects LobbyProtocol ;Actual useful protocol implementation ;MakeObjects GameProtocol ;TODO ;============================================================= .data upnp_tcp_port dd 0 NAT_IP_String db 256 dup (0) szExternalIP db 256 dup (0) .code Object UPNPNATProtocol,2345234,NetworkProtocol RedefineMethod Init,Pointer RedefineMethod FindDelimiter,Pointer,Pointer RedefineMethod ProcessReceivedData,Pointer RedefineMethod OnConnectCompleted,Pointer,dword RedefineMethod OnUserIOCompleted,Pointer,Pointer,dword VirtualMethod AddPortMapping,dword,BOOL DefineVariable Discovery_Stage,dword,0 DefineVariable hAddPortMapping,HANDLE,0 DefineVariable describe_url,byte, 1024 dup <(0)> DefineVariable controlUrl, byte, 1024 dup <(0)> ObjectEnd NetEngineID equ 32840 Object NetEngine,NetEngineID,Primer RedefineMethod Init, dword,dword,dword,dword RedefineMethod Done VirtualMethod Listen, Pointer,dword ;pProtocol,dListenPort VirtualMethod ConnectTo,Pointer,dword,dword,LPSTR,LPSTR ;pProtocol,dwHost,dwPort,pUsernameString,pPasswordString VirtualMethod UDP_Broadcast,Pointer,dword,dword,Pointer,dword;pProtocol,dwBroadcastAddress,dwPort,pData,dLen VirtualMethod GoodbyeClient,Pointer,Pointer ;pClient, pJob Private VirtualMethod OnIOCompleted,Pointer,dword ;-> XOVL, dwBytes VirtualMethod QueueAcceptorClient,Pointer VirtualMethod Worker VirtualMethod AntiZombie ;This method counts available number of logical cpus VirtualMethod GetLogicalCPUCount PrivateEnd DefineVariable wsadata,WSADATA,{} DefineVariable io_completion_port,Pointer,NULL DefineVariable end_event,dword,NULL DefineVariable ShuttingDown,BOOL,FALSE DefineVariable dMinimum_Pending_Accepts,dword,0 DefineVariable NumWorkerThreads,dword,NULL ;#Worker Threads waiting for jobs to complete DefineVariable bLocalMachineIsBehindNATDevice,BOOL,FALSE DefineVariable hWake,BOOL,FALSE DefineVariable _CS, CRITICAL_SECTION,{<>} Embed RootClientGroup,ClientGroup ;Support for a Tree of Named communication channels Embed InClients, ClientPool ;Pool of INBOUND Clients Embed OutClients, ClientPool ;Pool of OUTBOUND Clients Embed Listeners, ClientPool ;Pool of LISTENING Clients Embed IOJobs, IOJobPool ;Pool of IO Jobs Embed upnp_protocol, UPNPNATProtocol ;Support for NAT Traversal via UPNP ObjectEnd ;============================================================= align 4 if IMPLEMENT ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;Destructor method - trash 'everything' :) Method NetEngine.Done, uses esi SetObject esi ;Tell NetEngine Worker to die, and wait for the Death mov [esi].ShuttingDown,TRUE .while [esi].NumWorkerThreads!=0 invoke Sleep,500 .endw ;Destroy Events invoke CloseHandle, [esi].io_completion_port invoke CloseHandle, [esi].end_event ;Clean up resource pools OCall [esi].IOJobs::IOJobPool.Done OCall [esi].Listeners::ClientPool.Done OCall [esi].InClients::ClientPool.Done OCall [esi].OutClients::ClientPool.Done OCall [esi].RootClientGroup::ClientGroup.Done invoke DeleteCriticalSection,addr [esi]._CS ;Close down WinSock invoke WSACleanup MethodEnd ;Constructor method - initialize internal collections, threads, IOCP etc. ;Returns eax=pThis or NULL=Failed Method NetEngine.Init,uses esi,MinClients,MaxClients,MaxIOJobs,bWantPortForwarding:BOOL LOCAL pClient,pJob,tid,NumWorkers LOCAL buf[256]:byte SetObject esi invoke WSAStartup, 0202h, addr [esi].wsadata .if eax!= NULL DbgWarning "FAILED TO START WINSOCK 2.2 - QUITTING" xor eax,eax ExitMethod .endif m2m [esi].dMinimum_Pending_Accepts, MinClients invoke InitializeCriticalSectionAndSpinCount,addr [esi]._CS,500 ;Initialize some collections OCall [esi].InClients::ClientPool.Init,esi, MaxClients OCall [esi].OutClients::ClientPool.Init,esi, MaxClients OCall [esi].Listeners::ClientPool.Init,esi, MaxClients OCall [esi].IOJobs::IOJobPool.Init,esi, MaxIOJobs, 8192 ;The Root ClientGroup should be initialized with pOwner==NULL ;because this hierarchical object uses pOwner to point to Parent OCall [esi].RootClientGroup::ClientGroup.Init,NULL,$OfsCStr("Root"),$OfsCStr("Mother of all evil") ;Create the IOCP invoke CreateIoCompletionPort, INVALID_HANDLE_VALUE, 0, 0, 0 .if eax==NULL DbgWarning "FAILED TO CREATE IO COMPLETION PORT - QUITTING" jmp @F .endif mov [esi].io_completion_port, eax ;Create a special event to signal Worker Threads to terminate push esi invoke CreateEvent, 0, TRUE, FALSE, 0 pop esi mov [esi].end_event, eax .if eax== NULL DbgWarning "FAILED TO CREATE KILLNetEngine EVENT OBJECT - QUITTING" jmp @F .endif ;Create one Worker Thread per available logical cpu mov NumWorkers, $OCall (esi.GetLogicalCPUCount) DbgDec eax,"Number of available logical cpus" xor ebx,ebx .while ebx=16 && al<=31 DbgWarning "IP address appears to be in range 2" jmp @LAN .endif .elseif al==192 ;check for range 3 : 0000A8C0 - FFFFA8C0 = 192.168.0.0 - 192.168.255.255 shr eax,8 .if al==168 DbgWarning "IP address appears to be in range 3" jmp @LAN .endif .endif ;IP address appears to be an external ip address DbgWarning "IP address appears to be an external ip address" return esi @LAN: DbgWarning "Error - UPNP discovery has timed out" DbgWarning "Possible reasons for this are:" DbgWarning "-UPNP service is not running" DbgWarning "-Your firewall is blocking the UPNP ports (udp 1900, tcp 2869)" DbgWarning "-Your router has UPNP disabled" DbgWarning "-Your router is not UPNP compliant" .data szCaption db "WARNING",0 szWarning db "Your IP address appears to be behind a Router or other NAT device,",13,10 db "and UPNP discovey has timed out.",13,10 db "This means you cannot accept incoming connections from the Internet,",13,10 db "however you can still operate a Server on your local network (LAN).",13,10 db "If this is acceptable, choose the YES option below to continue.",13,10,13,10 db "If this is NOT acceptable, and you ARE behind a NAT device," db "please confirm that the following are TRUE:",13,10,13,10 db "-Your firewall is NOT blocking the UPNP ports (udp 1900, tcp 2869)" db "-The UPNP service is running (Universal Plug and Play)",13,10 db "If behind a router,",13,10 db "-Your router is UPNP compliant",13,10 db "-Your router has UPNP enabled",13,10,13,10 db "Continue without the ability to accept incoming connects from the internet?",0 .code invoke MessageBox,0,addr szWarning,addr szCaption,MB_YESNO .if eax==IDYES mov eax,esi .else ;Ouch xor eax,eax .endif .endif .else mov eax,esi .endif MethodEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;This method allocates a new Client and enqueues an Accept io job. Method NetEngine.QueueAcceptorClient,uses esi,pListener LOCAL pClient SetObject esi ;Allocate a Client, telling it to use the Listener's protocol mov pClient,$OCall ([esi].InClients::ClientPool.Allocate) mov edx,pListener push edx OCall pClient::Client.Init, [esi].io_completion_port,addr [esi].IOJobs,[edx].Client.pProtocol,pListener, FALSE ;Set Listener field mov eax,pClient pop [eax].Client.pListener ;Queue the accept job mov edx,pListener OCall pClient::Client.QueueAccept,[edx].Client.hSocket MethodEnd ;Create a "Listener" - ie, begin listening on a given Port, and for a given Protocol. ;Success: eax=NULL ;Failed: eax=error code Method NetEngine.Listen,uses esi,pProtocol,dListenPort local pListener LOCAL sockaddrin:sockaddr_in SetObject esi .if [esi].bLocalMachineIsBehindNATDevice==TRUE ;We should create a Port Forwarding ;otherwise we won't be able to accept incoming connects OCall [esi].upnp_protocol::UPNPNATProtocol.AddPortMapping,dListenPort,FALSE ; invoke inet_addr,addr NAT_IP_String ; OCall esi.ConnectTo,addr [esi].upnp_protocol,eax,upnp_tcp_port,NULL,NULL .else DbgWarning "Machine NOT behind NAT device" .endif ;Create a Client to represent our Listening socket mov pListener,$OCall ([esi].Listeners::ClientPool.Allocate) DbgHex pListener OCall pListener::Client.Init, [esi].io_completion_port,addr [esi].IOJobs,pProtocol,NULL,FALSE .if eax==TRUE DbgText "Created a Listening socket" mov edx,pListener DbgHex [edx].Client.hSocket ;Set local address and bind it OCall pListener::Client.SetLocalHost,INADDR_ANY,dListenPort mov edx,pListener invoke bind, [edx].Client.hSocket, addr [edx].Client.Sin_LocalHost, sizeof sockaddr_in .if eax!= NULL invoke WSAGetLastError DbgWarning "FAILED TO BIND LISTENING SOCKET TO PORT - QUITTING" DbgDec eax,"error code" jmp @F .endif DbgText "SocketAddress has been Bound to Socket" ;Start listening mov edx,pListener invoke listen,[edx].Client.hSocket, 127 .if eax!= NULL DbgWarning "LISTENING SOCKET REFUSES TO LISTEN - QUITTING" jmp @F .endif DbgText "Socket is Listening" ;Bind the Listening Socket to the IOCP mov edx,pListener invoke CreateIoCompletionPort, [edx].Client.hSocket, [esi].io_completion_port, 0, 0 .if eax==NULL DbgWarning "FAILED TO BIND LISTENING SOCKET TO IO COMPLETION PORT - QUITTING" mov eax,-1 jmp @F .endif DbgText "Socket has been Bound to IOCP" ;Queue some Acceptor clients and their Accept jobs xor ebx,ebx .while ebx<[esi].dMinimum_Pending_Accepts OCall esi.QueueAcceptorClient,pListener inc ebx .endw DbgText "Accepts are pending" xor eax,eax .else DbgWarning "Failed Client.Init" mov eax,-1 .endif @@: .if eax==-1 push eax OCall [esi].Listeners::ClientPool.Free, pListener pop eax .endif MethodEnd ;Creates an OUTBOUND Client, and initiates a Connect attempt. ;Returns TRUE/FALSE (eax=pClient or NULL) Method NetEngine.ConnectTo,uses esi,pProtocol,dwRemoteHost,dwPort,pUsername,pPass LOCAL pClient LOCAL pIOJob LOCAL userid[64]:byte ;Allocate a free Client and (re)initialize it ;The FALSE Boolean indicates that we want a TCP socket SetObject esi mov pClient,$OCall ([esi].OutClients::ClientPool.Allocate) OCall pClient::Client.Init, [esi].io_completion_port,addr [esi].IOJobs,pProtocol,NULL,FALSE .if eax==TRUE ;Register this socket with iocp, because we want socket event notifications mov edx,pClient invoke CreateIoCompletionPort, [edx].Client.hSocket, [esi].io_completion_port, pClient, 0 ;Set the Client's (desired) Name and PasswordHash (USERID) ;These can be ignored if not required by a given Protocol .if pUsername!=0 invoke lstrlen,pUsername inc eax mov byte ptr userid[0],al lea edx,userid[1] invoke lstrcpy,edx,pUsername OCall pClient::Client.SetUserID,addr userid,NULL .endif ;Initiate the Connection attempt : Returns eax=pClient or NULL DbgWarning "NetEngine issues a Connect" OCall pClient::Client.QueueConnect, dwRemoteHost, dwPort .if eax==NULL push eax OCall [esi].OutClients::ClientPool.Free,pClient pop eax .endif .else DbgWarning "Error in NetEngine.ConnectTo - failed Client.Init" .endif MethodEnd ;This method is for initiating a BROADCAST UDP session. ;These are 'connectionless', so its a little different to usual. ;But as usual, the Protocol Handler is what matters. ;WSASendTo and WSARecvFrom are used in the Client class. ;Returns pClient (success) or NULL (failed) Method NetEngine.UDP_Broadcast,uses esi, pProtocol,dwBroadcastAddress,dwPort,pData,dLen LOCAL pClient,bOptLen,bOptVal ;Allocate an outbound Client and (re)initialize it as a UDP Socket SetObject esi mov pClient,$OCall ([esi].OutClients::ClientPool.Allocate) OCall pClient::Client.Init, [esi].io_completion_port,addr [esi].IOJobs,pProtocol,NULL,TRUE ;Register this socket with iocp, because we want socket event notifications mov edx,pClient invoke CreateIoCompletionPort, [edx].Client.hSocket, [esi].io_completion_port, edx, 0 ;Enable the SO_BROADCAST option mov bOptVal,TRUE mov bOptLen ,sizeof BOOL mov edx,pClient invoke setsockopt,[edx].Client.hSocket, SOL_SOCKET, SO_BROADCAST, addr bOptVal, bOptLen .if eax==SOCKET_ERROR invoke WSAGetLastError DbgDec eax,"Error - failed to set socket option SO_BROADCAST in NetEngine.UDP_Broadcast" xor eax,eax .else ;Set the remote broadcast address we'll be sending to ;Note this will be overwritten if/when we receive a response! OCall pClient::Client.SetRemoteHost,dwBroadcastAddress,dwPort ;Set the local address we'll use to receive data ;In this case its "any remote ip, any remote port" OCall pClient::Client.SetLocalHost,INADDR_ANY,NULL ;Bind to local host address, else we cannot receive data mov edx,pClient invoke bind,[edx].Client.hSocket,addr [edx].Client.Sin_LocalHost,sizeof sockaddr_in .if eax==SOCKET_ERROR invoke WSAGetLastError DbgDec eax,"Error - failed to bind socket to local address in NetEngine.UDP_Broadcast" xor eax,eax .else ;Queue a READ to capture the response, if any ;Note we set up the 'pending recv' BEFORE we send anything.. ;We're setting a TRAP to capture the response!! OCall pClient::Client.QueueRead .if eax!=0 ;The recv trap has been set up.. ;Queue a WRITE to send our broadcast packet OCall pClient::Client.QueueWrite,pData,dLen ;Return ptr to Client mov eax,pClient .else ;failed to queue a READ operation DbgText "Error - failed to queue a READ operation in NetEngine.UDP_Broadcast" xor eax,eax .endif .endif .endif ;If we failed, release the client .if eax==0 OCall [esi].OutClients::ClientPool.Free,pClient xor eax,eax .endif MethodEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;This method determines what type of IO completed ;and then handles it according to its type. Method NetEngine.OnIOCompleted,uses esi ebx ecx,pXOVL,bytes LOCAL pClient,pJob,flags,dError invoke IsBadReadPtr,pXOVL,sizeof XOVL .if eax==TRUE DbgWarning "DANGER - XOVL BAD POINTER DISCARDED","WEIRDNESS" DbgHex pXOVL,"WEIRDNESS" ExitMethod .endif mov esi,pXOVL mov eax,[esi].XOVL.piojob mov pJob,eax invoke IsBadReadPtr,pJob,sizeof IOJob .if eax==TRUE DbgWarning "DANGER - IOJob BAD POINTER DISCARDED","WEIRDNESS" DbgHex pJob,"WEIRDNESS" ExitMethod .endif ;Determine whether the Operation completed successfully or not mov dError, NULL ;Obtain the Client pointer from the completed Job (its pOwner) mov eax,pJob m2m pClient,[eax].IOJob.pOwner,edx .if edx==0 DbgWarning "Error - NULL Client - trashing Job" SetObject ecx OCall [ecx].IOJobs::IOJobPool.Free,pJob ExitMethod .endif invoke WSAGetOverlappedResult,[edx].Client.hSocket,pXOVL,addr bytes,FALSE,addr flags .if eax==FALSE mov dError,$invoke (WSAGetLastError) .if eax==996 ;This is not really an error.. ;it means the operation is still pending... ;we can expect another Completion Notification soon, ;so dont do anything and certainly do not free this job ExitMethod .elseif eax==ERROR_NETNAME_DELETED ;The socket is toast - kill the Client OCall GoodbyeClient,pClient,pJob ExitMethod .elseif eax==ERROR_OPERATION_ABORTED ;The Client is toast - free the job SetObject ecx OCall [ecx].IOJobs::IOJobPool.Free,pJob ExitMethod ;The remaining errors will be passed to the client ;who may pass them to the protocol handler. .elseif eax==WSAETIMEDOUT .if [esi].XOVL.operation==OPERATION_CONNECT DbgWarning "An outbound connection attempt has timed out." .else ;I'm not expecting to ever see this DbgWarning "An IO operation has timed out." int 3 .endif .elseif eax==WSAEADDRNOTAVAIL DbgWarning "Remote Host Address not available" .else DbgWarning "A NETWORK IO Operation has FAILED" mov edx,pJob DbgDec [edx].IOJob.xovl.operation,"Type of operation" DbgDec eax,"ERROR CODE" .endif .endif ;regardless of whether the completed job succeeded or failed, ;we will marshal the notification to the protocol handler. .switch [esi].XOVL.operation .case OPERATION_DOACCEPT SetObject esi OCall pClient::Client.OnAcceptCompleted,[esi].io_completion_port,pJob,dError ;We just consumed a pending Acceptor client mov edx,pClient OCall QueueAcceptorClient,[edx].Client.pListener .case OPERATION_READ SetObject esi ;Detect special case of Client remotely Disconnecting.. mov ebx,pXOVL .if bytes==0 && [ebx].XOVL.bytes==0 ;A read of ZERO indicates that the session has terminated. @@: ;We should release this redundant Client object OCall esi.GoodbyeClient,pClient,pJob ExitMethod .endif OCall pClient::Client.OnReadCompleted,pJob, bytes ,dError .if eax==ERROR_BADPROTOCOL || eax==ERROR_USERQUIT jmp @B .elseif eax==FALSE ;The IOJob buffer is NOT EMPTY DbgWarning "Requeuing incomplete READ" OCall pClient::Client.DoRead,pXOVL ;Requeue this IOJob as a Read ExitMethod .else ;The IOJob buffer is EMPTY ;What to do with an empty iojob? mov ebx,pClient .if [ebx].Client.PendingReadCounter==1 DbgWarning "Requeuing redundant READ since theres none pending" OCall pClient::Client.DoRead,pXOVL .else DbgWarning "Trashing redundant READ" OCall pClient::Client.FreeJob,pJob .endif .endif .case OPERATION_DOWRITE ;Marshal the notification to the Client DbgHex esi OCall pClient::Client.OnWriteCompleted,pJob,bytes,dError .if eax==ERROR_USERQUIT OCall esi.GoodbyeClient,pClient,pJob .endif .case OPERATION_CONNECT ;Pass this notification to the Client's Protocol handler OCall pClient::Client.OnConnectCompleted,pJob,dError .if dError!=0 OCall GoodbyeClient,pClient,pJob .endif .default ;Operations of unknown Type are passed to this method OCall pClient::Client.OnUserIOCompleted,pJob,dError .endsw MethodEnd ;This method trashes a Client and optionally, an IOJob. ;It contains some safety checking that may be removed at a later date. ;WE SHOULD REMOVE CLIENT FROM ALL CLIENTGROUPS Method NetEngine.GoodbyeClient,uses esi,pClient,pJob LOCAL pListener SetObject esi DbgWarning "Terminating Client" mov eax,pClient m2m pListener,[eax].Client.pListener,edx ;Notify the dead Client's Protocol handler ;in order to free any Protocol-specific client resources OCall pClient::Client.Goodbye ;Remove the Client from all ClientGroups OCall [esi].RootClientGroup::ClientGroup.GoodbyeClient, pClient ;Attempt to locate and release the Client.. ;We keep inbound and outbound clients in separate pools ;Which pool does this client belong to? ;(Maybe clients should be tagged with this information?) OCall [esi].InClients::ClientPool.IsActive,pClient .if eax==TRUE ;If there was a defunct Job passed in with the defunct Client, ;we'd better trash that object as well. .if pJob!=0 OCall [esi].IOJobs::IOJobPool.IsActive,pJob .if eax==TRUE OCall pClient::Client.FreeJob,pJob .else DbgText "Attempt to free inactive IOJob suppressed" .endif .endif OCall [esi].InClients::ClientPool.Free,pClient DbgWarning "Inbound Client is set free" .else OCall [esi].OutClients::ClientPool.IsActive,pClient .if eax==TRUE .if pJob!=0 OCall [esi].IOJobs::IOJobPool.IsActive,pJob .if eax==TRUE OCall pClient::Client.FreeJob,pJob .else DbgText "Attempt to free inactive IOJob suppressed" .endif .endif OCall [esi].OutClients::ClientPool.Free,pClient DbgWarning "Outbound Client is set free" .else DbgWarning "Error - NetEngine.GoodbyeClient failed to locate target object" .endif .endif ;Ensure we have sufficient pending accepts ;associated with the dead client's listener mov eax,[esi].dMinimum_Pending_Accepts .if [esi].InClients.Available.dCount0 invoke EnterCriticalSection,addr [esi]._CS mov time,$invoke (GetTickCount) xor ecx,ecx .while ecx < [esi].InClients.Used.dCount .break .if [esi].ShuttingDown==TRUE push ecx OCall [esi].InClients.Used::Collection.ItemAt,ecx mov edx,time .if [eax].Client.dTimeIdleSinceAccept!=0 sub edx,[eax].Client.dTimeIdleSinceAccept .if edx>5000 OCall esi.GoodbyeClient,eax,NULL pop ecx .else pop ecx ;not time yet - check next client inc ecx .endif .else pop ecx ;client has sent something - check next client inc ecx .endif .endw invoke LeaveCriticalSection,addr [esi]._CS invoke Sleep,5000 .endif .until 0 MethodEnd ;Here is the HEART AND SOUL of the NetEngine. ;This thread is responsible for waiting on IOCP completion notifications ;and passing them to the 'OnIOCompleted' method. ;There can be several of these Worker threads operating asynchronously. ;=================================================== Method NetEngine.Worker, uses esi LOCAL compport:HANDLE LOCAL pJob:Pointer LOCAL pClient:Pointer LOCAL bytes:DWORD LOCAL accept_socket:SOCKET LOCAL wsabuf:PWSABUF LOCAL count:DWORD LOCAL flags:DWORD LOCAL err:DWORD LOCAL pOVL:Pointer LOCAL pAcceptJob:Pointer SetObject esi m2m compport, [esi].io_completion_port .repeat .break .if [esi].ShuttingDown==TRUE ;The GetQueuedCompletionStatus api call will (if successful) ;return two useful pieces of information to us. ;One is the "completion key" of the completed io, ;the other is a ptr to the XOVL which represents ;the io operation which (we hope) is completed. ;We can use either, or both of these. ;I am using pClient as a completion key, ;as we can obtain pIOJob from the xovl. ;In fact we can also obtain pClient from IOJob.pOwner !!! invoke GetQueuedCompletionStatus, compport, addr bytes, addr pClient, addr pOVL, 500 .if eax==TRUE .if pClient==NULL && pOVL==NULL DbgText "GetQueuedCompletionStatus went horribly wrong.","Worker" .break .endif ;Check for special 'time to die' message mov edx,pOVL .if [edx].XOVL.operation==OPERATION_ENDED DbgWarning "NetEngine WORKER DEATH" .break .endif ;Process the completed IOJob inside a CriticalSection ;This guarantees that no other invoke EnterCriticalSection,addr [esi]._CS OCall esi.OnIOCompleted,pOVL,bytes invoke LeaveCriticalSection,addr [esi]._CS .else .if pOVL==NULL ;simply means timeout .if [esi].ShuttingDown==TRUE .break .endif .else ;An IOJob was dequeued, but apparently it FAILED ;Lets take a closer look shall we? ;But first, check for special 'time to die' message mov edx,pOVL .if [edx].XOVL.operation==OPERATION_ENDED DbgWarning "NetEngine WORKER DEATH" .break .endif OCall esi.OnIOCompleted,pOVL,bytes .endif .endif .until 0 ;<-- condition 0 = infinite loop unless YOU break out of it, ; either using .break or by jumping. DbgWarning "NetEngine Worker Death" dec [esi].NumWorkerThreads invoke ExitThread,0 MethodEnd ;=================================================== ;==================================== ;======================= ;=========== ;This method counts the number of logical cpus available to the Process, ;this is the optimal number of Worker Threads. Method NetEngine.GetLogicalCPUCount,uses ebx edi esi LOCAL dProcessAffinity:dword, dSystemAffinity:dword, hCurrentProcessHandle:Handle LOCAL _si:SYSTEM_INFO SetObject esi invoke GetSystemInfo,addr _si DbgDec _si.dwNumberOfProcessors mov hCurrentProcessHandle, $invoke(GetCurrentProcess) invoke GetProcessAffinityMask, hCurrentProcessHandle, addr dProcessAffinity, addr dSystemAffinity .if eax != FALSE xor edi, edi xor ebx, ebx inc edi @@: test edi, dSystemAffinity .if !Zero? ;Check if this logical processor is available to this process .if $invoke(SetProcessAffinityMask, hCurrentProcessHandle, edi) invoke Sleep, 0 ;Give OS time to switch CPU inc ebx .endif .endif shl edi, 1 ;Prepare mask for the next logical processor jz @F ;Exit if the mask is above 32 cmp edi, dSystemAffinity jbe @B @@: ;Set previous processor affinity invoke SetProcessAffinityMask, hCurrentProcessHandle, dProcessAffinity mov eax, ebx .else DbgDec $invoke (GetLastError),"GetProcessAffinityMask Failure Reason" mov eax,1 .endif MethodEnd MakeObjects UPNPNATProtocol endif