ip65 technical reference

File : ip65/dhcp.s

 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
 

functions

functiondescription
dhcp_init
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

variables

variabledescriptionsize (bytes)
dhcp_stateflag indicating current state of dhcp initialization. 1

implementation

; 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 KPR_API_VERSION_NUMBER
  .define EQU     =
  .include "../inc/kipper_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 #KPR_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 #KPR_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 #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 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
  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  
  rts
:  
  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 --