; ; ETH.INC ; ; made by hidnplayr (hidnplayr@gmail.com) for KolibriOS ; ; The given code before every macro is only a simple example ; ; ; HISTORY ; ; v1.0: 18 august 2006 original release ; v1.1: december 2006 bugfixes and improvements ; macro mov arg1,arg2 { if arg1 eq arg2 else mov arg1,arg2 end if } TCB_LISTEN = 1 TCB_SYN_SENT = 2 TCB_SYN_RECEIVED = 3 TCB_ESTABLISHED = 4 TCB_FIN_WAIT_1 = 5 TCB_FIN_WAIT_2 = 6 TCB_CLOSE_WAIT = 7 TCB_CLOSING = 8 TCB_LAST_ASK = 9 TCB_TIME_WAIT = 10 TCB_CLOSED = 11 PASSIVE = 0 ACTIVE = 1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; eth.get_IP eax ; ; gets the current IP that is defined in Stack (return in eax in this example) macro eth.get_IP IP { mov ebx,1 mov eax,52 mcall mov IP ,eax } ; eth.get_GATEWAY eax ; ; gets the current GATEWAY that is defined in Stack (return in eax in this example) macro eth.get_GATEWAY GATEWAY { mov ebx,9 mov eax,52 mcall move GATEWAY ,eax } ; eth.get_SUBNET eax ; ; gets the current SUBNET that is defined in Stack (return in eax in this example) macro eth.get_SUBNET SUBNET { mov ebx,10 mov eax,52 mcall mov SUBNET ,eax } ; eth.get_DNS eax ; ; gets the current DNS that is defined in Stack (return in eax in this example) macro eth.get_DNS DNS { mov ebx,13 mov eax,52 mcall mov DNS ,eax } ; eth.set_IP eax ; ; set a new IP in stack (input in eax in this example) macro eth.set_IP IP { mov ecx,IP mov ebx,3 mov eax,52 mcall } ; eth.set_GATEWAY eax ; ; set a new GATEWAY in stack (input in eax in this example) macro eth.set_GATEWAY GATEWAY { mov ecx,GATEWAY mov ebx,11 mov eax,52 mcall } ; eth.set_SUBNET eax ; ; set a new SUBNET in stack (input in eax in this example) macro eth.set_SUBNET SUBNET { mov ecx,SUBNET mov ebx,12 mov eax,52 mcall } ; eth.set_DNS eax ; ; set a new DNS in stack (input in eax in this example) macro eth.set_DNS DNS { mov ecx,DNS mov ebx,14 mov eax,52 mcall } ; eth.open eax,80,ebx,[socket] ; ; open a socket on local port in eax to port 80 on server on ebx ; the socketnumber will be returned in [socket] (dword) macro eth.open_udp local,remote,ip,socket { mov ecx, local mov edx, remote mov esi, ip mov ebx, 0 mov eax, 53 mcall mov socket,eax } ; eth.close [socket] ; ; closes socket on socketnumber [socket] macro eth.close_udp socket { mov ecx, socket mov ebx, 1 mov eax, 53 mcall } ; eth.poll [socket],eax ; ; polls [socket] for data ; eax = 0 when there is data macro eth.poll socket { mov ecx, socket mov ebx, 2 mov eax, 53 mcall } ; eth.read_byte [socket], bl ; ; reads a byte from the socket and returns in bl macro eth.read_byte socket, result { mov ecx, socket mov ebx, 3 mov eax, 53 mcall mov result,bl } ; eth.read_byte [socket], bl ; ; reads a byte from the socket and returns in bl macro eth.read_packet socket, result { mov edx, result mov ecx, socket mov ebx, 10 mov eax, 53 mcall } ; eth.write [socket],12,msg ; msg db 'hello world!' ; ; send message msg to socket macro eth.write_udp socket,length,msg,verify { mov ecx, socket mov edx, length mov esi, msg mov ebx, 4 mov eax, 53 mcall if verify eq 1 call verifysend end if } verifysend: test eax,eax jnz @f ret @@: pusha mov eax,5 mov ebx,100 mcall popa mcall ret ; eth.open_tcp 80,80,eax,0,[socket] ; ; opens a tcp socket on port 80 to port 80 on IP eax with passive open ; returns socket number in eax macro eth.open_tcp local,remote,ip,passive,socket { mov ecx, local mov edx, remote mov esi, ip mov edi, passive ; 0 = PASSIVE open mov ebx, 5 mov eax, 53 mcall mov socket,eax } ; eth.socket_status [socket],eax ; ; returns socket status in eax macro eth.socket_status socket,result { mov ecx, socket mov ebx, 6 mov eax, 53 mcall mov result,eax } ; eth.write_tcp [socket],12,msg ; ; msg db 'hello world!' ; ; send message to TCP socket macro eth.write_tcp socket,length,msg,verify { mov ecx, socket mov edx, length mov esi, msg mov ebx, 7 mov eax, 53 mcall if verify eq 1 call verifysend end if } ; eth.close_tcp [socket] ; ; closes tcp socket [socket] macro eth.close_tcp socket { mov ecx, socket mov ebx, 8 mov eax, 53 mcall } ; eth.check_port 165,eax ; ; checks if port 165 is used ; return is 0 when port is free macro eth.check_port port,result { mov ecx, port mov ebx, 9 mov eax, 53 mcall mov result,eax } ; eth.status eax ; ; returns socket status in eax macro eth.status status { mov ebx, 255 mov ecx, 6 mov eax, 53 mcall mov status,eax } ; eth.search 165,edx ; ; searches a free local port starting from 166 (165 + 1 !) ; returns in edx macro eth.search_port port,result { mov edx,port @@: inc edx eth.check_port edx,eax cmp eax,0 je @r mov result,edx } ; eth.read_data [socket],buffer,512 ; buffer rb 512 ; socket dd ? ; ; reads data from socket into a buffer, stops when there is no more data or buffer is full. macro eth.read_data socket,dest,endptr,bufferl { local .getdata,.loop,.end mov eax, dest mov endptr, eax ; we have data - this will be the response .getdata: mov eax,endptr cmp eax,bufferl jg .end eth.read_byte socket,bl ; Store the data in the response buffer mov eax, endptr mov [eax], bl inc dword endptr eth.poll socket cmp eax,0 jne .getdata ; yes, so get it ; now we are going to wait 30 times 10 ms (300ms) mov edx,0 .loop: mov eax,5 mov ebx,1 mcall eth.poll socket cmp eax, 0 jne .getdata ; yes, so get it inc edx cmp edx,100 jl .loop .end: } ; eth.wait_for_data [socket],60,abort ; eth.read_data .... ; abort: ; ; Waits for data with timeout macro eth.wait_for_data socket,TIMEOUT,abort { mov edx,TIMEOUT @@: eth.poll socket cmp eax,0 jne @f dec edx jz abort mov eax,5 ; wait here for event mov ebx,10 mcall jmp @r @@: } ; The function 'resolve' resolves the address in edx and puts the resulting IP in eax. ; When the input is an IP-adress, the function will output this IP in eax. ; If something goes wrong, the result in eax should be 0 ; ; example: ; ; resolve query1,IP,PORT ; resolve '192.168.0.1',IP,PORT ; resolve query2,IP,PORT ; ; query1 db 'www.google.com',0 ; query2 db '49.78.84.45',0 ; IP dd ? ; PORT dd ? macro resolve query,result { if query eqtype 0 mov edx,query else local ..string, ..label jmp ..label ..string db query,0 ..label: mov edx,..string end if call __resolve mov result,eax } if used __resolve __resolve: if __DEBUG__ eq 1 DEBUGF 1,'Resolving started\n' end if ; This code validates if the query is an IP containing 4 numbers and 3 dots push edx ; push edx (query address) onto stack xor al, al ; make al (dot count) zero @@: cmp byte[edx],'0' ; check if this byte is a number, if not jump to no_IP jl no_IP ; cmp byte[edx],'9' ; jg no_IP ; inc edx ; the byte was a number, so lets check the next byte cmp byte[edx],0 ; is this byte zero? (have we reached end of query?) jz @f ; jump to next @@ then cmp byte[edx],':' jz @f cmp byte[edx],'.' ; is this byte a dot? jne @r ; if not, jump to previous @@ inc al ; the byte was a dot so increment al(dot count) inc edx ; next byte jmp @r ; lets check for numbers again (jump to previous @@) @@: ; we reach this when end of query reached cmp al,3 ; check if there where 3 dots jnz no_IP ; if not, jump to no_IP (this is where the DNS will take over) ; The following code will convert this IP into a dword and output it in eax ; If there is also a port number specified, this will be returned in ebx, otherwise ebx is -1 pop esi ; edx (query address) was pushed onto stack and is now popped in esi xor edx, edx ; result xor eax, eax ; current character xor ebx, ebx ; current byte .outer_loop: shl edx, 8 add edx, ebx xor ebx, ebx .inner_loop: lodsb test eax, eax jz .finish cmp al, '.' jz .outer_loop sub eax, '0' imul ebx, 10 add ebx, eax jmp .inner_loop .finish: shl edx, 8 add edx, ebx bswap edx ; we want little endian order mov eax, edx ret no_IP: pop edx ; The query is not an IP address, we will send the query to a DNS server and hope for answer ;) if __DEBUG__ eq 1 DEBUGF 1,'The query is no ip, Building request string from:%u\n',edx end if ; Build the request string mov eax, 0x00010100 mov [dnsMsg], eax mov eax, 0x00000100 mov [dnsMsg+4], eax mov eax, 0x00000000 mov [dnsMsg+8], eax ; domain name goes in at dnsMsg+12 mov esi, dnsMsg + 12 ; location of label length mov edi, dnsMsg + 13 ; label start mov ecx, 12 ; total string length so far td002: mov [esi], byte 0 inc ecx td0021: mov al, [edx] cmp al, 0 je td001 ; we have finished the string translation cmp al, '.' je td004 ; we have finished the label inc byte [esi] inc ecx mov [edi], al inc edi inc edx jmp td0021 td004: mov esi, edi inc edi inc edx jmp td002 ; write label len + label text td001: mov [edi], byte 0 inc ecx inc edi mov [edi], dword 0x01000100 add ecx, 4 mov [dnsMsgLen], ecx ; We'll need the length of the message when we send it ; Now, lets send this and wait for an answer eth.search_port 1024,edx ; Find a free port starting from 1025 and store in edx eth.get_DNS esi ; Read DNS IP from stack into esi eth.open_udp edx,53,esi,[socketNum] ; First, open socket if __DEBUG__ eq 1 DEBUGF 1,'Socket opened: %u (port %u)\n',[socketNum],ecx end if eth.write_udp [socketNum],[dnsMsgLen],dnsMsg ; Write to socket ( request DNS lookup ) if __DEBUG__ eq 1 DEBUGF 1,'Data written, length:%u offset:%u\n',[dnsMsgLen],dnsMsg DEBUGF 1,'Waiting for data: (timeout is %us)\n',TIMEOUT end if eth.wait_for_data [socketNum],TIMEOUT,abort ; Now, we wait for data from remote eth.read_data [socketNum],dnsMsg,[dnsMsgLen],dnsMsg+BUFFER ; Read the data into the buffer if __DEBUG__ eq 1 DEBUGF 1,'Data received, offset:%u buffer size:%u length:%u\n',dnsMsg,BUFFER,esi-dnsMsg end if eth.close_udp [socketNum] ; We're done, close the socket if __DEBUG__ eq 1 DEBUGF 1,'Closed Socket\n' end if ; Now parse the message to get the host IP. Man, this is complicated. It's described in RFC 1035 ; 1) Validate that we have an answer with > 0 responses ; 2) Find the answer record with TYPE 0001 ( host IP ) ; 3) Finally, copy the IP address to the display ; Note: The response is in dnsMsg, the end of the buffer is pointed to by [dnsMsgLen] mov esi, dnsMsg mov al, [esi+2] ; Is this a response to my question? and al, 0x80 cmp al, 0x80 jne abort if __DEBUG__ eq 1 DEBUGF 1,'It was a response to my question\n' end if mov al, [esi+3] ; Were there any errors? and al, 0x0F cmp al, 0x00 jne abort if __DEBUG__ eq 1 DEBUGF 1,'There were no errors\n' end if mov ax, [esi+6] ; Is there ( at least 1 ) answer? cmp ax, 0x00 je abort ; Header validated. Scan through and get my answer add esi, 12 ; Skip to the question field call skipName ; Skip through the question field add esi, 4 ; skip past the questions qtype, qclass ctr002z: ; Now at the answer. There may be several answers, find the right one ( TYPE = 0x0001 ) call skipName mov ax, [esi] cmp ax, 0x0100 ; Is this the IP address answer? jne ctr002c add esi, 10 ; Yes! Point eax to the first byte of the IP address mov eax,[esi] ret ctr002c: ; Skip through the answer, move to the next add esi, 8 movzx eax, byte [esi+1] mov ah, [esi] add esi, eax add esi, 2 cmp esi, [dnsMsgLen] ; Have we reached the end of the msg? This is an error condition, should not happen jl ctr002z ; Check next answer abort: if __DEBUG__ eq 1 DEBUGF 1,'Something went wrong, aborting\n' end if xor eax,eax ret skipName: ; Increment esi to the first byte past the name field ; Names may use compressed labels. Normally do. ; RFC 1035 page 30 gives details mov al, [esi] cmp al, 0 je sn_exit and al, 0xc0 cmp al, 0xc0 je sn001 movzx eax, byte [esi] inc eax add esi, eax jmp skipName sn001: add esi, 2 ; A pointer is always at the end ret sn_exit: inc esi ret dnsMsgLen: dd 0 socketNum: dd 0xFFFF if ~defined dnsMsg dnsMsg: rb BUFFER end if end if