.include "zeropage.inc" .include "../inc/common.inc" .export ip_init .export ip_process .export ip_calc_cksum .export ip_create_packet .export ip_send .export ip_inp .export ip_outp .export ip_broadcast .exportzp ip_ver_ihl .exportzp ip_tos .exportzp ip_len .exportzp ip_id .exportzp ip_frag .exportzp ip_ttl .exportzp ip_proto .exportzp ip_header_cksum .exportzp ip_src .exportzp ip_dest .exportzp ip_data .exportzp ip_proto_icmp .exportzp ip_proto_tcp .exportzp ip_proto_udp .import cfg_mac .import cfg_ip .import eth_tx .import eth_set_proto .import eth_inp .import eth_inp_len .import eth_outp .import eth_outp_len .importzp eth_dest .importzp eth_src .importzp eth_type .importzp eth_data .importzp eth_proto_ip .importzp eth_proto_arp .import arp_lookup .import arp_mac .import arp_ip .import icmp_init .import icmp_process .import udp_init .import udp_process .ifdef TCP .import tcp_init .import tcp_process .endif .exportzp ip_cksum_ptr = ptr2 .bss ip_cksum_len: .res 2 ; length of data to be checksummed ; ip packets start at ethernet packet + 14 ip_inp = eth_inp + eth_data ; pointer to start of IP packet in input ethernet frame ip_outp = eth_outp + eth_data ; pointer to start of IP packet in output ethernet frame ; temp storage for size calculation len: .res 2 ; flag for incoming broadcast packets ip_broadcast: .res 1 ; flag set when an incoming IP packet was sent to a broadcast address ; ip packet offsets ip_ver_ihl = 0 ; offset of 4 bit "version" field and 4 bit "header length" field in an IP packet header ip_tos = 1 ; offset of "type of service" field in an IP packet header ip_len = 2 ; offset of "length" field in an IP packet header ip_id = 4 ; offset of "identification" field in an IP packet header ip_frag = 6 ; offset of "fragmentation offset" field in an IP packet header ip_ttl = 8 ; offset of "time to live" field in an IP packet header ip_proto = 9 ; offset of "protocol number" field in an IP packet header ip_header_cksum = 10 ; offset of "ip header checksum" field in an IP packet header ip_src = 12 ; offset of "source address" field in an IP packet header ip_dest = 16 ; offset of "destination address" field in an IP packet header ip_data = 20 ; offset of data payload in an IP packet ; ip protocols ip_proto_icmp = 1 ip_proto_tcp = 6 ip_proto_udp = 17 ; temp for calculating checksum cksum: .res 3 ; bad packet counters bad_header: .res 2 bad_addr: .res 2 .code ; initialize ip routines ; inputs: none ; outputs: none ip_init: lda #0 sta bad_header sta bad_header + 1 sta bad_addr sta bad_addr + 1 jsr icmp_init .ifdef TCP jsr tcp_init .endif jsr udp_init rts ; process an incoming packet & call the appropriate protocol handler ; inputs: ; eth_inp: should point to the received ethernet packet ; outputs: ; carry flag - set on any error, clear if OK ; depending on the packet contents and the protocol handler, a response ; message may be generated and sent out (overwriting eth_outp buffer) ip_process: jsr verifyheader ; ver, ihl, len, frag, checksum bcc @ok @badpacket: sec rts @ok: jsr checkaddr ; make sure it's meant for us bcs @badpacket lda ip_inp + ip_proto cmp #ip_proto_icmp bne :+ jmp icmp_process ; jump to icmp handler .ifdef TCP : cmp #ip_proto_tcp bne :+ jmp tcp_process ; jump to tcp handler .endif : cmp #ip_proto_udp bne :+ jmp udp_process ; jump to udp handler : unknown_protocol: sec ; unknown protocol rts ; verify that header contains what we expect verifyheader: lda ip_inp + ip_ver_ihl ; IPv4 and no IP options cmp #$45 bne @badpacket ; lda ip_inp + ip_tos ; ignore ToS lda ip_inp + ip_len + 1 ; ip + 14 bytes ethernet header clc adc #14 sta len lda ip_inp + ip_len adc #0 sta len + 1 lda eth_inp_len ; check if advertised length is shorter sec ; than actual length sbc len lda eth_inp_len + 1 sbc len + 1 bmi @badpacket lda ip_inp + ip_frag ; check for fragmentation beq :+ cmp #$40 bne @badpacket : lda ip_inp + ip_frag + 1 bne @badpacket ; it is possible that the checksum has not been set in inbound packets ; this is especially likely to happen if we are running in an emulator on a host machine that ; does checksum offloading (i.e. where the NIC calculates the checksum) and then ; try to send traffic from the host to the emulated computer (e.g. ip65 running on AppleWin or VICE) ; in this case the traffic will be seen by the emulated computer BEFORE the host NIC ; has calculated the checksum ; so if the checksum field in the inbound packet is 0000, ignore it lda ip_inp+10 bne @has_cksum lda ip_inp+11 bne @has_cksum clc rts @has_cksum: ldax #ip_inp ; verify checksum stax ip_cksum_ptr ldax #20 jsr ip_calc_cksum cmp #0 bne @badpacket cpx #0 bne @badpacket clc rts @badpacket: inc bad_header bne :+ inc bad_header + 1 : sec rts ; check that this packet was addressed to us checkaddr: lda #0 sta ip_broadcast lda ip_inp + ip_dest ; compare ip address cmp cfg_ip bne @broadcast lda ip_inp + ip_dest + 1 cmp cfg_ip + 1 bne @broadcast lda ip_inp + ip_dest + 2 cmp cfg_ip + 2 bne @broadcast lda ip_inp + ip_dest + 3 cmp cfg_ip + 3 bne @broadcast @ok: clc rts @broadcast: ; jonno 2011-01-2 ; previously this was just checking for 255.255.255.255 ; however it is also possible to do a broadcast to a specific subnet, e.g. 10.5.1.255 ; this is particularly common with NETBIOS over TCP ; we really should use the netmask, but as a kludge, just see if last octet is 255. ; this will work on a /24 network inc ip_broadcast ; lda ip_inp + ip_dest ; check for broadcast ; and ip_inp + ip_dest + 1 ; and ip_inp + ip_dest + 2 ; and ip_inp + ip_dest + 3 lda ip_inp + ip_dest +3 ; check for broadcast cmp #$ff beq @ok inc bad_addr bne :+ inc bad_addr + 1 : sec rts ; create an IP header (with all the appropriate flags and common fields set) inside an ; ethernet frame ; inputs: ; eth_outp: should point to a buffer in which the ethernet frame is being built ; outputs: ; eth_outp: contains an IP header with version, TTL, flags, src address & IP header ; checksum fields set. ip_create_packet: lda #$45 ; set IP version and header length sta ip_outp + ip_ver_ihl lda #0 ; set type of service sta ip_outp + ip_tos ; skip length ; skip ID lda #$40 ; don't fragment - or should we not care? sta ip_outp + ip_frag lda #0 sta ip_outp + ip_frag + 1 lda #$40 ; set time to live sta ip_outp + ip_ttl ; skip protocol lda #0 ; clear checksum sta ip_outp + ip_header_cksum sta ip_outp + ip_header_cksum + 1 ldx #3 ; copy source address : lda cfg_ip,x sta ip_outp + ip_src,x dex bpl :- ; skip destination address rts ; send an IP packet ; inputs ; eth_outp: should point to an ethernet frame that has an IP header created (by ; calling ip_create_packet) ; ip_len: should contain length of IP packet (header + data) ; ip_id: should contain an ID that is unique for each packet ; ip_protocol: should contain protocol ID ; ip_dest: should contain the destination IP address ; outputs: ; eth_outp: ethernet frame updated with correct IP header, then sent out over ; the wire ; carry flag - set on any error, clear if OK ip_send: ldx #3 ; get mac addr from ip : lda ip_outp + ip_dest,x sta arp_ip,x dex bpl :- jsr arp_lookup bcc :+ rts ; packet buffer nuked, fail : ldax #ip_outp ; calculate ip header checksum stax ip_cksum_ptr ldax #20 jsr ip_calc_cksum stax ip_outp + ip_header_cksum ldx #5 : lda arp_mac,x ; copy destination mac address sta eth_outp + eth_dest,x lda cfg_mac,x ; copy my mac address sta eth_outp + eth_src,x dex bpl :- lda #eth_proto_ip ; set type to IP jsr eth_set_proto lda ip_outp + ip_len + 1 ; set packet length lsr bcc @dontpad rol ; pad with 0 ; clc adc #ip_outp sta ptr1 + 1 ldy #0 tya sta (ptr1),y sec ; round up to even number @dontpad: lda ip_outp + ip_len + 1 adc #eth_data sta eth_outp_len lda ip_outp + ip_len adc #0 sta eth_outp_len + 1 jmp eth_tx ; send packet and return status ; calculate checksum for a buffer according to the standard IP checksum algorithm ; David Schmidt discovered errors in the original ip65 implementation, and he replaced ; this with an implementation from the contiki project (http://www.sics.se/contiki/) ; when incorporating ip65 into ADTPro (http://adtpro.sourceforge.net/) ; So I have cribbed that version from ; http://adtpro.cvs.sourceforge.net/viewvc/adtpro/adtpro/client/src/ip65/ip.s ; inputs: ; ip_cksum_ptr: points at buffer to be checksummed ; AX: length of buffer to be checksumed ; outputs: ; AX: checkum of buffer ip_calc_cksum: sta ip_cksum_len ; save length stx ip_cksum_len + 1 lda #0 sta cksum sta cksum+1 lda ip_cksum_len+1 beq chksumlast ; If checksum is > 256, do the first runs. ldy #0 clc chksumloop_256: lda (ip_cksum_ptr),y adc cksum sta cksum iny lda (ip_cksum_ptr),y adc cksum+1 sta cksum+1 iny bne chksumloop_256 inc ip_cksum_ptr+1 dec ip_cksum_len+1 bne chksumloop_256 chksum_endloop_256: lda cksum adc #0 sta cksum lda cksum+1 adc #0 sta cksum+1 bcs chksum_endloop_256 chksumlast: lda ip_cksum_len lsr bcc chksum_noodd ldy ip_cksum_len dey lda (ip_cksum_ptr),y clc adc cksum sta cksum bcc noinc1 inc cksum+1 bne noinc1 inc cksum noinc1: dec ip_cksum_len chksum_noodd: clc php ldy ip_cksum_len chksum_loop1: cpy #0 beq chksum_loop1_end plp dey dey lda (ip_cksum_ptr),y adc cksum sta cksum iny lda (ip_cksum_ptr),y adc cksum+1 sta cksum+1 dey php jmp chksum_loop1 chksum_loop1_end: plp chksum_endloop: lda cksum adc #0 sta cksum lda cksum+1 adc #0 sta cksum+1 bcs chksum_endloop lda cksum+1 eor #$ff tax lda cksum eor #$ff rts ; -- LICENSE FOR ip.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 Per Olofsson, ; MagerValp@gmail.com. ; Portions created by the Initial Developer are Copyright (C) 2009 ; Per Olofsson. All Rights Reserved. ; -- LICENSE END --