; minimal dhcp implementation - ip addresses are requested from a dhcp server ; (aka 'leased') but are not renewed or released. although this is not correct ; behaviour according to the DHCP RFC, this works fine in practice in a typical ; home network environment. ; ; cfg_ip,cfg_netmask,cfg_gateway and cfg_dns variables are all overwritten, ; therefore, these values must be stored in RAM not ROM ; MAX_DHCP_MESSAGES_SENT = 12 ; timeout after sending 12 messages will be about 15 seconds (1+2+3...)/4 .include "../inc/common.inc" .include "../inc/error.inc" .export dhcp_init .import dhcp_server .export dhcp_state .import ip65_error .import cfg_mac .import cfg_ip .import cfg_netmask .import cfg_gateway .import cfg_dns .import arp_calculate_gateway_mask .import ip65_process .import udp_add_listener .import udp_remove_listener .import udp_callback .import udp_send .import udp_inp .importzp udp_data .import output_buffer .import udp_send_dest .import udp_send_src_port .import udp_send_dest_port .import udp_send_len .import check_for_abort_key .import timer_read .bss ; dhcp packet offsets dhcp_inp = udp_inp + udp_data dhcp_op = 0 dhcp_htype = 1 dhcp_hlen = 2 dhcp_hops = 3 dhcp_xid = 4 dhcp_secs = 8 dhcp_flags = 10 dhcp_ciaddr = 12 dhcp_yiaddr = 16 dhcp_siaddr = 20 dhcp_giaddr = 24 dhcp_chaddr = 28 dhcp_sname = 44 dhcp_file =108 dhcp_cookie =236 dhcp_options =240 dhcp_server_port = 67 dhcp_client_port = 68 ; dhcp state machine dhcp_initializing = 1 ; initial state dhcp_selecting = 2 ; sent a DHCPDISCOVER, waiting for a DHCPOFFER dhcp_ready_to_request = 3 ; got a DHCPOFFER, ready to send a DHCPREQUEST dhcp_requesting = 4 ; sent a DHCPREQUEST, waiting for a DHCPACK dhcp_bound = 5 ; we have been allocated an IP address ; flag indicating current state of dhcp initialization. dhcp_state: .res 1 dhcp_message_sent_count: .res 1 dhcp_timer: .res 1 dhcp_loop_count: .res 1 dhcp_break_polling_loop: .res 1 dhcp_ip: .res 4 ; DHCP constants BOOTREQUEST = 1 BOOTREPLY = 2 DHCPDISCOVER = 1 DHCPOFFER = 2 DHCPREQUEST = 3 DHCPDECLINE = 4 DHCPACK = 5 DHCPNAK = 6 DHCPRELEASE = 7 DHCPINFORM = 8 .code ; inputs: none (although ip65_init should be called first) ; outputs: ; carry flag clear means IP config has been sucesfully obtained and ; cfg_ip, cfg_netmask, cfg_gateway and cfg_dns will be set per response from dhcp server. ; dhcp_server will be set to address of server that provided configuration ; if carry flag is set there was an error. ; in either case, dhcp_state will indicate where dhcp initialization ended (to help debug) ; possible values for dhcp_state are: ; 1 - initial state ; 2 - sent a DHCPDISCOVER, waiting for a DHCPOFFER ; 3 - got a DHCPOFFER, ready to send a DHCPREQUEST ; 4 - sent a DHCPREQUEST, waiting for a DHCPACK ; 5 - we have been allocated an IP address dhcp_init: ldx #3 ; rewrite ip address lda #0 : sta cfg_ip,x dex bpl :- lda #dhcp_initializing sta dhcp_state ldax #dhcp_in stax udp_callback ldax #dhcp_client_port jsr udp_add_listener bcc :+ rts : lda #0 ; reset the "message sent" counter sta dhcp_message_sent_count jsr send_dhcpdiscover @dhcp_polling_loop: lda dhcp_message_sent_count adc #1 sta dhcp_loop_count ; we wait a bit longer between each resend @outer_delay_loop: lda #0 sta dhcp_break_polling_loop jsr timer_read stx dhcp_timer ; we only care about the high byte @inner_delay_loop: jsr ip65_process jsr check_for_abort_key bcc @no_abort lda #IP65_ERROR_ABORTED_BY_USER sta ip65_error rts @no_abort: lda #0 cmp dhcp_break_polling_loop bne @break_polling_loop jsr timer_read cpx dhcp_timer ; this will tick over after about 1/4 of a second beq @inner_delay_loop dec dhcp_loop_count bne @outer_delay_loop @break_polling_loop: inc dhcp_message_sent_count lda dhcp_message_sent_count cmp #MAX_DHCP_MESSAGES_SENT-1 bpl @too_many_messages_sent lda dhcp_state cmp #dhcp_initializing beq @initializing cmp #dhcp_selecting beq @selecting cmp #dhcp_ready_to_request beq @ready_to_request cmp #dhcp_bound beq @bound jmp @dhcp_polling_loop @initializing: @selecting: jsr send_dhcpdiscover jmp @dhcp_polling_loop @ready_to_request: jsr send_dhcprequest jmp @dhcp_polling_loop @bound: ldax #dhcp_client_port jsr udp_remove_listener rts @too_many_messages_sent: lda #IP65_ERROR_TIMEOUT_ON_RECEIVE sta ip65_error jsr @bound ; to remove the listener (thanks to ShadowM for bug report) sec ; signal an error rts dhcp_create_request_msg: lda #BOOTREQUEST sta output_buffer+dhcp_op lda #1 ; htype 1 = "10 MB ethernet" sta output_buffer+dhcp_htype lda #6 ; ethernet MACs are 6 bytes sta output_buffer+dhcp_hlen lda #0 ; hops = 0 sta output_buffer+dhcp_hops ldx #3 ; set xid to be "1234" clc : txa adc #01 sta output_buffer+dhcp_xid,x dex bpl :- lda #0 ; secs = 00 sta output_buffer+dhcp_secs sta output_buffer+dhcp_secs+1 ; initially turn off all flags sta output_buffer+dhcp_flags sta output_buffer+dhcp_flags+1 ldx #$0F ; set ciaddr to 0.0.0.0 ; set yiaddr to 0.0.0.0 ; set siaddr to 0.0.0.0 ; set giaddr to 0.0.0.0 : sta output_buffer+dhcp_ciaddr,x dex bpl :- ldx #5 ; set chaddr to mac : lda cfg_mac,x sta output_buffer+dhcp_chaddr,x dex bpl :- ldx #192 ; set sname & file both to null lda #0 : sta output_buffer+dhcp_sname-1,x dex bne :- lda #$63 ; copy the magic cookie sta output_buffer+dhcp_cookie+0 lda #$82 sta output_buffer+dhcp_cookie+1 lda #$53 sta output_buffer+dhcp_cookie+2 lda #$63 sta output_buffer+dhcp_cookie+3 ldax #dhcp_client_port ; set source port stax udp_send_src_port ldax #dhcp_server_port ; set destination port stax udp_send_dest_port rts send_dhcpdiscover: lda #dhcp_initializing sta dhcp_state jsr dhcp_create_request_msg lda #$80 ; broadcast flag = 1, all other bits 0 sta output_buffer+dhcp_flags ldx #dhcp_discover_options_length ; set destination address : lda dhcp_discover_options,x sta output_buffer+dhcp_options,x dex bpl :- ldx #3 ; set destination address lda #$FF ; des = 255.255.255.255 (broadcast) : sta udp_send_dest,x dex bpl :- ldax #dhcp_options+dhcp_discover_options_length stax udp_send_len ldax #output_buffer jsr udp_send bcc :+ rts : lda #dhcp_selecting sta dhcp_state rts dhcp_discover_options: .byte 53 ; option 53 - DHCP message type .byte 1 ; option length = 1 .byte DHCPDISCOVER ; message type .byte 55 ; option 55 - Parameter Request List .byte 3 ; option length .byte 1 ; subnet mask .byte 3 ; router (gateway) .byte 6 ; DNS server .byte $FF ; end of options dhcp_discover_options_length = *-dhcp_discover_options ; got a message on port 68 dhcp_in: lda dhcp_inp+dhcp_op cmp #BOOTREPLY beq :+ rts ; it's not what we were expecting : lda #0 cmp dhcp_inp+dhcp_yiaddr ; is the first byte in the assigned address 0? bne :+ rts ; if so, it's a bogus response - ignore : ldx #3 ; copy the new IP address : lda dhcp_inp+dhcp_yiaddr,x sta dhcp_ip,x dex bpl :- ldx #0 @unpack_dhcp_options: lda dhcp_inp+dhcp_options,x cmp #$ff bne :+ jmp @finished_unpacking_dhcp_options : cmp #53 ; is this field DHCP message type? bne @not_dhcp_message_type jmp @get_next_option lda dhcp_inp+dhcp_options+2,x cmp #DHCPOFFER ; if it's not a DHCP OFFER message, then stop processing beq :+ rts : jmp @get_next_option @not_dhcp_message_type: cmp #1 ; option 1 is netmask bne @not_netmask lda dhcp_inp+dhcp_options+2,x sta cfg_netmask lda dhcp_inp+dhcp_options+3,x sta cfg_netmask+1 lda dhcp_inp+dhcp_options+4,x sta cfg_netmask+2 lda dhcp_inp+dhcp_options+5,x sta cfg_netmask+3 jmp @get_next_option @not_netmask: cmp #3 ; option 3 is gateway bne @not_gateway lda dhcp_inp+dhcp_options+2,x sta cfg_gateway lda dhcp_inp+dhcp_options+3,x sta cfg_gateway+1 lda dhcp_inp+dhcp_options+4,x sta cfg_gateway+2 lda dhcp_inp+dhcp_options+5,x sta cfg_gateway+3 jmp @get_next_option @not_gateway: cmp #6 ; option 6 is dns server bne @not_dns_server lda dhcp_inp+dhcp_options+2,x sta cfg_dns lda dhcp_inp+dhcp_options+3,x sta cfg_dns+1 lda dhcp_inp+dhcp_options+4,x sta cfg_dns+2 lda dhcp_inp+dhcp_options+5,x sta cfg_dns+3 jmp @get_next_option @not_dns_server: cmp #54 ; option 54 is DHCP server bne @not_server lda dhcp_inp+dhcp_options+2,x sta dhcp_server lda dhcp_inp+dhcp_options+3,x sta dhcp_server+1 lda dhcp_inp+dhcp_options+4,x sta dhcp_server+2 lda dhcp_inp+dhcp_options+5,x sta dhcp_server+3 jmp @get_next_option @not_server: @get_next_option: txa clc adc #02 adc dhcp_inp+dhcp_options+1,x bcs @finished_unpacking_dhcp_options ; if we overflow, then we're done tax jmp @unpack_dhcp_options @finished_unpacking_dhcp_options: jsr arp_calculate_gateway_mask ; we have modified our netmask, so we need to recalculate gw_test lda dhcp_state cmp #dhcp_bound beq :+ lda #dhcp_ready_to_request sta dhcp_state : lda #1 sta dhcp_break_polling_loop rts send_dhcprequest: jsr dhcp_create_request_msg lda #53 ; option 53 - DHCP message type sta output_buffer+dhcp_options+0 lda #1 ; option length is 1 sta output_buffer+dhcp_options+1 lda #DHCPREQUEST sta output_buffer+dhcp_options+2 lda #50 ; option 50 - requested IP address sta output_buffer+dhcp_options+3 ldx #4 ; option length is 4 stx output_buffer+dhcp_options+4 dex : lda dhcp_ip,x sta output_buffer+dhcp_options+5,x dex bpl :- lda #54 ; option 54 - DHCP server sta output_buffer+dhcp_options+9 ldx #4 ; option length is 4 stx output_buffer+dhcp_options+10 dex : lda dhcp_server,x sta output_buffer+dhcp_options+11,x lda #$ff ; bugfix by ShadowM - DHCP request should be broadcast sta udp_send_dest,x dex bpl :- ; A still = option FF = end of options sta output_buffer+dhcp_options+15 ldax #dhcp_options+16 stax udp_send_len ldax #output_buffer jsr udp_send bcs :++ ; if we didn't send the message we probably need to wait for an ARP reply to come back. lda #dhcp_bound ; technically, we should wait till we get a DHCPACK message. but we'll assume success sta dhcp_state ldx #3 ; set the new IP address : lda dhcp_ip,x sta cfg_ip,x dex bpl :- : rts ; -- LICENSE FOR dhcp.s -- ; The contents of this file are subject to the Mozilla Public License ; Version 1.1 (the "License"); you may not use this file except in ; compliance with the License. You may obtain a copy of the License at ; http://www.mozilla.org/MPL/ ; ; Software distributed under the License is distributed on an "AS IS" ; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the ; License for the specific language governing rights and limitations ; under the License. ; ; The Original Code is ip65. ; ; The Initial Developer of the Original Code is Jonno Downes, ; jonno@jamtronix.com. ; Portions created by the Initial Developer are Copyright (C) 2009 ; Jonno Downes. All Rights Reserved. ; -- LICENSE END --