;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2010-2015. 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 "" ; CONFIGURATION TIMEOUT = 3 ; in seconds BUFFER = 1024 ; in bytes DHCP_TRIES = 3 ; number of times to try contacting DHCP server __DEBUG__ = 1 ; enable/disable __DEBUG_LEVEL__ = 2 ; 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) MAX_INTERFACES = 8 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 include '../../proc32.inc' include '../../macros.inc' include '../../debug-fdo.inc' include '../../network.inc' include 'dhcp.inc' include '../../dll.inc' struct dhcp_msg op db ? ; Operation Code htype db ? ; Hardware type hlen db ? ; Hardware address length hops db ? xid dd ? ; Transaction Identifier secs dw ? ; Seconds since boot flags dw ? ciaddr dd ? ; Client IP address yiaddr dd ? ; "Your" IP address siaddr dd ? ; Server IP address giaddr dd ? ; Gateway IP address chaddr rb 16 ; Client hardware address sname rb 64 ; Server name file rb 128 ; boot filename cookie dd ? ; Magic cookie (0x63538263) options rb 512 ends struct interface number dd ? state dd ? ; 0 - disconnected, 1 - connected mode dd ? ; 0 - static, 1 - dhcp, 2 - auto (zero config) tries dd ? lease dd ? ServerIP dd ? ip dd ? subnet dd ? dns dd ? gateway dd ? socketNum dd ? timeout dd ? ip_conflicts dd ? ends START: mcall 68, 11 ; init heap stdcall dll.Load, @IMPORT ; load libraries or eax, eax jnz exit_immediately DEBUGF 2, "Zero-config service loaded\n" mcall 40, EVM_STACK2 ; We only want low-level network events ; Set up interface list mov edi, device_list xor ebx, ebx @@: inc ebx mov eax, ebx stosd mov ecx, sizeof.interface/4-1 xor eax,eax rep stosd cmp ebx, MAX_INTERFACES jb @b mov ebp, device_list mainloop: cmp [ebp + interface.state], 0 je .link_up? jmp .maintain_link .next: cmp [ebp + interface.number], MAX_INTERFACES je .wait add ebp, sizeof.interface jmp mainloop .wait: mcall 10 ; Wait for event mov ebp, device_list jmp mainloop .link_up?: mov bh, byte[ebp + interface.number] mov bl, 0 ; Get device type mcall 74 cmp eax, 1 ; Ethernet jne mainloop.next mov bl, 10 ; Get Link status mcall 74 test eax, eax jz mainloop.next mov [ebp + interface.state], 1 call create_str_ini_int ; Try to read settings from .ini file invoke ini.get_str, ini_path, str_ini_int, str_ip_type, inibuf, 16, str_null test eax, eax jz @f ; If settings not found, use default settings from 'ip?' section mov dword[str_ini_int], 'ip?' @@: mov ebx, API_ETH + 0 mov bh, byte[ebp + interface.number] mcall 76 ; get MAC of the ethernet interface mov word[tx_msg.chaddr], bx mov dword[tx_msg.chaddr+2], eax DEBUGF 1, "MAC: %x-%x-%x-%x-%x-%x\n", \ [tx_msg.chaddr+0]:2, [tx_msg.chaddr+1]:2, [tx_msg.chaddr+2]:2, \ [tx_msg.chaddr+3]:2, [tx_msg.chaddr+4]:2, [tx_msg.chaddr+5]:2 invoke ini.get_str, ini_path, str_ini_int, str_ip_type, inibuf, 16, str_null test eax, eax jnz .fail mov eax, dword[inibuf] or eax, 0x20202020 mov [ebp + interface.mode], 0 cmp eax, 'stat' je static mov [ebp + interface.mode], 1 cmp eax, 'dhcp' je dhcp mov [ebp + interface.mode], 2 cmp eax, 'auto' je dhcp .fail: DEBUGF 2, "Invalid network.ini settings\n" mcall -1 ; Give up .maintain_link: ; Check for IP conflicts mov ebx, API_ARP mov bh, byte[ebp + interface.number] mov bl, 7 mcall 76 ; Number of IP conflicts cmp eax, [ebp + interface.ip_conflicts] je @f mov [ebp + interface.ip_conflicts], eax DEBUGF 2, "IP address conflict on interface %u\n", [ebp + interface.number] ; Notify user of the IP address conflict mov [notify_struct.msg], str_conflict mcall 70, notify_struct @@: ; Check if device is still there mov bh, byte[ebp + interface.number] mov bl, 0 ; Get device type mcall 74 test eax, eax ; No device jz .link_down ; Check if link is still there mov bl, 10 ; Get Link status mcall 74 test eax, eax jnz .next .link_down: mov [ebp + interface.state], 0 ; Notify user that the link is down mov [notify_struct.msg], str_disconnected mcall 70, notify_struct ; CHECKME: should we do this in kernel instead? Should we even do this at all? xor ecx, ecx mov ebx, API_IPv4 + 3 mov bh, byte[ebp + interface.number] mcall 76 ; ip mov bl, 5 mcall 76 ; dns mov bl, 7 mcall 76 ; subnet mov bl, 9 mcall 76 ; gateway jmp .next link_up: ; Read number of previous IP conflicts mov ebx, API_ARP mov bh, byte[ebp + interface.number] mov bl, 7 mcall 76 mov [ebp + interface.ip_conflicts], eax ; Notify user that the link is up and running mov [notify_struct.msg], str_connected mcall 70, notify_struct .fail: mcall 40, EVM_STACK2 jmp mainloop.next static: DEBUGF 1, "Applying Static IP settings\n" invoke ini.get_str, ini_path, str_ini_int, str_ip, inibuf, 16, str_null mov esi, inibuf call ip_str_to_dword mov ebx, API_IPv4 + 3 ; set IP mov bh, byte[ebp + interface.number] mcall 76 invoke ini.get_str, ini_path, str_ini_int, str_subnet, inibuf, 16, str_null mov esi, inibuf call ip_str_to_dword mov ebx, API_IPv4 + 7 ; set subnet mov bh, byte[ebp + interface.number] mcall 76 invoke ini.get_str, ini_path, str_ini_int, str_gateway, inibuf, 16, str_null mov esi, inibuf call ip_str_to_dword mov ebx, API_IPv4 + 9 ; set gateway mov bh, byte[ebp + interface.number] mcall 76 .dns: invoke ini.get_str, ini_path, str_ini_int, str_dns, inibuf, 16, str_null mov esi, inibuf call ip_str_to_dword mov ebx, API_IPv4 + 5 ; set DNS mov bh, byte[ebp + interface.number] mcall 76 jmp link_up dhcp: DEBUGF 2, "Trying to contact DHCP server\n" mcall 40, EVM_STACK mcall 75, 0, AF_INET4, SOCK_DGRAM, 0 ; open socket (parameters: domain, type, reserved) cmp eax, -1 je dhcp_error mov [ebp + interface.socketNum], eax DEBUGF 1, "Socket %x opened\n", eax mcall 75, 2, [ebp + interface.socketNum], sock_local, 18 ; bind socket to local port 68 cmp eax, -1 je socket_error DEBUGF 1, "Socket Bound to local port 68\n" pushd [ebp + interface.number] pushd 4 ; length of option pushd 1 shl 9 ; SO_BINDTODEVICE pushd 0 ; SOL_SOCKET mcall 75, 8, [ebp + interface.socketNum], esp add esp, 16 cmp eax, -1 je socket_error DEBUGF 1, "Socket Bound to local interface %u\n", [ebp + interface.number] mcall 75, 4, [ebp + interface.socketNum], sock_remote, 18 ; connect to 255.255.255.255 on port 67 cmp eax, -1 je socket_error DEBUGF 1, "Connected to 255.255.255.255 on port 67\n" ; Read preferred IP address from settings file invoke ini.get_str, ini_path, str_ini_int, str_ip, inibuf, 16, str_null mov esi, inibuf call ip_str_to_dword mov [ebp + interface.ip], ecx call random mov [tx_msg.xid], eax ; randomize session ID mov [tx_msg_type], 1 ; DHCP discover build_dhcp_packet: DEBUGF 1, "Building DHCP packet\n" mov [ebp + interface.tries], DHCP_TRIES ; Boot protocol legacy mov [tx_msg.op], 1 ; Boot request mov [tx_msg.htype], 1 ; Ethernet mov [tx_msg.hlen], 6 ; Ethernet address h/w len mov [tx_msg.hops], 0 mcall 26, 9 ; Time since boot xor edx, edx mov ebx, 100 div ebx ; Divide by 100 to get number of seconds mov [tx_msg.secs], ax mov [tx_msg.flags], 0 ; DHCP extension mov [tx_msg.cookie], 0x63538263 ; magic cookie mov word[tx_msg+240], 0x0135 ; option DHCP msg type mov al,[tx_msg_type] mov [tx_msg+240+2], al mov word[tx_msg+240+3], 0x0433 ; option Lease time mov dword[tx_msg+240+5], -1 ; infinite mov word[tx_msg+240+9], 0x0432 ; option requested IP address mov eax,[ebp + interface.ip] mov [tx_msg+240+11], eax mov word[tx_msg+240+15], 0x0437 ; option request list mov dword[tx_msg+240+17], 0x0f060301 cmp [tx_msg_type], 1 ; Check which msg we are sending jne .request mov byte[tx_msg+240+21], 0xff ; end of options marker mov [tx_msg_len], 262 ; length jmp send_dhcp_packet .request: mov word[tx_msg+240+21], 0x0436 ; server IP mov eax,[ebp + interface.ServerIP] mov [tx_msg+240+23], eax mov byte[tx_msg+240+27], 0xff ; end of options marker mov [tx_msg_len], 268 ; length send_dhcp_packet: DEBUGF 1, "Sending DHCP packet\n" lea edx, [tx_msg] mcall 75, 6, [ebp + interface.socketNum], , [tx_msg_len] ; Wait for reply mcall 26, 9 add eax, TIMEOUT*100 mov [ebp + interface.timeout], eax mov ebx, TIMEOUT*100 .wait: mcall 23 ; Wait for event with timeout read_packet: ; we have data - this will be the response lea edx, [rx_msg] mcall 75, 7, [ebp + interface.socketNum], , BUFFER, MSG_DONTWAIT ; read data from socket cmp eax, -1 jne .got_data mcall 26, 9 mov ebx, eax sub ebx, [ebp + interface.timeout] ja send_dhcp_packet.wait DEBUGF 2, "No answer from DHCP server\n" dec [ebp + interface.tries] jnz send_dhcp_packet jmp dhcp_fail .got_data: DEBUGF 1, "%d bytes received\n", eax mov [rx_msg_len], 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 [tx_msg_type], 1 ; did we send a discover? je discover_sent cmp [tx_msg_type], 3 ; did we send a request? je request_sent jmp exit_immediately discover_sent: call parse_dhcp_reply cmp [rx_msg_type], 2 ; Was the response an offer? jne read_packet DEBUGF 1, "Got offer, making request\n" mov [tx_msg_type], 3 ; make it a request jmp build_dhcp_packet request_sent: call parse_dhcp_reply cmp [rx_msg_type], 5 ; Was the response an ACK? It should be jne read_packet ; NO - read next packets DEBUGF 2, "IP assigned by DHCP server successfully\n" mcall close, [ebp + interface.socketNum] mov ebx, API_IPv4 + 3 mov bh, byte[ebp + interface.number] mcall 76, , [ebp + interface.ip] ; ip mov bl, 7 mcall 76, , [ebp + interface.subnet] ; subnet mov bl, 9 mcall 76, , [ebp + interface.gateway] ; gateway invoke ini.get_str, ini_path, str_ini_int, str_dns_type, inibuf, 16, str_null test eax, eax jnz @f mov eax, dword[inibuf] or eax, 0x202020 cmp eax, 'stat' je static.dns @@: mov ebx, API_IPv4 + 5 mov bh, byte[ebp + interface.number] mcall 76, , [ebp + interface.dns] ; dns jmp link_up parse_dhcp_reply: DEBUGF 1, "Parsing response\n" mov [rx_msg_type], 0 ; Verify if session ID matches mov eax, [tx_msg.xid] cmp [rx_msg.xid], eax jne .done pushd [rx_msg.yiaddr] pop [ebp + interface.ip] DEBUGF 1, "Client: %u.%u.%u.%u\n", \ [rx_msg.yiaddr]:1, [rx_msg.yiaddr+1]:1, [rx_msg.yiaddr+2]:1, [rx_msg.yiaddr+3]:1 ; Verify magic cookie cmp [rx_msg.cookie], 0x63538263 jne .done ; Parse the DHCP options lea esi, [rx_msg] mov ecx, 240 ; point to the first option .next_option: ; TODO: check if we still are inside the buffer! add esi, ecx lodsb ; get message identifier mov bl, al cmp bl, 0xff ; End of options? je .done test bl, bl jz .pad lodsb ; load data length movzx ecx, al cmp bl, dhcp_msg_type ; Msg type is a single byte option je .msgtype cmp bl, dhcp_dhcp_server_id je .server cmp bl, dhcp_address_time je .lease cmp bl, dhcp_subnet_mask je .subnet cmp bl, dhcp_router je .router cmp bl, dhcp_domain_server je .dns DEBUGF 1, "Unsupported DHCP option: %u\n", bl jmp .next_option .pad: xor ecx, ecx inc ecx jmp .next_option .msgtype: mov al, [esi] mov [rx_msg_type], al DEBUGF 1, "DHCP Msg type: %u\n", al jmp .next_option ; Get next option .server: pushd [esi] pop [ebp + interface.ServerIP] DEBUGF 1, "Server: %u.%u.%u.%u\n", [esi]:1, [esi+1]:1, [esi+2]:1, [esi+3]:1 jmp .next_option .lease: pusha mov eax,[esi] bswap eax mov [ebp + interface.lease], eax DEBUGF 1, "Lease: %d\n", eax popa jmp .next_option .subnet: pushd [esi] pop [ebp + interface.subnet] DEBUGF 1, "Subnet: %u.%u.%u.%u\n", [esi]:1, [esi+1]:1, [esi+2]:1, [esi+3]:1 jmp .next_option .router: pushd [esi] pop [ebp + interface.gateway] DEBUGF 1, "Gateway: %u.%u.%u.%u\n", [esi]:1, [esi+1]:1, [esi+2]:1, [esi+3]:1 jmp .next_option .dns: pushd [esi] pop [ebp + interface.dns] DEBUGF 1, "DNS: %u.%u.%u.%u\n", [esi]:1, [esi+1]:1, [esi+2]:1, [esi+3]:1 jmp .next_option .done: ret exit_immediately: DEBUGF 2, "Zeroconf failed!\n" mcall -1 socket_error: DEBUGF 2, "Socket error!\n" dhcp_fail: mcall close, [ebp + interface.socketNum] dhcp_error: DEBUGF 1, "DHCP failed\n" cmp [ebp + interface.mode], 2 ; zero config mode? jne link_up link_local: ; TODO: send ARP probes before setting the IP address in stack! call random mov cx, ax shl ecx, 16 mov cx, 0xfea9 ; IP 169.254.0.0 link local net, see RFC3927 mov ebx, API_IPv4 + 3 mov bh, byte[ebp + interface.number] mcall 76, , ecx ; mask is 255.255.0.0 DEBUGF 2, "Link Local IP assigned: 169.254.%u.%u\n", [generator+0]:1, [generator+1]:1 mov bl, 7 mcall 76, , 0xffff mov bl, 9 mcall 76, , 0x0 mov bl, 5 mcall 76, , 0x0 jmp link_up random: ; Pseudo random actually mov eax,[generator] add eax, -43ab45b5h ror eax, 1 bswap eax xor eax, dword[tx_msg.chaddr] ror eax, 1 xor eax, dword[tx_msg.chaddr+2] mov [generator], eax ret create_str_ini_int: mov eax, [ebp + interface.number] mov ebx, 10 xor edx, edx push 0 @@: div ebx add dl, '0' push edx test eax, eax jnz @r @@: mov edi, str_ini_int+2 @@: pop eax stosb test eax, eax jnz @r ret ; In: esi = ptr to ASCIIZ IP address ; Out: ecx = IP (0 on error) ip_str_to_dword: xor ecx, ecx ; end result .charloop: lodsb test al, al jz .finish cmp al, '.' je .dot sub al, '0' jb .fail cmp al, 9 ja .fail mov dl, cl shl cl, 2 jc .fail add cl, dl jc .fail add cl, cl jc .fail add cl, al jc .fail jmp .charloop .dot: shl ecx, 8 jc .fail xor cl, cl jmp .charloop .finish: bswap ecx ; we want little endian order ret .fail: xor ecx, ecx ret ; DATA AREA align 16 @IMPORT: library \ libini, 'libini.obj' import libini, \ ini.get_str, 'ini_get_str',\ ini.set_str, 'ini_set_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_ip_type db 'ip_type', 0 str_dns_type db 'dns_type', 0 str_ini_int db 'ip1', 0 rb 10 str_null db 0 sock_local: dw AF_INET4 dw 68 shl 8 ; local port dd 0 ; local IP rb 10 sock_remote: dw AF_INET4 dw 67 shl 8 ; destination port dd -1 ; destination IP rb 10 notify_struct: dd 7 ; run application dd 0 .msg dd 0 dd 0 dd 0 db '/sys/@notify', 0 str_connected db '"You are now connected to the network." -N', 0 str_disconnected db '"You are now disconnected from the network." -N', 0 str_conflict db '"An IP address conflict has been detected on the network." -W', 0 ini_path db '/sys/settings/network.ini',0 IM_END: generator dd ? inibuf rb 16 tx_msg_len dd ? rx_msg_len dd ? tx_msg_type db ? rx_msg_type db ? tx_msg dhcp_msg rx_msg dhcp_msg device_list rd MAX_INTERFACES*sizeof.interface I_END: