;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2010-2013. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;; zeroconfig.asm - Zeroconfig service for KolibriOS ;; ;; ;; ;; Written by hidnplayr@kolibrios.org ;; ;; Some code contributed by Derpenguin ;; ;; ;; ;; DHCP code is based on that by Mike Hibbet ;; ; (DHCP client for menuetos) ;; ;; ;; ;; GNU GENERAL PUBLIC LICENSE ;; ;; Version 2, June 1991 ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; format binary as "" use32 org 0x0 db 'MENUET01' ; 8 byte id dd 0x01 ; header version dd START ; start of code dd IM_END ; size of image dd (I_END+0x100) ; memory for app dd (I_END+0x100) ; esp dd 0, 0 ; I_Param, I_Path ; CONFIGURATION TIMEOUT = 60 ; in seconds BUFFER = 1024 ; in bytes __DEBUG__ = 1 ; enable/disable __DEBUG_LEVEL__ = 1 ; 1 = all, 2 = errors ; CONFIGURATION FOR LINK-LOCAL PROBE_WAIT = 1 ; second (initial random delay) PROBE_MIN = 1 ; second (minimum delay till repeated probe) PROBE_MAX = 2 ; seconds (maximum delay till repeated probe) PROBE_NUM = 3 ; (number of probe packets) ANNOUNCE_NUM = 2 ; (number of announcement packets) ANNOUNCE_INTERVAL = 2 ; seconds (time between announcement packets) ANNOUNCE_WAIT = 2 ; seconds (delay before announcing) MAX_CONFLICTS = 10 ; (max conflicts before rate limiting) RATE_LIMIT_INTERVAL = 60 ; seconds (delay between successive attempts) DEFEND_INTERVAL = 10 ; seconds (min. wait between defensive ARPs) include '../proc32.inc' include '../macros.inc' include '../debug-fdo.inc' include '../network.inc' include 'dhcp.inc' include '../dll.inc' Ip2dword: push edx ; This code validates if the query is an IP containing 4 numbers and 3 dots 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 ; 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 ret no_IP: pop edx xor edx, edx ret START: mcall 40, EVM_STACK2 DEBUGF 1,">Zero-config service loaded\n" .wait: mcall 76, API_ETH + 4 ; get MAC of ethernet interface 0 cmp eax, -1 jne .start mcall 10 jmp .wait .start: mov word[MAC], bx mov dword[MAC+2], eax DEBUGF 1,"->MAC: %x-%x-%x-%x-%x-%x\n", [MAC+0]:2, [MAC+1]:2, [MAC+2]:2, [MAC+3]:2, [MAC+4]:2, [MAC+5]:2 mcall 40, EVM_STACK mcall 68, 11 stdcall dll.Load,@IMPORT or eax, eax jnz try_dhcp invoke ini.get_str, path, str_ipconfig, str_type, inibuf, 16, 0 cmp dword[inibuf], 'stat' jne try_dhcp invoke ini.get_str, path, str_ipconfig, str_ip, inibuf, 16, 0 mov edx, inibuf call Ip2dword mcall 76, API_IPv4 + 3, edx invoke ini.get_str, path, str_ipconfig, str_gateway, inibuf, 16, 0 mov edx, inibuf call Ip2dword mcall 76, API_IPv4 + 9, edx invoke ini.get_str, path, str_ipconfig, str_dns, inibuf, 16, 0 mov edx, inibuf call Ip2dword mcall 76, API_IPv4 + 5, edx invoke ini.get_str, path, str_ipconfig, str_subnet, inibuf, 16, 0 mov edx, inibuf call Ip2dword mcall 76, API_IPv4 + 7, edx mcall -1 try_dhcp: DEBUGF 1,"->Trying DHCP\n" mcall 75, 0, AF_INET4, SOCK_DGRAM, 0 ; open socket (parameters: domain, type, reserved) cmp eax, -1 je error mov [socketNum], eax DEBUGF 1,"->Socket %x opened\n", eax mcall 75, 2, [socketNum], sockaddr1, 18 ; bind socket to local port 68 cmp eax, -1 je error DEBUGF 1,"->Socket Bound to local port 68\n" mcall 75, 4, [socketNum], sockaddr2, 18 ; connect to 255.255.255.255 on port 67 cmp eax, -1 je error DEBUGF 1,"->Connected to 255.255.255.255 on port 67\n" mov [dhcpMsgType], 0x01 ; DHCP discover mov [dhcpLease], esi ; esi is still -1 (-1 = forever) mcall 26, 9 ; Get system time imul eax, 100 mov [currTime], eax build_request: ; Creates a DHCP request packet. DEBUGF 1,"->Building request\n" stdcall mem.Alloc, BUFFER mov [dhcpMsg], eax test eax, eax jz dhcp_error ;;; todo: skip this bullcrap mov edi, eax mov ecx, BUFFER xor eax, eax rep stosb ;; todo: put this in a buffer instead of writing bytes and words! mov edx, [dhcpMsg] ; Boot protocol legacy mov [edx], byte 0x01 ; Boot request mov [edx+1], byte 0x01 ; Ethernet mov [edx+2], byte 0x06 ; Ethernet h/w len mov [edx+4], dword 0x11223344 ; xid ;;;;;;; mov eax, [currTime] mov [edx+8], eax ; secs, our uptime mov [edx+10], byte 0x80 ; broadcast flag set mov eax, dword [MAC] ; first 4 bytes of MAC mov [edx+28],dword eax mov ax, word [MAC+4] ; last 2 bytes of MAC mov [edx+32],word ax ; DHCP extension mov [edx+236], dword 0x63538263 ; magic cookie mov [edx+240], word 0x0135 ; option DHCP msg type mov al, [dhcpMsgType] mov [edx+240+2], al mov [edx+240+3], word 0x0433 ; option Lease time = infinity mov eax, [dhcpLease] mov [edx+240+5], eax mov [edx+240+9], word 0x0432 ; option requested IP address mov eax, [dhcp.ip] mov [edx+240+11], eax mov [edx+240+15], word 0x0437 ; option request list mov [edx+240+17], dword 0x0f060301 cmp [dhcpMsgType], byte 0x01 ; Check which msg we are sending jne request_options mov [edx+240+21], byte 0xff ; "Discover" options mov [dhcpMsgLen], dword 262 ; end of options marker jmp send_dhcpmsg request_options: mov [edx+240+21], word 0x0436 ; server IP mov eax, [dhcpServerIP] mov [edx+240+23], eax mov [edx+240+27], byte 0xff ; end of options marker mov [dhcpMsgLen], dword 268 send_dhcpmsg: mcall 75, 6, [socketNum], [dhcpMsg], [dhcpMsgLen] ; write to socket ( send broadcast request ) mov eax, [dhcpMsg] ; Setup the DHCP buffer to receive response mov [dhcpMsgLen], eax ; Used as a pointer to the data mcall 23, TIMEOUT*10 ; wait for data read_data: ; we have data - this will be the response mcall 75, 7, [socketNum], [dhcpMsg], BUFFER, 0 ; read data from socket DEBUGF 1,"->%d bytes received\n", eax cmp eax, -1 je dhcp_error mov [dhcpMsgLen], eax ; depending on which msg we sent, handle the response ; accordingly. ; If the response is to a dhcp discover, then: ; 1) If response is DHCP OFFER then ; 1.1) record server IP, lease time & IP address. ; 1.2) send a request packet ; If the response is to a dhcp request, then: ; 1) If the response is DHCP ACK then ; 1.1) extract the DNS & subnet fields. Set them in the stack cmp [dhcpMsgType], 0x01 ; did we send a discover? je discover cmp [dhcpMsgType], 0x03 ; did we send a request? je request call dhcp_end ; we should never reach here ;) jmp exit discover: call parse_response cmp [dhcpMsgType], 0x02 ; Was the response an offer? je send_request call dhcp_end jmp link_local send_request: mov [dhcpMsgType], 0x03 ; make it a request jmp build_request request: call parse_response cmp [dhcpMsgType], 0x05 ; Was the response an ACK? It should be jne read_data ; NO - read next packets call dhcp_end mcall 76, API_IPv4 + 3, [dhcp.ip] ; ip mcall 76, API_IPv4 + 5, [dhcp.dns] ; dns mcall 76, API_IPv4 + 7, [dhcp.subnet] ; subnet mcall 76, API_IPv4 + 9, [dhcp.gateway] ; gateway jmp exit dhcp_end: mcall close, [socketNum] stdcall mem.Free, [dhcpMsg] ret ;*************************************************************************** ; Function ; parseResponse ; ; Description ; extracts the fields ( client IP address and options ) from ; a DHCP response ; The values go into ; dhcpMsgType,dhcpLease,dhcpClientIP,dhcpServerIP, ; dhcpDNSIP, dhcpSubnet ; The message is stored in dhcpMsg ; ;*************************************************************************** parse_response: DEBUGF 1,"Data received, parsing response\n" mov edx, [dhcpMsg] push dword [edx+16] pop [dhcp.ip] DEBUGF 1,"Client: %u.%u.%u.%u\n", [edx+16]:1, [edx+17]:1, [edx+18]:1, [edx+19]:1 ; TODO: check if there really are options mov al, 240 ; Point to first option movzx ecx, al .next_option: add edx, ecx mov al, [edx] ; get message identifier cmp al, 0xff ; End of options? je .done cmp al, 0 je .pad ; TODO: check if we still are inside the buffer inc edx movzx ecx, byte [edx] ; get data length inc edx ; point to data cmp al, dhcp_msg_type ; Msg type is a single byte option je .msgtype cmp al, dhcp_dhcp_server_id je .server cmp al, dhcp_address_time je .lease cmp al, dhcp_subnet_mask je .subnet cmp al, dhcp_router je .router cmp al, dhcp_domain_server je .dns DEBUGF 1,"Unsupported DHCP option: %u\n", al jmp .next_option .pad: xor ecx, ecx inc ecx jmp .next_option .msgtype: mov al, [edx] mov [dhcpMsgType], al DEBUGF 1,"DHCP Msg type: %u\n", al jmp .next_option ; Get next option .server: mov eax, [edx] mov [dhcpServerIP], eax DEBUGF 1,"Server: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1 jmp .next_option .lease: pusha mov eax,[edx] bswap eax mov [dhcpLease],eax DEBUGF 1,"lease: %d\n",eax popa jmp .next_option .subnet: push dword [edx] pop [dhcp.subnet] DEBUGF 1,"Subnet: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1 jmp .next_option .router: push dword [edx] pop [dhcp.gateway] DEBUGF 1,"Gateway: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1 jmp .next_option .dns: push dword [edx] pop [dhcp.dns] DEBUGF 1,"DNS: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1 jmp .next_option .done: ret dhcp_error: call dhcp_end link_local: call random mov cx, ax shl ecx, 16 mov cx, 0xfea9 ; IP 169.254.0.0 link local net, see RFC3927 mcall 76, API_IPv4 + 3, ecx ; mask is 255.255.0.0 DEBUGF 1,"Link Local IP assinged: 169.254.%u.%u\n", [generator+0]:1, [generator+1]:1 mcall 76, API_IPv4 + 7, 0xffff mcall 76, API_IPv4 + 9, 0x0 mcall 76, API_IPv4 + 5, 0x0 mcall 5, PROBE_WAIT*100 xor esi, esi probe_loop: call random ; create a pseudo random number in eax (seeded by MAC) cmp al, PROBE_MIN*100 ; check if al is bigger then PROBE_MIN jae @f ; all ok add al, (PROBE_MAX-PROBE_MIN)*100 ; al is too small @@: cmp al, PROBE_MAX*100 jbe @f sub al, (PROBE_MAX-PROBE_MIN)*100 @@: movzx ebx,al DEBUGF 1,"Waiting %u0ms\n",ebx mcall 5 DEBUGF 1,"Sending Probe\n" mcall 76, API_ARP + 6 inc esi cmp esi, PROBE_NUM jb probe_loop ; now we wait further ANNOUNCE_WAIT seconds and send ANNOUNCE_NUM ARP announces. If any other host has assingned ; IP within this time, we should create another adress, that have to be done later DEBUGF 1,"Waiting %us\n", ANNOUNCE_WAIT mcall 5, ANNOUNCE_WAIT*100 xor esi, esi announce_loop: DEBUGF 1,"Sending Announce\n" mcall 76, API_ARP + 6 inc esi cmp esi,ANNOUNCE_NUM je @f DEBUGF 1,"Waiting %us\n", ANNOUNCE_INTERVAL mcall 5, ANNOUNCE_INTERVAL*100 jmp announce_loop @@: error: DEBUGF 1,"Socket error\n" exit: ; we should, instead of closing, detect ARP conflicts and detect if cable keeps connected ;) mcall -1 random: ; Pseudo random actually mov eax, [generator] add eax, -43ab45b5h ror eax, 1 bswap eax xor eax, dword[MAC] ror eax, 1 xor eax, dword[MAC+2] mov [generator], eax ret ; DATA AREA align 16 @IMPORT: library \ libini,'libini.obj' import libini, \ ini.get_str,'ini_get_str' include_debug_strings str_ip db 'ip', 0 str_subnet db 'subnet', 0 str_gateway db 'gateway', 0 str_dns db 'dns', 0 str_ipconfig db 'ipconfig', 0 str_type db 'type', 0 sockaddr1: dw AF_INET4 dw 68 shl 8 ; local port dd 0 ; local IP rb 10 sockaddr2: dw AF_INET4 dw 67 shl 8 ; destination port dd -1 ; destination IP rb 10 path db '/sys/network.ini' IM_END: inibuf rb 16 dhcpMsgType db ? dhcpLease dd ? dhcpServerIP dd ? dhcp: .ip dd ? .subnet dd ? .dns dd ? .gateway dd ? dhcpMsgLen dd ? socketNum dd ? MAC dp ? currTime dd ? generator dd ? dhcpMsg dd ? I_END: