; Zero-config
; v 1.4
;
; DHCP code is based on that by Mike Hibbet (DHCP client for menuetos)
;
; Written by HidnPlayr & Derpenguin

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		    ; memory for app
	       dd     I_END		    ; esp
	       dd     0x0 , path	     ; I_Param , I_Icon

; CONFIGURATION


TIMEOUT 	    equ 60		    ; in seconds
BUFFER		    equ 1024		    ; in bytes
__DEBUG__	    equ 1		    ; enable/disable
__DEBUG_LEVEL__     equ 1		    ; 1 = all, 2 = errors

; CONFIGURATION FOR LINK-LOCAL

PROBE_WAIT	    equ 1		    ; second  (initial random delay)
PROBE_MIN	    equ 1		    ; second  (minimum delay till repeated probe)
PROBE_MAX	    equ 2		    ; seconds (maximum delay till repeated probe)
PROBE_NUM	    equ 3		    ;         (number of probe packets)

ANNOUNCE_NUM	    equ 2		    ;         (number of announcement packets)
ANNOUNCE_INTERVAL   equ 2		    ; seconds (time between announcement packets)
ANNOUNCE_WAIT	    equ 2		    ; seconds (delay before announcing)

MAX_CONFLICTS	    equ 10		    ;         (max conflicts before rate limiting)

RATE_LIMIT_INTERVAL equ 60		    ; seconds (delay between successive attempts)

DEFEND_INTERVAL     equ 10		    ; seconds (min. wait between defensive ARPs)

include '../../../proc32.inc'
include '../../../macros.inc'
include 'eth.inc'
include 'debug-fdo.inc'
include 'dhcp.inc'
include 'dll.inc'

START:					    ; start of execution

    mcall 40, 0

    eth.set_network_drv 0x00000383

    DEBUGF  1,"Zero-config service:\n"

    eth.status eax			    ; Read the Stack status
    test    eax,eax			    ; if eax is zero, no driver was found
    jnz     @f
    DEBUGF  1,"No Card found!\n"
    jmp     close

   @@:
    DEBUGF  1,"Detected card: %x\n",eax
   @@:
    eth.check_cable eax
    test    al,al
    jnz     @f
    DEBUGF  1,"Cable disconnected!\n"
    mcall   5, 500			    ; loop until cable is connected (check every 5 sec)
    jmp     @r

   @@:
    eth.read_mac MAC
    DEBUGF  1,"MAC: %x-%x-%x-%x-%x-%x\n",[MAC]:2,[MAC+1]:2,[MAC+2]:2,[MAC+3]:2,[MAC+4]:2,[MAC+5]:2

    cld
    mov     edi, path	   ; Calculate the length of zero-terminated string
    xor     al , al
    mov     ecx, 1024
    repnz   scas byte[es:edi]
    dec     edi

    mov     esi, filename
    mov     ecx, 5
    rep     movsb

    mcall 68,11

    stdcall dll.Load,@IMPORT
    or	    eax,eax
    jnz     skip_ini


    invoke ini.get_str, path, str_ipconfig, str_type, inibuf, 16, 0

    mov  eax,dword[inibuf]

    cmp  eax,'stat'
    jne  skip_ini

    invoke ini.get_str, path, str_ipconfig, str_ip, inibuf, 16, 0
    mov    edx, inibuf
    call   Ip2dword
    eth.set_IP edx

    invoke ini.get_str, path, str_ipconfig, str_gateway, inibuf, 16, 0
    mov    edx, inibuf
    call   Ip2dword
    eth.set_GATEWAY edx

    invoke ini.get_str, path, str_ipconfig, str_dns, inibuf, 16, 0
    mov    edx, inibuf
    call   Ip2dword
    eth.set_DNS edx

    invoke ini.get_str, path, str_ipconfig, str_subnet, inibuf, 16, 0
    mov    edx, inibuf
    call   Ip2dword
    eth.set_SUBNET edx


    mcall  -1


skip_ini:

    eth.check_port 68,eax		    ; Check if port 68 is available
    cmp     eax,1
    je	    @f

    DEBUGF  1,"Port 68 is already in use!\n"
    jmp     close

   @@:
    eth.open_udp 68,67,-1,[socketNum]	    ; open socket (local,remote,ip,socket)
					    ; Setup the first msg we will send
    mov     byte [dhcpMsgType], 0x01	    ; DHCP discover
    mov     dword [dhcpLease], esi	    ; esi is still -1 (-1 = forever)

    mcall   26, 9
    imul    eax,100
    mov     [currTime],eax

buildRequest:				    ; Creates a DHCP request packet.
    stdcall mem.Alloc, BUFFER
    mov     [dhcpMsg], eax
    test    eax,eax
    jz	    apipa


    mov     edi, eax
    mov     ecx,BUFFER
    xor     eax,eax
    cld
    rep     stosb

    mov     edx,[dhcpMsg]

    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
    mov     [edx+236], dword 0x63538263     ; magic number
    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, [dhcpClientIP]
    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_request

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_request:
    eth.write_udp [socketNum],[dhcpMsgLen],[dhcpMsg] ; 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

    mov     eax,23			    ; wait here for event (data from remote)
    mov     ebx,TIMEOUT*10
    mcall

    eth.poll [socketNum]

    test    eax,eax
    jnz     read_data

    DEBUGF  2,"Timeout!\n"
    eth.close_udp [socketNum]
    jmp    apipa			    ; no server found, lets try zeroconf


read_data:				    ; we have data - this will be the response
    eth.read_packet [socketNum], [dhcpMsg], BUFFER
    mov     [dhcpMsgLen], eax
    eth.close_udp [socketNum]

    ; 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], byte 0x01	    ; did we send a discover?
    je	    discover
    cmp     [dhcpMsgType], byte 0x03	    ; did we send a request?
    je	    request

    jmp     close			    ; really unknown, what we did

discover:
    call    parseResponse

    cmp     [dhcpMsgType], byte 0x02	    ; Was the response an offer?
    jne     apipa			    ; NO - so we do zeroconf
    mov     [dhcpMsgType], byte 0x03	    ; DHCP request
    jmp     buildRequest

request:
    call    parseResponse

    cmp     [dhcpMsgType], byte 0x05	    ; Was the response an ACK? It should be
    jne     apipa			    ; NO - so we do zeroconf

    jmp     close

;***************************************************************************
;   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
;
;***************************************************************************
parseResponse:
    DEBUGF  1,"Data received, parsing response\n"
    mov     edx, [dhcpMsg]

    pusha
    eth.set_IP [edx+16]
    mov     eax,[edx]
    mov     [dhcpClientIP],eax
    DEBUGF  1,"Client: %u.%u.%u.%u\n",[edx+16]:1,[edx+17]:1,[edx+18]:1,[edx+19]:1
    popa

    add     edx, 240			    ; Point to first option
    xor     ecx, ecx

next_option:
    add     edx, ecx
pr001:
    mov     al, [edx]
    cmp     al, 0xff			    ; End of options?
    je	    pr_exit

    cmp     al, dhcp_msg_type		    ; Msg type is a single byte option
    jne     @f

    mov     al, [edx+2]
    mov     [dhcpMsgType], al
    add     edx, 3
    jmp     pr001			    ; Get next option

@@:
    inc     edx
    movzx   ecx, byte [edx]
    inc     edx 			    ; point to data

    cmp     al, dhcp_dhcp_server_id	    ; server ip
    jne     @f
    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

@@:
    cmp     al, dhcp_address_time
    jne     @f

    pusha
    mov     eax,[edx]
    bswap   eax
    mov     [dhcpLease],eax
    DEBUGF  1,"lease: %d\n",eax
    popa

    jmp     next_option

@@:
    cmp     al, dhcp_subnet_mask
    jne     @f

    pusha
    eth.set_SUBNET [edx]
    DEBUGF  1,"Subnet: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1
    popa

    jmp     next_option

@@:
    cmp     al, dhcp_router
    jne     @f

    pusha
    eth.set_GATEWAY [edx]
    DEBUGF  1,"Gateway: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1
    popa

    jmp     next_option


@@:
    cmp     al, dhcp_domain_server
    jne     next_option

    pusha
    eth.set_DNS [edx]
    DEBUGF  1,"DNS: %u.%u.%u.%u\n",[edx]:1,[edx+1]:1,[edx+2]:1,[edx+3]:1
    popa

    jmp     next_option

pr_exit:

;    DEBUGF  1,"Sending ARP announce\n"
;    eth.ARP_ANNOUNCE [dhcpClientIP]         ; send an ARP announce packet

    jmp close

apipa:
    stdcall mem.Free, [dhcpMsg]

link_local:
    call random
    mov  ecx,0xfea9			    ; IP 169.254.0.0 link local net, see RFC3927
    mov  cx,ax
    eth.set_IP ecx			    ; mask is 255.255.0.0
    DEBUGF 1,"Link Local IP assinged: 169.254.%u.%u\n",[generator+2]:1,[generator+3]:1
    eth.set_SUBNET 0xffff
    eth.set_GATEWAY 0x0
    eth.set_DNS 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
    jge   @f				    ; all ok
    add   al,(PROBE_MAX-PROBE_MIN)*100	    ; al is too small
   @@:

    cmp   al,PROBE_MAX*100
    jle   @f
    sub   al,(PROBE_MAX-PROBE_MIN)*100
   @@:

    movzx ebx,al
    DEBUGF  1,"Waiting %u0ms\n",ebx
    mcall 5

    DEBUGF  1,"Sending Probe\n"
;    eth.ARP_PROBE MAC
    inc   esi

    cmp   esi,PROBE_NUM
    jl	  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"
;    eth.ARP_ANNOUNCE MAC

    inc   esi
    cmp   esi,ANNOUNCE_NUM
    je	  @f

    DEBUGF  1,"Waiting %us\n",ANNOUNCE_INTERVAL
    mcall 5, ANNOUNCE_INTERVAL*100
    jmp   announce_loop
   @@:
    ; we should, instead of closing, detect ARP conflicts and detect if cable keeps connected ;)

close:
    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

filename db '.ini',0
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


IM_END:

inibuf		rb 16

dhcpClientIP	dd  ?
dhcpMsgType	db  ?
dhcpLease	dd  ?
dhcpServerIP	dd  ?

dhcpMsgLen	dd  ?
socketNum	dd  ?

MAC		dp  ?
currTime	dd  ?
renewTime	dd  ?
generator	dd  ?

dhcpMsg 	dd  ?

I_END_2:

path		rb  1024+5

I_END: