;Please note that the Object Definition for this class ;has been inserted into the NetEngine file, ;making these two classes 'friends'. ;NetEngine knows what UPNPNAtProtocol's interface looks like. ;UPNPNAtProtocol knows what NetEngine's interface looks like. ;This allows each class to call methods of the other class. ;This is a special situation, since UPNPNAtProtocol ;is embedded as a COMPONENT of NetEngine. ;Further Notes regarding this class: ;Usually, we call Init to set the pOwner field to be some window handle. ;However in this case, pOwner of this object will be the NetEngine object. ;This allows the protocol handler to call NetEngine methods directly. ;Notes regarding UPNP nat traversal: ;The first stage is called 'upnp discovery', which involves ;sending a special udp packet to a special udp broadcast address ;while listening for a udp reply packet. ;The reply contains the ip address of the 'upnp device controller', ;and the tcp Port we should then connect to on that ip address. ;The second stage is all performed using this second TCP connection. ;We connect to the IP and Port we obtained from the UDP reply, ;and then we send a http request for more information. ;Ultimately we wish to discover the 'external ip address' ;because we need that when we want to create a Port Forwarding. UPNP_DISCOVERED equ 666 .data ;UDP broadcast request for upnp discovery httpreqtemplate db "GET %s HTTP/1.1",13,10, "Accept: text/xml, application/xml",13,10, "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)",13,10, "Host: %s:%lu",13,10,"Connection: Keep-Alive",13,10,"Cache-Control: no-cache",13,10, "Pragma: no-cache",13,10,13,10,0 ;strings we need to find in the reply of upnp discovery szfirst db "urn:schemas-upnp-org:device:WANConnectionDevice:1",0 szsecond db "urn:schemas-upnp-org:service:WANIPConnection:1",0 ;header of request for upnp device controller szreqextip db "POST %s HTTP/1.1",13,10, "HOST: %s:%lu",13,10, "CONTENT-LENGTH: %lu",13,10, 'CONTENT-TYPE: text/xml; charset="utf-8"',13,10, 'SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"',13,10,13,10,0 ;body of request for upnp device controller szreqextipsoap db "',13,10," ",13,10,' ',13,10, ' ',13,10,' ',13,10,'',13,10,13,10,0 ;string we need to find in the reply of request for upnp device controller szextip db '',13,10,'', '', '',0 szAPMBodyB db '%lu', '%s', '%lu', '%s',0 szAPMBodyC db '1', '%s', '0','',13,10,0 .code ;---------------------------------------- ; Convert decimal string into dword value ; return value in eax ;---------------------------------------- a2dw proc uses ecx edi edx esi pString:DWORD mov edi, pString invoke lstrlen, pString xor ecx, ecx .while eax != 0 xor edx, edx mov dl, byte ptr [edi] sub dl, "0" ; subtrack each digit with "0" to convert it to hex value mov esi, eax dec esi push eax mov eax, edx push ebx mov ebx, 10 .while esi > 0 mul ebx dec esi .endw pop ebx add ecx, eax pop eax inc edi dec eax .endw mov eax, ecx ret a2dw endp ;This procedure attempts to locate the 'Content-Length' tag in a http header ;and convert the integer string that follows it into a dword. ;Possible return values are: ;eax = ERROR_BADPROTOCOL (Failed), or eax = #bytes expected in http payload parse_content_length proc pJob LOCAL buf[512]:BYTE LOCAL _begin, _end DbgText "Parsing Content Length" mov eax,pJob DbgStr [eax].IOJob.xovl.pbuforig invoke StrPos,[eax].IOJob.xovl.pbuforig,$OfsCStr("Content-Length: ") .if eax==0 DbgWarning "Failed to find Content Length" mov eax, ERROR_BADPROTOCOL .else add eax,16 ;sizeof "Content-Length: " mov _begin,eax mov _end,$invoke (StrScan,_begin,13) .if _end==0 DbgWarning "Failed to find Content Length delimiter" mov eax, ERROR_BADPROTOCOL .else ;Parse the content length and convert it to integer ;and store it in the Client StateBlock mov eax,_end sub eax,_begin inc eax push eax invoke lstrcpyn,addr buf,_begin,eax pop eax lea edx,buf mov byte ptr[eax+edx],0 invoke a2dw,addr buf .endif .endif ret parse_content_length endp Method UPNPNATProtocol.Init,uses esi,pOwner SetObject esi ACall esi.Init,pOwner MethodEnd Method UPNPNATProtocol.OnUserIOCompleted,uses esi,pClient, pJob, dError SetObject esi mov eax,pJob mov eax,[eax].IOJob.xovl.operation .if eax==UPNP_DISCOVERED DbgWarning "Response to upnp discovery packet obtained" ;We need to issue a new Client, using TCP instead invoke inet_addr,addr NAT_IP_String OCall [esi].pOwner::NetEngine.ConnectTo,esi,eax,upnp_tcp_port,NULL,NULL ;We don't need this UDP Client anymore, or this IOJob.. ;Note that we tell NetEngine to kill these resources, ;otherwise they won't be correctly recycled !! OCall [esi].pOwner::NetEngine.GoodbyeClient,pClient,pJob .endif MethodEnd ;Client class will call this method to determine if we have received ;at least one complete packet, given the rules of your Protocol. ;This is where your derived class examines the received data ;looking to find a valid Delimiter and doing Protocol enforcement. ;When you override this method in a derived class, you should return ;one of the following possible values: ;ERROR_BADPROTOCOL - The buffer content is not following our Protocol - the Client will get Booted for this offence ;ERROR_USERQUIT - The user sent a nice QUIT message using our Protocol - the Client will get Booted for this ;Positive INT32 - This many Bytes are part of the Complete Packet ;NULL - We expect more data to be Appended to this Job Method UPNPNATProtocol.FindDelimiter,uses esi ebx,pClient,pXOVL LOCAL payloadsize, headersize mov eax,pXOVL DbgDec [eax].XOVL.bytes,"bytes due to this recv" DbgDec [eax].XOVL.bytesused,"total bytes buffered in this job" DbgMem [eax].XOVL.pbuforig,[eax].XOVL.bytesused mov eax,pClient .if [eax].Client.bSocketIsUDP==TRUE ;The UDP packet is delimited by two CRLF's (13,10,13,10) mov eax,pXOVL mov ebx,[eax].XOVL.pbuforig invoke StrPos,ebx,$OfsCStr(13,10,13,10) .if eax!=0 ;We found the Delimiter - return #bytes in packet mov edx,pXOVL add eax,4 sub eax,[edx].XOVL.pbuforig DbgWarning "UDP PACKET DELIMITER FOUND" .endif .else mov eax,pXOVL mov edx,[eax].XOVL.pbuforig .if dword ptr [edx] == 'PTTH' && byte ptr[edx+4]=='/' DbgText "IS HTTP" ;Its a http Header ;We have received our first HTTP reply (header only) ;We should check for a '200 OK' message, then parse the http reply ;looking for the "content length" field. invoke StrPos,edx,$OfsCStr("200 OK") .if eax==0 DbgWarning "Failed to find http ok" invoke StrPos,edx,$OfsCStr("100 Continue") .if eax==0 return ERROR_BADPROTOCOL .else DbgWarning "HAVE COMPLETE PACKET" mov eax,pXOVL mov eax,[eax].XOVL.bytesused .endif .else mov eax, pXOVL mov payloadsize,$invoke (parse_content_length,[eax].XOVL.piojob) .if eax!=ERROR_BADPROTOCOL DbgDec eax,"Content Length (from HTTP header)" ;Calculate size of http header mov eax,pXOVL mov ebx,[eax].XOVL.pbuforig invoke StrPos,ebx,$OfsCStr(13,10,13,10) ;<-- http header delimiter .if eax!=0 mov edx,pXOVL sub eax,[edx].XOVL.pbuforig add eax,4 ;eax = size of http header mov headersize,eax mov edx,[edx].XOVL.bytesused sub edx,eax ;edx = size of available payload .if edx!=payloadsize DbgWarning "EXPECTING MORE DATA" mov eax,NULL .else DbgWarning "HAVE COMPLETE PACKET" mov eax,pXOVL mov eax,[eax].XOVL.bytesused .endif .else ;Did not find delimiter mov eax,ERROR_BADPROTOCOL .endif .else DbgWarning "Failed to parse Content Length (from HTTP header)" .endif .endif .else ;Theres no http header - whats going on? DbgWarning "Error - Expected HTTP Header" int 3 .endif .endif MethodEnd ;A client has finished sending us data, ;now we have to do something with that data. ;Make sure your derived method returns one of the following values: ;ERROR_BADPROTOCOL - The buffer content isnt following our Protocol - the Client will get Booted for this offence ;ERROR_USERQUIT - The user sent a nice QUIT message using our Protocol - the Client will get Booted for this ;Positive INT32 - This many Bytes were cleanly processed and should be cut from the IOJob's buffer Method UPNPNATProtocol.ProcessReceivedData,uses esi edi ebx,pClientJobs LOCAL bytes,_begin,_end LOCAL pJob local buf[1024]:BYTE LOCAL buf2[256]:BYTE mov bytes,0 mov edi,pClientJobs xor ebx,ebx SetObject esi .if [edi].Client.bSocketIsUDP==TRUE ; .while ebx<[edi].DwordCollection.dCount mov pJob,$OCall (edi::DwordCollection.ItemAt,ebx) invoke StrPos,[eax].IOJob.xovl.pbuforig,$OfsCStr("200 OK") .if eax==0 DbgWarning "Failed to find http ok" return ERROR_BADPROTOCOL .endif mov eax,pJob mov _begin,$invoke (StrPos,[eax].IOJob.xovl.pbuforig,$OfsCStr("http://")) .if eax==0 DbgWarning "Failed to find url of upnp device controller" return ERROR_BADPROTOCOL .endif mov _end,$invoke (StrScan,_begin,13) .if _end==0 DbgWarning "Failed to delimit url of upnp device controller" return ERROR_BADPROTOCOL .endif mov eax,_end sub eax,_begin push eax inc eax invoke lstrcpyn,addr [esi].describe_url,_begin,eax pop eax lea edx,[esi].describe_url mov byte ptr[eax+edx],0 ;DbgStr [esi].describe_url,"FOUND URL OF UPNP DEVICE CONTROLLER" ;We shall now parse the upnp device controller url for the ip and port add _begin,7 ;sizeof "http://" mov _end,$invoke (StrScan,_begin,':') .if _end==0 DbgWarning "Failed to find ip delimiter" return ERROR_BADPROTOCOL .endif mov eax,_end sub eax,_begin push eax inc eax invoke lstrcpyn,addr buf,_begin,eax pop eax inc eax add _begin,eax lea edx,buf mov byte ptr[eax+edx],0 invoke lstrcpy, addr NAT_IP_String,edx ;DbgStr NAT_IP_String mov _end,$invoke (StrScan,_begin,'/') .if _end==0 DbgWarning "Failed to find port delimiter" return ERROR_BADPROTOCOL .endif mov eax,_end sub eax,_begin inc eax push eax invoke lstrcpyn,addr buf2,_begin,eax pop eax lea edx,buf2 mov byte ptr[eax+edx],0 mov upnp_tcp_port,$invoke (a2dw,addr buf2) ;Since we've initiated the secondary (tcp) connection to the gateway device, ;we can tell NetEngine to trash the primary (udp) client. OCall pClientJobs::Client.Notify,UPNP_DISCOVERED ;Don't kill this Client yet, lets wait for the custom notification mov eax,pJob mov eax,[eax].IOJob.xovl.bytesused ; inc ebx ; .endw .else ;We have received some TCP data from the upnp device controller ;This protocol handler will make the dangerous assumption ;that a http packet ALWAYS fits inside a single IOJob buffer ;(ie, is not so large that multiple IOJobs are required to hold it) mov pJob,$OCall (edi::DwordCollection.ItemAt,ebx) DbgDec [esi].Discovery_Stage,,"stage" mov eax,[esi].Discovery_Stage .if eax==0 DbgWarning "Received first XML reply" inc [esi].Discovery_Stage ;If stage = 0 , its xml data describing the UPNP Gateway device ;We need to find this : urn:schemas-upnp-org:device:WANConnectionDevice:1 ;and then this: urn:schemas-upnp-org:service:WANIPConnection:1 ;and finally this: HERES THE URL WE NEED mov eax,pJob m2m _begin,[eax].IOJob.xovl.pbuforig,edx ;DbgStr _begin mov _begin,$invoke (StrPos,_begin,addr szfirst) .if eax==0 DbgWarning "Failed to find WANConnectionDevice devicetype" return ERROR_BADPROTOCOL .else add _begin,sizeof szfirst-1 mov _begin,$invoke (StrPos,_begin,addr szsecond) .if eax==0 DbgWarning "Failed to find WANIPConnection servicetype" return ERROR_BADPROTOCOL .else add _begin,sizeof szsecond-1 mov _begin,$invoke (StrPos,_begin,$OfsCStr("")) .if eax==0 DbgWarning "Failed to find controlURL" return ERROR_BADPROTOCOL .else add _begin,12 ;sizeof .endif mov _end,$invoke (StrScan,_begin,3Ch) ;'<' .if _end==0 DbgWarning "Failed to find controlURL delimiter" mov eax, ERROR_BADPROTOCOL .else ;Good, we found the control url mov eax,_end sub eax,_begin push eax inc eax invoke lstrcpyn,addr [esi].controlUrl,_begin,eax pop eax lea edx,[esi].controlUrl mov byte ptr [eax+edx],0 DbgWarning "Found upnp Control URL" ;We then build a request for the EXTERNAL IP ADDRESS ;DbgText "Formatting HTTP request for External IP address" invoke wsprintf,addr buf,addr szreqextip, addr [esi].controlUrl, addr NAT_IP_String,upnp_tcp_port, sizeof szreqextipsoap-1 ; DbgText "Appending SOAP request for External IP address" invoke lstrcat,addr buf,addr szreqextipsoap ;We send that request to the upnp host, and wait for the reply. ;DbgText "Determining length of request" invoke lstrlen,addr buf ; lea edx,buf ; DbgMem edx,eax OCall pClientJobs::Client.QueueWrite,addr buf,eax mov eax,pJob return [eax].IOJob.xovl.bytesused .endif .endif .endif .elseif eax==1 DbgText "Parsing External IP" inc [esi].Discovery_Stage ;We've received the XML payload for the 'External IP' request, ;and we need to parse it to get the external ip address. ;Once we have that, we're ready to create port mapping(s) :D mov eax,pJob DbgStr [eax].IOJob.xovl.pbuforig mov _begin,$invoke (StrPos, [eax].IOJob.xovl.pbuforig,addr szextip) .if eax!=0 add _begin,sizeof szextip-1 mov _begin,$invoke (StrScan,_begin,3Eh) ;'>' .if eax!=0 inc _begin mov _end,$invoke (StrScan,_begin,3Ch) ;'<' mov byte ptr[eax],0 invoke lstrcpy,addr szExternalIP,_begin DbgStr szExternalIP mov eax,[esi].pOwner mov [eax].NetEngine.bLocalMachineIsBehindNATDevice,TRUE .else DbgWarning "Failed to find External IP 1" .endif .else DbgWarning "Failed to find External IP 2" .endif mov eax,[esi].pOwner invoke SetEvent, [eax].NetEngine.hWake ;Whether we found the IP or not, this session is FINISHED. mov eax,ERROR_BADPROTOCOL .elseif eax==2 ;Port Mapping has been successfully added DbgWarning "SUCCESSFULLY ADDED A PORT FORWARDING" mov eax,ERROR_BADPROTOCOL ;not really error, just wanna terminate session .else DbgWarning "Client Stage not handled yet" DbgDec [esi].Discovery_Stage mov eax,ERROR_BADPROTOCOL .endif .endif MethodEnd ;When the secondary (tcp) client's attempt to connect to the gateway device has completed, ;we will be notified via this method. ;If dError is non-zero, the connect attempt failed. Method UPNPNATProtocol.OnConnectCompleted,uses esi ,pClient,dError LOCAL request[512]:BYTE SetObject esi .if dError==0 .if [esi].Discovery_Stage==0 DbgWarning "UPNPNATProtocol: Outbound Client has Connected to remote Host" ;We just made our first connect to the upnp device on its 'control port' ;Let's build our first request packet and send it. mov request[0],0 invoke wsprintf,addr request, addr httpreqtemplate,addr [esi].describe_url,addr NAT_IP_String, upnp_tcp_port ;DbgStr request invoke lstrlen,addr request OCall pClient::Client.QueueWrite,addr request,eax .else ;We just made a secondary connect to the upnp controller ;which means we're trying to add a new port mapping invoke SetEvent,[esi].hAddPortMapping DbgWarning "ADDING PORT MAPPING" .endif .else DbgWarning "UPNPNATProtocol: Failed to connect to upnp device controller." .endif MethodEnd Method UPNPNATProtocol.AddPortMapping,uses esi, dPort, bWantUDP:BOOL LOCAL buf[2048]:BYTE LOCAL body,request LOCAL localipstring LOCAL pclient DbgWarning "Adding a Port Mapping" SetObject esi mov [esi].hAddPortMapping,$invoke(CreateEvent,0,FALSE,FALSE,0) invoke inet_addr,addr NAT_IP_String mov pclient,$OCall ([esi].pOwner::NetEngine.ConnectTo,esi,eax,upnp_tcp_port,NULL,NULL) .if eax!=0 invoke WaitForSingleObject,[esi].hAddPortMapping,20000 .if eax==WAIT_OBJECT_0 DbgWarning "WAIT IS OVER" invoke gethostname,addr buf,sizeof buf .if eax==0 invoke gethostbyname,addr buf .if eax!=0 mov eax,[eax+12] mov eax,[eax] mov eax,[eax] mov localipstring,$invoke (inet_ntoa,eax) .if eax!=0 ;start building the request mov request,$MemAlloc(16384) mov body,$MemAlloc(16384) mov byte ptr[eax],0 ;First we build the 'body' , because we need its Length .if bWantUDP==TRUE mov edx,$OfsCStr("UDP") .else mov edx,$OfsCStr("TCP") .endif DbgWarning "BUILDING REQUEST" invoke wsprintf,addr buf,addr szAPMBodyB, dPort, edx, dPort, localipstring invoke lstrcpy,body,addr szAPMBodyA;,addr szExternalIP invoke lstrcat,body,addr buf ;would prefer to use the application name here invoke wsprintf,addr buf,addr szAPMBodyC, $OfsCStr("NetEngine") invoke lstrcat, body,addr buf ;Now format the header invoke lstrlen, body invoke wsprintf, request, addr szAPMHead,addr [esi].controlUrl,addr NAT_IP_String,upnp_tcp_port,eax invoke lstrcat, request, body invoke lstrlen, request DbgMem request, eax ;We are now ready to create a new client session ;in order to send this request to the upnp controller OCall pclient::Client.QueueWrite,request,eax MemFree request MemFree body .else DbgWarning "Error - inet_ntoa failed" .endif .else DbgWarning "Error - gethostbyname failed" .endif .else DbgWarning "Error - gethostname failed" .endif .else DbgWarning "Error - UPNP.AddPortMapping timed out" .endif .endif invoke CloseHandle,[esi].hAddPortMapping MethodEnd