; 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.i" .ifndef NB65_API_VERSION_NUMBER .define EQU = .include "../inc/nb65_constants.i" .endif .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 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 #NB65_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 #NB65_ERROR_TIMEOUT_ON_RECEIVE sta ip65_error 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 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 #DHCPDISCOVER sta output_buffer+dhcp_options+2 lda #$FF ;option FF = end of options sta output_buffer+dhcp_options+3 ldx #3 ; set destination address lda #$FF ; des = 255.255.255.255 (broadcast) : sta udp_send_dest,x dex bpl :- ldax #dhcp_options+4 stax udp_send_len ldax #output_buffer jsr udp_send bcc :+ rts : lda #dhcp_selecting sta dhcp_state rts ;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 #4 ;copy the our new IP address : lda dhcp_inp+dhcp_yiaddr,x sta cfg_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 3 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 cfg_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 sta udp_send_dest,x dex bpl :- lda #$FF ;option FF = end of options sta output_buffer+dhcp_options+17 ldax #dhcp_options+18 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 rts : rts