;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2004-2022. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;; IPv4.INC ;; ;; ;; ;; Part of the TCP/IP network stack for KolibriOS ;; ;; ;; ;; Based on the work of [Johnny_B] and [smb] ;; ;; ;; ;; Written by hidnplayr@kolibrios.org ;; ;; ;; ;; GNU GENERAL PUBLIC LICENSE ;; ;; Version 2, June 1991 ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; $Revision$ IPv4_MAX_FRAGMENTS = 64 IPv4_MAX_ROUTES = 64 IPv4_ROUTE_FLAG_UP = 1 shl 0 IPv4_ROUTE_FLAG_GATEWAY = 1 shl 1 IPv4_ROUTE_FLAG_HOST = 1 shl 2 IPv4_ROUTE_FLAG_D = 1 shl 3 ; Route was created by a redirect IPv4_ROUTE_FLAG_M = 1 shl 4 ; Route was modified by a redirect struct IPv4_header VersionAndIHL db ? ; Version[0-3 bits] and IHL(header length)[4-7 bits] TypeOfService db ? ; precedence [7-5] minimize delay [4], maximize throughput [3], maximize riliability [2] minimize momentary cost [1] and zero [0] TotalLength dw ? Identification dw ? FlagsAndFragmentOffset dw ? ; Flags[0-2] and FragmentOffset[3-15] TimeToLive db ? ; Protocol db ? HeaderChecksum dw ? SourceAddress dd ? DestinationAddress dd ? ends struct IPv4_FRAGMENT_slot ttl dw ? ; Time to live for this entry, 0 for empty slot's id dw ? ; Identification field from IP header SrcIP dd ? ; .. from IP header DstIP dd ? ; .. from IP header ptr dd ? ; Pointer to first packet ends struct IPv4_FRAGMENT_entry ; This structure will replace the ethernet header in fragmented ip packets PrevPtr dd ? ; Pointer to previous fragment entry (-1 for first packet) NextPtr dd ? ; Pointer to next fragment entry (-1 for last packet) Owner dd ? ; Pointer to structure of driver rb 2 ; to match ethernet header size ;;; FIXME ; Ip header begins here (we will need the IP header to re-construct the complete packet) ends ;struct IPv4_ROUTE ; ; Destination dd ? ; Gateway dd ? ; Flags dd ? ; Use dd ? ; Interface dd ? ; ;ends uglobal align 4 IPv4_address rd NET_DEVICES_MAX IPv4_subnet rd NET_DEVICES_MAX IPv4_nameserver rd NET_DEVICES_MAX IPv4_gateway rd NET_DEVICES_MAX IPv4_broadcast rd NET_DEVICES_MAX IPv4_packets_tx rd NET_DEVICES_MAX IPv4_packets_rx rd NET_DEVICES_MAX IPv4_packets_dumped rd NET_DEVICES_MAX IPv4_fragments rb IPv4_MAX_FRAGMENTS * sizeof.IPv4_FRAGMENT_slot ; IPv4_routes rd IPv4_MAX_ROUTES * sizeof.IPv4_ROUTE endg ;-----------------------------------------------------------------; ; ; ; ipv4_init: Resets all IPv4 variables ; ; ; ;-----------------------------------------------------------------; macro ipv4_init { xor eax, eax mov edi, IPv4_address mov ecx, 7*NET_DEVICES_MAX + (sizeof.IPv4_FRAGMENT_slot*IPv4_MAX_FRAGMENTS)/4 rep stosd } ;-----------------------------------------------------------------; ; ; ; Decrease TimeToLive of all fragment slots ; ; ; ;-----------------------------------------------------------------; macro ipv4_decrease_fragment_ttls { local .loop, .next mov esi, IPv4_fragments mov ecx, IPv4_MAX_FRAGMENTS .loop: cmp [esi + IPv4_FRAGMENT_slot.ttl], 0 je .next dec [esi + IPv4_FRAGMENT_slot.ttl] jz .died .next: add esi, sizeof.IPv4_FRAGMENT_slot dec ecx jnz .loop jmp .done .died: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4 Fragment slot timed-out!\n" ;;; TODO: clear all entry's of timed-out slot jmp .next .done: } macro ipv4_checksum ptr { ; This is the fast procedure to create or check an IP header without options ; To create a new checksum, the checksum field must be set to 0 before computation ; To check an existing checksum, leave the checksum as is, and it will be 0 after this procedure, if it was correct push ebx xor ebx, ebx add bl, [ptr+1] adc bh, [ptr+0] adc bl, [ptr+3] adc bh, [ptr+2] adc bl, [ptr+5] adc bh, [ptr+4] adc bl, [ptr+7] adc bh, [ptr+6] adc bl, [ptr+9] adc bh, [ptr+8] ; we skip 11th and 12th byte, they are the checksum bytes and should be 0 for re-calculation adc bl, [ptr+13] adc bh, [ptr+12] adc bl, [ptr+15] adc bh, [ptr+14] adc bl, [ptr+17] adc bh, [ptr+16] adc bl, [ptr+19] adc bh, [ptr+18] adc ebx, 0 push ecx mov ecx, ebx shr ecx, 16 and ebx, 0xffff add ebx, ecx mov ecx, ebx shr ecx, 16 add ebx, ecx not bx jnz .not_zero dec bx .not_zero: xchg bl, bh pop ecx neg word [ptr+10] ; zero will stay zero so we just get the checksum add word [ptr+10], bx ; , else we will get (new checksum - old checksum) in the end, wich should be 0 :) pop ebx } ;-----------------------------------------------------------------; ; ; ; ipv4_input: Check if IPv4 Packet isnt damaged and call ; ; appropriate handler. (TCP/UDP/ICMP/..) ; ; We will also re-construct fragmented packets. ; ; ; ; IN: Pointer to buffer in [esp] ; ; pointer to device struct in ebx ; ; pointer to IPv4 header in edx ; ; size of IPv4 packet in ecx ; ; ; ; OUT: / ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_input: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: packet from %u.%u.%u.%u ",\ [edx + IPv4_header.SourceAddress + 0]:1,[edx + IPv4_header.SourceAddress + 1]:1,\ [edx + IPv4_header.SourceAddress + 2]:1,[edx + IPv4_header.SourceAddress + 3]:1 DEBUGF DEBUG_NETWORK_VERBOSE, "to %u.%u.%u.%u\n",\ [edx + IPv4_header.DestinationAddress + 0]:1,[edx + IPv4_header.DestinationAddress + 1]:1,\ [edx + IPv4_header.DestinationAddress + 2]:1,[edx + IPv4_header.DestinationAddress + 3]:1 call net_ptr_to_num4 cmp edi, -1 je .invalid_device ;------------------------------- ; re-calculate the checksum ipv4_checksum edx jnz .dump ; if checksum isn't valid then dump packet DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Checksum ok\n" ;-------------------------------- ; Check if destination IP matches ; local ip (Using RFC1122 strong end system model) mov eax, [edx + IPv4_header.DestinationAddress] cmp eax, [IPv4_address + edi] je .ip_ok ; network layer broadcast cmp eax, [IPv4_broadcast + edi] je .ip_ok ; physical layer broadcast (255.255.255.255) cmp eax, 0xffffffff je .ip_ok ; multicast (224.0.0.0/4 = 224.0.0.0 to 239.255.255.255) and eax, 0x0fffffff cmp eax, 224 je .ip_ok ; maybe we just dont have an IP yet and should accept everything on the IP level cmp [IPv4_address + edi], 0 je .ip_ok ; or it's just not meant for us.. :( DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Destination address does not match!\n" jmp .dump ;------------------------ ; Now we can update stats .ip_ok: inc [IPv4_packets_rx + edi] ;---------------------------------- ; Check if the packet is fragmented test [edx + IPv4_header.FlagsAndFragmentOffset], 1 shl 5 ; Is 'more fragments' flag set ? jnz .has_fragments ; If so, we definately have a fragmented packet test [edx + IPv4_header.FlagsAndFragmentOffset], 0xff1f ; If flag is not set, but there is a fragment offset, the packet is last in series of fragmented packets jnz .is_last_fragment ;------------------------------------------------------------------- ; No, it's just a regular IP packet, pass it to the higher protocols .handle_it: ; We reach here if packet hasnt been fragmented, or when it already has been re-constructed movzx esi, [edx + IPv4_header.VersionAndIHL] ; Calculate Header length by using IHL field and esi, 0x0000000f ; shl esi, 2 ; movzx ecx, [edx + IPv4_header.TotalLength] ; Calculate length of encapsulated Packet xchg cl, ch ; sub ecx, esi ; mov al, [edx + IPv4_header.Protocol] add esi, edx ; make esi ptr to data cmp al, IP_PROTO_TCP je tcp_input cmp al, IP_PROTO_UDP je udp_input cmp al, IP_PROTO_ICMP je icmp_input ;------------------------------- ; Look for a matching RAW socket pusha mov ecx, socket_mutex call mutex_lock popa add ecx, esi sub ecx, edx mov esi, edx movzx edx, al mov eax, net_sockets .next_socket: mov eax, [eax + SOCKET.NextPtr] or eax, eax jz .dump_unlock cmp [eax + SOCKET.Domain], AF_INET4 jne .next_socket cmp [eax + SOCKET.Protocol], edx jne .next_socket pusha mov ecx, socket_mutex call mutex_unlock popa DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: found matching RAW socket: 0x%x\n", eax pusha lea ecx, [eax + SOCKET.mutex] call mutex_lock popa jmp socket_input .dump_unlock: pusha mov ecx, socket_mutex call mutex_unlock popa DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: unknown protocol %u\n", al .dump: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: dumping\n" inc [IPv4_packets_dumped + edi] call net_buff_free ret .invalid_device: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_input: packet originated from invalid device\n" call net_buff_free ret ;--------------------------- ; Fragmented packet handler .has_fragments: movzx eax, [edx + IPv4_header.FlagsAndFragmentOffset] xchg al, ah shl ax, 3 DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: fragmented packet offset=%u id=%x ptr=0x%x\n", ax, [edx + IPv4_header.Identification]:4, edx test ax, ax ; Is this the first packet of the fragment? jz .is_first_fragment ;------------------------------------------------------- ; We have a fragmented IP packet, but it's not the first DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Middle fragment packet received!\n" call ipv4_find_fragment_slot cmp esi, -1 je .dump mov [esi + IPv4_FRAGMENT_slot.ttl], 15 ; Reset the ttl mov esi, [esi + IPv4_FRAGMENT_slot.ptr] or edi, -1 .find_last_entry: ; The following routine will try to find the last entry cmp edi, [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.PrevPtr] jne .destroy_slot ; Damn, something screwed up, remove the whole slot (and free buffers too if possible!) mov edi, esi mov esi, [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr] cmp esi, -1 jne .find_last_entry ; We found the last entry (pointer is now in edi) ; We are going to overwrite the ethernet header in received packet with a FRAGMENT_entry structure pop eax ; pointer to packet mov [edi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr], eax ; update pointer of previous entry to the new entry mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr], -1 mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.PrevPtr], edi mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.Owner], ebx ret ;------------------------------------ ; We have received the first fragment .is_first_fragment: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: First fragment packet received!\n" ; try to locate a free slot.. mov ecx, IPv4_MAX_FRAGMENTS mov esi, IPv4_fragments .find_free_slot: cmp word [esi + IPv4_FRAGMENT_slot.ttl], 0 je .found_free_slot add esi, sizeof.IPv4_FRAGMENT_slot loop .find_free_slot jmp .dump ; If no free slot was found, dump the packet .found_free_slot: ; We found a free slot, let's fill in the FRAGMENT_slot structure mov [esi + IPv4_FRAGMENT_slot.ttl], 15 ; RFC recommends 15 secs as ttl mov ax, [edx + IPv4_header.Identification] mov [esi + IPv4_FRAGMENT_slot.id], ax mov eax, [edx + IPv4_header.SourceAddress] mov [esi + IPv4_FRAGMENT_slot.SrcIP], eax mov eax, [edx + IPv4_header.DestinationAddress] mov [esi + IPv4_FRAGMENT_slot.DstIP], eax pop eax mov [esi + IPv4_FRAGMENT_slot.ptr], eax ; Now, replace ethernet header in original buffer with a FRAGMENT_entry structure mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr], -1 mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.PrevPtr], -1 mov [eax + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.Owner], ebx ret ;----------------------------------- ; We have received the last fragment .is_last_fragment: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Last fragment packet received!\n" call ipv4_find_fragment_slot cmp esi, -1 je .dump mov esi, [esi + IPv4_FRAGMENT_slot.ptr] ; We found the first entry, let's calculate total size of the packet in eax, so we can allocate a buffer push esi ; Save pointer to first buffer on stack push edi ; Save device index on stack xor eax, eax or edi, -1 .count_bytes: cmp [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.PrevPtr], edi jne .destroy_slot_pop ; Damn, something screwed up, remove the whole slot (and free buffers too if possible!) mov cx, [esi + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry + IPv4_header.TotalLength] ; Add total length xchg cl, ch DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Packet size=%u\n", cx add ax, cx movzx cx, [esi + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry + IPv4_header.VersionAndIHL] ; Sub Header length and cx, 0x000F shl cx, 2 DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Header size=%u\n", cx sub ax, cx mov edi, esi mov esi, [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr] cmp esi, -1 jne .count_bytes mov esi, [esp+8] ; Take the current (last) buffer pointer mov [edi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr], esi ; Add this packet to the chain, this simplifies the following code mov [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr], -1 mov [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.PrevPtr], edi mov [esi + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.Owner], ebx mov cx, [edx + IPv4_header.TotalLength] ; Note: This time we dont substract Header length xchg cl, ch DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Packet size=%u\n", cx add ax, cx DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Total Received data size=%u\n", eax push eax mov ax, [edx + IPv4_header.FlagsAndFragmentOffset] xchg al, ah shl ax, 3 add cx, ax pop eax DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Total Fragment size=%u\n", ecx cmp ax, cx jne .destroy_slot_pop push eax ; Save total size of packet on stack push eax call kernel_alloc test eax, eax je .destroy_slot_pop12 ; If we dont have enough space to allocate the buffer, discard all packets in slot mov edx, [esp+8] ; Get pointer to first fragment entry back in edx ; FIXME: Allocate NET_BUFF here instead of raw IP packet buffer .rebuild_packet_loop: movzx ecx, [edx + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry + IPv4_header.FlagsAndFragmentOffset] ; Calculate the fragment offset xchg cl, ch ; intel byte order shl cx, 3 ; multiply by 8 and clear first 3 bits DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Fragment offset=%u\n", cx lea edi, [eax + ecx] ; Notice that edi will be equal to eax for first fragment movzx ebx, [edx + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry + IPv4_header.VersionAndIHL] ; Find header size (in ebx) of fragment and bx, 0x000F ; shl bx, 2 ; lea esi, [edx + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry] ; Set esi to the correct begin of fragment movzx ecx, [edx + sizeof.NET_BUFF + sizeof.IPv4_FRAGMENT_entry + IPv4_header.TotalLength] ; Calculate total length of fragment xchg cl, ch ; intel byte order cmp edi, eax ; Is this packet the first fragment ? je .first_fragment sub cx, bx ; If not, dont copy the header add esi, ebx ; add edi, ebx ; FIXME: We should add size of header of first fragment here ; instead of size of currently copying fragment ; because the fragment offset is offset within the big IP packet ; data (not within the packet, within packet's contents) .first_fragment: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Copying %u bytes from 0x%x to 0x%x\n", ecx, esi, edi push cx ; First copy dword-wise, then byte-wise shr cx, 2 ; rep movsd ; pop cx ; and cx, 3 ; rep movsb ; push eax push [edx + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.Owner] ; we need to remeber the owner, in case this is the last packet push [edx + sizeof.NET_BUFF + IPv4_FRAGMENT_entry.NextPtr] ; Set edx to the next pointer push edx ; Push pointer to fragment onto stack DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Next Fragment: 0x%x\n", edx call net_buff_free ; free the previous fragment buffer (this uses the value from stack) pop edx ebx eax cmp edx, -1 ; Check if it is last fragment in chain jne .rebuild_packet_loop pop ecx ; Restore the total size of IP packet pop edi ; Restore the device index xchg cl, ch mov edx, eax mov [edx + IPv4_header.TotalLength], cx add esp, 8 ; Remove pointer to first buffer and pointer to last buffer from the stack xchg cl, ch push edx ; Push pointer to the new buffer with full IP packet ; FIXME: Remove this block once allocated network buffers handling is implemented. if 1 DEBUGF DEBUG_NETWORK_ERROR, "IPv4_input: fragmented packet is dropped\n", ecx call kernel_free ; Ptr to buffer is on the stack already inc [IPv4_packets_dumped + edi] ret end if jmp .handle_it ; edx = buf ptr, ecx = size, [esp] buf ptr, ebx=device ptr .destroy_slot_pop12: add esp, 4 ; Remove total size from the stack .destroy_slot_pop: pop edi ; Restore device index add esp, 4 ; Remove first buffer pointer from the stack .destroy_slot: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Destroy fragment slot!\n" ; TODO: What? ; Last buffer pointer is on the stack now jmp .dump ;-----------------------------------------------------------------; ; ; ; ipv4_find_fragment_slot ; ; ; ; IN: pointer to fragmented packet in edx ; ; ; ; OUT: pointer to slot in esi, -1 on error ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_find_fragment_slot: ;;; TODO: the RFC says we should check protocol number too push eax ebx ecx edx mov ax, [edx + IPv4_header.Identification] mov ecx, IPv4_MAX_FRAGMENTS mov esi, IPv4_fragments mov ebx, [edx + IPv4_header.SourceAddress] mov edx, [edx + IPv4_header.DestinationAddress] .find_slot: cmp [esi + IPv4_FRAGMENT_slot.id], ax jne .try_next cmp [esi + IPv4_FRAGMENT_slot.SrcIP], ebx jne .try_next cmp [esi + IPv4_FRAGMENT_slot.DstIP], edx je .found_slot .try_next: add esi, sizeof.IPv4_FRAGMENT_slot loop .find_slot or esi, -1 .found_slot: pop edx ecx ebx eax ret ;------------------------------------------------------------------; ; ; ; ipv4_output ; ; ; ; IN: al = protocol ; ; ah = TTL ; ; ebx = device ptr (or 0 to let IP layer decide) ; ; ecx = data length ; ; edx = Source IP ; ; edi = Destination IP ; ; ; ; OUT: eax = pointer to buffer start ; ; eax = 0 on error ; ; ebx = device ptr (send packet through this device) ; ; ecx = data length ; ; edx = size of complete frame ; ; edi = start of IPv4 payload ; ; ; ;------------------------------------------------------------------; align 4 ipv4_output: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_output: size=%u ip=0x%x\n", ecx, edi cmp ecx, 65500 ; Max IPv4 packet size ja .too_large push ecx ax edi mov eax, edi call ipv4_route ; outputs device number in edi, dest ip in eax, source IP in edx test eax, eax jz .no_route push edx test edi, edi jz .loopback call arp_ip_to_mac test eax, 0xffff0000 ; error bits jnz .arp_error push ebx ; push the mac onto the stack push ax inc [IPv4_packets_tx + edi] ; update stats mov ax, ETHER_PROTO_IPv4 mov ebx, [net_device_list + edi] mov ecx, [esp + 6 + 8 + 2] add ecx, sizeof.IPv4_header mov edx, esp call eth_output jz .eth_error add esp, 6 ; pop the mac out of the stack .continue: xchg cl, ch ; internet byte order mov [edi + IPv4_header.VersionAndIHL], 0x45 ; IPv4, normal length (no Optional header) mov [edi + IPv4_header.TypeOfService], 0 ; nothing special, just plain ip packet mov [edi + IPv4_header.TotalLength], cx mov [edi + IPv4_header.Identification], 0 ; fragment id: FIXME mov [edi + IPv4_header.FlagsAndFragmentOffset], 0 mov [edi + IPv4_header.HeaderChecksum], 0 popd [edi + IPv4_header.SourceAddress] popd [edi + IPv4_header.DestinationAddress] pop word[edi + IPv4_header.TimeToLive] ; ttl shl 8 + protocol ; [edi + IPv4_header.Protocol] pop ecx ipv4_checksum edi add edi, sizeof.IPv4_header DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_output: success!\n" ret .eth_error: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: ethernet error\n" add esp, 3*4+2+6 xor eax, eax ret .no_route: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: No route to host!\n" add esp, 2*4+2 xor eax, eax ret .arp_error: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: ARP error=%x\n", eax add esp, 4 pop eax DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: ip=0x%x\n", eax add esp, 4+2 xor eax, eax ret .too_large: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: Packet too large!\n" xor eax, eax ret .loopback: inc [IPv4_packets_tx + edi] ; update stats mov dword [esp], eax ; set source IP to dest IP mov ecx, [esp + 10] add ecx, sizeof.IPv4_header mov edi, AF_INET4 call loop_output jmp .continue ;------------------------------------------------------------------; ; ; ; ipv4_output_raw ; ; ; ; IN: eax = socket ptr ; ; ecx = data length ; ; esi = data ptr ; ; ; ; OUT: eax = -1 on error ; ; ; ;------------------------------------------------------------------; align 4 ipv4_output_raw: DEBUGF 1,"IPv4_output_raw: size=%u ptr=%x socket=%x\n", ecx, esi, eax sub esp, 8 push esi eax call ipv4_route call arp_ip_to_mac test eax, 0xffff0000 ; error bits jnz .arp_error push ebx ; push the mac push ax inc [IPv4_packets_tx + 4*edi] mov ax, ETHER_PROTO_IPv4 mov ebx, [net_device_list + 4*edi] mov ecx, [esp + 6 + 4] add ecx, sizeof.IPv4_header mov edx, esp call eth_output jz .error add esp, 6 ; pop the mac mov dword[esp+4+4], edx mov dword[esp+4+4+4], eax pop eax esi ;; TODO: check socket options if we should add header, or just compute checksum push edi ecx rep movsb pop ecx edi ; [edi + IPv4_header.VersionAndIHL] ; IPv4, normal length (no Optional header) ; [edi + IPv4_header.TypeOfService] ; nothing special, just plain ip packet ; [edi + IPv4_header.TotalLength] ; [edi + IPv4_header.TotalLength] ; internet byte order ; [edi + IPv4_header.FlagsAndFragmentOffset] mov [edi + IPv4_header.HeaderChecksum], 0 ; [edi + IPv4_header.TimeToLive] ; ttl shl 8 + protocol ; [edi + IPv4_header.Protocol] ; [edi + IPv4_header.Identification] ; fragment id ; [edi + IPv4_header.SourceAddress] ; [edi + IPv4_header.DestinationAddress] ipv4_checksum edi ;;;; todo: checksum for IP packet with options! add edi, sizeof.IPv4_header DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_output_raw: device=%x\n", ebx call [ebx + NET_DEVICE.transmit] ret .error: add esp, 6+8+4+4 mov ebx, ENOBUFS ; FIXME: NOBUFS or MSGSIZE error or eax, -1 ret .arp_error: add esp, 8+4+4 mov ebx, ENOTCONN or eax, -1 ret ;-----------------------------------------------------------------; ; ; ; ipv4_fragment ; ; ; ; IN: [esp] = ptr to packet buffer to fragment ; ; edi = ptrr to ip header in that buffer ; ; ebx = device ptr ; ; ; ; OUT: / ; ; ; ;-----------------------------------------------------------------; proc ipv4_fragment stdcall buffer locals offset dd ? headerlength dd ? headerptr dd ? dataptr dd ? remaining dd ? segmentsize dd ? endl DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_fragment\n" ; We must be able to put at least 8 bytes per segment movzx eax, byte[edi] ; IHL and eax, 0xf shl eax, 2 mov [headerlength], eax add eax, 8 mov ecx, [ebx + NET_DEVICE.mtu] and ecx, not 11b cmp ecx, eax jb .fail mov [edi + IPv4_header.HeaderChecksum], 0 mov [segmentsize], ecx mov [headerptr], edi movzx ecx, [edi + IPv4_header.TotalLength] xchg cl, ch sub ecx, [headerlength] mov [remaining], ecx mov [offset], 0 add edi, [headerlength] mov [dataptr], edi .loop: DEBUGF DEBUG_NETWORK_VERBOSE, "Ipv4_fragment: new fragment" mov ecx, [segmentsize] cmp ecx, [remaining] jbe @f mov ecx, [remaining] @@: mov ax, ETHER_PROTO_IPv4 mov edx, [esp] add edx, [edx + NET_BUFF.offset] ; add edx, ETH_header.DstMAC ; = 0 call ETH_output jz .fail push edi mov edx, ecx ; copy header mov esi, [headerptr] mov ecx, [headerlength] shr ecx, 2 rep movsd ; copy data mov esi, [dataptr] add esi, [offset] mov ecx, edx sub ecx, [headerlength] shr ecx, 2 rep movsd pop edi ; now, correct header ; packet length mov ax, dx xchg al, ah mov [edi + IPv4_header.TotalLength], ax ; offset mov eax, [offset] xchg al, ah sub edx, [headerlength] sub [remaining], edx je @f jb .fail or ah, 1 shl 2 ; more fragments add [offset], edx @@: mov [edi + IPv4_header.FlagsAndFragmentOffset], ax ; Send the fragment IPv4_checksum edi call [ebx + NET_DEVICE.transmit] cmp [remaining], 0 jne .loop call NET_BUFF_free ret .fail: DEBUGF DEBUG_NETWORK_ERROR, "Ipv4_fragment: failed\n" call NET_BUFF_free ret endp ;-----------------------------------------------------------------; ; ; ; ipv4_route ; ; ; ; IN: eax = Destination IP ; ; ebx = outgoing device / 0 ; ; edx = Source IP ; ; ; ; OUT: eax = Destination IP (may be gateway), 0 on error ; ; edx = Source IP ; ; edi = device number*4 ; ; ; ; DESTROYED: ; ; ecx ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_route: test ebx, ebx jnz .got_device ; Broadcast does not need gateway cmp eax, 0xffffffff je .broadcast xor edi, edi .loop: mov ebx, [IPv4_address + edi] and ebx, [IPv4_subnet + edi] jz .next mov ecx, eax and ecx, [IPv4_subnet + edi] cmp ebx, ecx je .got_it .next: add edi, 4 cmp edi, 4*NET_DEVICES_MAX jb .loop mov eax, [IPv4_gateway + 4] ; TODO: let user (or a user space daemon) configure default route .broadcast: mov edi, 4 ; TODO: same as above .got_it: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_route: %u\n", edi test edx, edx jnz @f mov edx, [IPv4_address + edi] @@: ret .got_device: ; Validate device ptr and convert to device number call net_ptr_to_num4 cmp edi, -1 je .fail mov edx, [IPv4_address + edi] ; Source IP ; Broadcast does not need gateway cmp eax, 0xffffffff je @f ; Check if we should route to gateway or not mov ebx, [IPv4_address + edi] and ebx, [IPv4_subnet + edi] mov ecx, eax and ecx, [IPv4_subnet + edi] cmp ecx, ebx je @f mov eax, [IPv4_gateway + edi] @@: DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_route: %u\n", edi ret .fail: DEBUGF DEBUG_NETWORK_ERROR, "IPv4_route failed\n" xor eax, eax ret ;-----------------------------------------------------------------; ; ; ; ipv4_get_frgmnt_num ; ; ; ; IN: / ; ; ; ; OUT: ax = fragment number ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_get_frgmnt_num: xor ax, ax ;;; TODO: replace this with real code ret ;-----------------------------------------------------------------; ; ; ; ipv4_connect ; ; ; ; IN: eax = socket pointer ; ; ; ; OUT: eax = 0 on success ; ; eax = -1 on error ; ; ebx = error code on error ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_connect: push eax edx lea ecx, [eax + SOCKET.mutex] call mutex_lock pop edx eax ; Fill in local IP cmp [eax + IP_SOCKET.LocalIP], 0 jne @f push [IPv4_address + 4] ; FIXME: use correct local IP pop [eax + IP_SOCKET.LocalIP] ; Fill in remote IP pushd [edx + 4] pop [eax + IP_SOCKET.RemoteIP] lea ecx, [eax + SOCKET.mutex] call mutex_unlock xor eax, eax ret ;-----------------------------------------------------------------; ; ; ; ipv4_API: Part of system function 76. ; ; ; ; IN: bl = subfunction number ; ; bh = device number ; ; ecx, edx, .. depends on subfunction ; ; ; ; OUT: depends on subfunction ; ; ; ;-----------------------------------------------------------------; align 4 ipv4_api: movzx eax, bh shl eax, 2 and ebx, 0x000000ff cmp ebx, .number ja .error jmp dword [.table + 4*ebx] .table: dd .packets_tx ; 0 dd .packets_rx ; 1 dd .read_ip ; 2 dd .write_ip ; 3 dd .read_dns ; 4 dd .write_dns ; 5 dd .read_subnet ; 6 dd .write_subnet ; 7 dd .read_gateway ; 8 dd .write_gateway ; 9 .number = ($ - .table) / 4 - 1 .error: mov eax, -1 ret .packets_tx: mov eax, [IPv4_packets_tx + eax] ret .packets_rx: mov eax, [IPv4_packets_rx + eax] ret .read_ip: mov eax, [IPv4_address + eax] ret .write_ip: mov [IPv4_address + eax], ecx mov edi, eax ; device number, we'll need it for ARP ; pre-calculate the local broadcast address mov ebx, [IPv4_subnet + eax] not ebx or ebx, ecx mov [IPv4_broadcast + eax], ebx mov ebx, [net_device_list + eax] mov eax, [IPv4_address + eax] call arp_output_request ; now send a gratuitous ARP call net_send_event xor eax, eax ret .read_dns: mov eax, [IPv4_nameserver + eax] ret .write_dns: mov [IPv4_nameserver + eax], ecx call net_send_event xor eax, eax ret .read_subnet: mov eax, [IPv4_subnet + eax] ret .write_subnet: mov [IPv4_subnet + eax], ecx ; pre-calculate the local broadcast address mov ebx, [IPv4_address + eax] not ecx or ecx, ebx mov [IPv4_broadcast + eax], ecx call net_send_event xor eax, eax ret .read_gateway: mov eax, [IPv4_gateway + eax] ret .write_gateway: mov [IPv4_gateway + eax], ecx call net_send_event xor eax, eax ret