emailler/ip65/dhcp.s
2022-09-14 13:02:41 -04:00

485 lines
12 KiB
ArmAsm

; 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 --